/ 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 }