Skip to content

Instantly share code, notes, and snippets.

@joseamirandavelez
Created December 30, 2016 06:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joseamirandavelez/06579fb06a04eb52b63c68345c34999c to your computer and use it in GitHub Desktop.
Save joseamirandavelez/06579fb06a04eb52b63c68345c34999c to your computer and use it in GitHub Desktop.
Arduino Rover with Radio Shack's Robotics IR controller
/*
* IRremote
* Version 0.11 August, 2009
* Copyright 2009 Ken Shirriff
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
*
* Modified by Paul Stoffregen <paul@pjrc.com> to support other boards and timers
* Modified by Mitra Ardron <mitra@mitra.biz>
* Added Sanyo and Mitsubishi controllers
* Modified Sony to spot the repeat codes that some Sony's send
*
* 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/
*
* JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post)
*/
#include "IRremote.h"
#include "IRremoteInt.h"
// Provides ISR
#include <avr/interrupt.h>
volatile irparams_t irparams;
// These versions of MATCH, MATCH_MARK, and MATCH_SPACE are only for debugging.
// To use them, set DEBUG in IRremoteInt.h
// Normally macros are used for efficiency
#ifdef DEBUG
int MATCH(int measured, int desired) {
Serial.print("Testing: ");
Serial.print(TICKS_LOW(desired), DEC);
Serial.print(" <= ");
Serial.print(measured, DEC);
Serial.print(" <= ");
Serial.println(TICKS_HIGH(desired), DEC);
return measured >= TICKS_LOW(desired) && measured <= TICKS_HIGH(desired);
}
int MATCH_MARK(int measured_ticks, int desired_us) {
Serial.print("Testing mark ");
Serial.print(measured_ticks * USECPERTICK, DEC);
Serial.print(" vs ");
Serial.print(desired_us, DEC);
Serial.print(": ");
Serial.print(TICKS_LOW(desired_us + MARK_EXCESS), DEC);
Serial.print(" <= ");
Serial.print(measured_ticks, DEC);
Serial.print(" <= ");
Serial.println(TICKS_HIGH(desired_us + MARK_EXCESS), DEC);
return measured_ticks >= TICKS_LOW(desired_us + MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us + MARK_EXCESS);
}
int MATCH_SPACE(int measured_ticks, int desired_us) {
Serial.print("Testing space ");
Serial.print(measured_ticks * USECPERTICK, DEC);
Serial.print(" vs ");
Serial.print(desired_us, DEC);
Serial.print(": ");
Serial.print(TICKS_LOW(desired_us - MARK_EXCESS), DEC);
Serial.print(" <= ");
Serial.print(measured_ticks, DEC);
Serial.print(" <= ");
Serial.println(TICKS_HIGH(desired_us - MARK_EXCESS), DEC);
return measured_ticks >= TICKS_LOW(desired_us - MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us - MARK_EXCESS);
}
#endif
void IRsend::sendNEC(unsigned long data, int nbits)
{
enableIROut(38);
mark(NEC_HDR_MARK);
space(NEC_HDR_SPACE);
for (int i = 0; i < nbits; i++) {
if (data & TOPBIT) {
mark(NEC_BIT_MARK);
space(NEC_ONE_SPACE);
}
else {
mark(NEC_BIT_MARK);
space(NEC_ZERO_SPACE);
}
data <<= 1;
}
mark(NEC_BIT_MARK);
space(0);
}
void IRsend::sendSony(unsigned long data, int nbits) {
enableIROut(40);
mark(SONY_HDR_MARK);
space(SONY_HDR_SPACE);
data = data << (32 - nbits);
for (int i = 0; i < nbits; i++) {
if (data & TOPBIT) {
mark(SONY_ONE_MARK);
space(SONY_HDR_SPACE);
}
else {
mark(SONY_ZERO_MARK);
space(SONY_HDR_SPACE);
}
data <<= 1;
}
}
void IRsend::sendRaw(unsigned int buf[], int len, int hz)
{
enableIROut(hz);
for (int i = 0; i < len; i++) {
if (i & 1) {
space(buf[i]);
}
else {
mark(buf[i]);
}
}
space(0); // Just to be sure
}
// Note: first bit must be a one (start bit)
void IRsend::sendRC5(unsigned long data, int nbits)
{
enableIROut(36);
data = data << (32 - nbits);
mark(RC5_T1); // First start bit
space(RC5_T1); // Second start bit
mark(RC5_T1); // Second start bit
for (int i = 0; i < nbits; i++) {
if (data & TOPBIT) {
space(RC5_T1); // 1 is space, then mark
mark(RC5_T1);
}
else {
mark(RC5_T1);
space(RC5_T1);
}
data <<= 1;
}
space(0); // Turn off at end
}
// Caller needs to take care of flipping the toggle bit
void IRsend::sendRC6(unsigned long data, int nbits)
{
enableIROut(36);
data = data << (32 - nbits);
mark(RC6_HDR_MARK);
space(RC6_HDR_SPACE);
mark(RC6_T1); // start bit
space(RC6_T1);
int t;
for (int i = 0; i < nbits; i++) {
if (i == 3) {
// double-wide trailer bit
t = 2 * RC6_T1;
}
else {
t = RC6_T1;
}
if (data & TOPBIT) {
mark(t);
space(t);
}
else {
space(t);
mark(t);
}
data <<= 1;
}
space(0); // Turn off at end
}
void IRsend::sendPanasonic(unsigned int address, unsigned long data) {
enableIROut(35);
mark(PANASONIC_HDR_MARK);
space(PANASONIC_HDR_SPACE);
for(int i=0;i<16;i++)
{
mark(PANASONIC_BIT_MARK);
if (address & 0x8000) {
space(PANASONIC_ONE_SPACE);
} else {
space(PANASONIC_ZERO_SPACE);
}
address <<= 1;
}
for (int i=0; i < 32; i++) {
mark(PANASONIC_BIT_MARK);
if (data & TOPBIT) {
space(PANASONIC_ONE_SPACE);
} else {
space(PANASONIC_ZERO_SPACE);
}
data <<= 1;
}
mark(PANASONIC_BIT_MARK);
space(0);
}
void IRsend::sendJVC(unsigned long data, int nbits, int repeat)
{
enableIROut(38);
data = data << (32 - nbits);
if (!repeat){
mark(JVC_HDR_MARK);
space(JVC_HDR_SPACE);
}
for (int i = 0; i < nbits; i++) {
if (data & TOPBIT) {
mark(JVC_BIT_MARK);
space(JVC_ONE_SPACE);
}
else {
mark(JVC_BIT_MARK);
space(JVC_ZERO_SPACE);
}
data <<= 1;
}
mark(JVC_BIT_MARK);
space(0);
}
void IRsend::mark(int time) {
// Sends an IR mark for the specified number of microseconds.
// The mark output is modulated at the PWM frequency.
TIMER_ENABLE_PWM; // Enable pin 3 PWM output
delayMicroseconds(time);
}
/* Leave pin off for time (given in microseconds) */
void IRsend::space(int time) {
// Sends an IR space for the specified number of microseconds.
// A space is no output, so the PWM output is disabled.
TIMER_DISABLE_PWM; // Disable pin 3 PWM output
delayMicroseconds(time);
}
void IRsend::enableIROut(int khz) {
// 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://arcfn.com/2009/07/secrets-of-arduino-pwm.html for details.
// Disable the Timer2 Interrupt (which is used for receiving IR)
TIMER_DISABLE_INTR; //Timer2 Overflow Interrupt
pinMode(TIMER_PWM_PIN, OUTPUT);
digitalWrite(TIMER_PWM_PIN, LOW); // When not sending PWM, we want it low
// COM2A = 00: disconnect OC2A
// COM2B = 00: disconnect OC2B; to send signal set to 10: OC2B non-inverted
// WGM2 = 101: phase-correct PWM with OCRA as top
// CS2 = 000: no prescaling
// The top value for the timer. The modulation frequency will be SYSCLOCK / 2 / OCR2A.
TIMER_CONFIG_KHZ(khz);
}
IRrecv::IRrecv(int recvpin)
{
irparams.recvpin = recvpin;
irparams.blinkflag = 0;
}
// initialization
void IRrecv::enableIRIn() {
cli();
// setup pulse clock timer interrupt
//Prescale /8 (16M/8 = 0.5 microseconds per tick)
// Therefore, the timer interval can range from 0.5 to 128 microseconds
// depending on the reset value (255 to 0)
TIMER_CONFIG_NORMAL();
//Timer2 Overflow Interrupt Enable
TIMER_ENABLE_INTR;
TIMER_RESET;
sei(); // enable interrupts
// initialize state machine variables
irparams.rcvstate = STATE_IDLE;
irparams.rawlen = 0;
// set pin modes
pinMode(irparams.recvpin, INPUT);
}
// enable/disable blinking of pin 13 on IR processing
void IRrecv::blink13(int blinkflag)
{
irparams.blinkflag = blinkflag;
if (blinkflag)
pinMode(BLINKLED, OUTPUT);
}
// TIMER2 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(TIMER_INTR_NAME)
{
TIMER_RESET;
uint8_t irdata = (uint8_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 == 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 == 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 == 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 == MARK) { // reset gap timer
irparams.timer = 0;
}
break;
}
if (irparams.blinkflag) {
if (irdata == MARK) {
BLINKLED_ON(); // turn pin 13 LED on
}
else {
BLINKLED_OFF(); // turn pin 13 LED off
}
}
}
void IRrecv::resume() {
irparams.rcvstate = STATE_IDLE;
irparams.rawlen = 0;
}
// Decodes the received IR message
// Returns 0 if no data ready, 1 if data ready.
// Results of decoding are stored in results
int IRrecv::decode(decode_results *results) {
results->rawbuf = irparams.rawbuf;
results->rawlen = irparams.rawlen;
if (irparams.rcvstate != STATE_STOP) {
return ERR;
}
#ifdef DEBUG
Serial.println("Attempting NEC decode");
#endif
if (decodeNEC(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting Sony decode");
#endif
if (decodeSony(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting Sanyo decode");
#endif
if (decodeSanyo(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting Mitsubishi decode");
#endif
if (decodeMitsubishi(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting RC5 decode");
#endif
if (decodeRC5(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting RC6 decode");
#endif
if (decodeRC6(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting Panasonic decode");
#endif
if (decodePanasonic(results)) {
return DECODED;
}
#ifdef DEBUG
Serial.println("Attempting JVC decode");
#endif
if (decodeJVC(results)) {
return DECODED;
}
// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
// If you add any decodes, add them before this.
if (decodeHash(results)) {
return DECODED;
}
// Throw away and start over
resume();
return ERR;
}
// NECs have a repeat only 4 items long
long IRrecv::decodeNEC(decode_results *results) {
long data = 0;
int offset = 1; // Skip first space
// Initial mark
if (!MATCH_MARK(results->rawbuf[offset], NEC_HDR_MARK)) {
return ERR;
}
offset++;
// Check for repeat
if (irparams.rawlen == 4 &&
MATCH_SPACE(results->rawbuf[offset], NEC_RPT_SPACE) &&
MATCH_MARK(results->rawbuf[offset+1], NEC_BIT_MARK)) {
results->bits = 0;
results->value = REPEAT;
results->decode_type = NEC;
return DECODED;
}
if (irparams.rawlen < 2 * NEC_BITS + 4) {
return ERR;
}
// Initial space
if (!MATCH_SPACE(results->rawbuf[offset], NEC_HDR_SPACE)) {
return ERR;
}
offset++;
for (int i = 0; i < NEC_BITS; i++) {
if (!MATCH_MARK(results->rawbuf[offset], NEC_BIT_MARK)) {
return ERR;
}
offset++;
if (MATCH_SPACE(results->rawbuf[offset], NEC_ONE_SPACE)) {
data = (data << 1) | 1;
}
else if (MATCH_SPACE(results->rawbuf[offset], NEC_ZERO_SPACE)) {
data <<= 1;
}
else {
return ERR;
}
offset++;
}
// Success
results->bits = NEC_BITS;
results->value = data;
results->decode_type = NEC;
return DECODED;
}
long IRrecv::decodeSony(decode_results *results) {
long data = 0;
if (irparams.rawlen < 2 * SONY_BITS + 2) {
return ERR;
}
int offset = 0; // Dont skip first space, check its size
// Some Sony's deliver repeats fast after first
// unfortunately can't spot difference from of repeat from two fast clicks
if (results->rawbuf[offset] < SONY_DOUBLE_SPACE_USECS) {
// Serial.print("IR Gap found: ");
results->bits = 0;
results->value = REPEAT;
results->decode_type = SANYO;
return DECODED;
}
offset++;
// Initial mark
if (!MATCH_MARK(results->rawbuf[offset], SONY_HDR_MARK)) {
return ERR;
}
offset++;
while (offset + 1 < irparams.rawlen) {
if (!MATCH_SPACE(results->rawbuf[offset], SONY_HDR_SPACE)) {
break;
}
offset++;
if (MATCH_MARK(results->rawbuf[offset], SONY_ONE_MARK)) {
data = (data << 1) | 1;
}
else if (MATCH_MARK(results->rawbuf[offset], SONY_ZERO_MARK)) {
data <<= 1;
}
else {
return ERR;
}
offset++;
}
// Success
results->bits = (offset - 1) / 2;
if (results->bits < 12) {
results->bits = 0;
return ERR;
}
results->value = data;
results->decode_type = SONY;
return DECODED;
}
// I think this is a Sanyo decoder - serial = SA 8650B
// Looks like Sony except for timings, 48 chars of data and time/space different
long IRrecv::decodeSanyo(decode_results *results) {
long data = 0;
if (irparams.rawlen < 2 * SANYO_BITS + 2) {
return ERR;
}
int offset = 0; // Skip first space
// Initial space
/* Put this back in for debugging - note can't use #DEBUG as if Debug on we don't see the repeat cos of the delay
Serial.print("IR Gap: ");
Serial.println( results->rawbuf[offset]);
Serial.println( "test against:");
Serial.println(results->rawbuf[offset]);
*/
if (results->rawbuf[offset] < SANYO_DOUBLE_SPACE_USECS) {
// Serial.print("IR Gap found: ");
results->bits = 0;
results->value = REPEAT;
results->decode_type = SANYO;
return DECODED;
}
offset++;
// Initial mark
if (!MATCH_MARK(results->rawbuf[offset], SANYO_HDR_MARK)) {
return ERR;
}
offset++;
// Skip Second Mark
if (!MATCH_MARK(results->rawbuf[offset], SANYO_HDR_MARK)) {
return ERR;
}
offset++;
while (offset + 1 < irparams.rawlen) {
if (!MATCH_SPACE(results->rawbuf[offset], SANYO_HDR_SPACE)) {
break;
}
offset++;
if (MATCH_MARK(results->rawbuf[offset], SANYO_ONE_MARK)) {
data = (data << 1) | 1;
}
else if (MATCH_MARK(results->rawbuf[offset], SANYO_ZERO_MARK)) {
data <<= 1;
}
else {
return ERR;
}
offset++;
}
// Success
results->bits = (offset - 1) / 2;
if (results->bits < 12) {
results->bits = 0;
return ERR;
}
results->value = data;
results->decode_type = SANYO;
return DECODED;
}
// Looks like Sony except for timings, 48 chars of data and time/space different
long IRrecv::decodeMitsubishi(decode_results *results) {
// Serial.print("?!? decoding Mitsubishi:");Serial.print(irparams.rawlen); Serial.print(" want "); Serial.println( 2 * MITSUBISHI_BITS + 2);
long data = 0;
if (irparams.rawlen < 2 * MITSUBISHI_BITS + 2) {
return ERR;
}
int offset = 0; // Skip first space
// Initial space
/* Put this back in for debugging - note can't use #DEBUG as if Debug on we don't see the repeat cos of the delay
Serial.print("IR Gap: ");
Serial.println( results->rawbuf[offset]);
Serial.println( "test against:");
Serial.println(results->rawbuf[offset]);
*/
/* Not seeing double keys from Mitsubishi
if (results->rawbuf[offset] < MITSUBISHI_DOUBLE_SPACE_USECS) {
// Serial.print("IR Gap found: ");
results->bits = 0;
results->value = REPEAT;
results->decode_type = MITSUBISHI;
return DECODED;
}
*/
offset++;
// Typical
// 14200 7 41 7 42 7 42 7 17 7 17 7 18 7 41 7 18 7 17 7 17 7 18 7 41 8 17 7 17 7 18 7 17 7
// Initial Space
if (!MATCH_MARK(results->rawbuf[offset], MITSUBISHI_HDR_SPACE)) {
return ERR;
}
offset++;
while (offset + 1 < irparams.rawlen) {
if (MATCH_MARK(results->rawbuf[offset], MITSUBISHI_ONE_MARK)) {
data = (data << 1) | 1;
}
else if (MATCH_MARK(results->rawbuf[offset], MITSUBISHI_ZERO_MARK)) {
data <<= 1;
}
else {
// Serial.println("A"); Serial.println(offset); Serial.println(results->rawbuf[offset]);
return ERR;
}
offset++;
if (!MATCH_SPACE(results->rawbuf[offset], MITSUBISHI_HDR_SPACE)) {
// Serial.println("B"); Serial.println(offset); Serial.println(results->rawbuf[offset]);
break;
}
offset++;
}
// Success
results->bits = (offset - 1) / 2;
if (results->bits < MITSUBISHI_BITS) {
results->bits = 0;
return ERR;
}
results->value = data;
results->decode_type = MITSUBISHI;
return DECODED;
}
// 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 -1 for error (measured time interval is not a multiple of t1).
int IRrecv::getRClevel(decode_results *results, int *offset, int *used, int t1) {
if (*offset >= results->rawlen) {
// After end of recorded buffer, assume SPACE.
return SPACE;
}
int width = results->rawbuf[*offset];
int val = ((*offset) % 2) ? MARK : SPACE;
int correction = (val == MARK) ? MARK_EXCESS : - MARK_EXCESS;
int avail;
if (MATCH(width, t1 + correction)) {
avail = 1;
}
else if (MATCH(width, 2*t1 + correction)) {
avail = 2;
}
else if (MATCH(width, 3*t1 + correction)) {
avail = 3;
}
else {
return -1;
}
(*used)++;
if (*used >= avail) {
*used = 0;
(*offset)++;
}
#ifdef DEBUG
if (val == MARK) {
Serial.println("MARK");
}
else {
Serial.println("SPACE");
}
#endif
return val;
}
long IRrecv::decodeRC5(decode_results *results) {
if (irparams.rawlen < MIN_RC5_SAMPLES + 2) {
return ERR;
}
int offset = 1; // Skip gap space
long data = 0;
int used = 0;
// Get start bits
if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR;
if (getRClevel(results, &offset, &used, RC5_T1) != SPACE) return ERR;
if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR;
int nbits;
for (nbits = 0; offset < irparams.rawlen; nbits++) {
int levelA = getRClevel(results, &offset, &used, RC5_T1);
int levelB = getRClevel(results, &offset, &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 ERR;
}
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = RC5;
return DECODED;
}
long IRrecv::decodeRC6(decode_results *results) {
if (results->rawlen < MIN_RC6_SAMPLES) {
return ERR;
}
int offset = 1; // Skip first space
// Initial mark
if (!MATCH_MARK(results->rawbuf[offset], RC6_HDR_MARK)) {
return ERR;
}
offset++;
if (!MATCH_SPACE(results->rawbuf[offset], RC6_HDR_SPACE)) {
return ERR;
}
offset++;
long data = 0;
int used = 0;
// Get start bit (1)
if (getRClevel(results, &offset, &used, RC6_T1) != MARK) return ERR;
if (getRClevel(results, &offset, &used, RC6_T1) != SPACE) return ERR;
int nbits;
for (nbits = 0; offset < results->rawlen; nbits++) {
int levelA, levelB; // Next two levels
levelA = getRClevel(results, &offset, &used, RC6_T1);
if (nbits == 3) {
// T bit is double wide; make sure second half matches
if (levelA != getRClevel(results, &offset, &used, RC6_T1)) return ERR;
}
levelB = getRClevel(results, &offset, &used, RC6_T1);
if (nbits == 3) {
// T bit is double wide; make sure second half matches
if (levelB != getRClevel(results, &offset, &used, RC6_T1)) return ERR;
}
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 ERR; // Error
}
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = RC6;
return DECODED;
}
long IRrecv::decodePanasonic(decode_results *results) {
unsigned long long data = 0;
int offset = 1;
if (!MATCH_MARK(results->rawbuf[offset], PANASONIC_HDR_MARK)) {
return ERR;
}
offset++;
if (!MATCH_MARK(results->rawbuf[offset], PANASONIC_HDR_SPACE)) {
return ERR;
}
offset++;
// decode address
for (int i = 0; i < PANASONIC_BITS; i++) {
if (!MATCH_MARK(results->rawbuf[offset++], PANASONIC_BIT_MARK)) {
return ERR;
}
if (MATCH_SPACE(results->rawbuf[offset],PANASONIC_ONE_SPACE)) {
data = (data << 1) | 1;
} else if (MATCH_SPACE(results->rawbuf[offset],PANASONIC_ZERO_SPACE)) {
data <<= 1;
} else {
return ERR;
}
offset++;
}
results->value = (unsigned long)data;
results->panasonicAddress = (unsigned int)(data >> 32);
results->decode_type = PANASONIC;
results->bits = PANASONIC_BITS;
return DECODED;
}
long IRrecv::decodeJVC(decode_results *results) {
long data = 0;
int offset = 1; // Skip first space
// Check for repeat
if (irparams.rawlen - 1 == 33 &&
MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK) &&
MATCH_MARK(results->rawbuf[irparams.rawlen-1], JVC_BIT_MARK)) {
results->bits = 0;
results->value = REPEAT;
results->decode_type = JVC;
return DECODED;
}
// Initial mark
if (!MATCH_MARK(results->rawbuf[offset], JVC_HDR_MARK)) {
return ERR;
}
offset++;
if (irparams.rawlen < 2 * JVC_BITS + 1 ) {
return ERR;
}
// Initial space
if (!MATCH_SPACE(results->rawbuf[offset], JVC_HDR_SPACE)) {
return ERR;
}
offset++;
for (int i = 0; i < JVC_BITS; i++) {
if (!MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK)) {
return ERR;
}
offset++;
if (MATCH_SPACE(results->rawbuf[offset], JVC_ONE_SPACE)) {
data = (data << 1) | 1;
}
else if (MATCH_SPACE(results->rawbuf[offset], JVC_ZERO_SPACE)) {
data <<= 1;
}
else {
return ERR;
}
offset++;
}
//Stop bit
if (!MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK)){
return ERR;
}
// Success
results->bits = JVC_BITS;
results->value = data;
results->decode_type = JVC;
return DECODED;
}
/* -----------------------------------------------------------------------
* hashdecode - decode an arbitrary IR code.
* Instead of decoding using a standard encoding scheme
* (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value.
*
* The algorithm: look at the sequence of MARK signals, and see if each one
* is shorter (0), the same length (1), or longer (2) than the previous.
* Do the same with the SPACE signals. Hszh the resulting sequence of 0's,
* 1's, and 2's to a 32-bit value. This will give a unique value for each
* different code (probably), for most code systems.
*
* http://arcfn.com/2010/01/using-arbitrary-remotes-with-arduino.html
*/
// Compare two tick values, returning 0 if newval is shorter,
// 1 if newval is equal, and 2 if newval is longer
// Use a tolerance of 20%
int IRrecv::compare(unsigned int oldval, unsigned int newval) {
if (newval < oldval * .8) {
return 0;
}
else if (oldval < newval * .8) {
return 2;
}
else {
return 1;
}
}
// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
#define FNV_PRIME_32 16777619
#define FNV_BASIS_32 2166136261
/* Converts the raw code values into a 32-bit hash code.
* Hopefully this code is unique for each button.
* This isn't a "real" decoding, just an arbitrary value.
*/
long IRrecv::decodeHash(decode_results *results) {
// Require at least 6 samples to prevent triggering on noise
if (results->rawlen < 6) {
return ERR;
}
long hash = FNV_BASIS_32;
for (int i = 1; i+2 < results->rawlen; i++) {
int value = compare(results->rawbuf[i], results->rawbuf[i+2]);
// Add value into the hash
hash = (hash * FNV_PRIME_32) ^ value;
}
results->value = hash;
results->bits = 32;
results->decode_type = UNKNOWN;
return DECODED;
}
/* Sharp and DISH support by Todd Treece ( http://unionbridge.org/design/ircommand )
The Dish send function needs to be repeated 4 times, and the Sharp function
has the necessary repeat built in because of the need to invert the signal.
Sharp protocol documentation:
http://www.sbprojects.com/knowledge/ir/sharp.htm
Here are the LIRC files that I found that seem to match the remote codes
from the oscilloscope:
Sharp LCD TV:
http://lirc.sourceforge.net/remotes/sharp/GA538WJSA
DISH NETWORK (echostar 301):
http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx
For the DISH codes, only send the last for characters of the hex.
i.e. use 0x1C10 instead of 0x0000000000001C10 which is listed in the
linked LIRC file.
*/
void IRsend::sendSharp(unsigned long data, int nbits) {
unsigned long invertdata = data ^ SHARP_TOGGLE_MASK;
enableIROut(38);
for (int i = 0; i < nbits; i++) {
if (data & 0x4000) {
mark(SHARP_BIT_MARK);
space(SHARP_ONE_SPACE);
}
else {
mark(SHARP_BIT_MARK);
space(SHARP_ZERO_SPACE);
}
data <<= 1;
}
mark(SHARP_BIT_MARK);
space(SHARP_ZERO_SPACE);
delay(46);
for (int i = 0; i < nbits; i++) {
if (invertdata & 0x4000) {
mark(SHARP_BIT_MARK);
space(SHARP_ONE_SPACE);
}
else {
mark(SHARP_BIT_MARK);
space(SHARP_ZERO_SPACE);
}
invertdata <<= 1;
}
mark(SHARP_BIT_MARK);
space(SHARP_ZERO_SPACE);
delay(46);
}
void IRsend::sendDISH(unsigned long data, int nbits)
{
enableIROut(56);
mark(DISH_HDR_MARK);
space(DISH_HDR_SPACE);
for (int i = 0; i < nbits; i++) {
if (data & DISH_TOP_BIT) {
mark(DISH_BIT_MARK);
space(DISH_ONE_SPACE);
}
else {
mark(DISH_BIT_MARK);
space(DISH_ZERO_SPACE);
}
data <<= 1;
}
}
/*
* IRremote
* Version 0.1 July, 2009
* Copyright 2009 Ken Shirriff
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.htm http://arcfn.com
* Edited by Mitra to add new controller SANYO
*
* 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/
*
* JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post)
*/
#ifndef IRremote_h
#define IRremote_h
// The following are compile-time library options.
// If you change them, recompile the library.
// If DEBUG is defined, a lot of debugging output will be printed during decoding.
// 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 DEBUG
// #define TEST
// Results returned from the decoder
class decode_results {
public:
int decode_type; // NEC, SONY, RC5, UNKNOWN
unsigned int panasonicAddress; // This is only used for decoding Panasonic data
unsigned long value; // Decoded value
int bits; // Number of bits in decoded value
volatile unsigned int *rawbuf; // Raw intervals in .5 us ticks
int rawlen; // Number of records in rawbuf.
};
// Values for decode_type
#define NEC 1
#define SONY 2
#define RC5 3
#define RC6 4
#define DISH 5
#define SHARP 6
#define PANASONIC 7
#define JVC 8
#define SANYO 9
#define MITSUBISHI 10
#define UNKNOWN -1
// Decoded value for NEC when a repeat code is received
#define REPEAT 0xffffffff
// main class for receiving IR
class IRrecv
{
public:
IRrecv(int recvpin);
void blink13(int blinkflag);
int decode(decode_results *results);
void enableIRIn();
void resume();
private:
// These are called by decode
int getRClevel(decode_results *results, int *offset, int *used, int t1);
long decodeNEC(decode_results *results);
long decodeSony(decode_results *results);
long decodeSanyo(decode_results *results);
long decodeMitsubishi(decode_results *results);
long decodeRC5(decode_results *results);
long decodeRC6(decode_results *results);
long decodePanasonic(decode_results *results);
long decodeJVC(decode_results *results);
long decodeHash(decode_results *results);
int compare(unsigned int oldval, unsigned int newval);
}
;
// Only used for testing; can remove virtual for shorter code
#ifdef TEST
#define VIRTUAL virtual
#else
#define VIRTUAL
#endif
class IRsend
{
public:
IRsend() {}
void sendNEC(unsigned long data, int nbits);
void sendSony(unsigned long data, int nbits);
// Neither Sanyo nor Mitsubishi send is implemented yet
// void sendSanyo(unsigned long data, int nbits);
// void sendMitsubishi(unsigned long data, int nbits);
void sendRaw(unsigned int buf[], int len, int hz);
void sendRC5(unsigned long data, int nbits);
void sendRC6(unsigned long data, int nbits);
void sendDISH(unsigned long data, int nbits);
void sendSharp(unsigned long data, int nbits);
void sendPanasonic(unsigned int address, unsigned long data);
void sendJVC(unsigned long data, int nbits, int repeat); // *Note instead of sending the REPEAT constant if you want the JVC repeat signal sent, send the original code value and change the repeat argument from 0 to 1. JVC protocol repeats by skipping the header NOT by sending a separate code value like NEC does.
// private:
void enableIROut(int khz);
VIRTUAL void mark(int usec);
VIRTUAL void space(int usec);
}
;
// Some useful constants
#define USECPERTICK 50 // microseconds per clock interrupt tick
#define RAWBUF 100 // Length of raw duration buffer
// Marks tend to be 100us too long, and spaces 100us too short
// when received due to sensor lag.
#define MARK_EXCESS 100
#endif
/****************************************************************************************************************************************
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 <IRremote.h>
//#include <Filter.h>
//ExponentialFilter<float> FilteredTime(80,0);
//Create a receiver object to listen on pin 11
int IRpin = 11;
IRrecv irrecv(IRpin);
decode_results results;
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 RIGHT_ARROW 2953994586
#define LEFT_ARROW 2999730842
#define SELECT_BUTTON 2054033594
#define UP_ARROW 2519209162
#define DOWN_ARROW 1502638218
#define SENSOR_L 2534555274
#define SENSOR_R 472047109
#define SENSOR_C 2368359343
#define AUTO 4011529036
int degree = 0;
boolean autonomous = true;
int distance = 0;
void setup() {
irrecv.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 (irrecv.decode(&results)) {
switch(results.value) {
case LEFT_ARROW: autonomous=false; rotateLeft(25); break;
case RIGHT_ARROW: autonomous=false; rotateRight(25); break;
case SELECT_BUTTON: autonomous=false; stopMoving(); break;
case UP_ARROW: autonomous=false; moveFwd(100); break;
case DOWN_ARROW: autonomous=false; moveAft(50); break;
case SENSOR_L: autonomous=false; sensorAngle(45); break;
case SENSOR_R: autonomous=false; sensorAngle(-45); break;
case SENSOR_C: autonomous=false; sensorAngle(0); break;
case AUTO: autonomous=false; autonomous = true; break;
default: Serial.println(results.value); break;
}
irrecv.resume(); // Receive the next value
}
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