the enable/disable logic became somewhat hairy with autodetections and
authorJohn Tsiombikas <nuclear@member.fsf.org>
Tue, 9 Mar 2021 06:20:09 +0000 (08:20 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Tue, 9 Mar 2021 06:20:09 +0000 (08:20 +0200)
timeouts, but I think it works ok for now

src/i2c.c
src/i2c.h
src/main.c

index c7af41d..c37dd18 100644 (file)
--- a/src/i2c.c
+++ b/src/i2c.c
@@ -3,10 +3,14 @@
 #include <avr/interrupt.h>
 #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)
index e26e43d..fe880a4 100644 (file)
--- 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);
 
 
index 995149e..a3f30c8 100644 (file)
@@ -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
 #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;