From 4fca6c11e173901b79a38c431db6717857231b22 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Tue, 9 Mar 2021 08:20:09 +0200 Subject: [PATCH] the enable/disable logic became somewhat hairy with autodetections and timeouts, but I think it works ok for now --- src/i2c.c | 21 ++++- src/i2c.h | 1 + src/main.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 226 insertions(+), 45 deletions(-) diff --git a/src/i2c.c b/src/i2c.c index c7af41d..c37dd18 100644 --- a/src/i2c.c +++ b/src/i2c.c @@ -3,10 +3,14 @@ #include #include "i2c.h" +#define PC_SDA 0x10 +#define PC_SCL 0x20 + enum { I2C_IDLE, I2C_MASTER_WRITE, - I2C_MASTER_READ + I2C_MASTER_READ, + I2C_HOLD = 0xff }; enum { @@ -326,13 +330,22 @@ void i2c_check_async(void) void i2c_hold(void) { TWCR = 0; /* make sure the AVR i2c hardware is disabled */ - DDRC = 0x20; /* ... and drive SCL pin low */ - PORTC = 0; + DDRC |= PC_SCL; /* ... and drive SCL pin low */ + PORTC &= ~PC_SCL; + + i2c_mode = I2C_HOLD; } void i2c_release(void) { - DDRC = 0; + DDRC &= ~PC_SCL; + + i2c_mode = I2C_IDLE; +} + +unsigned char i2c_isheld(void) +{ + return i2c_mode == I2C_HOLD; } void i2c_abort(void) diff --git a/src/i2c.h b/src/i2c.h index e26e43d..fe880a4 100644 --- a/src/i2c.h +++ b/src/i2c.h @@ -9,6 +9,7 @@ void i2c_async(void (*donecb)(void)); void i2c_check_async(void); void i2c_hold(void); void i2c_release(void); +unsigned char i2c_isheld(void); void i2c_abort(void); diff --git a/src/main.c b/src/main.c index 995149e..a3f30c8 100644 --- a/src/main.c +++ b/src/main.c @@ -7,6 +7,41 @@ #include "serial.h" #include "i2c.h" +enum { + IR_CMD_ONOFF = 0x0c, + IR_CMD_MUTE = 0x0d, + IR_CMD_VOLUP = 0x10, + IR_CMD_VOLDN = 0x11, + IR_CMD_PROGUP = 0x20, + IR_CMD_PROGDN = 0x21, + IR_CMD_OK = 0x25, + IR_CMD_YELLOW = 0x32, + IR_CMD_BLUE = 0x34, + IR_CMD_GREEN = 0x36, + IR_CMD_RED = 0x37, + IR_CMD_TVAV = 0x38, + IR_CMD_MENU = 0x3b +}; + +enum { + STAT_ON = 1, + STAT_SYNC = 2, + STAT_60HZ = 4, + STAT_COLOR_PAL = 0x10, + STAT_COLOR_NTSC = 0x20, + STAT_COLOR_SECAM = 0x30, + + STAT_EN = 0x40, + STAT_BUSHOLD = 0x80 +}; +#define STAT_COLOR_MASK 0x30 + +enum { + PEND_IR = 1, + PEND_EN = 2, + PEND_VOL = 4 +}; + /* TODO * IR decoding, connected to PD2 * enable/disable with switch on PCINT1 @@ -15,23 +50,6 @@ #define PD_IR 2 #define PD_ENSW 3 -#define EVQ_SIZE 64 -uint16_t evq[EVQ_SIZE]; -volatile int evq_wr, evq_rd; - -#define LOGNUM(x) EVLOG(EV_DEBUG, (x)) - -#define EVLOG(ev, data) \ - do { \ - if(ev != EV_DEBUG || dbgmode) { \ - evq[evq_wr] = ((uint16_t)(ev) << 8) | (data); \ - evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \ - if(evq_wr == evq_rd) { \ - evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \ - } \ - } \ - } while(0) - /* serial input buffer */ static char input[64]; static unsigned char inp_cidx; @@ -44,8 +62,14 @@ static int dbgmode; static volatile unsigned char pending; static volatile unsigned char enable, prev_enable; +static unsigned char stat, prev_stat; static volatile uint16_t ir_input; +static unsigned char volume, mute; +static unsigned char grab_timeout; + +static void updhold(void); +static unsigned char read_status(void); static void proc_cmd(char *input); static void printstat(void); static void printdump(void); @@ -54,6 +78,7 @@ static void printdump(void); int main(void) { unsigned char pend; + uint16_t timer, prev_time; /* tri-state everything and disable pullups */ DDRB = 1; /* B0: activity LED */ @@ -74,10 +99,20 @@ int main(void) */ TCCR0A = 2; /* clear on compare match */ TCCR0B = 3; /* clock select: ioclock / 64 <- prescaler */ - OCR0A = 192; /* count top */ + OCR0A = 192; /* count top (tweaked until it works) */ + + /* setup timer 1 as a freerunning 16bit counter */ + TCCR1A = 0; + TCCR1B = 5; /* prescaler: clk / 1024: 14.4khz */ + + timer = prev_time = 0; /* read initial enable switch state */ enable = (~PIND >> PD_ENSW) & 1; + volume = 31; + mute = 0; + + if(enable) grab_timeout = 10; i2c_init(); @@ -87,18 +122,57 @@ int main(void) printf("TV i2c hack\n"); for(;;) { + timer = TCNT1; i2c_check_async(); cli(); pend = pending; pending = 0; sei(); - if(pend & 1) { - printf("IR: %04x (%s)\n", (unsigned int)ir_input & 0x7fff, ir_input & 0x8000 ? "err" : "ok"); + if((pend & PEND_IR) && !(ir_input & 0x8000)) { + printf("IR addr: %02x cmd: %02x (%04x)\n", (ir_input >> 6) & 0x1f, ir_input & 0x3f, ir_input); + + if(((ir_input >> 6) & 0x1f) == 0) { /* TV remote sends IR address 0 */ + switch(ir_input & 0x3f) { + case IR_CMD_GREEN: + enable = 1; + pend |= PEND_EN; + break; + + case IR_CMD_RED: + enable = 0; + pend |= PEND_EN; + break; + + case IR_CMD_VOLUP: + if(volume < 0x3f) { + volume++; + pend |= PEND_VOL; + } + break; + + case IR_CMD_VOLDN: + if(volume > 0) { + volume--; + pend |= PEND_VOL; + } + break; + + case IR_CMD_MUTE: + mute ^= 1; + pend |= PEND_VOL; + break; + } + } } - if(pend & 2 && enable != prev_enable) { + if((pend & PEND_EN) && enable != prev_enable) { printf("enable: %d\n", enable); prev_enable = enable; + + if(!enable && i2c_isheld()) { + i2c_release(); + printf("rel1\n"); + } } if(have_input()) { @@ -115,24 +189,121 @@ int main(void) } } - /* read from queue and send over the serial port */ - /* - uint16_t val; - val = 0xffff; - cli(); - if(evq_wr != evq_rd) { - val = evq[evq_rd]; - evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); + if(timer - prev_time >= 14400) { /* 1 sec */ + prev_time = timer; + + prev_stat = stat; + stat = read_status(); + + if(stat != prev_stat) { + printf("[%s|%s|%dHz|", stat & STAT_ON ? " on" : "off", + stat & STAT_SYNC ? "lock" : " ", stat & STAT_60HZ ? 60 : 50); + + switch(stat & STAT_COLOR_MASK) { + case STAT_COLOR_PAL: + printf(" PAL ]"); + break; + case STAT_COLOR_NTSC: + printf(" NTSC]"); + break; + case STAT_COLOR_SECAM: + printf("SECAM]"); + break; + default: + printf(" ]"); + } + + printf(" [%s|%s]\n", stat & STAT_EN ? "en" : " ", + stat & STAT_BUSHOLD ? "hold" : " "); + } + /*printf(" %02x %02x %02x\n", data[0], data[1], data[2]);*/ + + + if(enable) { + static unsigned char rel_timeout; + if(i2c_isheld()) { + if(!(stat & STAT_ON) || (stat & STAT_COLOR_MASK)) { + /* if we turn the TV off, or if we don't have RGB input + * release the i2c bus (after a few seconds to make sure it's not a glitch) + */ + if(++rel_timeout > 3) { + i2c_release(); + printf("rel2\n"); + rel_timeout = 0; + grab_timeout = 10; + } + } else { + rel_timeout = 0; + /* handle any pending volume changes */ + /* + if(pend & PEND_VOL) { + updhold(); + } + */ + } + + } else { + /* we're not currently holding the bus */ + if((stat & STAT_ON) && !(stat & STAT_COLOR_MASK)) { + /* if the TV is on and we're in RGB mode, grab the bus + * (after a few seconds to allow the tv to initialize) + */ + if(grab_timeout > 0) { + grab_timeout--; + } else { + updhold(); + grab_timeout = 0; + } + } + } + } } - sei(); + } + return 0; +} - if(val != 0xffff) { - unsigned char ev = val >> 8; - printf("%02x: %02x\n", ev, val & 0xff); +static void updhold(void) +{ + data[0] = mute ? 0 : volume; + data[1] = 0x1f; + i2c_write(0x8a, 0x1f, data, 1); + i2c_wait(); + i2c_write(0x8a, 0x1c, data + 1, 1); + i2c_wait(); + i2c_hold(); +} + +static unsigned char read_status(void) +{ + unsigned char s, col; + + s = i2c_isheld() ? STAT_BUSHOLD : 0; + if(enable) s |= STAT_EN; + + i2c_read(0x8a, 0, data, 3); + if(s & STAT_BUSHOLD) { + i2c_wait(); + i2c_hold(); + } else { + i2c_wait(); + } + + if(data[2] & 0x80) s |= STAT_ON; + if(data[0] & 0x10) s |= STAT_SYNC; + if(data[1] & 0x20) s |= STAT_60HZ; + col = data[0] & 0x0f; + + if(col != 0) { + if(col == 10) { + s |= STAT_COLOR_SECAM; + } else if(col & 1) { + s |= STAT_COLOR_NTSC; + } else { + s |= STAT_COLOR_PAL; } - */ } - return 0; + + return s; } static void proc_cmd(char *input) @@ -260,7 +431,7 @@ ISR(INT0_vect) if(pending & 1) return; /* IR going low */ - ir_input = 0; + ir_input = 0x8000; nsamples = 1; /* we're starting in the middle of the first bit which should be 01 (=1) */ samples = 0; EIMSK &= 0xfe; /* disable further interrupts while decoding IR code */ @@ -268,16 +439,12 @@ ISR(INT0_vect) TCNT0 = 96; /* reset the counter to half the range so it'll trigger in the middle of the current pulse */ TIFR0 |= 1 << OCF0A; /* clear pending interrupts */ TIMSK0 = 1 << OCIE0A; /* enable output compare interrupt */ - - //PORTB ^= 1; } ISR(TIMER0_COMPA_vect) { static unsigned char err; - PORTB ^= 1; - samples = (samples << 1) | (~(PIND >> PD_IR) & 1); if((++nsamples & 1) == 0) { if((samples & 3) == 0 || (samples & 3) == 3) { @@ -290,7 +457,7 @@ ISR(TIMER0_COMPA_vect) } if(nsamples >= 28) { - if(err) { + if(err || !(ir_input & 0x2000)) { ir_input |= 0x8000; } pending |= 1; -- 1.7.10.4