/*
Title: EEG02 
Author: Joerg Hansmann
Date: 8/2000
Purpose: Firmware for 6 Channel EEG Amplifier connected to a PC via 
optically isolated RS232
needed
Software: AVR-GCC
needed
Hardware: AT90S4433/4434 on RS232EEG board, 7.3728 Mhz XTAL


based on the AVR-GCC test programs for the STK200 eva board from Volker Oth
(volkeroth@gmx.de, homepage with AVR stuff: http://members.xoom.com/volkeroth)

updates:
001021: translation of comments to english language, all tabs translated to spaces
001111: sample frequency modified to 256 Hz

*/

#include <io.h>
#include <inttypes.h>
#include <interrupt.h>
#include <signal.h>
#include "uart.h"

#define out(port,val) outp(val, port) //correction of parameter sequence 

/*
>From AVR-FAQ:
-------------
Two bits in the A/D Control and Status Register(ADCSR) is used 
to detect the end of an A/D conversion: 

The A/D Start Conversion (ADSC) bit is used to test when a new 
conversion can start, but the result of the previous conversion is not 
yet ready in the A/D data register. When the A/D data register is read 
the result of the last conversion is present. 

To get the result of the latest conversion, test the A/D Interrupt 
Flag (ADIF) or enable global interrupt and execute the reading of the 
A/D converter in the interrupt routine. 

Example code: 

ldi R16,1 ; Select channel 
out ADMUX,R16 
sbi ADCSR,adif ; Reset interrupt flag 
sbi ADCSR,adsc ; Start A/D conversion 
wait: sbis ADCSR,adif ; Wait until the ADIF is set 
rjmp wait 
......................................................................... 

description of communication protocol 1 (old):
--------------------------------------
The Atmel-processor(AT90S4433) transmits 200 times per second the following data block
to the PC:
$a5 :1. sync byte
$5a :2. sync byte
$01 :version byte
ch1_low_byte
ch1_high_byte
ch2_low_byte
ch2_high_byte
ch3_low_byte
ch3_high_byte
ch4_low_byte
ch4_high_byte
ch5_low_byte
ch5_high_byte
ch6_low_byte
ch6_high byte
switches: 1 byte digital inputs (PD2..PD5) in the lower 4 bits.

all together 16 byte.

>From the AD-channel-data only the lower 10 bit are used as unsigned
integer.
The remaining 6 high bits are 0.



######################



RS232 transmission parameters are:
1 startbit, 8 data bits, 1 stopbit, no parity, 57600 baud

At the moment communication direction is only from Atmel-processor to PC.
The hardware however supports full duplex communication. This feature
will be used in later firmware releases to support the PWM-output and
LED-Goggles.


calculation of required baud rate:
----------------------------------
Bits per datenbyte sent: 1 startbit + 8 datenbits + 1 stopbit + 0 Parity =10 bit
minimal baudrate is: 16 byte * 200Hz * 10 bit/byte = 32000 baud.

to be open for future extensions I choose 57600 baud.
(that corresponds to a baudrate divisor UBR=7 at 7.3728Mhz Xtal-frequency)


calculation of frequency/timing:
--------------------------------
SIG_OVERFLOW0 (Timer 0 -Interrupt) : 3600 Hz. /18 = 200 Hz

transmission of 1 byte(1+8+1 bit) at 57600 baud : 5760 Hz

sampling of 1 10-bit AD-value: 26(1st) , 14(single conversion) cycles
ADC-Prescaler: > ((XTAL / 200khz)=36.864).
chosen:64 (ADPS2=1,ADPS1=1,ADPS0=0)
ADCYCLE=XTAL/64=115200Hz or 8.681E-6 Sec/Cycle
14(single conversion) cycles = 121.5E-6 sec (8.2E3 samples/sec)
26(1st conversion) cycles = 225.69E-6 sec

*/



#define bin8const(d7,d6,d5,d4,d3,d2,d1,d0) ((d7<<7)+(d6<<6)+(d5<<5)+(d4<<4)+(d3<<3)+(d2<<2)+(d1<<1)+d0)

#define LED_left_on() cbi(PORTD,PD7)
#define LED_left_off() sbi(PORTD,PD7)

#define LED_right_on() cbi(PORTD,PD6)
#define LED_right_off() sbi(PORTD,PD6)

#define LED_status_1_on() cbi(PORTB,PB0)
#define LED_status_1_off() sbi(PORTB,PB0)

#define LED_status_2_on() cbi(PORTB,PB2)
#define LED_status_2_off() sbi(PORTB,PB2)

#define LAST_AD_CHANNEL 5 //0 is first channel

volatile uint8_t ad_channel=0;
volatile uint8_t  
buffer_ADCL[LAST_AD_CHANNEL+1], //sampling buffer array
buffer_ADCH[LAST_AD_CHANNEL+1];

SIGNAL(SIG_ADC) //signal handler AD-Conversion complete interrupt 
{
buffer_ADCL[ad_channel]=inp(ADCL);  
buffer_ADCH[ad_channel]=inp(ADCH);

ad_channel++;
if(ad_channel<=LAST_AD_CHANNEL){
out(ADMUX,ad_channel); //set Multiplexer to next channel
sbi(ADCSR,ADSC); //start new conversion
}else{
out(ADMUX,0); //prepare Multiplexer for new frame
//do nothing until adc_new_frame() starts new frame
}
}


void adc_new_frame(void){ //starts sampling of LAST_AD_CHANNEL+1 AD-Values
ad_channel=0;
sbi(ADCSR,ADSC); //start new conversion
}


/*
When an interrupt occurs, the Global Interrupt Enable I-bit is cleared (zero) and all interrupts are disabled. The user soft-ware
can set (one) the I-bit to enable nested interrupts. The I-bit is set (one) when a Return from Interrupt instruction - RETI
- is executed.
When the Program Counter is vectored to the actual interrupt vector in order to execute the interrupt handling routine, hard-ware
clears the corresponding flag that generated the interrupt. Some of the interrupt flags can also be cleared by writing a
logic one to the flag bit position(s) to be cleared.
*/

void adc_init(void){
ad_channel=0; //index to sampling buffer-array
out(ADMUX,0); //set Multiplexer to channel 0
//sbi(ADMUX,ADCBG); //enable bandgap-reference for calibration
 
sbi(ADCSR,ADPS2); //set AD-Prescaler to 64 with ADPS2=1,ADPS1=1,ADPS0=0
sbi(ADCSR,ADPS1);
cbi(ADCSR,ADPS0);

sbi(ADCSR,ADIF); //Reset interrupt flag to 0 ( (by writing a logical 1) == rather confusing !!!),ADIF will be set (by hardware) to 1 when finished  
cbi(ADCSR,ADFR); //Disable free running mode
sbi(ADCSR,ADEN); //enable ADC (Prescaler starts counting now)
sbi(ADCSR,ADSC); //Start A/D conversion,ADSC will be set (by hardware) to 0 when finished 

sbi(ADCSR,ADIE); //ADC-Interrupt enable  
}





volatile uint16_t timer_ticks;

SIGNAL(SIG_OVERFLOW0) /* signal handler for tcnt0 overflow interrupt */
{
timer_ticks++;
outp(0, TCNT0); /* reset counter to get this interrupt again */
}


void reset_timer(void){
cli();
timer_ticks=0;
sei();
}

void decr_timer(uint16_t decr){
cli();
timer_ticks-=decr;
sei();
}


uint16_t get_timer(void){
uint16_t buffer;

cli();
buffer=timer_ticks;
sei();
return buffer;
}


void fixed_delay(void){
uint16_t i;
uint8_t j,k;

/* outer delay loop */
for (i=0; i<2550; i++){
if(bit_is_clear(PIND, PD5)){ //poll switch and set LED
LED_status_2_on(); 
} 
if(bit_is_clear(PIND, PD4)){ //poll switch and set LED
LED_status_1_on(); 
} 
if(bit_is_clear(PIND, PD3)){ //poll switch and set LED
LED_right_on(); 
} 
if(bit_is_clear(PIND, PD2)){ //poll switch and set LED
LED_left_on(); 
} 
for(j=0; j<255;j++) /* inner delay loop */
k++; /* just do something - could also be a NOP */
}
}


void LED_test(void){
//test LEDs and switches at power on reset
uint8_t demo=0;
for (demo=1;demo<=1;demo++) {
//outp(~led, PORTD); /* invert the output since a zero means: LED on */
//outp(~led, PORTB); /* invert the output since a zero means: LED on */
fixed_delay();
LED_left_on();  
fixed_delay();
LED_left_off();

fixed_delay();
LED_right_on(); 
fixed_delay();
LED_right_off();

fixed_delay();
LED_status_1_on();  
fixed_delay();
LED_status_1_off();  

fixed_delay();
LED_status_2_on();  
fixed_delay();
LED_status_2_off();  
}
}


#define TCONST_200 ((uint8_t)(3600 / 200)) //200Hz , check that result does not exceed uint8_t !
#define TCONST_256 ((uint8_t)(3600 / 256)) //257.14Hz , check that result does not exceed uint8_t !

enum STATES { SYNC_ON, START_SAMPLING, HEX_DEBUG, FRAME_HEADER, FRAME_ADCDATA, FRAME_LEADOUT };

#define TRUE 1

int main( void )
{
uint8_t	out_bit=0; //is there a bit-type in AVRGCC like in PICC ? 
enum STATES	tx_state=SYNC_ON; //transmit data statemachine state
uint8_t	tx_ch=0; //currently transmitting ad-data from channel
uint8_t frame_counter=0;

//---initialize DataDirection

outp((uint8_t)(~ bin8const(0,0,1,1,1,1,0,1)) ,DDRD); //inverse logic compared to PIC (1=Input, 0=Output), therefore ~ before bitpattern
outp((uint8_t)(~ bin8const(1,1,1,1,1,0,0,0)) ,DDRB); //inverse logic compared to PIC (1=Input, 0=Output), therefore ~ before bitpattern

//---initialize Portpins (for output)/weak pullups(for inputs) 
outp(0xff,PORTD);
outp(0xff,PORTB);

//---initialize Timer 0 
outp((1<<TOIE0), TIMSK); /* enable TCNT0 overflow */
outp(0, TCNT0); /* reset TCNT0 */
outp(2, TCCR0); /* count with cpu clock/8 = 3600 Interrupts / second */
timer_ticks=0;

 
adc_init(); //---initialize AD-Converter
UART_Init(); //---initialize RS232 Communication
 
//---initialize WDT
// wdt_init(???);
LED_test(); //---does not use interrupts
sei(); /* enable interrupts */
LED_status_1_on(); // Power on indicator

while(TRUE){ /* loop forever */
//tx-state machine 
switch(tx_state){

case SYNC_ON: // sync on TCONST_256 Hz
if(get_timer() >= TCONST_256){
decr_timer(TCONST_256);
out_bit^=1;
if(out_bit){
LED_status_2_on();  
}else{
LED_status_2_off();
}
tx_state=START_SAMPLING; // sync done
}
break;

case START_SAMPLING: // start sampling
adc_new_frame();
//tx_state=2; // ASCII debug frame
tx_state=FRAME_HEADER; // binary data frame
break;

case HEX_DEBUG: // ASCII hex debug frame ch0 ch1
if(ad_channel>=2){
UART_SendByte('*');
UART_Printfu08(buffer_ADCH[0]);
UART_Printfu08(buffer_ADCL[0]);
UART_SendByte('*');
UART_Printfu08(buffer_ADCH[1]);
UART_Printfu08(buffer_ADCL[1]);
UART_PrintfEndOfLine();
tx_state=SYNC_ON; //sync again
}
// wait until ad-data available
break;

case FRAME_HEADER: // binary data frame: header
UART_SendByte(0xa5); //sync byte 1
UART_SendByte(0x5a); //sync byte 2
UART_SendByte(0x02); //version byte 
UART_SendByte(frame_counter++); //frame number 

/*
UART_SendByte(0x03); //ch data 
UART_SendByte(0x11); //ch data 
UART_SendByte(0x03); //ch data 
UART_SendByte(0x22); //ch data 
UART_SendByte(0x03); //ch data 
UART_SendByte(0x33); //ch data 
UART_SendByte(0x03); //ch data 
UART_SendByte(0x44); //ch data 
UART_SendByte(0x03); //ch data 
UART_SendByte(0x55); //ch data 
UART_SendByte(0x03); //ch data 
UART_SendByte(0x66); //ch data

UART_SendByte(0x0F); //switches byte 
*/

tx_ch=0;
tx_state=FRAME_ADCDATA;
break;

case FRAME_ADCDATA: // binary data frame: adc-data
if(ad_channel>tx_ch){ // data valid ?
UART_SendByte(buffer_ADCH[tx_ch]);
UART_SendByte(buffer_ADCL[tx_ch]);
tx_ch++;
}
if(tx_ch>LAST_AD_CHANNEL){
tx_state=FRAME_LEADOUT; // binary data frame: lead out  
}else{
// wait until data ready
}
break;

case FRAME_LEADOUT: // binary data frame: lead out
UART_SendByte( (inp(PIND)>>2) &0x0F ); // 4 Switches in lower 4 bit. switches have inverse logic (1: not pressed, 0:pressed)
/*
PIND, PD5 switch is placed over LED_status_2 : moved to bit 3
PIND, PD4 switch is placed over LED_status_1 : moved to bit 2
PIND, PD3 switch is placed over LED_right : moved to bit 1
PIND, PD2 switch is placed over LED_left : moved to bit 0
*/
tx_state=SYNC_ON; // sync again
break;

default:
LED_status_1_on(); // Error
//tx_state=2;
break;
}
}  
}