291 lines
10 KiB
C
291 lines
10 KiB
C
/*
|
|
TV-B-Gone Firmware version 1.2
|
|
for use with ATtiny85v and v1.2 hardware
|
|
(c) Mitch Altman + Limor Fried 2009
|
|
Last edits, August 16 2009
|
|
|
|
With some code from:
|
|
Kevin Timmerman & Damien Good 7-Dec-07
|
|
|
|
Distributed under Creative Commons 2.5 -- Attib & Share Alike
|
|
|
|
This is the 'universal' code designed for v1.2 - it will select EU or NA
|
|
depending on a pulldown resistor on pin B1 !
|
|
*/
|
|
|
|
#include <avr/io.h> // this contains all the IO port definitions
|
|
#include <avr/eeprom.h>
|
|
#include <avr/sleep.h> // definitions for power-down modes
|
|
#include <avr/pgmspace.h> // definitions or keeping constants in program memory
|
|
#include <avr/wdt.h>
|
|
#include "main.h"
|
|
#include "util.h"
|
|
|
|
|
|
/*
|
|
This project transmits a bunch of TV POWER codes, one right after the other,
|
|
with a pause in between each. (To have a visible indication that it is
|
|
transmitting, it also pulses a visible LED once each time a POWER code is
|
|
transmitted.) That is all TV-B-Gone does. The tricky part of TV-B-Gone
|
|
was collecting all of the POWER codes, and getting rid of the duplicates and
|
|
near-duplicates (because if there is a duplicate, then one POWER code will
|
|
turn a TV off, and the duplicate will turn it on again (which we certainly
|
|
do not want). I have compiled the most popular codes with the
|
|
duplicates eliminated, both for North America (which is the same as Asia, as
|
|
far as POWER codes are concerned -- even though much of Asia USES PAL video)
|
|
and for Europe (which works for Australia, New Zealand, the Middle East, and
|
|
other parts of the world that use PAL video).
|
|
|
|
Before creating a TV-B-Gone Kit, I originally started this project by hacking
|
|
the MiniPOV kit. This presents a limitation, based on the size of
|
|
the Atmel ATtiny2313 internal flash memory, which is 2KB. With 2KB we can only
|
|
fit about 7 POWER codes into the firmware's database of POWER codes. However,
|
|
the more codes the better! Which is why we chose the ATtiny85 for the
|
|
TV-B-Gone Kit.
|
|
|
|
This version of the firmware has the most popular 100+ POWER codes for
|
|
North America and 100+ POWER codes for Europe. You can select which region
|
|
to use by soldering a 10K pulldown resistor.
|
|
*/
|
|
|
|
|
|
/*
|
|
This project is a good example of how to use the AVR chip timers.
|
|
*/
|
|
|
|
|
|
/*
|
|
The hardware for this project is very simple:
|
|
ATtiny85 has 8 pins:
|
|
pin 1 RST + Button
|
|
pin 2 one pin of ceramic resonator MUST be 8.0 mhz
|
|
pin 3 other pin of ceramic resonator
|
|
pin 4 ground
|
|
pin 5 OC1A - IR emitters, through a '2907 PNP driver that connects
|
|
to 4 (or more!) PN2222A drivers, with 1000 ohm base resistor
|
|
and also connects to programming circuitry
|
|
pin 6 Region selector. Float for US, 10K pulldown for EU,
|
|
also connects to programming circuitry
|
|
pin 7 PB0 - visible LED, and also connects to programming circuitry
|
|
pin 8 +3-5v DC (such as 2-4 AA batteries!)
|
|
See the schematic for more details.
|
|
|
|
This firmware requires using an 8.0MHz ceramic resonator
|
|
(since the internal oscillator may not be accurate enough).
|
|
|
|
IMPORTANT: to use the ceramic resonator, you must perform the following:
|
|
make burn-fuse_cr
|
|
*/
|
|
|
|
/* This function is the 'workhorse' of transmitting IR codes.
|
|
Given the on and off times, it turns on the PWM output on and off
|
|
to generate one 'pair' from a long code. Each code has ~50 pairs! */
|
|
void xmitCodeElement(uint16_t ontime, uint16_t offtime, uint8_t PWM_code )
|
|
{
|
|
// start Timer0 outputting the carrier frequency to IR emitters on and OC0A
|
|
// (PB0, pin 5)
|
|
TCNT0 = 0; // reset the timers so they are aligned
|
|
TIFR = 0; // clean out the timer flags
|
|
|
|
if(PWM_code) {
|
|
// 99% of codes are PWM codes, they are pulses of a carrier frequecy
|
|
// Usually the carrier is around 38KHz, and we generate that with PWM
|
|
// timer 0
|
|
TCCR0A =_BV(COM0A0) | _BV(WGM01); // set up timer 0
|
|
TCCR0B = _BV(CS00);
|
|
} else {
|
|
// However some codes dont use PWM in which case we just turn the IR
|
|
// LED on for the period of time.
|
|
PORTB &= ~_BV(IRLED);
|
|
}
|
|
|
|
// Now we wait, allowing the PWM hardware to pulse out the carrier
|
|
// frequency for the specified 'on' time
|
|
delay_ten_us(ontime);
|
|
|
|
// Now we have to turn it off so disable the PWM output
|
|
TCCR0A = 0;
|
|
TCCR0B = 0;
|
|
// And make sure that the IR LED is off too (since the PWM may have
|
|
// been stopped while the LED is on!)
|
|
PORTB |= _BV(IRLED); // turn off IR LED
|
|
|
|
// Now we wait for the specified 'off' time
|
|
delay_ten_us(offtime);
|
|
}
|
|
|
|
/* transmit IR burst, using the miles tag II format
|
|
* one IR burst is ticks*600uS on, and 600uS off
|
|
* n=1 codes 0
|
|
* n=2 codes 1
|
|
* n=4 codes header
|
|
*/
|
|
void transmit_ir(uint8_t ticks) {
|
|
xmitCodeElement(ticks*600/10*TIMING_CORRECTION,600/10*TIMING_CORRECTION,1); // added some correction to transmit code
|
|
}
|
|
|
|
void transmit_ir_byte(uint8_t data) {
|
|
uint8_t tick;
|
|
int8_t i;
|
|
//DEBUGP(putstring("byte burst "));
|
|
for (i=7;i>=0;i--) {
|
|
tick = ((data>>i)&0x01);
|
|
//DEBUGP(putnum_ud(tick));
|
|
transmit_ir(tick+1);
|
|
}
|
|
//DEBUGP(putstring("\r\n"));
|
|
}
|
|
|
|
int main(void) {
|
|
uint8_t i;
|
|
|
|
TCCR1 = 0; // Turn off PWM/freq gen, should be off already
|
|
TCCR0A = 0;
|
|
TCCR0B = 0;
|
|
// set OCR for Timer1 to output this POWER code's carrier frequency (lasertag milestag II uses 56kHz)
|
|
OCR0A = freq_to_timerval(56000);
|
|
|
|
i = MCUSR; // Save reset reason
|
|
MCUSR = 0; // clear watchdog flag
|
|
WDTCR = _BV(WDCE) | _BV(WDE); // enable WDT disable
|
|
|
|
WDTCR = 0; // disable WDT while we setup
|
|
|
|
DDRB = _BV(LED) | _BV(IRLED) | _BV(TX); // set the visible and IR LED pins to outputs
|
|
PORTB = _BV(LED) | // visible LED is off when pin is high
|
|
_BV(IRLED) | // IR LED is off when pin is high
|
|
_BV(TX); // Turn on pullup on region switch pin
|
|
|
|
DEBUGP(putstring_nl("\r\nHello cock suckers! Ready to swallow some infrared?\r\n"));
|
|
|
|
// check the reset flags
|
|
if (i & _BV(BORF)) { // Brownout
|
|
// Flash out an error and go to sleep
|
|
flashslowLEDx(2);
|
|
tvbgone_sleep();
|
|
}
|
|
|
|
delay_ten_us(5000); // Let everything settle for a bit
|
|
|
|
// blink half the time for 4 seconds
|
|
for (i=0;i<4;i++) {
|
|
PORTB &= ~_BV(LED); // turn on visible LED at PB0 by pulling pin to ground
|
|
delay_ten_us(50000);// wait 0.5s
|
|
PORTB |= _BV(LED); // turn off visible LED at PB0 by pulling pin to +3V
|
|
delay_ten_us(50000);// wait 0.5s
|
|
}
|
|
|
|
// blink fast for 1 seconds
|
|
for (i=0;i<5;i++) {
|
|
PORTB &= ~_BV(LED); // turn on visible LED at PB0 by pulling pin to ground
|
|
delay_ten_us(10000); // wait 0.1s
|
|
PORTB |= _BV(LED); // turn off visible LED at PB0 by pulling pin to +3V
|
|
delay_ten_us(10000); // wait 0.1s
|
|
}
|
|
|
|
// turn on watchdog timer immediately, this protects against
|
|
// a 'stuck' system by resetting it
|
|
wdt_enable(WDTO_8S); // 1 second long timeout
|
|
|
|
DEBUGP(putstring("fire in the hole\r\n"));
|
|
transmit_ir(4);
|
|
transmit_ir_byte(0x83);
|
|
transmit_ir_byte(0x0b);
|
|
transmit_ir_byte(0xe8); // end byte not needed
|
|
|
|
delay_ten_us(50000); // wait 0.1s
|
|
// the protprietary recorded version
|
|
transmit_ir(4);
|
|
transmit_ir_byte(0x84);
|
|
transmit_ir_byte(0x00);
|
|
transmit_ir_byte(0xa4);
|
|
|
|
// We are done, no need for a watchdog timer anymore
|
|
wdt_disable();
|
|
|
|
// flash the visible LED on PB0 4 times to indicate that we're done
|
|
//delay_ten_us(65500); // wait maxtime
|
|
quickflashLEDx(4);
|
|
|
|
tvbgone_sleep();
|
|
}
|
|
|
|
|
|
/****************************** SLEEP FUNCTIONS ********/
|
|
|
|
void tvbgone_sleep( void )
|
|
{
|
|
// Shut down everything and put the CPU to sleep
|
|
TCCR0A = 0; // turn off frequency generator (should be off already)
|
|
TCCR0B = 0; // turn off frequency generator (should be off already)
|
|
PORTB |= _BV(LED) | // turn off visible LED
|
|
_BV(IRLED); // turn off IR LED
|
|
|
|
wdt_disable(); // turn off the watchdog (since we want to sleep
|
|
delay_ten_us(1000); // wait 10 millisec
|
|
|
|
MCUCR = _BV(SM1) | _BV(SE); // power down mode, SE enables Sleep Modes
|
|
sleep_cpu(); // put CPU into Power Down Sleep Mode
|
|
}
|
|
|
|
|
|
/****************************** LED AND DELAY FUNCTIONS ********/
|
|
|
|
|
|
// This function delays the specified number of 10 microseconds
|
|
// it is 'hardcoded' and is calibrated by adjusting DELAY_CNT
|
|
// in main.h Unless you are changing the crystal from 8mhz, dont
|
|
// mess with this.
|
|
void delay_ten_us(uint16_t us) {
|
|
uint8_t timer;
|
|
while (us != 0) {
|
|
// for 8MHz we want to delay 80 cycles per 10 microseconds
|
|
// this code is tweaked to give about that amount.
|
|
for (timer=0; timer <= DELAY_CNT; timer++) {
|
|
NOP;
|
|
NOP;
|
|
}
|
|
NOP;
|
|
us--;
|
|
}
|
|
}
|
|
|
|
|
|
// This function quickly pulses the visible LED (connected to PB0, pin 5)
|
|
// This will indicate to the user that a code is being transmitted
|
|
void quickflashLED( void ) {
|
|
PORTB &= ~_BV(LED); // turn on visible LED at PB0 by pulling pin to ground
|
|
delay_ten_us(3000); // 30 millisec delay
|
|
PORTB |= _BV(LED); // turn off visible LED at PB0 by pulling pin to +3V
|
|
}
|
|
|
|
// This function just flashes the visible LED a couple times, used to
|
|
// tell the user what region is selected
|
|
void quickflashLEDx( uint8_t x ) {
|
|
quickflashLED();
|
|
while(--x) {
|
|
wdt_reset();
|
|
delay_ten_us(15000); // 150 millisec delay between flahes
|
|
quickflashLED();
|
|
}
|
|
wdt_reset(); // kick the dog
|
|
}
|
|
|
|
// This is like the above but way slower, used for when the tvbgone
|
|
// crashes and wants to warn the user
|
|
void flashslowLEDx( uint8_t num_blinks )
|
|
{
|
|
uint8_t i;
|
|
for(i=0;i<num_blinks;i++)
|
|
{
|
|
// turn on visible LED at PB0 by pulling pin to ground
|
|
PORTB &= ~_BV(LED);
|
|
delay_ten_us(50000); // 500 millisec delay
|
|
wdt_reset(); // kick the dog
|
|
// turn off visible LED at PB0 by pulling pin to +3V
|
|
PORTB |= _BV(LED);
|
|
delay_ten_us(50000); // 500 millisec delay
|
|
wdt_reset(); // kick the dog
|
|
}
|
|
}
|