// morsedecoder.c
// for NerdKits with ATtiny26L
// mrobbins@mit.edu

// F_CPU defined for delay.c
#define F_CPU 8000000UL	// 8MHz

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

#include "delay.h"
#include "lcd.h"

// PIN DEFINITIONS:
//
// PA4 -- LCD RS (pin 4)
// PA5 -- LCD E (pin 6)
// PA7 -- keyer input switch (pulled to ground when pressed)
// PB1 -- piezo element or speaker
// PB3-6 -- LCD DB4-7 (pins 11-14)

inline void wait_for_timer_tick() {
  while(!(TIFR & (1<<TOV0))) {
    // do nothing
  }
  // clear timer tick
  TIFR |= (1<<TOV0);
}
#define INTERSYMBOL_TIMEOUT 100
#define LONGSHORT_CUTOFF 30

uint8_t time_next_keypress() {
  uint16_t counter;
  
  // waits until the button is pressed
  // and then released. (OR timeout)
  //
  // returns timer cycles between press&release
  // or returns 255 on timeout
  
  // wait FOREVER until the button is released
  // (to prevent resets from triggering a new dit or dah)
  while((PINA & (1<<PA7))==0) {
  }

  // turn off LED
  PORTA &= ~(1<<PA1);
  
  counter = 0;
  // wait until pressed
  while(PINA & (1<<PA7)) {
    wait_for_timer_tick();
    counter++;
    
    if(counter == INTERSYMBOL_TIMEOUT) {
      // timeout #1: key wasn't pressed within X timer cycles.
      // (should happen between different symbols)
      return 255;
    }
  }

  // turn on LED
  PORTA |= (1<<PA1);
  
  // wait one cycle as a cheap "debouncing" mechanism
  wait_for_timer_tick();
  
  // wait until released
  counter = 0;
  while((PINA & (1<<PA7))==0) {
    wait_for_timer_tick();
    counter++;
    
    // flip the speaker bit
    if(PORTB & (1<<PB1)) {
      PORTB &= ~(1<<PB1);
    } else {
      PORTB |= (1<<PB1);
    }

    if(counter == 255) {
      // timeout #2: key wasn't released within 255 timer cycles.
      // (should happen only to reset the screen)
      return 254;
    }
  }
  
  // turn off LED
  PORTA &= ~(1<<PA1);
  
  return counter;
}

// defining the lookup table.
// bits   7 6 5 4 3 2 1 0
// 765 define the length (0 to 7)
// 43210 define dits (0) and dahs (1)
#define MORSE_SIZE 26
#define MORSE(s, x)  ((s<<5) | x)
#define DIT(x) (x<<1)
#define DAH(x) ((x<<1) | 1)
unsigned char morse_coded[MORSE_SIZE] PROGMEM = 
{
  MORSE(2, DIT(DAH(0))),		//A
  MORSE(4, DAH(DIT(DIT(DIT(0))))),	//B
  MORSE(4, DAH(DIT(DAH(DIT(0))))),	//C
  MORSE(3, DAH(DIT(DIT(0)))),		//D
  MORSE(1, DIT(0)),			//E
  MORSE(4, DIT(DIT(DAH(DIT(0))))),	//F
  MORSE(3, DAH(DAH(DIT(0)))),		//G
  MORSE(4, DIT(DIT(DIT(DIT(0))))),	//H
  MORSE(2, DIT(DIT(0))),		//I
  MORSE(4, DIT(DAH(DAH(DAH(0))))),	//J
  MORSE(3, DAH(DIT(DAH(0)))),		//K
  MORSE(4, DIT(DAH(DIT(DIT(0))))),	//L
  MORSE(2, DAH(DAH(0))),		//M
  MORSE(2, DAH(DIT(0))),		//N
  MORSE(3, DAH(DAH(DAH(0)))),		//O
  MORSE(4, DIT(DAH(DAH(DIT(0))))),	//P
  MORSE(4, DAH(DAH(DIT(DAH(0))))),	//Q
  MORSE(3, DIT(DAH(DIT(0)))),		//R
  MORSE(3, DIT(DIT(DIT(0)))),		//S
  MORSE(1, DAH(0)),			//T
  MORSE(3, DIT(DIT(DAH(0)))),		//U
  MORSE(4, DIT(DIT(DIT(DAH(0))))),	//V
  MORSE(3, DIT(DAH(DAH(0)))),		//W
  MORSE(4, DAH(DIT(DIT(DAH(0))))),	//X
  MORSE(4, DAH(DIT(DAH(DAH(0))))),	//Y
  MORSE(4, DAH(DAH(DIT(DIT(0))))),	//Z
};
unsigned char morse_alpha[MORSE_SIZE] PROGMEM =
{ 
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};

unsigned char morse_lookup(unsigned char in) {
  // linearly go through the table (in program memory)
  // and find the matching one
  uint8_t i;
  unsigned char tmp;
  
  for(i=0; i<MORSE_SIZE; i++) {
    tmp = pgm_read_byte(&morse_coded[i]);
    
    if(tmp == in) {
      // matched morse character
      return pgm_read_byte(&morse_alpha[i]);
    }
  }
  return '?';
}

unsigned char bitwise_reverse(unsigned char in, uint8_t max) {
  // maps bits backwards
  // i.e. for max=5
  // in  XXX43210
  // out YYY01234
  unsigned char b = 0;
  
  uint8_t i;
  for(i=0; i<max; i++) {
    if(in & (1<<i))
    b |= (1<< (max-1-i) );
  }

  return b;
}

int main() {
  // internal RC oscillator calibration for 8MHz.
  OSCCAL = 176;

  // enable internal pullup on PA7 (the button)
  PORTA |= (1<<PA7);
  
  // enable LED on PA1
  DDRA |= (1<<PA1);
  
  // enable piezo out on PB1
  DDRB |= (1<<PB1);

  // use Timer0 as our clock source.
  // divide the 8MHz by 256, and then wait for overflow (another factor of 256)
  // so we get one overflow every 8.16ms
  
  // set prescale CK/256
  TCCR0 = (1<<CS02);
  
  // fire up the LCD
  lcd_init();
  lcd_home();

  uint8_t counter=0;
  uint8_t ditdahs=0; // counts dits and dahs along the top row
  unsigned char curchar=0, lastchar='_';
  uint8_t chars=0;   // counts chars along the bottom row
  uint8_t spacetimes=0;  // counts number of intersymbol times (before we call it a space)
  
  while(1) {
    counter = time_next_keypress();
    
    // decide what to do based on the timing
    if(counter == 254) {
      // clear everything
      lcd_home();
      lcd_write_string(PSTR("                        "));
      lcd_line_two();
      lcd_write_string(PSTR("                        "));
      ditdahs = 0;
      chars = 0;
      curchar = 0;
      lastchar = '_';
      spacetimes = 0;
      
    } else if(counter == 255) {
    
      // intersymbol timeout: clear 1st row
      lcd_home();
      lcd_write_string(PSTR("                        "));
      
      if(ditdahs > 0) {
        // lookup the character
        curchar = MORSE(ditdahs, bitwise_reverse(curchar, ditdahs));
        curchar = morse_lookup(curchar);
        
        // print it
        lcd_set_type_command();
        lcd_write_byte(0x80 + 0x40 + chars);	// move to 2nd line, chars^th column
        lcd_write_data(curchar);
        
        chars++;
        lastchar = curchar;
        spacetimes = 0;
      } else if(lastchar != '_') {
        spacetimes++;
        if(spacetimes == 4) {
          // as long as the last character wasn't a space, print a space 
          // (as an underscore so we can see it)
          curchar = '_';

          // print it
          lcd_set_type_command();
          lcd_write_byte(0x80 + 0x40 + chars);	// move to 2nd line, chars^th column
          lcd_write_data(curchar);
        
          chars++;
          lastchar = curchar;
        }
      }
      
      curchar = 0;
      ditdahs = 0;
    } else {

      // go to correct position
      lcd_set_type_command();
      lcd_write_byte(0x80 | ditdahs);	// move to 2nd line, chars^th column
    
      // dit or dah
      if(counter >= LONGSHORT_CUTOFF) {
        // dah
        lcd_write_data('-');
        curchar = DAH(curchar);
      } else {
        // dit
        lcd_write_data('.');
        curchar = DIT(curchar);
      }
      
      ditdahs++;
      spacetimes = 0;
    }
    
    // write the last timeout in the top right of the screen
    lcd_set_type_command();
    lcd_write_byte(0x80 | 21);	// go to position 21
    lcd_write_int16(counter);
    lcd_write_data(' ');
    lcd_write_data(' ');
    
  }
  
  return 0;
}
