/ Flora_TV_B_Gone / Flora_TV_B_Gone.ino
Flora_TV_B_Gone.ino
  1  // SPDX-FileCopyrightText: 2018 Limor Fried for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  Flora TV-B-Gone
  7  
  8  Code updated for Flora's 32u4 microcontroller by Phillip Burgess for Adafruit Industries
  9  
 10  Complete instructions: http://learn.adafruit.com/flora-tv-b-gone/
 11  
 12  The hardware for this project uses an Adafruit Flora (Arduino-compatible 32u4):
 13   Connect the base of an NPN bipolar transistor to pin 3, marked "SCL" on Flora (RLED).
 14   Connect the collector pin of the transistor to ground (Marked "GND" on Flora).
 15   Connect the transistor's emitter to the positive lead of an IR LED, and connect the LED's negative leg to ground through a 100-ohm resistor.
 16   Connect a pushbutton between pin D9 (TRIGGER) and ground.
 17   Pin 5 (REGIONSWITCH) is floating for North America, or wired to ground for Europe.
 18  
 19  -------------
 20  based on Ken Shirriff's port of TV-B-Gone to Arduino, version 1.2, Oct 23 2010
 21  http://www.arcfn.com/2009/12/tv-b-gone-for-arduino.html
 22  
 23  The original code is:
 24  TV-B-Gone Firmware version 1.2
 25   for use with ATtiny85v and v1.2 hardware
 26   (c) Mitch Altman + Limor Fried 2009
 27   Last edits, August 16 2009
 28  
 29  
 30   I added universality for EU or NA,
 31   and Sleep mode to Ken's Arduino port
 32        -- Mitch Altman  18-Oct-2010
 33   Thanks to ka1kjz for the code for adding Sleep
 34        <http://www.ka1kjz.com/561/adding-sleep-to-tv-b-gone-code/>
 35  
 36   With some code from:
 37   Kevin Timmerman & Damien Good 7-Dec-07
 38  
 39   Distributed under Creative Commons 2.5 -- Attib & Share Alike
 40  
 41   */
 42  
 43  #include "main.h"
 44  #include <avr/sleep.h>
 45  #include <avr/pgmspace.h>
 46  
 47  void xmitCodeElement(uint16_t ontime, uint16_t offtime, uint8_t PWM_code );
 48  void quickflashLEDx( uint8_t x );
 49  void delay_ten_us(uint16_t us);
 50  void quickflashLED( void );
 51  uint8_t read_bits(uint8_t count);
 52  
 53  /*
 54  This project transmits a bunch of TV POWER codes, one right after the other,
 55   with a pause in between each.  (To have a visible indication that it is
 56   transmitting, it also pulses a visible LED once each time a POWER code is
 57   transmitted.)  That is all TV-B-Gone does.  The tricky part of TV-B-Gone
 58   was collecting all of the POWER codes, and getting rid of the duplicates and
 59   near-duplicates (because if there is a duplicate, then one POWER code will
 60   turn a TV off, and the duplicate will turn it on again (which we certainly
 61   do not want).  I have compiled the most popular codes with the
 62   duplicates eliminated, both for North America (which is the same as Asia, as
 63   far as POWER codes are concerned -- even though much of Asia USES PAL video)
 64   and for Europe (which works for Australia, New Zealand, the Middle East, and
 65   other parts of the world that use PAL video).
 66  
 67   Before creating a TV-B-Gone Kit, I originally started this project by hacking
 68   the MiniPOV kit.  This presents a limitation, based on the size of
 69   the Atmel ATtiny2313 internal flash memory, which is 2KB.  With 2KB we can only
 70   fit about 7 POWER codes into the firmware's database of POWER codes.  However,
 71   the more codes the better! Which is why we chose the ATtiny85 for the
 72   TV-B-Gone Kit.
 73  
 74   This version of the firmware has the most popular 100+ POWER codes for
 75   North America and 100+ POWER codes for Europe. You can select which region
 76   to use by soldering a 10K pulldown resistor.
 77   */
 78  
 79  
 80  /*
 81  This project is a good example of how to use the AVR chip timers.
 82   */
 83  
 84  extern const *NApowerCodes[];
 85  extern const *EUpowerCodes[];
 86  extern const struct IrCode code_na000Code PROGMEM;
 87  extern uint8_t num_NAcodes, num_EUcodes;
 88  
 89  /* This function is the 'workhorse' of transmitting IR codes.
 90   Given the on and off times, it turns on the PWM output on and off
 91   to generate one 'pair' from a long code. Each code has ~50 pairs! */
 92  void xmitCodeElement(uint16_t ontime, uint16_t offtime, uint8_t PWM_code )
 93  {
 94  #ifdef __AVR_ATmega32U4__
 95    TCNT0 = 0;
 96  #else
 97    TCNT2 = 0;
 98  #endif
 99    if(PWM_code) {
100      pinMode(IRLED, OUTPUT);
101      // Fast PWM, setting top limit, divide by 8
102      // Output to pin 3
103  #ifdef __AVR_ATmega32U4__
104    #if (IRLED == 11)
105      TCCR0A = _BV(COM0A1) | _BV(COM0A0) | _BV(WGM01) | _BV(WGM00);
106    #elif (IRLED == 3)
107      TCCR0A = _BV(COM0B1) | _BV(COM0B0) | _BV(WGM01) | _BV(WGM00);
108    #else
109      #error "IR LED must be on Leonardo digital pin 3 or 11."
110    #endif
111      TCCR0B = _BV(WGM02) | _BV(CS01);
112  #else
113      TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
114      TCCR2B = _BV(WGM22) | _BV(CS21);
115  #endif
116    }
117    else {
118      // However some codes dont use PWM in which case we just turn the IR
119      // LED on for the period of time.
120      digitalWrite(IRLED, HIGH);
121    }
122  
123    // Now we wait, allowing the PWM hardware to pulse out the carrier
124    // frequency for the specified 'on' time
125    delay_ten_us(ontime);
126  
127    // Now we have to turn it off so disable the PWM output
128  #ifdef __AVR_ATmega32U4__
129    TCCR0A = 0;
130    TCCR0B = 0;
131  #else
132    TCCR2A = 0;
133    TCCR2B = 0;
134  #endif
135    // And make sure that the IR LED is off too (since the PWM may have
136    // been stopped while the LED is on!)
137    digitalWrite(IRLED, LOW);
138  
139    // Now we wait for the specified 'off' time
140    delay_ten_us(offtime);
141  }
142  
143  /* This is kind of a strange but very useful helper function
144   Because we are using compression, we index to the timer table
145   not with a full 8-bit byte (which is wasteful) but 2 or 3 bits.
146   Once code_ptr is set up to point to the right part of memory,
147   this function will let us read 'count' bits at a time which
148   it does by reading a byte into 'bits_r' and then buffering it. */
149  
150  uint8_t bitsleft_r = 0;
151  uint8_t bits_r=0;
152  PGM_P code_ptr;
153  
154  // we cant read more than 8 bits at a time so dont try!
155  uint8_t read_bits(uint8_t count)
156  {
157    uint8_t i;
158    uint8_t tmp=0;
159  
160    // we need to read back count bytes
161    for (i=0; i<count; i++) {
162      // check if the 8-bit buffer we have has run out
163      if (bitsleft_r == 0) {
164        // in which case we read a new byte in
165        bits_r = pgm_read_byte(code_ptr++);
166        // and reset the buffer size (8 bites in a byte)
167        bitsleft_r = 8;
168      }
169      // remove one bit
170      bitsleft_r--;
171      // and shift it off of the end of 'bits_r'
172      tmp |= (((bits_r >> (bitsleft_r)) & 1) << (count-1-i));
173    }
174    // return the selected bits in the LSB part of tmp
175    return tmp;
176  }
177  
178  
179  /*
180  The C compiler creates code that will transfer all constants into RAM when
181   the microcontroller resets.  Since this firmware has a table (powerCodes)
182   that is too large to transfer into RAM, the C compiler needs to be told to
183   keep it in program memory space.  This is accomplished by the macro PROGMEM
184   (this is used in the definition for powerCodes).  Since the C compiler assumes
185   that constants are in RAM, rather than in program memory, when accessing
186   powerCodes, we need to use the pgm_read_word() and pgm_read_byte macros, and
187   we need to use powerCodes as an address.  This is done with PGM_P, defined
188   below.
189   For example, when we start a new powerCode, we first point to it with the
190   following statement:
191   PGM_P thecode_p = pgm_read_word(powerCodes+i);
192   The next read from the powerCode is a byte that indicates the carrier
193   frequency, read as follows:
194   const uint8_t freq = pgm_read_byte(code_ptr++);
195   After that is a byte that tells us how many 'onTime/offTime' pairs we have:
196   const uint8_t numpairs = pgm_read_byte(code_ptr++);
197   The next byte tells us the compression method. Since we are going to use a
198   timing table to keep track of how to pulse the LED, and the tables are
199   pretty short (usually only 4-8 entries), we can index into the table with only
200   2 to 4 bits. Once we know the bit-packing-size we can decode the pairs
201   const uint8_t bitcompression = pgm_read_byte(code_ptr++);
202   Subsequent reads from the powerCode are n bits (same as the packing size)
203   that index into another table in ROM that actually stores the on/off times
204   const PGM_P time_ptr = (PGM_P)pgm_read_word(code_ptr);
205   */
206  
207  uint16_t ontime, offtime;
208  uint8_t i,num_codes, Loop;
209  uint8_t region;
210  uint8_t startOver;
211  
212  #define FALSE 0
213  #define TRUE 1
214  
215  void setup()   {
216    Serial.begin(9600);
217    if (DEBUG) {
218      while (!Serial);
219      delay(100);
220      Serial.println("TV B Gone");
221    }
222  
223  #ifdef __AVR_ATmega32U4__
224    // Timer0 is used on Arduino Leonardo (there is no Timer2).
225    // This means delay(), millis() etc. are not available,
226    // but they're currently not being used by this code.
227    TIMSK0 = 0; // Disable Timer0 interrupt
228    TCCR0A = 0;
229    TCCR0B = 0;
230  #else
231    TCCR2A = 0;
232    TCCR2B = 0;
233  #endif
234  
235    digitalWrite(LED, LOW);
236    digitalWrite(IRLED, LOW);
237    digitalWrite(DBG, LOW);     // debug
238    pinMode(LED, OUTPUT);
239    pinMode(IRLED, OUTPUT);
240    pinMode(DBG, OUTPUT);       // debug
241    pinMode(REGIONSWITCH, INPUT_PULLUP);
242    pinMode(TRIGGER, INPUT_PULLUP);
243  
244    delay_ten_us(5000);            // Let everything settle for a bit
245  
246    // determine region
247    if (digitalRead(REGIONSWITCH)) {
248      region = NA;
249      if (DEBUG) Serial.println(F("NA"));
250    }
251    else {
252      region = EU;
253      if (DEBUG) Serial.println(F("EU"));
254    }
255  
256    // Indicate how big our database is
257    if (DEBUG) {
258      Serial.print(F("\n\rNA Codesize: "));
259      Serial.println(num_NAcodes, DEC);
260      Serial.print(F("\n\rEU Codesize: "));
261      Serial.println(num_EUcodes, DEC);
262  
263      //uint16_t addr = &code_na000Code;
264      //Serial.print("Code NA000: $");
265      //Serial.println(addr, HEX);
266  
267    }
268  
269  
270    // Tell the user what region we're in  - 3 flashes is NA, 6 is EU
271    delay_ten_us(65500); // wait maxtime
272    delay_ten_us(65500); // wait maxtime
273    delay_ten_us(65500); // wait maxtime
274    delay_ten_us(65500); // wait maxtime
275    quickflashLEDx(3);
276    if (region == EU) {
277      quickflashLEDx(3);
278    }
279  }
280  
281  void sendAllCodes() {
282  Start_transmission:
283    // startOver will become TRUE if the user pushes the Trigger button while transmitting the sequence of all codes
284    startOver = FALSE;
285  
286    // determine region from REGIONSWITCH: 1 = NA, 0 = EU
287    if (digitalRead(REGIONSWITCH)) {
288      region = NA;
289      num_codes = num_NAcodes;
290    }  else {
291      region = EU;
292      num_codes = num_EUcodes;
293    }
294  
295    // for every POWER code in our collection
296    for (i=0 ; i < num_codes; i++) {
297      uint16_t data_ptr;
298  
299      // print out the code # we are about to transmit
300      if (DEBUG) {
301        Serial.print("\nCode #: ");
302        Serial.println(i);
303      }
304  
305      // point to next POWER code, from the right database
306      if (region == NA) {
307        data_ptr = NApowerCodes[i];
308      }
309      else {
310        data_ptr = EUpowerCodes[i];
311      }
312  
313      // print out the address in ROM memory we're reading
314      if (DEBUG) {
315        Serial.print("Addr: $");
316        Serial.println((uint16_t)data_ptr, HEX);
317      }
318      
319      // Read the carrier frequency from the first byte of code structure
320      const uint8_t freq = pgm_read_byte(data_ptr++);
321      // set OCR for Timer1 to output this POWER code's carrier frequency
322  #ifdef __AVR_ATmega32U4__
323      OCR0A = freq;
324      OCR0B = freq / 3; // 33% duty cycle
325  #else
326      OCR2A = freq;
327      OCR2B = freq / 3; // 33% duty cycle
328  #endif
329  
330      // Print out the frequency of the carrier and the PWM settings
331      if (DEBUG) {
332        Serial.print("OCR1: ");  Serial.println(freq);
333        uint16_t x = (freq+1) * 8;
334        Serial.print("Freq: "); Serial.println(F_CPU/x);
335      }
336  
337      // Get the number of pairs, the second byte from the code struct
338      const uint8_t numpairs = pgm_read_byte(data_ptr++);
339      if (DEBUG) {
340        Serial.print(F("On/off pairs: "));
341        Serial.println(numpairs);
342      }
343  
344      // Get the number of bits we use to index into the timer table
345      // This is the third byte of the structure
346      const uint8_t bitcompression = pgm_read_byte(data_ptr++);
347      if (DEBUG) {
348        Serial.print(F("\n\rCompression: "));
349        Serial.println(bitcompression, DEC);
350      }
351      
352      // Get pointer (address in memory) to pulse-times table
353      // The address is 16-bits (2 byte, 1 word)
354      PGM_P time_ptr = (PGM_P)pgm_read_word(data_ptr);
355      data_ptr+=2;
356      code_ptr = (PGM_P)pgm_read_word(data_ptr);
357  
358      // Transmit all codeElements for this POWER code
359      // (a codeElement is an onTime and an offTime)
360      // transmitting onTime means pulsing the IR emitters at the carrier
361      // frequency for the length of time specified in onTime
362      // transmitting offTime means no output from the IR emitters for the
363      // length of time specified in offTime
364  
365  #if 0
366      // print out all of the pulse pairs
367      for (uint8_t k=0; k<numpairs; k++) {
368        uint8_t ti;
369        ti = (read_bits(bitcompression)) * 4;
370        // read the onTime and offTime from the program memory
371        ontime = pgm_read_word(time_ptr+ti);
372        offtime = pgm_read_word(time_ptr+ti+2);
373        if (DEBUG) {
374          Serial.print("ti = "); Serial.print(ti>>2);
375          Serial.print("\tPair = "); Serial.print(ontime);
376          Serial.print(","); Serial.println(offtime);
377        }
378      }
379      continue;
380  #endif
381  
382      // For EACH pair in this code....
383      cli();
384      for (uint8_t k=0; k<numpairs; k++) {
385        uint16_t ti;
386  
387        // Read the next 'n' bits as indicated by the compression variable
388        // The multiply by 4 because there are 2 timing numbers per pair
389        // and each timing number is one word long, so 4 bytes total!
390        ti = (read_bits(bitcompression)) * 4;
391  
392        // read the onTime and offTime from the program memory
393        ontime = pgm_read_word(time_ptr+ti);  // read word 1 - ontime
394        offtime = pgm_read_word(time_ptr+ti+2);  // read word 2 - offtime
395        // transmit this codeElement (ontime and offtime)
396        xmitCodeElement(ontime, offtime, (freq!=0));
397      }
398      sei();
399  
400      //Flush remaining bits, so that next code starts
401      //with a fresh set of 8 bits.
402      bitsleft_r=0;
403  
404      // delay 205 milliseconds before transmitting next POWER code
405      delay_ten_us(20500);
406  
407      // visible indication that a code has been output.
408      quickflashLED();
409  
410      // if user is pushing Trigger button, stop transmission
411      if (digitalRead(TRIGGER) == 0) {
412        startOver = TRUE;
413        break;
414      }
415    }
416  
417    if (startOver) goto Start_transmission;
418    while (Loop == 1);
419  
420    // flash the visible LED on PB0  8 times to indicate that we're done
421    delay_ten_us(65500); // wait maxtime
422    delay_ten_us(65500); // wait maxtime
423    quickflashLEDx(8);
424  
425  }
426  
427  void loop() {
428    sleepNow();
429    // if the user pushes the Trigger button and lets go, then start transmission of all POWER codes
430    if (digitalRead(TRIGGER) == 0) {
431      delay_ten_us(3000);  // delay 30ms
432      if (digitalRead(TRIGGER) == 1) {
433        sendAllCodes();
434      }
435    }
436  }
437  
438  
439  /****************************** LED AND DELAY FUNCTIONS ********/
440  
441  
442  // This function delays the specified number of 10 microseconds
443  // it is 'hardcoded' and is calibrated by adjusting DELAY_CNT
444  // in main.h Unless you are changing the crystal from 8mhz, dont
445  // mess with this.
446  void delay_ten_us(uint16_t us) {
447    uint8_t timer;
448    while (us != 0) {
449      // for 8MHz we want to delay 80 cycles per 10 microseconds
450      // this code is tweaked to give about that amount.
451      for (timer=0; timer <= DELAY_CNT; timer++) {
452        NOP;
453        NOP;
454      }
455      NOP;
456      us--;
457    }
458  }
459  
460  
461  // This function quickly pulses the visible LED (connected to PB0, pin 5)
462  // This will indicate to the user that a code is being transmitted
463  void quickflashLED( void ) {
464    digitalWrite(LED, HIGH);
465    delay_ten_us(3000);   // 30 millisec delay
466    digitalWrite(LED, LOW);
467  }
468  
469  // This function just flashes the visible LED a couple times, used to
470  // tell the user what region is selected
471  void quickflashLEDx( uint8_t x ) {
472    quickflashLED();
473    while(--x) {
474      delay_ten_us(15000);     // 150 millisec delay between flahes
475      quickflashLED();
476    }
477  }
478  
479  
480  
481  
482  /****************************** SLEEP and WAKE FUNCTIONS ********/
483  // from kaqkjz:
484  // http://www.ka1kjz.com/561/adding-sleep-to-tv-b-gone-code/
485  
486  void sleepNow()
487  {
488    set_sleep_mode(TRIGGER);                    // sleep mode is set here
489  
490    sleep_enable();                             // enables the sleep bit in the mcucr register
491  
492    attachInterrupt(0, wakeUpNow, LOW);         // use interrupt 0 (pin 2) and run function
493    // wakeUpNow when pin 2 gets LOW
494  
495    sleep_mode();                               // here the device is actually put to sleep!!
496    // THE PROGRAM CONTINUES FROM HERE ON WAKE
497  
498    sleep_disable();                            // first thing after waking, disable sleep
499  
500    detachInterrupt(0);                         // disables int 0 as the wakeupnow code will
501                                                // not be executed during normal runtime
502  }
503  
504  void wakeUpNow()
505  {
506    // any needed wakeup code can be placed here
507  }