{"id":26680022,"url":"https://github.com/wagiminator/attiny13-tinyremote","last_synced_at":"2025-04-12T11:33:27.679Z","repository":{"id":48968440,"uuid":"249690000","full_name":"wagiminator/ATtiny13-TinyRemote","owner":"wagiminator","description":"IR Remote Control","archived":false,"fork":false,"pushed_at":"2022-09-10T13:15:19.000Z","size":3898,"stargazers_count":75,"open_issues_count":3,"forks_count":16,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-26T06:19:42.878Z","etag":null,"topics":["arduino","attiny","attiny13","attiny13a","avr","diy","ir","pcb","project","remote"],"latest_commit_sha":null,"homepage":"https://oshwlab.com/wagiminator/attiny13-tinyremote","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wagiminator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-24T11:27:52.000Z","updated_at":"2025-02-28T13:25:12.000Z","dependencies_parsed_at":"2023-01-18T03:01:03.524Z","dependency_job_id":null,"html_url":"https://github.com/wagiminator/ATtiny13-TinyRemote","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny13-TinyRemote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny13-TinyRemote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny13-TinyRemote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny13-TinyRemote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wagiminator","download_url":"https://codeload.github.com/wagiminator/ATtiny13-TinyRemote/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248560302,"owners_count":21124628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","attiny","attiny13","attiny13a","avr","diy","ir","pcb","project","remote"],"created_at":"2025-03-26T06:19:41.175Z","updated_at":"2025-04-12T11:33:27.634Z","avatar_url":"https://github.com/wagiminator.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TinyRemote - IR Remote Control based on ATtiny13A\nTinyRemote is a 5-button IR remote control based on an ATtiny13A powered by a CR2032 or LIR2032 coin cell battery.\n\n- Project Video (YouTube): https://youtu.be/ad3eyNCov9c\n- Design Files (EasyEDA): https://easyeda.com/wagiminator/attiny13-tinyremote\n\n![pic1.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_pic1.jpg)\n\nFor the 12-button version of the ATtiny13A-based IR remote control see [TinyRemoteXL](https://github.com/wagiminator/ATtiny13-TinyRemoteXL).\n\n# Hardware\nThe wiring is pretty simple:\n\n![Wiring.png](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_wiring.png)\n\nDue to the control with a high-frequency PWM, no series resistor is necessary for the LEDs. If you want to use only four buttons, you can leave KEY5 unsoldered and upload the 4-button version of the firmware. If you want to use all five buttons, you have to disable RESET on PB5 by burning the respective fuses after uploading the 5-button version of the firmware:\n\n```\navrdude -c usbasp -p t13 -U lfuse:w:0x2a:m -U hfuse:w:0xfe:m\n```\n\nWarning: You will need a high voltage fuse resetter to undo this change!\n\nFor a simple breadboard test you can directly connect an IR LED via a 220 Ohm resistor to PB1.\n\n# Software\nThere are a variety of communication protocols for infrared remote controls. Basically, most of them have in common that a carrier wave between 30kHz and 58kHz, depending on the protocol, is generated by means of PWM at the IR diode so that the receiver can distinguish the signal from the noise. The IR telegram is modulated onto the carrier wave using pulse code modulation (PCM) by simply switching the IR LED on and off in a defined pattern. The telegrams are composed of a start frame, the device address of the receiver and the key-dependent command. The three most widely used protocols are implemented for the TinyRemote:\n\n|Protocol|Carrier Frequency|Encoding Method|Start Frame|Address|Command|\n|:-|:-|:-|:-|:-|:-|\n|NEC|38kHz|Pulse Distance|9ms burst / 4.5ms space|8/16 bits|8 bits|\n|RC-5|36kHz|Manchester|Start bits|5 bits|6/7 bits|\n|Sony SIRC|40kHz|Pulse Length|2.4ms burst / 0.6ms space|5/8/13 bits|7 bits|\n\nSince the software implementation for all protocols is very similar, only the NEC protocol is explained in more detail below.\n\n## Implementation of the NEC Protocol\nTimer0 generates the 38kHz carrier frequency with a duty cycle of 25% on the output pin to the IR LED.\n\n```c\n// define values for 38kHz PWM frequency and 25% duty cycle\n#define TOP   31          // 1200kHz / 38kHz - 1 = 31\n#define DUTY  7           // 1200kHz / 38kHz / 4 - 1 = 7\n\n// set timer0 to toggle IR pin at 38 kHz\nTCCR0A = 0b00100011;      // PWM on OC0B (PB1)\nTCCR0B = 0b00001001;      // no prescaler\nOCR0A  = TOP;             // 38 kHz PWM frequency\nOCR0B  = DUTY;            // 25 % duty cycle\n```\n\nHere's the result, captured with a logic analyzer:\n\n![PWM.png](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_PWM.png)\n\nThe IR telegram is modulated by toggling the pin of the IR LED to input or output. Setting the pin to output enables the PWM on this pin and sends a burst of the carrier wave. Setting the pin to input turns off the LED completely. The NEC protocol uses pulse distance encoding, which means a data bit is defined by the time between the bursts. A \"0\" bit is a 562.5µs burst (LED on: 38kHz PWM) followed by a 562.5µs space (LED off), a \"1\" bit is a 562.5µs burst followed by a 1687.5µs space.\n\nAn IR telegram starts with a 9ms leading burst followed by a 4.5ms space. Afterwards 4 data bytes are transmitted, least significant bit first. A final 562.5µs burst signifies the end of the transmission. The four data bytes are in order:\n- the 8-bit address for the receiving device,\n- the 8-bit logical inverse of the address,\n- the 8-bit command and\n- the 8-bit logical inverse of the command.\n\n![NEC_transmission.png](https://techdocs.altium.com/sites/default/files/wiki_attachments/296329/NECMessageFrame.png)\n\n```c\n// macros to switch on/off IR LED\n#define IRon()   DDRB |= 0b00000010   // PB1 as output = IR at OC0B (38 kHz)\n#define IRoff()  DDRB \u0026= 0b11111101   // PB1 as input  = LED off\n\n// macros to modulate the signals according to NEC protocol with compensated timings\n#define startPulse()    {IRon(); _delay_us(9000); IRoff(); _delay_us(4500);}\n#define normalPulse()   {IRon(); _delay_us( 562); IRoff(); _delay_us( 557);}\n#define bit1Pause()     _delay_us(1120) // 1687.5us - 562.5us = 1125us\n\n// send a single byte via IR\nvoid sendByte(uint8_t value) {\n  for (uint8_t i=8; i; i--, value\u003e\u003e=1) {  // send 8 bits, LSB first\n    normalPulse();                        // 562us burst, 562us pause\n    if (value \u0026 1) bit1Pause();           // extend pause if bit is 1\n  }\n}\n\n// send complete telegram (start frame + address + command) via IR\nvoid sendCode(uint8_t cmd) {\n  startPulse();       // signify start of transmission\n  sendByte(ADDR);     // send address byte\n  sendByte(~ADDR);    // send inverse of address byte\n  sendByte(cmd);      // send command byte\n  sendByte(~cmd);     // send inverse of command byte\n  normalPulse();      // signify end of transmission\n}\n```\n\nThe Extended NEC protocol uses 16-bit addresses. Instead of sending an 8-bit address and its logically inverse, first the low byte and then the high byte of the address is transmitted.\n\n```c\n// send complete telegram (start frame + address + command) via IR (Extended NEC)\nvoid sendCode(uint8_t cmd) {\n  startPulse();           // signify start of transmission\n  sendByte(ADDR \u0026 0xFF);  // send address low byte\n  sendByte(ADDR \u003e\u003e 8);    // send address high byte\n  sendByte(cmd);          // send command byte\n  sendByte(~cmd);         // send inverse of command byte\n  normalPulse();          // signify end of transmission\n}\n```\n\nHere's the result, captured with a logic analyzer:\n\n![NEC_protocol.png](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_NEC.png)\n\nIf the key on the remote controller is kept depressed, a repeat code will be issued consisting of a 9ms leading burst, a 2.25ms pause and a 562.5µs burst to mark the end. The repeat code will continue to be sent out at 108ms intervals, until the key is finally released.\n\n![NEC_repeat.png](https://techdocs.altium.com/sites/default/files/wiki_attachments/296329/NECRepeatCodes.png)\n\n```c\n// macros to modulate the signals according to NEC protocol with compensated timings\n#define repeatPulse()   {IRon(); _delay_us(9000); IRoff(); _delay_us(2250);}\n#define repeatCode()    {_delay_ms(40); repeatPulse(); normalPulse(); _delay_ms(56);}\n\n// send repeat command until button is released\nwhile (~PINB \u0026 0b00111101) repeatCode();\n```\n\nThe main loop of the implementation is pretty simple:\n\n```c\n// IR codes (use 16-bit address for extended NEC protocol)\n#define ADDR  0x04  // Address: LG TV\n#define KEY1  0x02  // Command: Volume+\n#define KEY2  0x00  // Command: Channel+\n#define KEY3  0x03  // Command: Volume-\n#define KEY4  0x01  // Command: Channel-\n#define KEY5  0x08  // Command: Power\n\n// main loop\nwhile(1) {\n    sleep_mode();                             // sleep until button is pressed\n    _delay_ms(1);                             // debounce\n    uint8_t buttons = ~PINB \u0026 0b00111101;     // read button pins\n    switch (buttons) {                        // send corresponding IR code\n      case 0b00000001: sendCode(KEY1); break;\n      case 0b00000100: sendCode(KEY2); break;\n      case 0b00001000: sendCode(KEY3); break;\n      case 0b00010000: sendCode(KEY4); break;\n      case 0b00100000: sendCode(KEY5); break;\n      default: break;\n    }\n}\n```\n\n## Implementation of the Philips RC-5 Protocol\nThe Philips RC-5 protocol uses Manchester encoding on a carrier frequency of 36kHz. A \"0\" bit is an 889µs burst followed by an 889µs space, a \"1\" bit is an 889µs space followed by an 889µs burst. An IR telegram starts with two start bits. The first bit is always \"1\", the second bit is \"1\" in the original protocol and the inverted 7th bit of the command in the extended RC-5 protocol. The third bit toggles after each button release. The next five bits represent the device address and the last six bits represent the command, all transmitted MSB first.\n\n![RC5_transmission.png](https://techdocs.altium.com/sites/default/files/wiki_attachments/296330/RC5MessageFrame.png)\n\nAs long as a key remains down the telegram will be repeated every 114ms without changing the toggle bit.\n\n```c\n// define values for 36kHz PWM frequency and 25% duty cycle\n#define TOP   32                      // 1200kHz / 36kHz - 1 = 32\n#define DUTY  7                       // 1200kHz / 36kHz / 4 - 1 = 7\n\n// macros to switch on/off IR LED\n#define IRon()   DDRB |= 0b00000010   // PB1 as output = IR at OC0B (36 kHz)\n#define IRoff()  DDRB \u0026= 0b11111101   // PB1 as input  = LED off\n\n// macros to modulate the signals according to RC-5 protocol with compensated timings\n#define bit0Pulse()     {IRon();  _delay_us(889); IRoff(); _delay_us(884);}\n#define bit1Pulse()     {IRoff(); _delay_us(889); IRon();  _delay_us(884);}\n#define repeatDelay()   _delay_ms(89) // 114ms - 14 * 2 * 889us\n\n// bitmasks\n#define startBit  0b0010000000000000\n#define cmdBit7   0b0001000000000000\n#define toggleBit 0b0000100000000000\n\n// toggle state variable\nuint8_t toggle = 0;\n\n// send complete telegram (startbits + togglebit + address + command) via IR\nvoid sendCode(uint8_t cmd) {\n  // prepare the message\n  uint16_t message = ADDR \u003c\u003c 6;         // shift address to the right position\n  message |= (cmd \u0026 0x3f);              // add the low 6 bits of the command\n  if (~cmd \u0026 0x40) message |= cmdBit7;  // add inverse of 7th command bit\n  message |= startBit;                  // add start bit\n  if (toggle) message |= toggleBit;     // add toggle bit\n\n  // send the message\n  do {\n    uint16_t bitmask = startBit;        // set the bitmask to first bit to send\n    for(uint8_t i=14; i; i--, bitmask\u003e\u003e=1) {                // 14 bits, MSB first\n      (message \u0026 bitmask) ? (bit1Pulse()) : (bit0Pulse());  // send the bit\n    }\n    IRoff();                            // switch off IR LED\n    repeatDelay();                      // wait for next repeat\n  } while(~PINB \u0026 0b00111101);          // repeat sending until button is released\n  toggle ^= 1;                          // toggle the toggle bit\n}\n```\n\n## Implementation of the Sony SIRC Protocol\nThe Sony SIRC protocol uses pulse length encoding on a carrier frequency of 40kHz. A \"0\" bit is a 600µs burst followed by a 600µs space, a \"1\" bit is a 1200µs burst followed by a 600µs space. An IR telegram starts with a 2400µs leading burst followed by a 600µs space. The command and address bits are then transmitted, LSB first. Depending on the protocol version, these are in detail:\n- 12-bit version: 7 command bits, 5 address bits\n- 15-bit version: 7 command bits, 8 address bits\n- 20-bit version: 7 command bits, 5 address bits, 8 extended bits\n\nAs long as a key remains down the telegram will be repeated every 45ms.\n\n```c\n// define values for 40kHz PWM frequency and 25% duty cycle\n#define TOP   29                      // 1200kHz / 40kHz - 1 = 29\n#define DUTY  7                       // 1200kHz / 40kHz / 4 - 1 = 7\n\n// macros to switch on/off IR LED\n#define IRon()   DDRB |= 0b00000010   // PB1 as output = IR at OC0B (40kHz)\n#define IRoff()  DDRB \u0026= 0b11111101   // PB1 as input  = LED off\n\n// macros to modulate the signals according to SONY protocol with compensated timings\n#define startPulse()    {IRon(); _delay_us(2400); IRoff(); _delay_us( 595);}\n#define bit0Pulse()     {IRon(); _delay_us( 600); IRoff(); _delay_us( 595);}\n#define bit1Pulse()     {IRon(); _delay_us(1200); IRoff(); _delay_us( 595);}\n#define repeatPause()   _delay_ms(27)\n\n\n// send \"number\" of bits of \"value\" via IR\nvoid sendByte(uint8_t value, uint8_t number) {\n  do {                                    // send number of bits, LSB first\n    (value \u0026 1) ? (bit1Pulse()) : (bit0Pulse());  // send bit\n    value\u003e\u003e=1;                            // next bit\n  } while(--number);\n}\n\n// send complete telegram (start frame + command + address) via IR\nvoid sendCode(uint8_t cmd) {\n  do {\n    startPulse();               // signify start of transmission\n    sendByte(cmd, 7);           // send 7 command bits\n    #if BITS == 12              // if 12-bit version:\n      sendByte(ADDR, 5);        // send 5 address bits\n    #elif BITS == 15            // if 15-bit version:\n      sendByte(ADDR, 8);        // send 8 address bits\n    #elif BITS == 20            // if 20-bit version:\n      sendByte(ADDR, 5);        // send 5 address bits\n      sendByte(EXTB, 8);        // send 8 extended bits\n    #endif\n    repeatPause();              // wait until next repeat\n  } while (~PINB \u0026 0b00011101); // repeat sending until button is released\n}\n```\n\n## Power Saving\nThe code shuts down unused peripherals and utilizes the sleep mode power down function. It wakes up on every button press by pin change interrupt.\n\n```c\n// setup pin change interrupt\nGIMSK = 0b00100000;                   // turn on pin change interrupts\nPCMSK = 0b00111101;                   // turn on interrupt on button pins\nSREG |= 0b10000000;                   // enable global interrupts\n\n// disable unused peripherals and set sleep mode to save power\nADCSRA = 0b00000000;                  // disable ADC\nACSR   = 0b10000000;                  // disable analog comperator\nPRR    = 0b00000001;                  // shut down ADC\nset_sleep_mode(SLEEP_MODE_PWR_DOWN);  // set sleep mode to power down\n```\n\nAs long as no button is pressed, the ATtiny remains in sleep mode power down and consumes a current of around 150nA at a voltage of 3V. The typical capacity of a CR2032 battery is 230mAh. This results in a theoretical battery life of 1.5 million hours or 179 years. In real life, of course, no battery will last that long due to its self-discharge. When the button is pressed, peaks of up to 30mA are consumed. The diagram below shows the course of the current consumption when a button is pressed and a NEC telegram is sent according to a measurement with the [Power Profiler Kit II](https://www.nordicsemi.com/Products/Development-hardware/Power-Profiler-Kit-2):\n\n![Current.png](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_current.png)\n\nWhen sending a NEC telegram, the current requirement increases to an average of around 2.75mA for 71ms. Theoretically, over 4 million telegrams could be sent with one battery. Note that the rechargeable LIR2032 batteries have a significantly lower capacity.\n\n## Timing Accuracy\nThe accuracy of the internal oscillator of the ATtiny13 is +/-10% with the factory calibration. Usually this is sufficient for an infrared remote control. Slight deviations in timing are tolerated by the receiver, since cheap remote controls are usually not more accurate. Nevertheless, it certainly doesn't hurt to [manually calibrate](https://github.com/wagiminator/ATtiny84-TinyCalibrator) the internal oscillator and set the corresponding OSCCAL value at the beginning of the code.\n\n```c\n// oscillator calibration value (uncomment and set if necessary)\n#define OSCCAL_VAL  0x48\n```\n\n# References, Links and Notes\n1. [IR remote control explanations by San Bergmans](https://www.sbprojects.net/knowledge/ir/index.php)\n2. [IR remote control by Christoph Niessen (german)](http://chris.cnie.de/avr/tcm231421.html)\n3. [IR remote control detective by David Johnson-Davies](http://www.technoblogy.com/show?24A9)\n4. [Infrared communication concepts (altium.com)](https://techdocs.altium.com/display/FPGA/Infrared+Communication+Concepts)\n5. [NEC decoder based on  ATtiny13A](https://github.com/wagiminator/ATtiny13-TinyDecoder)\n6. [TinyRemote XL](https://github.com/wagiminator/ATtiny13-TinyRemoteXL)\n7. [TinyRemote RF](https://github.com/wagiminator/ATtiny13-TinyRemoteRF)\n8. [OSC Calibrator](https://github.com/wagiminator/ATtiny84-TinyCalibrator)\n9. [ATtiny13A datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/doc8126.pdf)\n\n![pic2.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_pic2.jpg)\n![pic3.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_pic3.jpg)\n![pic4.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny13-TinyRemote/master/documentation/TinyRemote_pic4.jpg)\n\n# License\n![license.png](https://i.creativecommons.org/l/by-sa/3.0/88x31.png)\n\nThis work is licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License. \n(http://creativecommons.org/licenses/by-sa/3.0/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagiminator%2Fattiny13-tinyremote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwagiminator%2Fattiny13-tinyremote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagiminator%2Fattiny13-tinyremote/lists"}