//
//
// hello.reflect.45.c
//
// light reflection synchronous detection hello-world
//    9600 baud FTDI interface
//
// Neil Gershenfeld
// 10/25/12
//
// (c) Massachusetts Institute of Technology 2012
// This work may be reproduced, modified, distributed,
// performed, and displayed for any purpose. Copyright is
// retained and must be preserved. The work is provided
// as is; no warranty is provided, and users accept all 
// liability.
//

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <string.h>

#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define char_delay() _delay_ms(10) // char delay
#define awk_delay() _delay_ms(100) // LED flash delay

#define serial_port PORTB
#define serial_direction DDRB
#define serial_pins PINB
#define serial_pin_in (1 << PB1)
#define serial_pin_out (1 << PB2)

#define led_port PORTB
#define led_direction DDRB
#define led_pin (1 << PB3)

#define awk_port PORTB
#define awk_direction DDRB
#define awk_pin (1 << PB0)

#define nloop 100 // number of loops to accumulate
#define eps 0.9 // filter time constant
#define amp 25.0 // difference amplitude
#define threshold 100 // at point when surface is close enough

#define node_id '1' // 1st prox node

void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
	//
	// read character into rxbyte on pins pin
	//    assumes line driver (inverts bits)
	//
	*rxbyte = 0;
	while (pin_test(*pins,pin))
	//
	// wait for start bit
	//
	;
	//
	// delay to middle of first data bit
	//
	half_bit_delay();
	bit_delay();
	//
	// unrolled loop to read data bits
	//
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 0);
	else
	*rxbyte |= (0 << 0);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 1);
	else
	*rxbyte |= (0 << 1);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 2);
	else
	*rxbyte |= (0 << 2);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 3);
	else
	*rxbyte |= (0 << 3);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 4);
	else
	*rxbyte |= (0 << 4);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 5);
	else
	*rxbyte |= (0 << 5);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 6);
	else
	*rxbyte |= (0 << 6);
	bit_delay();
	if pin_test(*pins,pin)
	*rxbyte |= (1 << 7);
	else
	*rxbyte |= (0 << 7);
	//
	// wait for stop bit
	//
	bit_delay();
	half_bit_delay();
}

void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
	//
	// send character in txchar on port pin
	//    assumes line driver (inverts bits)
	//
	// start bit
	//
	clear(*port,pin);
	bit_delay();
	//
	// unrolled loop to write data bits
	//
	if bit_test(txchar,0)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,1)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,2)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,3)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,4)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,5)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,6)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	if bit_test(txchar,7)
	set(*port,pin);
	else
	clear(*port,pin);
	bit_delay();
	//
	// stop bit
	//
	set(*port,pin);
	bit_delay();
	//
	// char delay
	//
	bit_delay();
}

void put_string(volatile unsigned char *port, unsigned char pin, PGM_P str) {
	//
	// send character in txchar on port pin
	//    assumes line driver (inverts bits)
	//
	static char chr;
	static int index;
	index = 0;
	do {
		chr = pgm_read_byte(&(str[index]));
		put_char(&serial_port, serial_pin_out, chr);
		++index;
	} while (chr != 0);
}

void flash() {
	//
	// LED flash delay
	//
	clear(awk_port, awk_pin);
	awk_delay();
	set(awk_port, awk_pin);
}	

int get_reading() {
   static uint16_t on,off;
   static char reading;
   static unsigned char count;
   int on_low;
   int on_high;
   int off_low;
   int off_high;
   int on_value;
   int off_value;
   int filter = 0;
   int new_filter;
   int binary;	
   //
   // accumulate
   //
   on = 0;
   off = 0;
   for (count = 0; count < nloop; ++count) {
	   //
	   // LED off
	   //
	   set(led_port, led_pin);
	   //
	   // initiate conversion
	   //
	   ADCSRA |= (1 << ADSC);
	   //
	   // wait for completion
	   //
	   while (ADCSRA & (1 << ADSC))
	   ;
      //
      // save result
      //
      off += ADC;
	  //
	  // LED on
	  //
	  clear(led_port, led_pin);
	  //
	  // initiate conversion
	  //
	  ADCSRA |= (1 << ADSC);
	  //
	  // wait for completion
	  //
	  while (ADCSRA & (1 << ADSC))
	  ;
	  //
      // save result
      //
	  on += ADC;
      }
      on_low = (on & 255);
      on_high = (((on >> 8) & 255));
      off_low = (off & 255);
      off_high = ((off >> 8) & 255);
      on_value = (256*on_high + on_low)/nloop;
      off_value = (256*off_high + off_low)/nloop;
      new_filter = (1-eps)*filter + eps*amp*(on_value-off_value);
      filter = new_filter;
      binary = filter/threshold;
      if (binary > 0){
	      reading = 'h';
	      clear(awk_port, awk_pin); // leave awk led on
      }
      else {
         reading = 'l';
	     set(awk_port, awk_pin); // turn awk led off
	  }
	  return reading;
}

int main(void) {
   //
   // main
   //
   static char rding;
   rding = 'l'; // initial reading
   //
   // set clock divider to /1
   //
   CLKPR = (1 << CLKPCE);
   CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
   //
   // initialize output pins
   //
   set(serial_port, serial_pin_out);
   input(serial_direction, serial_pin_out);
   set(awk_port, awk_pin);
   output(awk_direction, awk_pin);
   set(led_port, led_pin);
   output(led_direction, led_pin);
   //
   // init A/D
   //
   ADMUX = (0 << REFS2) | (0 << REFS1) | (0 << REFS0) // Vcc ref
      | (0 << ADLAR) // right adjust
      | (0 << MUX3) | (0 << MUX2) | (1 << MUX1) | (0 << MUX0); // ADC2
   ADCSRA = (1 << ADEN) // enable
      | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
   //
   // main loop
   //
   while (1) {
      rding = get_reading();
	  output(serial_direction, serial_pin_out);		
	     //
	     // send framing
	     //
		 put_char(&serial_port, serial_pin_out, '1');
         char_delay();
         put_char(&serial_port, serial_pin_out, '2');
         char_delay();
         put_char(&serial_port, serial_pin_out, '3');
         char_delay();
         put_char(&serial_port, serial_pin_out, '4');
         char_delay();
         //
         // send result
         //
         put_char(&serial_port, serial_pin_out, rding);
         char_delay();
    }
}
