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