/* 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 #include #include #include #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<= 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; } } }