Commit | Line | Data |
---|---|---|
21c4e167 H |
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 |