Skip to content

Instantly share code, notes, and snippets.

@joseamirandavelez
Last active December 30, 2016 06:47
Show Gist options
  • Save joseamirandavelez/f479bbc97aa8dba8e389607f0f654cbf to your computer and use it in GitHub Desktop.
Save joseamirandavelez/f479bbc97aa8dba8e389607f0f654cbf to your computer and use it in GitHub Desktop.
Arduino Robot
/* IRLib.cpp from IRLib - an Arduino library for infrared encoding and decoding
* Version 1.51 March 2015
* Copyright 2014 by Chris Young http://cyborg5.com
*
* This library is a major rewrite of IRemote by Ken Shirriff which was covered by
* GNU LESSER GENERAL PUBLIC LICENSE which as I read it allows me to make modified versions.
* That same license applies to this modified version. See his original copyright below.
* The latest Ken Shirriff code can be found at https://github.com/shirriff/Arduino-IRremote
* My purpose was to reorganize the code to make it easier to add or remove protocols.
* As a result I have separated the act of receiving a set of raw timing codes from the act of decoding them
* by making them separate classes. That way the receiving aspect can be more black box and implementers
* of decoders and senders can just deal with the decoding of protocols. It also allows for alternative
* types of receivers independent of the decoding. This makes porting to different hardware platforms easier.
* Also added provisions to make the classes base classes that could be extended with new protocols
* which would not require recompiling of the original library nor understanding of its detailed contents.
* Some of the changes were made to reduce code size such as unnecessary use of long versus bool.
* Some changes were just my weird programming style. Also extended debugging information added.
*/
/*
* IRremote
* Version 0.1 July, 2009
* Copyright 2009 Ken Shirriff
* For details, see http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html http://www.righto.com/
*
* Interrupt code based on NECIRrcv by Joe Knapp
* http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556
* Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/
*/
#include "IRLib.h"
#include "IRLibMatch.h"
#include "IRLibRData.h"
#include <Arduino.h>
volatile irparams_t irparams;
/*
* Returns a pointer to a flash stored string that is the name of the protocol received.
*/
const __FlashStringHelper *Pnames(IRTYPES Type) {
if(Type>LAST_PROTOCOL) Type=UNKNOWN;
// You can add additional strings before the entry for hash code.
const __FlashStringHelper *Names[LAST_PROTOCOL+1]={F("Unknown"),F("NEC"),F("Sony"),F("RC5"),F("RC6"),F("Panasonic Old"),F("JVC"),F("NECx"),F("Hash Code")};
return Names[Type];
};
#define TOPBIT 0x80000000
/*
* The IRsend classes contain a series of methods for sending various protocols.
* Each of these begin by calling enableIROut(unsigned char kHz) to set the carrier frequency.
* It then calls mark(int usec) and space(inc usec) to transmit marks and
* spaces of varying length of microseconds however the protocol defines.
* Because we want to separate the hardware specific portions of the code from the general programming
* portions of the code, the code for IRsendBase::IRsendBase, IRsendBase::enableIROut,
* IRsendBase::mark and IRsendBase::space can be found in the lower section of this file.
*/
/*
* Most of the protocols have a header consisting of a mark/space of a particular length followed by
* a series of variable length mark/space signals. Depending on the protocol they very the lengths of the
* mark or the space to indicate a data bit of "0" or "1". Most also end with a stop bit of "1".
* The basic structure of the sending and decoding these protocols led to lots of redundant code.
* Therefore I have implemented generic sending and decoding routines. You just need to pass a bunch of customized
* parameters and it does the work. This reduces compiled code size with only minor speed degradation.
* You may be able to implement additional protocols by simply passing the proper values to these generic routines.
* The decoding routines do not encode stop bits. So you have to tell this routine whether or not to send one.
*/
void IRsendBase::sendGeneric(unsigned long data, unsigned char Num_Bits, unsigned int Head_Mark, unsigned int Head_Space,
unsigned int Mark_One, unsigned int Mark_Zero, unsigned int Space_One, unsigned int Space_Zero,
unsigned char kHz, bool Use_Stop, unsigned long Max_Extent) {
Extent=0;
data = data << (32 - Num_Bits);
enableIROut(kHz);
//Some protocols do not send a header when sending repeat codes. So we pass a zero value to indicate skipping this.
if(Head_Mark) mark(Head_Mark);
if(Head_Space) space(Head_Space);
for (int i = 0; i <Num_Bits; i++) {
if (data & TOPBIT) {
mark(Mark_One); space(Space_One);
}
else {
mark(Mark_Zero); space(Space_Zero);
}
data <<= 1;
}
if(Use_Stop) mark(Mark_One); //stop bit of "1"
if(Max_Extent) {
#ifdef IRLIB_TRACE
Serial.print("Max_Extent="); Serial.println(Max_Extent);
Serial.print("Extent="); Serial.println(Extent);
Serial.print("Difference="); Serial.println(Max_Extent-Extent);
#endif
space(Max_Extent-Extent);
}
else space(Space_One);
};
void IRsendNEC::send(unsigned long data)
{
if (data==REPEAT) {
enableIROut(38);
mark (564* 16); space(564*4); mark(564);space(56*173);
}
else {
sendGeneric(data,32, 564*16, 564*8, 564, 564, 564*3, 564, 38, true);
}
};
/*
* Sony is backwards from most protocols. It uses a variable length mark and a fixed length space rather than
* a fixed mark and a variable space. Our generic send will still work. According to the protocol you must send
* Sony commands at least three times so we automatically do it here.
*/
void IRsendSony::send(unsigned long data, int nbits) {
for(int i=0; i<3;i++){
sendGeneric(data,nbits, 600*4, 600, 600*2, 600, 600, 600, 40, false,((nbits==8)? 22000:45000));
}
};
/*
* This next section of send routines were added by Chris Young. They all use the generic send.
*/
void IRsendNECx::send(unsigned long data)
{
sendGeneric(data,32, 564*8, 564*8, 564, 564, 564*3, 564, 38, true, 108000);
};
void IRsendPanasonic_Old::send(unsigned long data)
{
sendGeneric(data,22, 833*4, 833*4, 833, 833, 833*3, 833,57, true);
};
/*
* JVC omits the mark/space header on repeat sending. Therefore we multiply it by 0 if it's a repeat.
* The only device I had to test this protocol was an old JVC VCR. It would only work if at least
* 2 frames are sent separated by 45us of "space". Therefore you should call this routine once with
* "First=true" and it will send a first frame followed by one repeat frame. If First== false,
* it will only send a single repeat frame.
*/
void IRsendJVC::send(unsigned long data, bool First)
{
sendGeneric(data, 16,525*16*First, 525*8*First, 525, 525,525*3, 525, 38, true);
space(525*45);
if(First) sendGeneric(data, 16,0,0, 525, 525,525*3, 525, 38, true);
}
/*
* The remaining protocols require special treatment. They were in the original IRremote library.
*/
void IRsendRaw::send(unsigned int buf[], unsigned char len, unsigned char hz)
{
enableIROut(hz);
for (unsigned char i = 0; i < len; i++) {
if (i & 1) {
space(buf[i]);
}
else {
mark(buf[i]);
}
}
space(0); // Just to be sure
}
/*
* The RC5 protocol uses a phase encoding of data bits. A space/mark pair indicates "1"
* and a mark/space indicates a "0". It begins with a single "1" bit which is not encoded
* in the data. The high order data bit is a toggle bit that indicates individual
* keypresses. You must toggle this bit yourself when sending data.
*/
#define RC5_T1 889
#define RC5_RPT_LENGTH 46000
void IRsendRC5::send(unsigned long data)
{
enableIROut(36);
data = data << (32 - 13);
Extent=0;
mark(RC5_T1); // First start bit
//Note: Original IRremote library incorrectly assumed second bit was always a "1"
//bit patterns from this decoder are not backward compatible with patterns produced
//by original library. Uncomment the following two lines to maintain backward compatibility.
//space(RC5_T1); // Second start bit
//mark(RC5_T1); // Second start bit
for (unsigned char i = 0; i < 13; i++) {
if (data & TOPBIT) {
space(RC5_T1); mark(RC5_T1);// 1 is space, then mark
}
else {
mark(RC5_T1); space(RC5_T1);// 0 is mark, then space
}
data <<= 1;
}
space(114000-Extent); // Turn off at end
}
/*
* The RC6 protocol also phase encodes databits although the phasing is opposite of RC5.
*/
#define RC6_HDR_MARK 2666
#define RC6_HDR_SPACE 889
#define RC6_T1 444
void IRsendRC6::send(unsigned long data, unsigned char nbits)
{
enableIROut(36);
data = data << (32 - nbits);
Extent=0;
mark(RC6_HDR_MARK); space(RC6_HDR_SPACE);
mark(RC6_T1); space(RC6_T1);// start bit "1"
int t;
for (int i = 0; i < nbits; i++) {
if (i == 3) {
t = 2 * RC6_T1; // double-wide trailer bit
}
else {
t = RC6_T1;
}
if (data & TOPBIT) {
mark(t); space(t);//"1" is a Mark/space
}
else {
space(t); mark(t);//"0" is a space/Mark
}
data <<= 1;
}
space(107000-Extent); // Turn off at end
}
/*
* This method can be used to send any of the supported types except for raw and hash code.
* There is no hash code send possible. You can call sendRaw directly if necessary.
* Typically "data2" is the number of bits.
*/
void IRsend::send(IRTYPES Type, unsigned long data, unsigned int data2) {
switch(Type) {
case NEC: IRsendNEC::send(data); break;
case SONY: IRsendSony::send(data,data2); break;
case RC5: IRsendRC5::send(data); break;
case RC6: IRsendRC6::send(data,data2); break;
case PANASONIC_OLD: IRsendPanasonic_Old::send(data); break;
case NECX: IRsendNECx::send(data); break;
case JVC: IRsendJVC::send(data,(bool)data2); break;
//case ADDITIONAL: IRsendADDITIONAL::send(data); break;//add additional protocols here
//You should comment out protocols you will likely never use and/or add extra protocols here
}
}
/*
* The irparams definitions which were located here have been moved to IRLibRData.h
*/
/*
* We've chosen to separate the decoding routines from the receiving routines to isolate
* the technical hardware and interrupt portion of the code which should never need modification
* from the protocol decoding portion that will likely be extended and modified. It also allows for
* creation of alternative receiver classes separate from the decoder classes.
*/
IRdecodeBase::IRdecodeBase(void) {
rawbuf=(volatile unsigned int*)irparams.rawbuf;
IgnoreHeader=false;
Reset();
};
/*
* Normally the decoder uses irparams.rawbuf but if you want to resume receiving while
* still decoding you can define a separate buffer and pass the address here.
* Then IRrecvBase::GetResults will copy the raw values from its buffer to yours allowing you to
* call IRrecvBase::resume immediately before you call decode.
*/
void IRdecodeBase::UseExtnBuf(void *P){
rawbuf=(volatile unsigned int*)P;
};
/*
* Copies rawbuf and rawlen from one decoder to another. See IRhashdecode example
* for usage.
*/
void IRdecodeBase::copyBuf (IRdecodeBase *source){
memcpy((void *)rawbuf,(const void *)source->rawbuf,sizeof(irparams.rawbuf));
rawlen=source->rawlen;
};
/*
* This routine is actually quite useful. Allows extended classes to call their parent
* if they fail to decode themselves.
*/
bool IRdecodeBase::decode(void) {
return false;
};
void IRdecodeBase::Reset(void) {
decode_type= UNKNOWN;
value=0;
bits=0;
rawlen=0;
};
#ifndef USE_DUMP
void DumpUnavailable(void) {Serial.println(F("DumpResults unavailable"));}
#endif
/*
* This method dumps useful information about the decoded values.
*/
void IRdecodeBase::DumpResults(void) {
#ifdef USE_DUMP
int i;unsigned long Extent;int interval;
if(decode_type<=LAST_PROTOCOL){
Serial.print(F("Decoded ")); Serial.print(Pnames(decode_type));
Serial.print(F("(")); Serial.print(decode_type,DEC);
Serial.print(F("): Value:")); Serial.print(value, HEX);
};
Serial.print(F(" (")); Serial.print(bits, DEC); Serial.println(F(" bits)"));
Serial.print(F("Raw samples(")); Serial.print(rawlen, DEC);
Serial.print(F("): Gap:")); Serial.println(rawbuf[0], DEC);
Serial.print(F(" Head: m")); Serial.print(rawbuf[1], DEC);
Serial.print(F(" s")); Serial.println(rawbuf[2], DEC);
int LowSpace= 32767; int LowMark= 32767;
int HiSpace=0; int HiMark= 0;
Extent=rawbuf[1]+rawbuf[2];
for (i = 3; i < rawlen; i++) {
Extent+=(interval= rawbuf[i]);
if (i % 2) {
LowMark=min(LowMark, interval); HiMark=max(HiMark, interval);
Serial.print(i/2-1,DEC); Serial.print(F(":m"));
}
else {
if(interval>0)LowSpace=min(LowSpace, interval); HiSpace=max (HiSpace, interval);
Serial.print(F(" s"));
}
Serial.print(interval, DEC);
int j=i-1;
if ((j % 2)==1)Serial.print(F("\t"));
if ((j % 4)==1)Serial.print(F("\t "));
if ((j % 8)==1)Serial.println();
if ((j % 32)==1)Serial.println();
}
Serial.println();
Serial.print(F("Extent=")); Serial.println(Extent,DEC);
Serial.print(F("Mark min:")); Serial.print(LowMark,DEC);Serial.print(F("\t max:")); Serial.println(HiMark,DEC);
Serial.print(F("Space min:")); Serial.print(LowSpace,DEC);Serial.print(F("\t max:")); Serial.println(HiSpace,DEC);
Serial.println();
#else
DumpUnavailable();
#endif
}
/*
* Again we use a generic routine because most protocols have the same basic structure. However we need to
* indicate whether or not the protocol varies the length of the mark or the space to indicate a "0" or "1".
* If "Mark_One" is zero. We assume that the length of the space varies. If "Mark_One" is not zero then
* we assume that the length of Mark varies and the value passed as "Space_Zero" is ignored.
* When using variable length Mark, assumes Head_Space==Space_One. If it doesn't, you need a specialized decoder.
*/
bool IRdecodeBase::decodeGeneric(unsigned char Raw_Count, unsigned int Head_Mark, unsigned int Head_Space,
unsigned int Mark_One, unsigned int Mark_Zero, unsigned int Space_One, unsigned int Space_Zero) {
// If raw samples count or head mark are zero then don't perform these tests.
// Some protocols need to do custom header work.
unsigned long data = 0; unsigned char Max; offset=1;
if (Raw_Count) {if (rawlen != Raw_Count) return RAW_COUNT_ERROR;}
if(!IgnoreHeader) {
if (Head_Mark) {
if (!MATCH(rawbuf[offset],Head_Mark)) return HEADER_MARK_ERROR(Head_Mark);
}
}
offset++;
if (Head_Space) {if (!MATCH(rawbuf[offset],Head_Space)) return HEADER_SPACE_ERROR(Head_Space);}
if (Mark_One) {//Length of a mark indicates data "0" or "1". Space_Zero is ignored.
offset=2;//skip initial gap plus header Mark.
Max=rawlen;
while (offset < Max) {
if (!MATCH(rawbuf[offset], Space_One)) return DATA_SPACE_ERROR(Space_One);
offset++;
if (MATCH(rawbuf[offset], Mark_One)) {
data = (data << 1) | 1;
}
else if (MATCH(rawbuf[offset], Mark_Zero)) {
data <<= 1;
}
else return DATA_MARK_ERROR(Mark_Zero);
offset++;
}
bits = (offset - 1) / 2;
}
else {//Mark_One was 0 therefore length of a space indicates data "0" or "1".
Max=rawlen-1; //ignore stop bit
offset=3;//skip initial gap plus two header items
while (offset < Max) {
if (!MATCH (rawbuf[offset],Mark_Zero)) return DATA_MARK_ERROR(Mark_Zero);
offset++;
if (MATCH(rawbuf[offset],Space_One)) {
data = (data << 1) | 1;
}
else if (MATCH (rawbuf[offset],Space_Zero)) {
data <<= 1;
}
else return DATA_SPACE_ERROR(Space_Zero);
offset++;
}
bits = (offset - 1) / 2 -1;//didn't encode stop bit
}
// Success
value = data;
return true;
}
/*
* This routine has been modified significantly from the original IRremote.
* It assumes you've already called IRrecvBase::GetResults and it was true.
* The purpose of GetResults is to determine if a complete set of signals
* has been received. It then copies the raw data into your decoder's rawbuf
* By moving the test for completion and the copying of the buffer
* outside of this "decode" method you can use the individual decode
* methods or make your own custom "decode" without checking for
* protocols you don't use.
* Note: Don't forget to call IRrecvBase::resume(); after decoding is complete.
*/
bool IRdecode::decode(void) {
if (IRdecodeNEC::decode()) return true;
if (IRdecodeSony::decode()) return true;
if (IRdecodeRC5::decode()) return true;
if (IRdecodeRC6::decode()) return true;
if (IRdecodePanasonic_Old::decode()) return true;
if (IRdecodeNECx::decode()) return true;
if (IRdecodeJVC::decode()) return true;
//if (IRdecodeADDITIONAL::decode()) return true;//add additional protocols here
//Deliberately did not add hash code decoding. If you get decode_type==UNKNOWN and
// you want to know a hash code you can call IRhash::decode() yourself.
// BTW This is another reason we separated IRrecv from IRdecode.
return false;
}
#define NEC_RPT_SPACE 2250
bool IRdecodeNEC::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("NEC"));
// Check for repeat
if (rawlen == 4 && MATCH(rawbuf[2], NEC_RPT_SPACE) &&
MATCH(rawbuf[3],564)) {
bits = 0;
value = REPEAT;
decode_type = NEC;
return true;
}
if(!decodeGeneric(68, 564*16, 564*8, 0, 564, 564*3, 564)) return false;
decode_type = NEC;
return true;
}
// According to http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sony8
// Sony protocol can only be 8, 12, 15, or 20 bits in length.
bool IRdecodeSony::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("Sony"));
if(rawlen!=2*8+2 && rawlen!=2*12+2 && rawlen!=2*15+2 && rawlen!=2*20+2) return RAW_COUNT_ERROR;
if(!decodeGeneric(0, 600*4, 600, 600*2, 600, 600,0)) return false;
decode_type = SONY;
return true;
}
/*
* The next several decoders were added by Chris Young. They illustrate some of the special cases
* that can come up when decoding using the generic decoder.
*/
/*
* A very good source for protocol information is... http://www.hifi-remote.com/johnsfine/DecodeIR.html
* I used that information to understand what they call the "Panasonic old" protocol which is used by
* Scientific Atlanta cable boxes. That website uses a very strange notation called IRP notation.
* For this protocol, the notation was:
* {57.6k,833}<1,-1|1,-3>(4,-4,D:5,F:6,~D:5,~F:6,1,-???)+
* This indicates that the frequency is 57.6, the base length for the pulse is 833
* The first part of the <x,-x|x,-x> section tells you what a "0" is and the second part
* tells you what a "1" is. That means "0" is 833 on, 833 off while an "1" is 833 on
* followed by 833*3=2499 off. The section in parentheses tells you what data gets sent.
* The protocol begins with header consisting of 4*833 on and 4*833 off. The other items
* describe what the remaining data bits are.
* It reads as 5 device bits followed by 6 function bits. You then repeat those bits complemented.
* It concludes with a single "1" bit followed by and an undetermined amount of blank space.
* This makes the entire protocol 5+6+5+6= 22 bits long since we don't encode the stop bit.
* The "+" at the end means you only need to send it once and it can repeat as many times as you want.
*/
bool IRdecodePanasonic_Old::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("Panasonic_Old"));
if(!decodeGeneric(48,833*4,833*4,0,833,833*3,833)) return false;
/*
* The protocol spec says that the first 11 bits described the device and function.
* The next 11 bits are the same thing only it is the logical Bitwise complement.
* Many protocols have such check features in their definition but our code typically doesn't
* perform these checks. For example NEC's least significant 8 bits are the complement of
* of the next more significant 8 bits. While it's probably not necessary to error check this,
* you can un-comment the next 4 lines of code to do this extra checking.
*/
// long S1= (value & 0x0007ff); // 00 0000 0000 0111 1111 1111 //00000 000000 11111 111111
// long S2= (value & 0x3ff800)>> 11; // 11 1111 1111 1000 0000 0000 //11111 111111 00000 000000
// S2= (~S2) & 0x0007ff;
// if (S1!=S2) return IRLIB_REJECTION_MESSAGE(F("inverted bit redundancy"));
// Success
decode_type = PANASONIC_OLD;
return true;
}
bool IRdecodeNECx::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("NECx"));
if(!decodeGeneric(68,564*8,564*8,0,564,564*3,564)) return false;
decode_type = NECX;
return true;
}
// JVC does not send any header if there is a repeat.
bool IRdecodeJVC::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("JVC"));
if(!decodeGeneric(36,525*16,525*8,0,525,525*3,525))
{
IRLIB_ATTEMPT_MESSAGE(F("JVC Repeat"));
if (rawlen==34)
{
if(!decodeGeneric(0,525,0,0,525,525*3,525))
{return IRLIB_REJECTION_MESSAGE(F("JVC repeat failed generic"));}
else {
//If this is a repeat code then IRdecodeBase::decode fails to add the most significant bit
if (MATCH(rawbuf[4],(525*3)))
{
value |= 0x8000;
}
else
{
if (!MATCH(rawbuf[4],525)) return DATA_SPACE_ERROR(525);
}
}
bits++;
}
else return RAW_COUNT_ERROR;
}
decode_type =JVC;
return true;
}
/*
* The remaining protocols from the original IRremote library require special handling
* This routine gets one undecoded level at a time from the raw buffer.
* The RC5/6 decoding is easier if the data is broken into time intervals.
* E.g. if the buffer has MARK for 2 time intervals and SPACE for 1,
* successive calls to getRClevel will return MARK, MARK, SPACE.
* offset and used are updated to keep track of the current position.
* t1 is the time interval for a single bit in microseconds.
* Returns ERROR if the measured time interval is not a multiple of t1.
*/
IRdecodeRC::RCLevel IRdecodeRC::getRClevel(unsigned char *used, const unsigned int t1) {
if (offset >= rawlen) {
// After end of recorded buffer, assume SPACE.
return SPACE;
}
unsigned int width = rawbuf[offset];
IRdecodeRC::RCLevel val;
if ((offset) % 2) val=MARK; else val=SPACE;
unsigned char avail;
if (MATCH(width, t1)) {
avail = 1;
}
else if (MATCH(width, 2*t1)) {
avail = 2;
}
else if (MATCH(width, 3*t1)) {
avail = 3;
}
else {
if((IgnoreHeader) && (offset==1) && (width<t1))
avail =1;
else{
return ERROR;}
}
(*used)++;
if (*used >= avail) {
*used = 0;
(offset)++;
}
return val;
}
#define MIN_RC5_SAMPLES 11
#define MIN_RC6_SAMPLES 1
bool IRdecodeRC5::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("RC5"));
if (rawlen < MIN_RC5_SAMPLES + 2) return RAW_COUNT_ERROR;
offset = 1; // Skip gap space
data = 0;
used = 0;
// Get start bits
if (getRClevel(&used, RC5_T1) != MARK) return HEADER_MARK_ERROR(RC5_T1);
//Note: Original IRremote library incorrectly assumed second bit was always a "1"
//bit patterns from this decoder are not backward compatible with patterns produced
//by original library. Uncomment the following two lines to maintain backward compatibility.
//if (getRClevel(&used, RC5_T1) != SPACE) return HEADER_SPACE_ERROR(RC5_T1);
//if (getRClevel(&used, RC5_T1) != MARK) return HEADER_MARK_ERROR(RC5_T1);
for (nbits = 0; offset < rawlen; nbits++) {
RCLevel levelA = getRClevel(&used, RC5_T1);
RCLevel levelB = getRClevel(&used, RC5_T1);
if (levelA == SPACE && levelB == MARK) {
// 1 bit
data = (data << 1) | 1;
}
else if (levelA == MARK && levelB == SPACE) {
// zero bit
data <<= 1;
}
else return DATA_MARK_ERROR(RC5_T1);
}
// Success
bits = 13;
value = data;
decode_type = RC5;
return true;
}
bool IRdecodeRC6::decode(void) {
IRLIB_ATTEMPT_MESSAGE(F("RC6"));
if (rawlen < MIN_RC6_SAMPLES) return RAW_COUNT_ERROR;
// Initial mark
if (!IgnoreHeader) {
if (!MATCH(rawbuf[1], RC6_HDR_MARK)) return HEADER_MARK_ERROR(RC6_HDR_MARK);
}
if (!MATCH(rawbuf[2], RC6_HDR_SPACE)) return HEADER_SPACE_ERROR(RC6_HDR_SPACE);
offset=3;//Skip gap and header
data = 0;
used = 0;
// Get start bit (1)
if (getRClevel(&used, RC6_T1) != MARK) return DATA_MARK_ERROR(RC6_T1);
if (getRClevel(&used, RC6_T1) != SPACE) return DATA_SPACE_ERROR(RC6_T1);
for (nbits = 0; offset < rawlen; nbits++) {
RCLevel levelA, levelB; // Next two levels
levelA = getRClevel(&used, RC6_T1);
if (nbits == 3) {
// T bit is double wide; make sure second half matches
if (levelA != getRClevel(&used, RC6_T1)) return TRAILER_BIT_ERROR(RC6_T1);
}
levelB = getRClevel(&used, RC6_T1);
if (nbits == 3) {
// T bit is double wide; make sure second half matches
if (levelB != getRClevel(&used, RC6_T1)) return TRAILER_BIT_ERROR(RC6_T1);
}
if (levelA == MARK && levelB == SPACE) { // reversed compared to RC5
// 1 bit
data = (data << 1) | 1;
}
else if (levelA == SPACE && levelB == MARK) {
// zero bit
data <<= 1;
}
else {
return DATA_MARK_ERROR(RC6_T1);
}
}
// Success
bits = nbits;
value = data;
decode_type = RC6;
return true;
}
/*
* This Hash decoder is based on IRhashcode
* Copyright 2010 Ken Shirriff
* For details see http://www.righto.com/2010/01/using-arbitrary-remotes-with-arduino.html
* Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
* Converts the raw code values into a 32-bit hash code.
* Hopefully this code is unique for each button.
*/
#define FNV_PRIME_32 16777619
#define FNV_BASIS_32 2166136261
// Compare two tick values, returning 0 if newval is shorter,
// 1 if newval is equal, and 2 if newval is longer
int IRdecodeHash::compare(unsigned int oldval, unsigned int newval) {
if (newval < oldval * .8) return 0;
if (oldval < newval * .8) return 2;
return 1;
}
bool IRdecodeHash::decode(void) {
hash = FNV_BASIS_32;
for (int i = 1; i+2 < rawlen; i++) {
hash = (hash * FNV_PRIME_32) ^ compare(rawbuf[i], rawbuf[i+2]);
}
//note: does not set decode_type=HASH_CODE nor "value" because you might not want to.
return true;
}
/* We have created a new receiver base class so that we can use its code to implement
* additional receiver classes in addition to the original IRremote code which used
* 50us interrupt sampling of the input pin. See IRrecvLoop and IRrecvPCI classes
* below. IRrecv is the original receiver class with the 50us sampling.
*/
IRrecvBase::IRrecvBase(unsigned char recvpin)
{
irparams.recvpin = recvpin;
Init();
}
void IRrecvBase::Init(void) {
irparams.blinkflag = 0;
Mark_Excess=100;
}
unsigned char IRrecvBase::getPinNum(void){
return irparams.recvpin;
}
/* Any receiver class must implement a GetResults method that will return true when a complete code
* has been received. At a successful end of your GetResults code you should then call IRrecvBase::GetResults
* and it will copy the data from the receiver structures into your decoder. Some receivers
* provide results in rawbuf measured in ticks on some number of microseconds while others
* return results in actual microseconds. If you use ticks then you should pass a multiplier
* value in Time_per_Ticks.
*/
bool IRrecvBase::GetResults(IRdecodeBase *decoder, const unsigned int Time_per_Tick) {
decoder->Reset();//clear out any old values.
decoder->rawlen = irparams.rawlen;
/* Typically IR receivers over-report the length of a mark and under-report the length of a space.
* This routine adjusts for that by subtracting Mark_Excess from recorded marks and
* deleting it from a recorded spaces. The amount of adjustment used to be defined in IRLibMatch.h.
* It is now user adjustable with the old default of 100;
* By copying the the values from irparams to decoder we can call IRrecvBase::resume
* immediately while decoding is still in progress.
*/
for(unsigned char i=0; i<irparams.rawlen; i++) {
decoder->rawbuf[i]=irparams.rawbuf[i]*Time_per_Tick + ( (i % 2)? -Mark_Excess:Mark_Excess);
}
return true;
}
void IRrecvBase::enableIRIn(void) {
pinMode(irparams.recvpin, INPUT);
resume();
}
void IRrecvBase::resume() {
irparams.rawlen = 0;
}
/* This receiver uses no interrupts or timers. Other interrupt driven receivers
* allow you to do other things and call GetResults at your leisure to see if perhaps
* a sequence has been received. Typically you would put GetResults in your loop
* and it would return false until the sequence had been received. However because this
* receiver uses no interrupts, it takes control of your program when you call GetResults
* and doesn't let go until it's got something to show you. The advantage is you don't need
* interrupts which would make it easier to use and nonstandard hardware and will allow you to
* use any digital input pin. Timing of this routine is only as accurate as your "micros();"
*/
bool IRrecvLoop::GetResults(IRdecodeBase *decoder) {
bool Finished=false;
byte OldState=HIGH;byte NewState;
unsigned long StartTime, DeltaTime, EndTime;
StartTime=micros();
while(irparams.rawlen<RAWBUF) { //While the buffer not overflowing
while(OldState==(NewState=digitalRead(irparams.recvpin))) { //While the pin hasn't changed
if( (DeltaTime = (EndTime=micros()) - StartTime) > 10000) { //If it's a very long wait
if(Finished=irparams.rawlen) break; //finished unless it's the opening gap
}
}
if(Finished) break;
do_Blink();
irparams.rawbuf[irparams.rawlen++]=DeltaTime;
OldState=NewState;StartTime=EndTime;
};
IRrecvBase::GetResults(decoder);
return true;
}
#ifdef USE_ATTACH_INTERRUPTS
/* This receiver uses the pin change hardware interrupt to detect when your input pin
* changes state. It gives more detailed results than the 50µs interrupts of IRrecv
* and theoretically is more accurate than IRrecvLoop. However because it only detects
* pin changes, it doesn't always know when it's finished. GetResults attempts to detect
* a long gap of space but sometimes the next signal gets there before GetResults notices.
* This means the second set of signals can get messed up unless there is a long gap.
* This receiver is based in part on Arduino firmware for use with AnalysIR IR signal analysis
* software for Windows PCs. Many thanks to the people at http://analysir.com for their
* assistance in developing this section of code.
*/
IRrecvPCI::IRrecvPCI(unsigned char inum) {
Init();
intrnum=inum;
irparams.recvpin=Pin_from_Intr(inum);
}
void IRrecvPCI_Handler(){
unsigned long volatile ChangeTime=micros();
unsigned long DeltaTime=ChangeTime-irparams.timer;
switch(irparams.rcvstate) {
case STATE_STOP: return;
case STATE_RUNNING:
do_Blink();
if (DeltaTime>10000) {
irparams.rcvstate=STATE_STOP;
//Setting gap to 0 is a flag to let you know why we stopped For debugging purposes
//irparams.rawbuf[0]=0;
return;
};
break;
case STATE_IDLE:
if(digitalRead(irparams.recvpin)) return; else irparams.rcvstate=STATE_RUNNING;
break;
};
irparams.rawbuf[irparams.rawlen]=DeltaTime;
irparams.timer=ChangeTime;
if(++irparams.rawlen>=RAWBUF) {
irparams.rcvstate=STATE_STOP;
//Setting gap to 1 is a flag to let you know why we stopped For debugging purposes
//irparams.rawbuf[0]=1;
}
}
void IRrecvPCI::resume(void) {
irparams.rcvstate = STATE_IDLE;
IRrecvBase::resume();
irparams.timer=micros();
attachInterrupt(intrnum, IRrecvPCI_Handler, CHANGE);
};
bool IRrecvPCI::GetResults(IRdecodeBase *decoder) {
if(irparams.rcvstate==STATE_RUNNING) {
unsigned long ChangeTime=irparams.timer;
if( (micros()-ChangeTime) > 10000) {
irparams.rcvstate=STATE_STOP;
//Setting gap to 2 is a flag to let you know why we stopped For debugging purposes
//irparams.rawbuf[0]=2;
}
}
if (irparams.rcvstate != STATE_STOP) return false;
detachInterrupt(intrnum);
IRrecvBase::GetResults(decoder);
return true;
};
/* This class facilitates detection of frequency of an IR signal. Requires a TSMP58000
* or equivalent device connected to the hardware interrupt pin.
* Create an instance of the object passing the interrupt number.
*/
volatile unsigned FREQUENCY_BUFFER_TYPE *IRfreqTimes;
volatile unsigned char IRfreqCount;
IRfrequency::IRfrequency(unsigned char inum) { //Note this is interrupt number, not pin number
intrnum=inum;
pin= Pin_from_Intr(inum);
//ISR cannot be passed parameters. If I declare the buffer global it would
//always eat RAN even if this object was not declared. So we make global pointer
//and copy the address to it. ISR still puts data in the object.
IRfreqTimes= & (Time_Stamp[0]);
};
// Note ISR handler cannot be part of a class/object
void IRfreqISR(void) {
IRfreqTimes[IRfreqCount++]=micros();
}
void IRfrequency::enableFreqDetect(void){
attachInterrupt(intrnum,IRfreqISR, FALLING);
for(i=0; i<256; i++) Time_Stamp[i]=0;
IRfreqCount=0;
Results= 0.0;
Samples=0;
};
/* Test to see if we have collected at least one full buffer of data.
* Note values are always zeroed before beginning so any non-zero data
* in the final elements means we have collected at least a buffer full.
* By chance the final might be zero so we test two of them. Would be
* nearly impossible for two consecutive elements to be zero unless
* we had not yet collected data.
*/
bool IRfrequency::HaveData(void) {
return (Time_Stamp[255] || Time_Stamp[254]);
};
void IRfrequency::disableFreqDetect(void){
detachInterrupt(intrnum);
};
void IRfrequency::ComputeFreq(void){
Samples=0; Sum=0;
for(i=1; i<256; i++) {
unsigned char Interval=Time_Stamp[i]-Time_Stamp[i-1];
if(Interval>50 || Interval<10) continue;//ignore extraneous results
Sum+=Interval;//accumulate usable intervals
Samples++; //account usable intervals
};
if(Sum)
Results=(double) Samples/(double)Sum*1000;
else
Results= 0.0;
};
//Didn't need to be a method that we made one following example of IRrecvBase
unsigned char IRfrequency::getPinNum(void) {
return pin;
}
void IRfrequency::DumpResults(bool Detail) {
ComputeFreq();
#ifdef USE_DUMP
Serial.print(F("Number of samples:")); Serial.print(Samples,DEC);
Serial.print(F("\t Total interval (us):")); Serial.println(Sum,DEC);
Serial.print(F("Avg. interval(us):")); Serial.print(1.0*Sum/Samples,2);
Serial.print(F("\t Aprx. Frequency(kHz):")); Serial.print(Results,2);
Serial.print(F(" (")); Serial.print(int(Results+0.5),DEC);
Serial.println(F(")"));
if(Detail) {
for(i=1; i<256; i++) {
unsigned int Interval=Time_Stamp[i]-Time_Stamp[i-1];
Serial.print(Interval,DEC); Serial.print("\t");
if ((i % 4)==0)Serial.print(F("\t "));
if ((i % 8)==0)Serial.println();
if ((i % 32)==0)Serial.println();
}
Serial.println();
}
#else
DumpUnavailable();
#endif
};
#endif // ifdef USE_ATTACH_INTERRUPTS
/*
* The remainder of this file is all related to interrupt handling and hardware issues. It has
* nothing to do with IR protocols. You need not understand this is all you're doing is adding
* new protocols or improving the receiving, decoding and sending of protocols.
*/
//See IRLib.h comment explaining this function
unsigned char Pin_from_Intr(unsigned char inum) {
const unsigned char PROGMEM attach_to_pin[]= {
#if defined(__AVR_ATmega256RFR2__)//Assume Pinoccio Scout
4,5,SCL,SDA,RX1,TX1,7
#elif defined(__AVR_ATmega32U4__) //Assume Arduino Leonardo
3,2,0,1,7
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)//Assume Arduino Mega
2,3, 21, 20, 1, 18
#else //Assume Arduino Uno or other ATmega328
2, 3
#endif
};
#if defined(ARDUINO_SAM_DUE)
return inum;
#endif
if (inum<sizeof attach_to_pin) {//note this works because we know it's one byte per entry
return attach_to_pin[inum];
} else {
return 255;
}
}
// Provides ISR
#include <avr/interrupt.h>
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define CLKFUDGE 5 // fudge factor for clock interrupt overhead
#ifdef F_CPU
#define SYSCLOCK F_CPU // main Arduino clock
#else
#define SYSCLOCK 16000000 // main Arduino clock
#endif
#define PRESCALE 8 // timer clock prescale
#define CLKSPERUSEC (SYSCLOCK/PRESCALE/1000000) // timer clocks per microsecond
#include <IRLibTimer.h>
/*
* This section contains the hardware specific portions of IRrecvBase
*/
/* If your hardware is set up to do both output and input but your particular sketch
* doesn't do any output, this method will ensure that your output pin is low
* and doesn't turn on your IR LED or any output circuit.
*/
void IRrecvBase::No_Output (void) {
#if defined(IR_SEND_PWM_PIN)
pinMode(IR_SEND_PWM_PIN, OUTPUT);
digitalWrite(IR_SEND_PWM_PIN, LOW); // When not sending PWM, we want it low
#endif
}
// enable/disable blinking of pin 13 on IR processing
void IRrecvBase::blink13(bool blinkflag)
{
irparams.blinkflag = blinkflag;
if (blinkflag)
pinMode(BLINKLED, OUTPUT);
}
//Do the actual blinking off and on
//This is not part of IRrecvBase because it may need to be inside an ISR
//and we cannot pass parameters to them.
void do_Blink(void) {
if (irparams.blinkflag) {
if(irparams.rawlen % 2) {
BLINKLED_ON(); // turn pin 13 LED on
}
else {
BLINKLED_OFF(); // turn pin 13 LED off
}
}
}
#ifdef USE_IRRECV
/*
* The original IRrecv which uses 50µs timer driven interrupts to sample input pin.
*/
void IRrecv::resume() {
// initialize state machine variables
irparams.rcvstate = STATE_IDLE;
IRrecvBase::resume();
}
void IRrecv::enableIRIn(void) {
IRrecvBase::enableIRIn();
// setup pulse clock timer interrupt
cli();
IR_RECV_CONFIG_TICKS();
IR_RECV_ENABLE_INTR;
sei();
}
bool IRrecv::GetResults(IRdecodeBase *decoder) {
if (irparams.rcvstate != STATE_STOP) return false;
IRrecvBase::GetResults(decoder,USECPERTICK);
return true;
}
#define _GAP 5000 // Minimum map between transmissions
#define GAP_TICKS (_GAP/USECPERTICK)
/*
* This interrupt service routine is only used by IRrecv and may or may not be used by other
* extensions of the IRrecBase. It is timer driven interrupt code to collect raw data.
* Widths of alternating SPACE, MARK are recorded in rawbuf. Recorded in ticks of 50 microseconds.
* rawlen counts the number of entries recorded so far. First entry is the SPACE between transmissions.
* As soon as a SPACE gets long, ready is set, state switches to IDLE, timing of SPACE continues.
* As soon as first MARK arrives, gap width is recorded, ready is cleared, and new logging starts.
*/
ISR(IR_RECV_INTR_NAME)
{
enum irdata_t {IR_MARK=0, IR_SPACE=1};
irdata_t irdata = (irdata_t)digitalRead(irparams.recvpin);
irparams.timer++; // One more 50us tick
if (irparams.rawlen >= RAWBUF) {
// Buffer overflow
irparams.rcvstate = STATE_STOP;
}
switch(irparams.rcvstate) {
case STATE_IDLE: // In the middle of a gap
if (irdata == IR_MARK) {
if (irparams.timer < GAP_TICKS) {
// Not big enough to be a gap.
irparams.timer = 0;
}
else {
// gap just ended, record duration and start recording transmission
irparams.rawlen = 0;
irparams.rawbuf[irparams.rawlen++] = irparams.timer;
irparams.timer = 0;
irparams.rcvstate = STATE_MARK;
}
}
break;
case STATE_MARK: // timing MARK
if (irdata == IR_SPACE) { // MARK ended, record time
irparams.rawbuf[irparams.rawlen++] = irparams.timer;
irparams.timer = 0;
irparams.rcvstate = STATE_SPACE;
}
break;
case STATE_SPACE: // timing SPACE
if (irdata == IR_MARK) { // SPACE just ended, record it
irparams.rawbuf[irparams.rawlen++] = irparams.timer;
irparams.timer = 0;
irparams.rcvstate = STATE_MARK;
}
else { // SPACE
if (irparams.timer > GAP_TICKS) {
// big SPACE, indicates gap between codes
// Mark current code as ready for processing
// Switch to STOP
// Don't reset timer; keep counting space width
irparams.rcvstate = STATE_STOP;
}
}
break;
case STATE_STOP: // waiting, measuring gap
if (irdata == IR_MARK) { // reset gap timer
irparams.timer = 0;
}
break;
}
do_Blink();
}
#endif //end of ifdef USE_IRRECV
/*
* The hardware specific portions of IRsendBase
*/
void IRsendBase::enableIROut(unsigned char khz) {
//NOTE: the comments on this routine accompanied the original early version of IRremote library
//which only used TIMER2. The parameters defined in IRLibTimer.h may or may not work this way.
// Enables IR output. The khz value controls the modulation frequency in kilohertz.
// The IR output will be on pin 3 (OC2B).
// This routine is designed for 36-40KHz; if you use it for other values, it's up to you
// to make sure it gives reasonable results. (Watch out for overflow / underflow / rounding.)
// TIMER2 is used in phase-correct PWM mode, with OCR2A controlling the frequency and OCR2B
// controlling the duty cycle.
// There is no prescaling, so the output frequency is 16MHz / (2 * OCR2A)
// To turn the output on and off, we leave the PWM running, but connect and disconnect the output pin.
// A few hours staring at the ATmega documentation and this will all make sense.
// See my Secrets of Arduino PWM at http://www.righto.com/2009/07/secrets-of-arduino-pwm.html for details.
// Disable the Timer2 Interrupt (which is used for receiving IR)
IR_RECV_DISABLE_INTR; //Timer2 Overflow Interrupt
pinMode(IR_SEND_PWM_PIN, OUTPUT);
digitalWrite(IR_SEND_PWM_PIN, LOW); // When not sending PWM, we want it low
IR_SEND_CONFIG_KHZ(khz);
}
IRsendBase::IRsendBase () {
pinMode(IR_SEND_PWM_PIN, OUTPUT);
digitalWrite(IR_SEND_PWM_PIN, LOW); // When not sending PWM, we want it low
}
//The Arduino built in function delayMicroseconds has limits we wish to exceed
//Therefore we have created this alternative
void My_delay_uSecs(unsigned int T) {
if(T){if(T>16000) {delayMicroseconds(T % 1000); delay(T/1000); } else delayMicroseconds(T);};
}
void IRsendBase::mark(unsigned int time) {
IR_SEND_PWM_START;
IR_SEND_MARK_TIME(time);
Extent+=time;
}
void IRsendBase::space(unsigned int time) {
IR_SEND_PWM_STOP;
My_delay_uSecs(time);
Extent+=time;
}
/*
* Various debugging routines
*/
#ifdef IRLIB_TRACE
void IRLIB_ATTEMPT_MESSAGE(const __FlashStringHelper * s) {Serial.print(F("Attempting ")); Serial.print(s); Serial.println(F(" decode:"));};
void IRLIB_TRACE_MESSAGE(const __FlashStringHelper * s) {Serial.print(F("Executing ")); Serial.println(s);};
byte IRLIB_REJECTION_MESSAGE(const __FlashStringHelper * s) { Serial.print(F(" Protocol failed because ")); Serial.print(s); Serial.println(F(" wrong.")); return false;};
byte IRLIB_DATA_ERROR_MESSAGE(const __FlashStringHelper * s, unsigned char index, unsigned int value, unsigned int expected) {
IRLIB_REJECTION_MESSAGE(s); Serial.print(F("Error occurred with rawbuf[")); Serial.print(index,DEC); Serial.print(F("]=")); Serial.print(value,DEC);
Serial.print(F(" expected:")); Serial.println(expected,DEC); return false;
};
#endif
/* IRLib.h from IRLib – an Arduino library for infrared encoding and decoding
* Version 1.51 March 2015
* Copyright 2014 by Chris Young http://cyborg5.com
*
* This library is a major rewrite of IRemote by Ken Shirriff which was covered by
* GNU LESSER GENERAL PUBLIC LICENSE which as I read it allows me to make modified versions.
* That same license applies to this modified version. See his original copyright below.
* The latest Ken Shirriff code can be found at https://github.com/shirriff/Arduino-IRremote
* My purpose was to reorganize the code to make it easier to add or remove protocols.
* As a result I have separated the act of receiving a set of raw timing codes from the act of decoding them
* by making them separate classes. That way the receiving aspect can be more black box and implementers
* of decoders and senders can just deal with the decoding of protocols. It also allows for alternative
* types of receivers independent of the decoding. This makes porting to different hardware platforms easier.
* Also added provisions to make the classes base classes that could be extended with new protocols
* which would not require recompiling of the original library nor understanding of its detailed contents.
* Some of the changes were made to reduce code size such as unnecessary use of long versus bool.
* Some changes were just my weird programming style. Also extended debugging information added.
*/
/*
* IRremote
* Version 0.1 July, 2009
* Copyright 2009 Ken Shirriff
* For details, see http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html http://www.righto.com/
*
* Interrupt code based on NECIRrcv by Joe Knapp
* http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556
* Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/
*/
#ifndef IRLib_h
#define IRLib_h
#include <Arduino.h>
// The following are compile-time library options.
// If you change them, recompile the library.
// If IRLIB_TRACE is defined, some debugging information about the decode will be printed
// IRLIB_TEST must be defined for the IRtest unittests to work. It will make some
// methods virtual, which will be slightly slower, which is why it is optional.
//#define IRLIB_TRACE
// #define IRLIB_TEST
/* If not using the IRrecv class but only using IRrecvPCI or IRrecvLoop you can eliminate
* some conflicts with the duplicate definition of ISR by turning this feature off.
* Comment out the following define to eliminate the conflicts.
*/
#define USE_IRRECV
/* Similarly some other libraries have conflicts with the built in Arduino functions
* "attachInterrupt()" and "detachInterrupt()" which are used by the IRrecvPCI and
* IRfrequency classes. If you're not using either of those classes and get conflicts
* related to INT0_vect then comment out the following line to eliminate the conflicts.
*/
#define USE_ATTACH_INTERRUPTS
/* If not using either DumpResults methods of IRdecode nor IRfrequency you can
* comment out the following define to eliminate considerable program space.
*/
#define USE_DUMP
// Only used for testing; can remove virtual for shorter code
#ifdef IRLIB_TEST
#define VIRTUAL virtual
#else
#define VIRTUAL
#endif
#define RAWBUF 100 // Length of raw duration buffer (cannot exceed 255)
typedef char IRTYPES; //formerly was an enum
#define UNKNOWN 0
#define NEC 1
#define SONY 2
#define RC5 3
#define RC6 4
#define PANASONIC_OLD 5
#define JVC 6
#define NECX 7
//#define ADDITIONAL (number) //make additional protocol 8 and change HASH_CODE to 9
#define HASH_CODE 8
#define LAST_PROTOCOL HASH_CODE
const __FlashStringHelper *Pnames(IRTYPES Type); //Returns a character string that is name of protocol.
// Base class for decoding raw results
class IRdecodeBase
{
public:
IRdecodeBase(void);
IRTYPES decode_type; // NEC, SONY, RC5, UNKNOWN etc.
unsigned long value; // Decoded value
unsigned char bits; // Number of bits in decoded value
volatile unsigned int *rawbuf; // Raw intervals in microseconds
unsigned char rawlen; // Number of records in rawbuf.
bool IgnoreHeader; // Relaxed header detection allows AGC to settle
virtual void Reset(void); // Initializes the decoder
virtual bool decode(void); // This base routine always returns false override with your routine
bool decodeGeneric(unsigned char Raw_Count, unsigned int Head_Mark, unsigned int Head_Space,
unsigned int Mark_One, unsigned int Mark_Zero, unsigned int Space_One, unsigned int Space_Zero);
virtual void DumpResults (void);
void UseExtnBuf(void *P); //Normally uses same rawbuf as IRrecv. Use this to define your own buffer.
void copyBuf (IRdecodeBase *source);//copies rawbuf and rawlen from one decoder to another
protected:
unsigned char offset; // Index into rawbuf used various places
};
class IRdecodeHash: public virtual IRdecodeBase
{
public:
unsigned long hash;
virtual bool decode(void);//made virtual in case you want to substitute your own hash code
protected:
int compare(unsigned int oldval, unsigned int newval);//used by decodeHash
};
class IRdecodeNEC: public virtual IRdecodeBase
{
public:
virtual bool decode(void);
};
class IRdecodeSony: public virtual IRdecodeBase
{
public:
virtual bool decode(void);
};
class IRdecodeRC: public virtual IRdecodeBase
{
public:
enum RCLevel {MARK, SPACE, ERROR};//used by decoders for RC5/RC6
// These are called by decode
RCLevel getRClevel(unsigned char *used, const unsigned int t1);
protected:
unsigned char nbits;
unsigned char used;
long data;
};
class IRdecodeRC5: public virtual IRdecodeRC
{
public:
virtual bool decode(void);
};
class IRdecodeRC6: public virtual IRdecodeRC
{
public:
virtual bool decode(void);
};
class IRdecodePanasonic_Old: public virtual IRdecodeBase
{
public:
virtual bool decode(void);
};
class IRdecodeJVC: public virtual IRdecodeBase
{
public:
virtual bool decode(void);
};
class IRdecodeNECx: public virtual IRdecodeBase
{
public:
virtual bool decode(void);
};
// main class for decoding all supported protocols
class IRdecode:
public virtual IRdecodeNEC,
public virtual IRdecodeSony,
public virtual IRdecodeRC5,
public virtual IRdecodeRC6,
public virtual IRdecodePanasonic_Old,
public virtual IRdecodeJVC,
public virtual IRdecodeNECx
// , public virtual IRdecodeADDITIONAL //add additional protocols here
{
public:
virtual bool decode(void); // Calls each decode routine individually
};
//Base class for sending signals
class IRsendBase
{
public:
IRsendBase();
void sendGeneric(unsigned long data, unsigned char Num_Bits, unsigned int Head_Mark, unsigned int Head_Space,
unsigned int Mark_One, unsigned int Mark_Zero, unsigned int Space_One, unsigned int Space_Zero,
unsigned char kHz, bool Stop_Bits, unsigned long Max_Extent=0);
protected:
void enableIROut(unsigned char khz);
VIRTUAL void mark(unsigned int usec);
VIRTUAL void space(unsigned int usec);
unsigned long Extent;
unsigned char OnTime,OffTime,iLength;//used by bit-bang output.
};
class IRsendNEC: public virtual IRsendBase
{
public:
void send(unsigned long data);
};
class IRsendSony: public virtual IRsendBase
{
public:
void send(unsigned long data, int nbits);
};
class IRsendRaw: public virtual IRsendBase
{
public:
void send(unsigned int buf[], unsigned char len, unsigned char khz);
};
class IRsendRC5: public virtual IRsendBase
{
public:
void send(unsigned long data);
};
class IRsendRC6: public virtual IRsendBase
{
public:
void send(unsigned long data, unsigned char nbits);
};
class IRsendPanasonic_Old: public virtual IRsendBase
{
public:
void send(unsigned long data);
};
class IRsendJVC: public virtual IRsendBase
{
public:
void send(unsigned long data, bool First);
};
class IRsendNECx: public virtual IRsendBase
{
public:
void send(unsigned long data);
};
class IRsend:
public virtual IRsendNEC,
public virtual IRsendSony,
public virtual IRsendRaw,
public virtual IRsendRC5,
public virtual IRsendRC6,
public virtual IRsendPanasonic_Old,
public virtual IRsendJVC,
public virtual IRsendNECx
// , public virtual IRsendADDITIONAL //add additional protocols here
{
public:
void send(IRTYPES Type, unsigned long data, unsigned int data2);
};
// Changed this to a base class so it can be extended
class IRrecvBase
{
public:
IRrecvBase(void) {};
IRrecvBase(unsigned char recvpin);
void No_Output(void);
void blink13(bool blinkflag);
bool GetResults(IRdecodeBase *decoder, const unsigned int Time_per_Ticks=1);
void enableIRIn(void);
virtual void resume(void);
unsigned char getPinNum(void);
unsigned char Mark_Excess;
protected:
void Init(void);
};
/* Original IRrecv class uses 50µs interrupts to sample input. While this is generally
* accurate enough for everyday purposes, it may be difficult to port to other
* hardware unless you know a lot about hardware timers and interrupts. Also
* when trying to analyze unknown protocols, the 50µs granularity may not be sufficient.
* In that case use either the IRrecvLoop or the IRrecvPCI class.
*/
#ifdef USE_IRRECV
class IRrecv: public IRrecvBase
{
public:
IRrecv(unsigned char recvpin):IRrecvBase(recvpin){};
bool GetResults(IRdecodeBase *decoder);
void enableIRIn(void);
void resume(void);
};
#endif
/* This receiver uses no interrupts or timers. Other interrupt driven receivers
* allow you to do other things and call GetResults at your leisure to see if perhaps
* a sequence has been received. Typically you would put GetResults in your loop
* and it would return false until the sequence had been received. However because this
* receiver uses no interrupts, it takes control of your program when you call GetResults
* and doesn't let go until it's got something to show you. The advantage is you don't need
* interrupts which would make it easier to use and nonstandard hardware and will allow you to
* use any digital input pin. Timing of this routine is only as accurate as your "micros();"
*/
class IRrecvLoop: public IRrecvBase
{
public:
IRrecvLoop(unsigned char recvpin):IRrecvBase(recvpin){};
bool GetResults(IRdecodeBase *decoder);
};
/* This receiver uses the pin change hardware interrupt to detect when your input pin
* changes state. It gives more detailed results than the 50µs interrupts of IRrecv
* and theoretically is more accurate than IRrecvLoop. However because it only detects
* pin changes, it doesn't always know when it's finished. GetResults attempts to detect
* a long gap of space but sometimes the next signal gets there before GetResults notices.
* This means the second set of signals can get messed up unless there is a long gap.
* This receiver is based in part on Arduino firmware for use with AnalysIR IR signal analysis
* software for Windows PCs. Many thanks to the people at http://analysir.com for their
* assistance in developing this section of code.
*/
#ifdef USE_ATTACH_INTERRUPTS
class IRrecvPCI: public IRrecvBase
{
public:
//Note this is interrupt number not pin number
IRrecvPCI(unsigned char inum);
bool GetResults(IRdecodeBase *decoder);
void resume(void);
private:
unsigned char intrnum;
};
/* This class facilitates detection of frequency of an IR signal. Requires a TSMP58000
* or equivalent device connected to the hardware interrupt pin.
* Create an instance of the object passing the interrupt number.
*/
//Un-comment only one of the following three lines depending on available RAM
//#define FREQUENCY_BUFFER_TYPE unsigned char
#define FREQUENCY_BUFFER_TYPE int
//#define FREQUENCY_BUFFER_TYPE long
class IRfrequency
{
public:
//Note this is interrupt number, not pin number
IRfrequency(unsigned char inum);
void enableFreqDetect(void);
bool HaveData(void); //detective data received
void disableFreqDetect(void);
void ComputeFreq(void); //computes but does not print results
void DumpResults(bool Detail); //computes and prints result
unsigned char getPinNum(void);//get value computed from interrupt number
double Results; //results in kHz
unsigned char Samples; //number of samples used in computation
private:
volatile unsigned FREQUENCY_BUFFER_TYPE Time_Stamp[256];
unsigned char intrnum, pin;
unsigned int i;
unsigned long Sum;
};
#endif // ifdef USE_ATTACH_INTERRUPTS
//Do the actual blinking off and on
//This is not part of IRrecvBase because it may need to be inside an ISR
//and we cannot pass parameters to them.
void do_Blink(void);
/* This routine maps interrupt numbers used by attachInterrupt() into pin numbers.
* NOTE: these interrupt numbers which are passed to “attachInterrupt()” are not
* necessarily identical to the interrupt numbers in the datasheet of the processor
* chip you are using. These interrupt numbers are a system unique to the
* “attachInterrupt()” Arduino function. It is used by both IRrecvPCI and IRfrequency.
*/
unsigned char Pin_from_Intr(unsigned char inum);
// Some useful constants
// Decoded value for NEC when a repeat code is received
#define REPEAT 0xffffffff
#endif //IRLib_h
/****************************************************************************************************************************************
Simple Arduino robot controller
Version: 1.0
Author: José Miranda
License: Creative COmmons (CC) Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) (https://creativecommons.org/licenses/by-sa/4.0/)
Dependencies: Adafruit PMWServoDriver.h
Requirements: Arduino
Adafruit Servo Shield
Regular servo at channel 0
Continuous rotation servos at channels 1 and 2
Sunfounder SF-SR02 ultrasonic distance finder at analog input 0
*****************************************************************************************************************************************/
#include <Adafruit_PWMServoDriver.h>
#include <IRLib.h>
//#include <Filter.h>
//ExponentialFilter<float> FilteredTime(80,0);
//Create a receiver object to listen on pin 11
IRrecv My_Receiver(11);
//Create a decoder object
IRdecode My_Decoder;
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); //Sets the shield to address 0x40
#define sensorServo 0 //Sensor Servo PWM channel
#define leftServo 1 //Left motion servo
#define rightServo 2 //Right motion servo
#define moveSensorServo true //Set to false to stop the sensor servo
#define SIG A0 //Sensor signal write/read at Analog 0
#define debug false //Set the debug flag
#define debugDist false //Set the debug distance flag
#define debugIR true //Set the debug Infra Red Sensor flag
#define servoLowLimit 100 //Servo's low
#define servoHighLimit 300 //Servo's high
#define stopSpeed 200 //Servo's stop speed
#define sensorDelay 250
#define rotationDelay 250
#define reverseDelay 500
#define stopDelay 100
#define pulseTime 15
#define enableSignalFilter false
const int nReadings = 10;
#define MY_PROTOCOL SONY
#define RIGHT_ARROW 909576
#define LEFT_ARROW 385288
#define SELECT_BUTTON 254216
#define UP_ARROW 123144
#define DOWN_ARROW 647432
#define SENSOR_L 41230
#define SENSOR_R 565518
#define SENSOR_C 28683
int degree = 0;
boolean autonomous = true;
int distance = 0;
void setup() {
My_Receiver.No_Output(); //Turn off any unused IR LED output circuit
My_Receiver.enableIRIn(); // Start the receiver
if(debug==true || debugDist==true || debugIR==true){
Serial.begin(9600);
Serial.println("Running Setup routine...");
Serial.flush();
}
pwm.begin();
pwm.setPWMFreq(60); //Sets the PWM frequency to 60 Hz. Analog servos run at ~60 Hz updates.
yield();
}
void loop() {
if (My_Receiver.GetResults(&My_Decoder)) {
My_Decoder.decode();
if(My_Decoder.decode_type==MY_PROTOCOL) {
autonomous = false;
switch(My_Decoder.value) {
case LEFT_ARROW: rotateLeft(100); break;
case RIGHT_ARROW: rotateRight(100); break;
case SELECT_BUTTON: stopMoving(); break;
case UP_ARROW: moveFwd(100); break;
case DOWN_ARROW: moveAft(100); break;
case SENSOR_L: sensorAngle(-45); break;
case SENSOR_R: sensorAngle(45); break;
case SENSOR_C: sensorAngle(0); break;
}
}
My_Receiver.resume(); //Restart the receiver
}
if (autonomous) {
distance = ping(); //Measure distance
if (distance > 50) {
moveFwd(100); //If the distance is longer than 50cm, keep moving forward.
}/* else if (distance <= 50) { //If distance is less than 50 cm
moveFwd(3.4*(distance-20)); //Move forward at a speed calculated with the formula: speed%=3.4*(distance-20).
//This slows down as the robot approaches an obstacle. In the future, I am going
//to take advantage of this and will start determining the best route before reaching the obstacle.
//For now it just slows down until it stops. The formula may have to be tweaked in order to get a
//smoother stop.
}*/
if (distance <= 10) {
stopMoving();
moveAft(100);
rotateToBestAngle();
} else if (distance <= 20) {
stopMoving();
rotateToBestAngle();
}
}
}
//Moves the sensor servo to the desired angular position between -45 and 45 deg
void sensorAngle(int degree) {
int servomin = 220;
int servomax = 370;
uint16_t pulselength;
pulselength = map(degree, -45, 45, servomin, servomax);
Serial.print("Sensor Angle: ");
Serial.println(degree);
if(debug==true){
Serial.println(degree);
Serial.flush();
Serial.println("");
Serial.flush();
}
pwm.setPWM(sensorServo, 0, pulselength);
delay(sensorDelay);
}
void stopMoving() {
int degree =0;
uint16_t pulselength = map(degree, -100, 100, servoLowLimit, servoHighLimit);
pwm.setPWM(leftServo, 0, pulselength);
pwm.setPWM(rightServo, 0, pulselength);
Serial.println("Stop");
if(debug==true){
Serial.println("Stop");
Serial.flush();
}
delay(stopDelay);
}
//Moves the robot forward
void moveFwd(int speedValue) {
uint16_t pulselengthLeft = map(speedValue, 0, 100, stopSpeed, servoHighLimit);
uint16_t pulselengthRight = map(speedValue, 0, 100, stopSpeed, servoLowLimit);
pwm.setPWM(leftServo, 0, pulselengthLeft);
pwm.setPWM(rightServo, 0, pulselengthRight);
Serial.println("Move Fwd");
if(debug==true){
Serial.print("Speed:");
Serial.println(speedValue);
Serial.print(" Left: ");
Serial.println(pulselengthLeft);
Serial.print("Right: ");
Serial.println(pulselengthRight);
Serial.println("");
Serial.flush();
}
}
//Moves the robot aft
void moveAft(int speedValue) {
uint16_t pulselengthLeft = map(speedValue, 0, 100, stopSpeed, servoLowLimit);
uint16_t pulselengthRight = map(speedValue, 0, 100, stopSpeed, servoHighLimit);
pwm.setPWM(leftServo, 0, pulselengthLeft);
pwm.setPWM(rightServo, 0, pulselengthRight);
Serial.println("Move Aft");
if(debug==true){
Serial.print("Speed:");
Serial.println(speedValue);
Serial.print(" Left: ");
Serial.println(pulselengthLeft);
Serial.print("Right: ");
Serial.println(pulselengthRight);
Serial.println("");
Serial.flush();
}
delay(reverseDelay);
}
//Rotates the robot left
void rotateLeft(int speedValue) {
uint16_t pulselengthLeft = map(speedValue, 0, 100, stopSpeed, servoLowLimit);
uint16_t pulselengthRight = map(speedValue, 0, 100, stopSpeed, servoLowLimit);
pwm.setPWM(leftServo, 0, pulselengthLeft);
pwm.setPWM(rightServo, 0, pulselengthRight);
Serial.println("Rotate Left");
if(debug==true){
Serial.print("Speed:");
Serial.println(speedValue);
Serial.print(" Left: ");
Serial.println(pulselengthLeft);
Serial.print("Right: ");
Serial.println(pulselengthRight);
Serial.println("");
Serial.flush();
}
delay(rotationDelay);
}
//Rotates the robot right
void rotateRight(int speed) {
uint16_t pulselengthLeft = map(speed, 0, 100, stopSpeed, servoHighLimit);
uint16_t pulselengthRight = map(speed, 0, 100, stopSpeed, servoHighLimit);
pwm.setPWM(leftServo, 0, pulselengthLeft);
pwm.setPWM(rightServo, 0, pulselengthRight);
Serial.println("Rotate Right");
if(debug==true){
Serial.print("Speed:");
Serial.println(speed);
Serial.print(" Left: ");
Serial.println(pulselengthLeft);
Serial.print("Right: ");
Serial.println(pulselengthRight);
Serial.println("");
Serial.flush();
}
delay(rotationDelay);
}
//Measure the distance using the ultrasonic sensor
int ping() {
int dist;
int count=0;
float readings[10];
float total = 0;
float average;
float deviations[10];
float averageDeviation;
unsigned long rxTime;
if (enableSignalFilter) {
for (int reading = 1; reading <= nReadings; reading++) {
pinMode(SIG, OUTPUT); //Set SIG as Output to send ping
digitalWrite(SIG, HIGH); //Generate a pulse of 10us
delayMicroseconds(pulseTime);
digitalWrite(SIG, LOW);
pinMode(SIG, INPUT); //Set SIG as Input to read ping
//rxTime = pulseIn(SIG, HIGH); //Wait for the ping to be heard
readings[reading] = pulseIn(SIG, HIGH); //Wait for the ping to be heard
total=total+readings[reading];
}
average = total / nReadings;
total = 0;
for (int reading = 1; reading <= nReadings; reading++) {
deviations[reading] = abs(readings[reading] - average);
total = total + deviations[reading];
}
averageDeviation = total / nReadings;
total = 0;
for (int reading = 1; reading <= nReadings; reading++) {
if (deviations[reading] < 2 * averageDeviation) {
total = total + readings[reading];
count++;
}
}
rxTime = total / count;
} else {
pinMode(SIG, OUTPUT); //Set SIG as Output to send ping
digitalWrite(SIG, HIGH); //Generate a pulse of 10us
delayMicroseconds(pulseTime);
digitalWrite(SIG, LOW);
pinMode(SIG, INPUT); //Set SIG as Input to read ping
rxTime = pulseIn(SIG, HIGH); //Wait for the ping to be heard
}
dist = (float)rxTime * 34029 / 1000000; //Conver the time to distance
if (dist < 2) {
dist = 0;
}
if(debugDist==true){
Serial.print("Average: ");
Serial.println(dist);
Serial.flush();
}
return dist;
}
int getAngle() {
int pingResult;
int bestAngle;
for (int angle =-45; angle <= 45; angle +=90) { //Check left and right (can be adjusted to iterate on more angles)
sensorAngle(angle); //Rotate sensor
pingResult = ping(); //Get distance
if (pingResult >= distance) { //For each of the angles, store the one with the longest distance
distance = pingResult;
bestAngle = angle;
}
}
sensorAngle(0); //Move sensor back to home position
return bestAngle;
}
void rotateToBestAngle() {
int bestAngle;
bestAngle=getAngle(); //Get angle with longest clearance
if (bestAngle > 0) { //If best angle is less than zero (to the left)
rotateLeft(25); //Rotate left
} else { //If not
rotateRight(25); //Rotate right
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment