| 1 | /*\r |
| 2 | TV-B-Gone\r |
| 3 | Firmware\r |
| 4 | for use with ATtiny85\r |
| 5 | Mitch Altman + Limor Fried\r |
| 6 | 7-Oct-07\r |
| 7 | \r |
| 8 | Distributed under Creative Commons 2.5 -- Attib & Share Alike\r |
| 9 | */\r |
| 10 | \r |
| 11 | #include <avr/io.h> // this contains all the IO port definitions\r |
| 12 | #include <avr/interrupt.h> // definitions for interrupts\r |
| 13 | #include <avr/sleep.h> // definitions for power-down modes\r |
| 14 | #include <avr/pgmspace.h> // definitions or keeping constants in program memory\r |
| 15 | #include <avr/wdt.h>\r |
| 16 | #include "main.h"\r |
| 17 | \r |
| 18 | #define LED PB2 // visible LED\r |
| 19 | #define IRLED1 PB0 // IR LED\r |
| 20 | #define IRLED2 PB1 // IR LED\r |
| 21 | \r |
| 22 | /*\r |
| 23 | This project transmits a bunch of TV POWER codes, one right after the other, \r |
| 24 | with a pause in between each. (To have a visible indication that it is \r |
| 25 | transmitting, it also pulses a visible LED once each time a POWER code is \r |
| 26 | transmitted.) That is all TV-B-Gone does. The tricky part of TV-B-Gone \r |
| 27 | was collecting all of the POWER codes, and getting rid of the duplicates and \r |
| 28 | near-duplicates (because if there is a duplicate, then one POWER code will \r |
| 29 | turn a TV off, and the duplicate will turn it on again (which we certainly \r |
| 30 | do not want). I have compiled the top-40 most popular codes with the \r |
| 31 | duplicates eliminated, both for North America (which is the same as Asia, as \r |
| 32 | far as POWER codes are concerned -- even though much of Asia USES PAL video) \r |
| 33 | and for Europe (which works for Australia, New Zealand, the Middle East, and \r |
| 34 | other parts of the world that use PAL video).\r |
| 35 | \r |
| 36 | Before creating a TV-B-Gone Kit, I originally started this project by hacking \r |
| 37 | the MiniPOV kit. This presents a limitation, based on the size of\r |
| 38 | the Atmel ATtiny2313 internal flash memory, which is 2KB. With 2KB we can only \r |
| 39 | fit about 7 POWER codes into the firmware's database of POWER codes. 40 codes\r |
| 40 | requires 8KB of flash memory, which is why we chose the ATtiny85 for the \r |
| 41 | TV-B-Gone Kit.\r |
| 42 | \r |
| 43 | This version of the firmware has the most popular 40 POWER codes for North America.\r |
| 44 | */\r |
| 45 | \r |
| 46 | \r |
| 47 | /*\r |
| 48 | This project is a good example of how to use the AVR chip timers.\r |
| 49 | */\r |
| 50 | \r |
| 51 | \r |
| 52 | /*\r |
| 53 | The hardware for this project is very simple:\r |
| 54 | ATtiny85 has 8 pins:\r |
| 55 | pin 1 connects to programming circuitry\r |
| 56 | pin 2 one pin of ceramic resonator\r |
| 57 | pin 3 one pin of ceramic resonator\r |
| 58 | pin 4 ground\r |
| 59 | pin 5 PB0 - visible LED, and also connects to programming circuitry\r |
| 60 | pin 6 OC1A - IR emitter, through a PN2222A driver (with 47 ohm base resistor), and also connects to programming circuitry\r |
| 61 | pin 7 push-button switch, and also connects to serial port programming circuitry\r |
| 62 | pin 8 +3v\r |
| 63 | See the schematic for more details.\r |
| 64 | \r |
| 65 | This firmware requires using an 8.0MHz ceramic resonator \r |
| 66 | (since the internal oscillator may not be accurate enough).\r |
| 67 | \r |
| 68 | IMPORTANT: to use the ceramic resonator, you must perform the following:\r |
| 69 | make burn-fuse_cr\r |
| 70 | */\r |
| 71 | \r |
| 72 | \r |
| 73 | /*\r |
| 74 | The C compiler creates code that will transfer all constants into RAM when the microcontroller\r |
| 75 | resets. Since this firmware has a table (powerCodes) that is too large to transfer into RAM,\r |
| 76 | the C compiler needs to be told to keep it in program memory space. This is accomplished by\r |
| 77 | the macro PROGMEM (this is used in the definition for powerCodes). Since the\r |
| 78 | C compiler assumes that constants are in RAM, rather than in program memory, when accessing\r |
| 79 | powerCodes, we need to use the pgm_read_word() and pgm_read_byte macros, and we need\r |
| 80 | to use powerCodes as an address. This is done with PGM_P, defined below. \r |
| 81 | For example, when we start a new powerCode, we first point to it with the following statement:\r |
| 82 | PGM_P thecode_p = pgm_read_word(powerCodes+i);\r |
| 83 | The next read from the powerCode is a byte that indicates the carrier frequency, read as follows:\r |
| 84 | uint8_t freq = pgm_read_byte(thecode_p);\r |
| 85 | Subsequent reads from the powerCode are onTime/offTime pairs, which are words, read as follows:\r |
| 86 | ontime = pgm_read_word(thecode_p+(offset_into_table);\r |
| 87 | offtime = pgm_read_word(thecode_p+(offset_into_table);\r |
| 88 | */\r |
| 89 | \r |
| 90 | #define NOP __asm__ __volatile__ ("nop")\r |
| 91 | // This function delays the specified number of 10 microseconds\r |
| 92 | #define DELAY_CNT 11\r |
| 93 | void delay_ten_us(uint16_t us) {\r |
| 94 | uint8_t timer;\r |
| 95 | while (us != 0) {\r |
| 96 | for (timer=0; timer <= DELAY_CNT; timer++) {\r |
| 97 | NOP;\r |
| 98 | NOP;\r |
| 99 | }\r |
| 100 | NOP;\r |
| 101 | us--;\r |
| 102 | }\r |
| 103 | }\r |
| 104 | \r |
| 105 | \r |
| 106 | // This function quickly pulses the visible LED (connected to PB0, pin 5)\r |
| 107 | void quickflashLED( void ) {\r |
| 108 | // pulse LED on for 30ms\r |
| 109 | \r |
| 110 | PORTB &= ~_BV(LED); // turn on visible LED at PB0 by pulling pin to ground\r |
| 111 | delay_ten_us(3000); // 30 millisec delay\r |
| 112 | PORTB |= _BV(LED); // turn off visible LED at PB0 by pulling pin to +3V\r |
| 113 | }\r |
| 114 | \r |
| 115 | \r |
| 116 | // This function quickly pulses the visible LED (connected to PB0, pin 5) 4 times\r |
| 117 | void quickflashLED4x( void ) {\r |
| 118 | quickflashLED();\r |
| 119 | delay_ten_us(15000); // 150 millisec delay\r |
| 120 | quickflashLED();\r |
| 121 | delay_ten_us(15000); // 150 millisec delay\r |
| 122 | quickflashLED();\r |
| 123 | delay_ten_us(15000); // 150 millisec delay\r |
| 124 | quickflashLED();\r |
| 125 | }\r |
| 126 | \r |
| 127 | \r |
| 128 | // This function transmits one Code Element of a POWER code to the IR emitter, \r |
| 129 | // given offTime and onTime for the codeElement\r |
| 130 | // If offTime = 0 that signifies the last Code Element of the POWER code\r |
| 131 | // and the delay_ten_us function will have no delay for offTime \r |
| 132 | // (but we'll delay for 250 milliseconds in the main function)\r |
| 133 | void xmitCodeElement(uint16_t ontime, uint16_t offtime ) {\r |
| 134 | // start Timer1 outputting the carrier frequency to IR emitters on OC1A (PB1, pin 6) and OC0A (PB0, pin 5)\r |
| 135 | TCNT0 = 0; // reset the timers so they are aligned\r |
| 136 | TCNT1 = 0;\r |
| 137 | TIFR = 0; // clean out the timer flags\r |
| 138 | \r |
| 139 | TCCR0A =_BV(COM0A0) | _BV(WGM01); // set up timer 0\r |
| 140 | \r |
| 141 | TCCR1 =_BV(COM1A0) | _BV(CS10) | _BV(CTC1); // set up and turn on timer 1\r |
| 142 | //TCCR1 = 0b10010001 // CTC1 = 1 to reset Timer1 to 0 when it reaches the value in OCR1C (i.e., on Compare Match)\r |
| 143 | // PWM1A = 0 to disable PWM mode\r |
| 144 | // COM1A1:0 = 01 to toggle OC1A on Compare Match\r |
| 145 | // CS13:10 = 0001 to start Timer1 with prescaler set to divide by 1 (i.e., no prescaler divide)\r |
| 146 | TCCR0B = _BV(CS00); // turn on timer 0 exactly 1 instruction later\r |
| 147 | \r |
| 148 | // keep transmitting carrier for onTime\r |
| 149 | delay_ten_us(ontime);\r |
| 150 | \r |
| 151 | \r |
| 152 | // turn off output to IR emitters on 0C1A (PB1, pin 6) for offTime\r |
| 153 | TCCR1 = 0; // stop Timer 1\r |
| 154 | TCCR0B = 0; // stop Timer 0, exactly one instruction later\r |
| 155 | TCCR0A = 0;\r |
| 156 | \r |
| 157 | PORTB &= ~_BV(IRLED1) & ~_BV(IRLED2); // turn off IR LED\r |
| 158 | \r |
| 159 | delay_ten_us(offtime);\r |
| 160 | }\r |
| 161 | \r |
| 162 | \r |
| 163 | void gotosleep(void) {\r |
| 164 | // Shut down everything and put the CPU to sleep\r |
| 165 | // put CPU into Power Down Sleep Mode\r |
| 166 | \r |
| 167 | TCCR1 = 0; // turn off frequency generator (should be off already)\r |
| 168 | TCCR0B = 0;\r |
| 169 | PORTB |= _BV(LED); // turn on the button pullup, turn off visible LED\r |
| 170 | PORTB &= ~_BV(IRLED1) & ~_BV(IRLED2); // turn off IR LED\r |
| 171 | delay_ten_us(1000); // wait 10 millisec second\r |
| 172 | \r |
| 173 | wdt_disable();\r |
| 174 | \r |
| 175 | MCUCR = _BV(SM1) | _BV(SE); // power down mode, SE=1 (bit 5) -- enables Sleep Modes\r |
| 176 | sleep_cpu(); \r |
| 177 | }\r |
| 178 | \r |
| 179 | //extern const struct powercode powerCodes[] PROGMEM;\r |
| 180 | extern const PGM_P *powerCodes[] PROGMEM;\r |
| 181 | \r |
| 182 | extern uint8_t num_codes;\r |
| 183 | \r |
| 184 | int main(void) {\r |
| 185 | uint8_t i, j;\r |
| 186 | uint16_t ontime, offtime;\r |
| 187 | \r |
| 188 | TCCR1 = 0; // turn off frequency generator (should be off already)\r |
| 189 | TCCR0B = 0;\r |
| 190 | \r |
| 191 | i = MCUSR; // find out why we reset\r |
| 192 | MCUSR = 0; // clear reset flags immediately\r |
| 193 | \r |
| 194 | // turn on watchdog timer immediately, this protects against\r |
| 195 | // a 'stuck' system by resetting it\r |
| 196 | wdt_enable(WDTO_8S); // 1 second long timeout\r |
| 197 | \r |
| 198 | // Set the inputs and ouputs\r |
| 199 | PORTB &= ~_BV(IRLED1) & ~_BV(IRLED2); // IR LED is off when pin is low\r |
| 200 | DDRB = _BV(LED) | _BV(IRLED1) | _BV(IRLED2); // set the visible and IR LED pins to outputs\r |
| 201 | PORTB = _BV(LED); // visible LED is off when pin is high\r |
| 202 | \r |
| 203 | // check the reset flags\r |
| 204 | if ((i & _BV(PORF)) || // batteries were inserted\r |
| 205 | (i & _BV(BORF))) { // brownout reset\r |
| 206 | gotosleep(); // we only want to do something when the reset button is pressed\r |
| 207 | }\r |
| 208 | \r |
| 209 | for (i=0; i<num_codes; i++) { // for every POWER code in our collection\r |
| 210 | wdt_reset(); // make sure we dont get 'stuck' in a code\r |
| 211 | \r |
| 212 | quickflashLED(); // visible indication that a code is being output\r |
| 213 | PGM_P thecode_p = pgm_read_word(powerCodes+i); // point to next POWER code\r |
| 214 | \r |
| 215 | uint8_t freq = pgm_read_byte(thecode_p);\r |
| 216 | // set OCR for Timer1 and Timer0 to output this POWER code's carrier frequency\r |
| 217 | OCR0A = OCR1C = freq; \r |
| 218 | \r |
| 219 | // transmit all codeElements for this POWER code (a codeElement is an onTime and an offTime)\r |
| 220 | // transmitting onTime means pulsing the IR emitters at the carrier frequency for the length of time specified in onTime\r |
| 221 | // transmitting offTime means no output from the IR emitters for the length of time specified in offTime\r |
| 222 | j = 0; // index into codeElements of this POWER code\r |
| 223 | do {\r |
| 224 | // read the onTime and offTime from the program memory\r |
| 225 | ontime = pgm_read_word(thecode_p+(j*4)+1);\r |
| 226 | offtime = pgm_read_word(thecode_p+(j*4)+3);\r |
| 227 | \r |
| 228 | xmitCodeElement(ontime, offtime); // transmit this codeElement (ontime and offtime)\r |
| 229 | j++;\r |
| 230 | } while ( offtime != 0 ); // offTime = 0 signifies last codeElement for a POWER code\r |
| 231 | \r |
| 232 | PORTB &= ~_BV(IRLED1) & ~_BV(IRLED2); // turn off IR LED\r |
| 233 | \r |
| 234 | // delay 250 milliseconds before transmitting next POWER code\r |
| 235 | delay_ten_us(25000);\r |
| 236 | }\r |
| 237 | \r |
| 238 | \r |
| 239 | // flash the visible LED on PB0 4 times to indicate that we're done\r |
| 240 | delay_ten_us(65500); // wait maxtime \r |
| 241 | quickflashLED4x();\r |
| 242 | \r |
| 243 | gotosleep();\r |
| 244 | }\r |