the enable/disable logic became somewhat hairy with autodetections and
[tv_i2c_hack] / src / main.c
index 2584292..a3f30c8 100644 (file)
@@ -7,28 +7,48 @@
 #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 PCINT0
+ * IR decoding, connected to PD2
  * enable/disable with switch on PCINT1
  * some of the buttons multiplexed on ADC0
  */
-
-#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)
+#define PD_IR  2
+#define PD_ENSW        3
 
 /* serial input buffer */
 static char input[64];
@@ -40,6 +60,16 @@ static uint16_t dump_addr;
 static int dump_count;
 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);
@@ -47,7 +77,8 @@ static void printdump(void);
 
 int main(void)
 {
-       uint16_t val;
+       unsigned char pend;
+       uint16_t timer, prev_time;
 
        /* tri-state everything and disable pullups */
        DDRB = 1;       /* B0: activity LED */
@@ -55,7 +86,33 @@ int main(void)
        DDRD = 0;
        PORTB = 0;
        PORTC = 0;
-       PORTD = 0;
+       PORTD = 1 << PD_ENSW;
+
+       /* setup external interrupts */
+       EICRA = 0x6;    /* INT0 falling edge, INT1 both */
+       EIMSK = 3;              /* enable INT0 and INT1 */
+
+       /* we'll use the timer 0 compare interrupt for sampling the IR input pulses
+        * we need to sample every 889us or 1124.86 Hz
+        * interrupt frequency = F_CPU / (prescaler * (1 - OCRnx))
+        * 14745600 / (64.0 * (1.0 + 204)) -> 1123.9 Hz
+        */
+       TCCR0A = 2;             /* clear on compare match */
+       TCCR0B = 3;             /* clock select: ioclock / 64 <- prescaler */
+       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();
 
@@ -65,8 +122,59 @@ int main(void)
        printf("TV i2c hack\n");
 
        for(;;) {
+               timer = TCNT1;
                i2c_check_async();
 
+               cli();
+               pend = pending;
+               pending = 0;
+               sei();
+               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 & 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()) {
                        int c = getchar();
                        putchar(c);
@@ -81,21 +189,121 @@ int main(void)
                        }
                }
 
-               /* read from queue and send over the serial port */
-               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;
+}
+
+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();
+}
 
-               if(val != 0xffff) {
-                       unsigned char ev = val >> 8;
-                       printf("%02x: %02x\n", ev, val & 0xff);
+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)
@@ -213,3 +421,54 @@ static void printdump(void)
                putchar('\n');
        }
 }
+
+static unsigned char nsamples;
+static uint32_t samples;
+
+ISR(INT0_vect)
+{
+       /* ignore interrupts while a previous input is pending */
+       if(pending & 1) return;
+
+       /* IR going low */
+       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 */
+
+       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 */
+}
+
+ISR(TIMER0_COMPA_vect)
+{
+       static unsigned char err;
+
+       samples = (samples << 1) | (~(PIND >> PD_IR) & 1);
+       if((++nsamples & 1) == 0) {
+               if((samples & 3) == 0 || (samples & 3) == 3) {
+                       /* 00 or 11 are invalid sequences, we lost sync */
+                       err = 1;
+               }
+
+               /* 01->1, 10->0 */
+               ir_input = (ir_input << 1) | (samples & 1);
+       }
+
+       if(nsamples >= 28) {
+               if(err || !(ir_input & 0x2000)) {
+                       ir_input |= 0x8000;
+               }
+               pending |= 1;
+               err = 0;
+               TIMSK0 &= ~(1 << OCIE0A);       /* disable the sampling interrupt */
+               EIMSK |= 1;             /* re-enable the edge-detect interrupt for the next input */
+       }
+}
+
+ISR(INT1_vect)
+{
+       enable = (~PIND >> PD_ENSW) & 1;
+       pending |= 2;
+}