Skip to content

Instantly share code, notes, and snippets.

@christer-watson
Forked from SeeJayDee/tiny_IRremote.cpp
Last active June 1, 2021 07:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christer-watson/5efc1ea7bd8d7c4f74cd8a00bfdc69b6 to your computer and use it in GitHub Desktop.
Save christer-watson/5efc1ea7bd8d7c4f74cd8a00bfdc69b6 to your computer and use it in GitHub Desktop.
tiny_IRremote - Arduino IRremote ported to the ATtiny
/*
* tiny_IRremote
* Version 0.2 July, 2016
* Christian D'Abrera
* Fixed what was originally rather broken code from http://www.gammon.com.au/Arduino/
* ...itself based on work by Ken Shirriff.
*
* This code was tested for both sending and receiving IR on an ATtiny85 DIP-8 chip.
* IMPORTANT: IRsend only works from PB4 ("pin 4" according to Arduino). You will need to
* determine which physical pin this corresponds to for your chip, and connect your transmitter
* LED there.
*
* Copyright 2009 Ken Shirriff
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
*
* 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 "tiny_IRremote.h"
#include "tiny_IRremoteInt.h"
// Provides ISR
#include <avr/interrupt.h>
volatile irparams_t irparams;
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::mark(int time) {
// Sends an IR mark for the specified number of microseconds.
// The mark output is modulated at the PWM frequency.
TCCR1 |= _BV(COM1A1); // Enable pin PWM output (PB1 - Arduino D4)
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.
TCCR1 &= ~(_BV(COM1A1)); // Disable pin PWM output (PB1 - Arduino D4)
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 (PB1 - Arduino D) (OC1A).
// 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.)
// TIMER1 is used in fast PWM mode, with OCR1Ccontrolling the frequency and OCR1A
// controlling the duty cycle.
// There is no prescaling, so the output frequency is 8MHz / (2 * OCR1C)
// 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 Timer1 Interrupt (which is used for receiving IR)
TIMSK &= ~_BV(TOIE1); //Timer1 Overflow Interrupt
pinMode(1, OUTPUT); // (PBA - Arduino D - physical pin )
digitalWrite(1, LOW); // When not sending PWM, we want it low
// CTC1 = 1: TOP value set to OCR1C
// CS = 0001: No Prescaling
// TCCR1 = _BV(CTC1) | _BV(CS10);
// PWM1A = 1: Enable PWM for OCR1A
// GTCCR = _BV(PWM1A);
// perhaps this should be instead...?
// TCCR1 = _BV(PWM1A);
//or...
TCCR1 = _BV(PWM1A) | _BV(CTC1) | _BV(CS10);
//or...
//TCCR1 = _BV(PWM1A) | 3<<COM1A0 | _BV(CS10);
// The top value for the timer. The modulation frequency will be SYSCLOCK / OCR1C.
OCR1C = SYSCLOCK / khz / 1000;
OCR1A = OCR1C / 3; // 33% duty cycle
}
/*
* tiny_IRremote
* Version 0.2 July, 2016
* Christian D'Abrera
* Fixed what was originally rather broken code from http://www.gammon.com.au/Arduino/
* ...itself based on work by Ken Shirriff.
*
* This code was tested for both sending and receiving IR on an ATtiny85 DIP-8 chip.
* IMPORTANT: IRsend only works from PB4 ("pin 4" according to Arduino). You will need to
* determine which physical pin this corresponds to for your chip, and connect your transmitter
* LED there.
*
* Copyright 2009 Ken Shirriff
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.htm http://arcfn.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 tiny_IRremote_h
#define tiny_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 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 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);
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 decodeRC5(decode_results *results);
long decodeRC6(decode_results *results);
}
;
// 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);
void sendRaw(unsigned int buf[], int len, int hz);
void sendRC5(unsigned long data, int nbits);
void sendRC6(unsigned long data, int nbits);
// 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 76 // 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
/*
* tiny_IRremote
* Version 0.2 July, 2016
* Christian D'Abrera
* Fixed what was originally rather broken code from http://www.gammon.com.au/Arduino/
* ...itself based on work by Ken Shirriff.
*
* This code was tested for both sending and receiving IR on an ATtiny85 DIP-8 chip.
* IMPORTANT: IRsend only works from PB4 ("pin 4" according to Arduino). You will need to
* determine which physical pin this corresponds to for your chip, and connect your transmitter
* LED there.
*
* Copyright 2009 Ken Shirriff
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
*
* 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 tiny_IRremoteint_h
#define tiny_IRremoteint_h
#include <Arduino.h>
#define CLKFUDGE 5 // fudge factor for clock interrupt overhead
#define CLK 256 // max value for clock (timer 2)
#define PRESCALE 4 // TIMER1 clock prescale
#if defined (F_CPU)
#define SYSCLOCK F_CPU // main Arduino clock
#else
#define SYSCLOCK 8000000 // default ATtiny clock
#endif
#define CLKSPERUSEC (SYSCLOCK/PRESCALE/1000000) // timer clocks per microsecond
#define ERR 0
#define DECODED 1
// 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
// clock timer reset value
#define INIT_TIMER_COUNT1 (CLK - USECPERTICK*CLKSPERUSEC + CLKFUDGE)
#define RESET_TIMER1 TCNT1 = INIT_TIMER_COUNT1
// pulse parameters in usec
#define NEC_HDR_MARK 9000
#define NEC_HDR_SPACE 4500
#define NEC_BIT_MARK 560
#define NEC_ONE_SPACE 1600
#define NEC_ZERO_SPACE 560
#define NEC_RPT_SPACE 2250
#define SONY_HDR_MARK 2400
#define SONY_HDR_SPACE 600
#define SONY_ONE_MARK 1200
#define SONY_ZERO_MARK 600
#define SONY_RPT_LENGTH 45000
#define RC5_T1 889
#define RC5_RPT_LENGTH 46000
#define RC6_HDR_MARK 2666
#define RC6_HDR_SPACE 889
#define RC6_T1 444
#define RC6_RPT_LENGTH 46000
#define TOLERANCE 25 // percent tolerance in measurements
#define LTOL (1.0 - TOLERANCE/100.)
#define UTOL (1.0 + TOLERANCE/100.)
#define _GAP 5000 // Minimum map between transmissions
#define GAP_TICKS (_GAP/USECPERTICK)
#define TICKS_LOW(us) (int) (((us)*LTOL/USECPERTICK))
#define TICKS_HIGH(us) (int) (((us)*UTOL/USECPERTICK + 1))
#ifndef DEBUG
#define MATCH(measured_ticks, desired_us) ((measured_ticks) >= TICKS_LOW(desired_us) && (measured_ticks) <= TICKS_HIGH(desired_us))
#define MATCH_MARK(measured_ticks, desired_us) MATCH(measured_ticks, (desired_us) + MARK_EXCESS)
#define MATCH_SPACE(measured_ticks, desired_us) MATCH((measured_ticks), (desired_us) - MARK_EXCESS)
// Debugging versions are in tiny_IRremote.cpp
#endif
// receiver states
#define STATE_IDLE 2
#define STATE_MARK 3
#define STATE_SPACE 4
#define STATE_STOP 5
// information for the interrupt handler
typedef struct {
uint8_t recvpin; // pin for IR data from detector
uint8_t rcvstate; // state machine
unsigned int timer; // state timer, counts 50uS ticks.
unsigned int rawbuf[RAWBUF]; // raw data
uint8_t rawlen; // counter of entries in rawbuf
}
irparams_t;
// Defined in tiny_IRremote.cpp
extern volatile irparams_t irparams;
// IR detector output is active low
#define MARK 0
#define SPACE 1
#define TOPBIT 0x80000000
#define NEC_BITS 32
#define SONY_BITS 12
#define MIN_RC5_SAMPLES 11
#define MIN_RC6_SAMPLES 1
#endif
@NathanBxer
Copy link

Any chance of adding some examples?
Im looking to receive a NEC code and send a different NEC code using an ATTiny85.
New Google Chromecast (CCWGTV) doesnt list all TVs.

@christer-watson
Copy link
Author

I think this is the code I used for sending NEC IR codes from a home-built remote control, so a lot of the code is dealing with looking for button pushes
`/* IR Remote Wand v2 - see http://www.technoblogy.com/show?25TN

David Johnson-Davies - www.technoblogy.com - 13th May 2018
ATtiny85 @ 1 MHz (internal oscillator; BOD disabled)

CC BY 4.0
Licensed under a Creative Commons Attribution 4.0 International license:
http://creativecommons.org/licenses/by/4.0/

*/

#include <avr/sleep.h>
#include <tiny_IRremote.h>
#include <tiny_IRremoteInt.h>

IRsend irsend;

// IR transmitter **********************************************

// Buttons

const int S1 = 4;
const int S2 = 3;
const int S3 = 5; // Reset
const int S4 = 2;
const int S5 = 0;
const int LED = 1; // IR LED output

// Pin change interrupt service routine
ISR (PCINT0_vect) {
int in = PINB;
if ((in & 1<<S1) == 0)
{
irsend.sendNEC(0x61A00AF5,32); // HDMI toggle
}
else if ((in & 1<<S2) == 0)
{
irsend.sendNEC(0x61A0B04F,32); // vol down
delay(300);
while ((PINB & 1<<S2) == 0)
{
irsend.sendNECrepeat(); // repeat
}
}
else if ((in & 1<<S4) == 0)
{
irsend.sendNEC(0x61A030CF,32); // vol up
delay(300);
while ((PINB & 1<<S4) == 0)
{
irsend.sendNECrepeat(); // repeat
delay(200);
}

}
else if ((in & 1<<S5) == 0)
{
irsend.sendNEC(0x61A0F00F,32); // powerup
}
}

// Setup demo **********************************************

void setup() {
// pinMode(LED, OUTPUT);
pinMode(S1, INPUT_PULLUP);
pinMode(S2, INPUT_PULLUP);
pinMode(S4, INPUT_PULLUP);
pinMode(S5, INPUT_PULLUP);
// Configure pin change interrupts to wake on button presses
PCMSK = 1<<S1 | 1<<S2 | 1<<S4 | 1<<S5;
GIMSK = 1<<PCIE; // Enable interrupts
GIFR = 1<<PCIF; // Clear interrupt flag
// Disable what we don't need to save power
ADCSRA &= ~(1<<ADEN); // Disable ADC
PRR = 1<<PRUSI | 1<<PRADC; // Turn off clocks to unused peripherals
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Send S3 code on reset
// Send('R', 0x0013, 0x000C);
}

// Stay asleep and just respond to interrupts
void loop() {
sleep_enable();
sleep_cpu();
}`

@NathanBxer
Copy link

Thanks ill check it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment