/*
  USB Telegraph sounder.
*/

#define F_CPU          12000000
#define USART_BAUDRATE 9600 
#define BAUD_PRESCALE  (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

/* USART error conditions. */
#define FRAMING_ERROR _BV(FE)
#define DATA_OVERRUN  _BV(DOR)
#define PARITY_ERROR  _BV(UPE)

#define PORT PORTD
#define DDR  DDRD

#define OUT _BV(PD6)
#define DTR _BV(PD5)
#define DSR _BV(PD4)
#define RTS _BV(PD3)
#define CTS _BV(PD2)

#define OUTBITS OUT | RTS | DTR
#define INBITS  CTS | DSR

#define MODE_OUTPUT    0
#define MODE_SET_WPM   1

/* Macro to get number of entries in ASCII -> MORSE table. */
#define CHAR_COUNT sizeof(ascii_lookup_table) / sizeof(ascii_lookup_table[0])

/* Macros to implement RTS/CTS flow control. */
#define USART_PAUSE()    PORT |= RTS
#define USART_RESUME()   PORT &= ~RTS
#define USART_CLEAR()    PORT & CTS


/*****/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <inttypes.h>

uint8_t mode;
uint8_t wpm;
/*
  struct mapping an ASCII char -> morse code.
  Each two bits are encoded as:
    01  ->  short  (1)
    11  ->  long   (3)

    00 marks the end of the letter.
*/
const struct ascii_morse {
  uint8_t ascii;
  uint16_t  morse;
} ascii_lookup_table[]
PROGMEM = {
  { '0',  0b1111111111000000 },
  { '1',  0b0111111111000000 },
  { '2',  0b0101111111000000 },
  { '3',  0b0101011111000000 },
  { '4',  0b0101010111000000 },
  { '5',  0b0101010101000000 },
  { '6',  0b1101010101000000 },
  { '7',  0b1111010101000000 },
  { '8',  0b1111110101000000 },
  { '9',  0b1111111101000000 },

  { 'A',  0b0111000000000000 },
  { 'B',  0b1101010100000000 },
  { 'C',  0b1101110100000000 },
  { 'D',  0b0011010100000000 },
  { 'E',  0b0100000000000000 },
  { 'F',  0b0101110100000000 },
  { 'G',  0b1111010000000000 },
  { 'H',  0b0101010100000000 },
  { 'I',  0b0101000000000000 },
  { 'J',  0b0111111100000000 },
  { 'K',  0b1101110000000000 },
  { 'L',  0b0111010100000000 },
  { 'M',  0b1111000000000000 },
  { 'N',  0b1101000000000000 },
  { 'O',  0b1111110000000000 },
  { 'P',  0b0111110100000000 },
  { 'Q',  0b1111011100000000 },
  { 'R',  0b0111010000000000 },
  { 'S',  0b0101010000000000 },
  { 'T',  0b1100000000000000 },
  { 'U',  0b0101110000000000 },
  { 'V',  0b0101011100000000 },
  { 'W',  0b0111110000000000 },
  { 'X',  0b1101011100000000 },
  { 'Y',  0b1101111100000000 },
  { 'Z',  0b1111010100000000 },

  { '\'', 0b0111111111010000 },
  { '/',  0b1101011101000000 },
  { '"',  0b0111010111010000 },
  { ',',  0b1111010111110000 },
  { '.',  0b0111011101110000 },
  { ':',  0b1111110101010000 },
  { ';',  0b1101110111000000 },
  { '?',  0b0101111101010000 },
  { '(',  0b1101111101000000 },
  { ')',  0b1101111101110000 },
  { '=',  0b1101010111000000 },
  { '+',  0b0111011101000000 },
  { '-',  0b1101010101110000 },
  { '@',  0b0111110111010000 },

  // Not standard, but widely used.
  { '!',  0b1101110111110000 },

  // Non-ITU
  { '&',  0b0111010101000000 },
  { '$',  0b0101011101011100 },
  { '_',  0b0101111101110000 },

  { ' ',  0b0000000000000000 }
};

const uint8_t intro_str[] PROGMEM = "What hath god wrought!";


/***
  Incoming queue.  Note that the add and remove functions disable interrupts for
    their duration in order to ensure that they are atomic.

  QUEUE_LENGTH is the total size of the queue.
  When the free slot count fallse to QUEUE_MIN_FREE, hardware flow control
    stops incoming bytes.
  When the free slot rises to QUEUE_MAX_FREE, hardware flow control allows
    incoming bytes again.

***/
#define QUEUE_LENGTH 64
#define QUEUE_MIN_FREE 8
#define QUEUE_MAX_FREE 16

#define QUEUE_ERROR_NONE  0x00
#define QUEUE_ERROR_EMPTY 0x01
#define QUEUE_ERROR_FULL  0x02

volatile uint8_t queue[QUEUE_LENGTH];
volatile uint8_t queue_front;
volatile uint8_t queue_filled;
volatile uint8_t queue_error;

#define QUEUE_REMAINING QUEUE_LENGTH - queue_filled


/*
  Append a given number of characters to the queue.  Returns the number of
    characters actually added.  Set queue_error as necesessary.
*/
uint8_t queue_add(uint8_t *in, int len)
{
  uint8_t out_idx, in_idx;

  cli();

  out_idx = queue_front + queue_filled;
  in_idx = 0;

  queue_error = QUEUE_ERROR_NONE;

  while (in_idx < len) {
    while (out_idx >= QUEUE_LENGTH)
      out_idx -= QUEUE_LENGTH;

    if (queue_filled == QUEUE_LENGTH) {
      queue_error = QUEUE_ERROR_FULL;
      break;
    }

    queue[out_idx] = in[in_idx];
    queue_filled++;
    out_idx++;
    in_idx++;
  }

  sei();
  return in_idx;
}

/* Add a given number of characters from program memory to the queue. */
void queue_add_pgmem(uint8_t *addr, uint8_t len)
{
  uint8_t i;
  uint8_t byte;

  for(i = 0; i < len; i++) {
    byte = pgm_read_byte( &(addr[i]) );
    queue_add( &byte, 1);
  }
}


/*
  Remove an element fron the queue.  Set queue_error as necessary.
*/
uint8_t queue_remove()
{
  uint8_t element = 0;

  cli();

  queue_error = QUEUE_ERROR_NONE;

  if(queue_filled == 0) {
    queue_error = QUEUE_ERROR_EMPTY;
    return 0;
  }

  element = queue[queue_front];

  queue_filled--;
  queue_front++;
  while (queue_front >= QUEUE_LENGTH)
    queue_front -= QUEUE_LENGTH;

  sei();
  return element;
}


/****
 _delay_ms is a library macro, but only works properly with constant values.
 Therefore we give it this wrapper.

The ATTiny2313 does not support multiply / divide, so we use two loops to get
 approximately the right delay.  The standard time of a 'dit' in Morse code is
 (1200 / wpm) in milliseconds.
****/
void delay (uint8_t n)
{
  uint16_t i;
  uint8_t  j;

  for (j = 0; j < n; j++)
    for (i = 0; i < 1200; i += wpm)
      _delay_ms(1);
}

/*****
  Look up a character in the table and return it's encoded form.
*****/
uint16_t ascii_to_morse (uint8_t letter)
{
  uint8_t n;

  // Convert to upper-case.
  if (letter >= 'a' && letter <= 'z')
    letter -= 32;

  // Find in table.  This returns ' ' if not found.
  for (
    n = 0;
    n < CHAR_COUNT - 1
      && pgm_read_byte(&(ascii_lookup_table[n].ascii)) != letter;
    n++
  );
  return pgm_read_word(&(ascii_lookup_table[n].morse));
}

/*****
   Output an ascii character to the telegraph via GPIO.
*****/
void telegraph_letter (uint8_t letter)
{
  uint16_t s = ascii_to_morse (letter);

  PORT &= ~OUT;

  // Space
  if (!s)
    delay (5);

  while(s)
  {
    PORT |= OUT;
    delay (s >> 14);
    PORT &= ~OUT;
    delay (1);

    s = s << 2;
  }

  // Total delay after letter = 3; if space = 7
  delay (2);
}


void initialize (void)
{
  DDR |= OUTBITS;                      // Enable outputs
  DDR &= ~INBITS;                      // Enable inputs

  UCSRB = (1 << RXEN) | (1 << TXEN);   // Enable UART TX and RX
  UBRRL = BAUD_PRESCALE;               // baud rate
  UCSRC = (1 << UCSZ1) | (1 << UCSZ0); // 8N1

  PORT = 0;                            // Clear outputs.

  UCSRB |= (1 << RXCIE);               // Enable USART rx interrupt.

  sei();                               // Enable interrupts
}

void uart_tx (uint8_t data)
{
  while (!(UCSRA & (1 << UDRE)));
  UDR = data;
}

/***
  Interrupt fires when USART receives a byte.  Add it to the queue.
***/
ISR(USART_RX_vect)
{
  uint16_t status = UCSRA;
  uint8_t byte = UDR;

  // If the queue is getting filled, de-assert RTS to stop incoming bytes.
  if (QUEUE_REMAINING < QUEUE_MIN_FREE)
    USART_PAUSE();

  if ( (status & (FRAMING_ERROR | DATA_OVERRUN | PARITY_ERROR)) == 0)
    queue_add(&byte, 1);
}

/***
  Main.  Watch for queue entries in a tight loop; output when found
***/
int main (void)
{
  uint8_t letter = ' ';

  wpm = 20;
  initialize ();
  queue_add_pgmem ( intro_str, sizeof(intro_str) );


  while (1)
  {
    if (QUEUE_REMAINING > QUEUE_MAX_FREE)
      USART_RESUME();

    if (! queue_filled)
      continue;
    letter = queue_remove();

    switch ( letter ) {

      case 0x17:                             //  Control-W, set morse wpm.
        mode = MODE_SET_WPM;
        wpm = 0;
        break;

      default:
        if ( mode == MODE_SET_WPM && letter >= '0' && letter <= '9' ) {
          wpm *= 10;
          wpm += (letter - '0');
        } else {
          mode = MODE_OUTPUT;

          if (wpm < 5) wpm = 5;

          telegraph_letter (letter);
          uart_tx (letter);                   // echo byte
        }
        break;
    }
  }
}

