/* Copyright (c) 2008 Michael Hanselmann
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * Includes parts of Peter Fleury's great UART library:
 * http://jump.to/fleury
 * http://homepage.hispeed.ch/peterfleury/avr-software.html#libs
 *
 * Serial protocol:
 * 'S' 256 * [LED brightness in hex] '\n'
 */

/*
 * PD0: AVRRXD
 * PD1: AVRTXD
 * PD2: CANINT
 * PD3: JOYFIRE
 * PD4: JOY1GND
 * PD5: JOY2GND
 * PD6: ROWCLK
 * PD7: ROWDAT
 */

#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <string.h>

static const uint8_t level_duration[16] = {
  1, 2, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 39, 51, 67
};

#define UART_TX_BUFFER_SIZE 8
#define UART_RX_BUFFER_SIZE 64
#define UART_RX_BUFFER_MASK (UART_RX_BUFFER_SIZE - 1)
#define UART_TX_BUFFER_MASK (UART_TX_BUFFER_SIZE - 1)
#define UART_BUFFER_OVERFLOW  0x0200
#define UART_NO_DATA          0x0100

static volatile unsigned char UART_TxBuf[UART_TX_BUFFER_SIZE];
static volatile unsigned char UART_RxBuf[UART_RX_BUFFER_SIZE];
static volatile unsigned char UART_TxHead;
static volatile unsigned char UART_TxTail;
static volatile unsigned char UART_RxHead;
static volatile unsigned char UART_RxTail;
static volatile unsigned char UART_LastRxError;

static volatile uint8_t current_level;
static volatile uint8_t current_row;
static volatile uint8_t update_state;

/* Cache for timer interrupt */
static uint16_t current_level_start;

/* 4 bits per LED */
static volatile uint8_t pixmap[16 /* Levels */ * 16 /* Rows */ * 2];
static uint8_t pixmap_in[16 * 16 * 2];

static int scan_fromhex(unsigned char c) {
  if (c >= '0' && c <= '9')
    return c - '0';
  else if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  else if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  else
    return -1;
}

static void init_pixmap(void)
{
  memset((void *)pixmap, 0xff, sizeof(pixmap));
}

ISR(TIMER0_COMP_vect)
{
  wdt_reset();

  if (++current_row == 16) {
    current_row = 0;
  }

  PORTA = 0;
  PORTC = 0;

  if (update_state == 1) {
    memcpy((void *)pixmap, pixmap_in, sizeof(pixmap));
    update_state = 2;
  }

  if (current_row == 0) {
    PORTD &= ~(1 << PD4);
    /*PORTD |= (1 << PD4);*/
    PORTD |= (1 << PD7);
    PORTD |= (1 << PD6);
    PORTD &= ~(1 << PD6);
    PORTD &= ~(1 << PD7);
  } else {
    /* Next row */
    PORTD |= (1 << PD6);
    PORTD &= ~(1 << PD6);
  }

  const uint16_t idx = current_level_start + (current_row * 2);
  PORTA = pixmap[idx];
  PORTC = pixmap[idx + 1];

  if (current_row == 0) {
    if (++current_level == 16) {
      current_level = 0;
    }

    current_level_start = current_level * 16 * 2;
  }

  OCR0 = level_duration[current_level];
}

ISR(USART_RXC_vect)
{
  unsigned char tmphead;
  unsigned char data;
  unsigned char usr;
  unsigned char lastRxError;

  /* read UART status register and UART data register */
  usr = UCSRA;
  data = UDR;

  lastRxError = (usr & (_BV(FE) | _BV(DOR)));

  /* calculate buffer index */
  tmphead = (UART_RxHead + 1) & UART_RX_BUFFER_MASK;

  if (tmphead == UART_RxTail) {
    /* error: receive buffer overflow */
    lastRxError = UART_BUFFER_OVERFLOW >> 8;
  } else {
    /* store new index */
    UART_RxHead = tmphead;
    /* store received data in buffer */
    UART_RxBuf[tmphead] = data;
  }

  UART_LastRxError = lastRxError;
}

ISR(USART_UDRE_vect)
{
  unsigned char tmptail;

  if (UART_TxHead != UART_TxTail) {
    /* calculate and store new buffer index */
    tmptail = (UART_TxTail + 1) & UART_TX_BUFFER_MASK;
    UART_TxTail = tmptail;
    /* get one byte from buffer and write it to UART */
    UDR = UART_TxBuf[tmptail];  /* start transmission */
  } else {
    /* tx buffer empty, disable UDRE interrupt */
    UCSRB &= ~_BV(UDRIE);
  }
}

unsigned int uart_getc(void)
{
  unsigned char tmptail;
  unsigned char data;

  if (UART_RxHead == UART_RxTail) {
    /* no data available */
    return UART_NO_DATA;
  }

  /* calculate /store buffer index */
  tmptail = (UART_RxTail + 1) & UART_RX_BUFFER_MASK;
  UART_RxTail = tmptail;

  /* get data from receive buffer */
  data = UART_RxBuf[tmptail];

  return (UART_LastRxError << 8) + data;
}

unsigned int uart_getc_wait(void)
{
  unsigned int c;
  do {
    c = uart_getc();
  } while (c & UART_NO_DATA);
  return c;
}

void uart_putc(unsigned char data)
{
  unsigned char tmphead;

  tmphead = (UART_TxHead + 1) & UART_TX_BUFFER_MASK;

  /* wait for free space in buffer */
  while (tmphead == UART_TxTail);

  UART_TxBuf[tmphead] = data;
  UART_TxHead = tmphead;

  /* enable UDRE interrupt */
  UCSRB |= _BV(UDRIE);
}

void uart_puts(const char *s )
{
  while (*s) uart_putc(*s++);
}

int main()
{
  wdt_disable();

  UCSRA &= ~_BV(U2X);
  UBRRH = 0;

  /* 38400 Baud */
  UBRRL = 25;

  /* 57600 Baud */
  /*UBRRL = 16;*/

  UCSRC = _BV(URSEL) | _BV(UCSZ0) | _BV(UCSZ1);

  init_pixmap();

  /* Configure pins */
  /* Cols */
  DDRA = 0xff;
  DDRC = 0xff;
  PORTA = 0;
  PORTC = 0;

  /* Rows */
  DDRD = (1 << PD6) | (1 << PD7) | (1 << PD1);
  PORTD = 0;

  /* Setup timer */
  TCCR0 = (1 << WGM01)
    | _BV(CS01)
    | _BV(CS00)
    ;
  TCNT0 = 0;
  OCR0 = 3;
  TIMSK = (1 << OCIE0);

  /* Setup 15ms watchdog */
  wdt_reset();
  wdt_enable(WDTO_15MS);

  /* Serial */
  UCSRB |= (1 << (RXCIE));

  /* Enable interrupts */
  sei();

  while (!(UCSRA & (1<<UDRE)));
  UDR = 'R';

  for (;;) {
    /* Wait for start */
    while (uart_getc_wait() != 'S');

    unsigned int c;
    uint16_t pos;
    uint8_t level;

    for (pos = 0; pos < 256; ++pos) {
      c = uart_getc_wait();
      if ((c >> 8) & 0xff) {
        /* Problem */
        while (!(UCSRA & (1<<UDRE)));
        UDR = 'E';
        break;
      }

      int value = scan_fromhex(c);
      if (value == -1) {
        /* Problem */
        while (!(UCSRA & (1<<UDRE)));
        UDR = 'H';
        break;
      }

      uint8_t field = pos / 8;
      uint8_t col = 7 - pos % 8;

      for (level = 0; level < value; ++level) {
        pixmap_in[(level * 16 * 2) + field] |= _BV(col);
      }

      for (; level < 16; ++level) {
        pixmap_in[(level * 16 * 2) + field] &= ~_BV(col);
      }
    }

    c = uart_getc_wait();
    if (c == '\n') {
      /* Wait for timer to copy data */
      update_state = 1;
      while (update_state != 2);
      update_state = 0;

      /* We're done */
      while (!(UCSRA & (1<<UDRE)));
      UDR = 'O';
    }
  }
}
/* vim: set sw=2 sts=2 et foldmethod=marker : */

