Open ReVolt/Software

From EcoModder

Jump to: navigation, search
   The Open ReVolt Project: Wiki main page | Paul's website | Forum thread | Donate

svn repository (browse only)

Development Environmet

ATmega8 Manual


Current Controller Software--ZeroGasoline 19:44, 21 May 2009 (EDT)



Contents

Latest Software (cut and paste into avrstudio):

Create the 3 files listed in AVRStudio and add the code to the associated file.

Cougar.c

/*
firmware for cougar open source DC motor controller
*/

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/iom8.h>
#include <util/crc16.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cougar.h"

// now using automatically generated CRC file
#include "autocrc.h"

typedef struct {
	int throttle_ref;
	int current_ref;
	int current_fb;
	unsigned raw_hs_temp;
	unsigned raw_throttle;
} realtime_data_type;

typedef struct {
	long K1;
	long K2;
	long error_new;
	long error_old;
	long pwm;
} pi_storage_type;

typedef struct {
	unsigned magic;						// must be 0x12ab
	int Kp;								// PI loop proportional gain
	int Ki;								// PI loop integreal gain
	unsigned throttle_min_raw_counts;	// throttle low voltage (pedal to metal)
	unsigned throttle_max_raw_counts;	// throttle high voltage (foot off pedal)
	unsigned throttle_pos_gain;			// gain for actual throttle position
	unsigned throttle_pwm_gain;			// gain for pwm (voltage)
	int current_ramp_rate;				// current ramp rate
	int spare[7];						// spare parameters
	unsigned crc;						// checksum for verification
} config_type;

config_type default_config PROGMEM = {
	0x12ab,								// magic
	2,									// PI loop P gain (Joe's was 1)
	160,								// PI loop I gain (Joe's was 20)
	413,								// throttle low voltage (pedal to metal)
	683,								// throttle high voltage (foot off pedal)
	8,									// throttle pedal position gain
	0,									// throttle pwm (voltage) gain
	6,									// current ramp rate (from throttle)
};

unsigned char counter_16k = 0;
unsigned char counter_8k = 0;
unsigned char counter_4k = 0;
unsigned char ad_channel = 0;
unsigned char oc_cycles_off_counter = 0;
unsigned char in_pi_loop = 0;
volatile unsigned char pi_run_tm = 0;

volatile unsigned counter_1k = 0;		// 1KHz (976Hz to be exact) counter for timing

volatile unsigned raw_current_fb;		// AD channel 2
volatile unsigned raw_hs_temp;			// AD channel 1
volatile unsigned raw_throttle;			// AD channel 0

unsigned vref = 0;						// zero current voltage for LEM current sensor
unsigned ocr1a_lpf = 0;					// ocr1a run through lowpass filter (sort of averaged)
unsigned max_current_ref = MAX_CURRENT_REF;	// max_current_ref in variable so controlled by temperature
int throttle_ref = 0;					// reference (desired) throttle
int current_ref = 0;					// reference (desired) current
int current_fb = 0;						// current feedback (actual current)

unsigned long idle_loopcount;			// how many loops we do while micro is not executing PI

pi_storage_type pi;
config_type config;
realtime_data_type rt_data;

#ifdef crc_address
// calc CRC for program (firmware)
unsigned int calc_prog_crc(unsigned nbytes)
{
	unsigned n, crc;
	
	crc = 0xffff;
	for (n = PROGSTART; n < nbytes; n++) {
		crc = _crc_ccitt_update (crc, pgm_read_byte(n));
	}
	return(crc);
}
#endif

// calc CRC on block in SRAM
unsigned int calc_block_crc(unsigned nbytes, unsigned char *buf)
{
	unsigned n, crc;
	
	crc = 0xffff;
	for (n = 0; n < nbytes; n++) {
		crc = _crc_ccitt_update (crc, *(buf + n));
	}
	return(crc);
}

inline void clear_oc(void)
{
	PORTB &= ~PB_OC_CLEAR;				// OC clear low (low to clear)
	asm("nop"); asm("nop");				// 4 nops = 1/4th uS - enough for 74HC00
	asm("nop"); asm("nop"); 
	PORTB |= PB_OC_CLEAR;				// OC clear high (high for normal operation)
}

inline unsigned get_time(void)
{
	unsigned t;
	
	cli(); t = counter_1k; sei();
	return(t);
}

inline unsigned diff_time(unsigned before)
{
	unsigned now;
	
	cli(); now = counter_1k; sei();
	return(now - before);
	
}

unsigned long wait_time(unsigned howlong)
{
	unsigned begin;
	unsigned long loopcount;
		
	loopcount = 0;
	begin = get_time();
	while (diff_time(begin) < howlong) loopcount++;
	return(loopcount);
}

// PI loop code - runs at 4Khz
void pi_loop(void)
{
	static unsigned char throttle_counter = 0;
	unsigned char entry_tm, exec_tm;
	unsigned loc_current_fb, loc_throttle;
	unsigned uv1, uv2;
	int i;
	
	entry_tm = counter_16k;
	loc_current_fb = raw_current_fb;
	loc_throttle = raw_throttle;
	sei();
	// now we have a snapshot of all ADC readings and interrupts are enabled
	// the timer 1 overflow ISR should re-enter on itself if it needs to
	// we also have a snapshot of the entry time (16KHz counter) so we can measure execution time
	
	// convert loc_current_fb from raw value to scaled (0 to 511)
	// current starts in [512, 512 + 213]  (if it's in 0 to 500 amps for the LEM 300)
	if (loc_current_fb < vref) loc_current_fb = 0;
	else loc_current_fb -= vref;
	// now current is in the range [0, 213] or so
	current_fb = (loc_current_fb * 19) >> 3;			// (19/8 is almost 2.4)
	// now current is in [0, 506] or so, close to same as current reference range
	pi.error_new = current_ref - current_fb;
	// execute PI loop
	// first, K1 = Kp << 10;
	// second, K2 = Ki - K1;
	if (current_ref == 0) {
		pi.pwm = 0;
		//pi.Kp = 0;
		//pi.Ki = 0;

		// if Kp and Ki = 0, then K1 and K2 = 0;
		// if K1 and K2 = 0, then pwm = pwm + 0, and 0 + 0 = 0
		// so we don't need to run the PI loop, just set error_old to error_new
	}
	else {
		pi.pwm += (pi.K1 * pi.error_new) + (pi.K2 * pi.error_old);
	}
	pi.error_old = pi.error_new;
	if (pi.pwm > (510L << 16)) pi.pwm = (510L << 16);
	else if (pi.pwm < 0L) pi.pwm = 0L;
	if (pi.pwm & 0x8000) OCR1A = (pi.pwm >> 16) + 1;
	else OCR1A = (pi.pwm >> 16);

	// calculate average OCR1A value
	// OCR1A max value is 511, so we can multiply it by up to 127 times
	ocr1a_lpf = ((ocr1a_lpf * 15) + (unsigned)OCR1A) >> 4;

	throttle_counter++;
	if ((throttle_counter & 0x03) == 0x00) {
		// run throttle logic at 1KHz - calculate throttle_ref

		if (loc_throttle > config.throttle_max_raw_counts)
			loc_throttle = config.throttle_max_raw_counts;
		else if (loc_throttle < config.throttle_min_raw_counts)
			loc_throttle = config.throttle_min_raw_counts;
		
		loc_throttle -= config.throttle_min_raw_counts;
		// now loc_throttle is in [0, (throttle_max_raw_counts - throttle_min_raw_counts)]
		loc_throttle = (config.throttle_max_raw_counts - config.throttle_min_raw_counts) -
			loc_throttle;
		/*
		now, 0 throttle is 0,
		and max throttle is (throttle_max_raw_counts - throttle_min_raw_counts)
		*/
		throttle_ref = (long)loc_throttle * (long)MAX_CURRENT_REF /
			(long)(config.throttle_max_raw_counts - config.throttle_min_raw_counts);
		// now throttle ref in [0 to 511]
	}
	else if ((throttle_counter & 0x03) == 0x01) {
		// run throttle logic at 1KHz - calculate current_ref from throttle_ref

		// throttle gain logic
		uv1 = (throttle_ref * config.throttle_pos_gain) >> 8;
		uv2 = (throttle_ref * config.throttle_pwm_gain) >> 8;
		if (uv1 > uv2) loc_throttle = uv1 - uv2;
		else loc_throttle = 0;
		
		// current_ref ramp rate logic
		if (loc_throttle > max_current_ref) i = (max_current_ref - current_ref);
		else i = (int)loc_throttle - current_ref;
		if (i > config.current_ramp_rate) i = config.current_ramp_rate;
		else if (i < -config.current_ramp_rate) i = -config.current_ramp_rate;
		current_ref += i;
	}
	// measure how long execution took
	exec_tm = counter_16k - entry_tm;
	if (exec_tm > pi_run_tm) pi_run_tm = exec_tm;
}

// TIMER1 overflow interrupt
// This occurs center aligned with PWM output - best time to sample current sensor
// Rate is 16KHz
ISR(TIMER1_OVF_vect)
{
	unsigned ui;
	
	counter_16k++;
	if (counter_16k & 0x01) {
		// every other time (8KHz)
		counter_8k++;
		if (counter_8k & 0x01) {
			// conversion on throttle or heatsink done - grab result
			ui = ADC;
			ADMUX = ADMUX = (1 << REFS0) | 2;			// start conversion on current fb chan
			ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADSC);
			if (ad_channel == 0) raw_throttle = ui;
			else if (ad_channel == 1) raw_hs_temp = ui;
			counter_4k++;
			if ((counter_4k & 0x03) == 0) counter_1k++;	// 1 KHz counter for delays, etc.
			// overcurrent trip logic
			if (PINB & PINB_OC_STATE) {
				// overcurrent circuit tripped
				oc_cycles_off_counter++;
			}
			if (oc_cycles_off_counter >= NUM_OC_CYCLES_OFF) {
				// time to reset overcurrent trip circuit
				oc_cycles_off_counter = 0;
				#ifdef OC_CLEAR_ENABLED
				clear_oc();
				#endif
			}
		}
		else {
			// convertion on current sensor reading complete (4KHz)
			raw_current_fb = ADC;						// get conversion result
			ad_channel++;								// next channel channel
			if (ad_channel > 1) ad_channel = 0;			// wrap around logic
			ADMUX = ADMUX = (1 << REFS0) | ad_channel;	// set channel and start conversion
			ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADSC);
			// execute PI loop with re-entrancy check
			if (!in_pi_loop) {
				in_pi_loop = 1; pi_loop(); in_pi_loop = 0;
			}
		}
	}
}

// timer 1 input capture ISR (1000 hertz)
SIGNAL(SIG_INPUT_CAPTURE1)
{
	counter_1k++;							// 1 KHz counter for delays, etc.	
}

unsigned char measure_vref(void)
{
	unsigned char lp;
	unsigned sum;
	
	sum = 0;
	for (lp = 0; lp < 16; lp++) {
		// do a conversion on channel 2 - current sensor
		ADMUX = ADMUX = (1 << REFS0) | 2;
		ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADSC);
		while (ADCSRA & (1 << ADSC));
		sum += ADC;
	}
	vref = sum >> 4;
	if ((vref > (512 + 50)) || (vref < (512 - 50))) return(0);
	return(1);
}

void config_pi(void)
{
/* A couple of points:
	We are now doing OCR1A = (pwm >> 16) instead of (pwm >> 15)
	Because of this, both Kp and Ki must double for same loop response
	Also, the PI loop is run at 4KHz instead of 16, so Ki must quadruple for same loop response
	So for same loop response, Kp is 2X and Ki is 8X */
	
	long v1, v2;
	
	v1 = (long)config.Kp << 10;
	v2 = (long)config.Ki - pi.K1;
	cli(); pi.K1 = v1; pi.K2 = v2; sei();
}

void fetch_rt_data(void)
{
	// fetch variable with interrupts off, then re-enable interrupts (interrupts can happen during NOPs)
	cli(); rt_data.throttle_ref = (volatile int)throttle_ref; sei();
	asm("nop"); asm("nop"); asm("nop"); asm("nop");

	// fetch variable with interrupts off, then re-enable interrupts (interrupts can happen during NOPs)
	cli(); rt_data.current_ref = (volatile int)current_ref; sei();
	asm("nop"); asm("nop"); asm("nop"); asm("nop");

	// fetch variable with interrupts off, then re-enable interrupts (interrupts can happen during NOPs)
	cli(); rt_data.current_fb = (volatile int)current_fb; sei();
	asm("nop"); asm("nop"); asm("nop"); asm("nop");

	// fetch variable with interrupts off, then re-enable interrupts (interrupts can happen during NOPs)
	cli(); rt_data.raw_hs_temp = (volatile unsigned)raw_hs_temp; sei();
	asm("nop"); asm("nop"); asm("nop"); asm("nop");
	
	// fetch variable with interrupts off, then re-enable interrupts (interrupts can happen during NOPs)
	cli(); rt_data.raw_throttle = (volatile unsigned)raw_throttle; sei();
	asm("nop"); asm("nop"); asm("nop"); asm("nop");
}

void read_config(void)
{
	eeprom_read_block(&config, (void *)EE_CONFIG_ADDRESS, sizeof(config));
	if (config.magic == 0x12ab) {
		// magic OK
		if (calc_block_crc(sizeof(config) - sizeof(unsigned), (unsigned char *)&config) ==
		  config.crc) {
		  	// CRC ok
			return;
		}
	}
	memcpy_P(&config, &default_config, sizeof(config));
}

void write_config(void)
{
	config.crc = calc_block_crc(sizeof(config) - sizeof(unsigned), (unsigned char *)&config);
	eeprom_write_block(&config, (void *)EE_CONFIG_ADDRESS, sizeof(config));
}

void show_menu(char *str)
{
	sprintf_P(str, PSTR("Cougar OS controller firmware v%d.%d\r\n"),
		MAJOR_VERSION, MINOR_VERSION);
	uart_putstr(str);
}

void show_config(char *str)
{
	sprintf_P(str, PSTR("Kp=%d Ki=%d\r\n"), config.Kp, config.Ki);
	uart_putstr(str);
	sprintf_P(str, PSTR("throttle_min_raw_counts=%u throttle_max_raw_counts=%u\r\n"),
		config.throttle_min_raw_counts, config.throttle_max_raw_counts);
	uart_putstr(str);
	sprintf_P(str, PSTR("throttle_pos_gain=%u throttle_pwm_gain=%u\r\n"),
		config.throttle_pos_gain, config.throttle_pos_gain);
	uart_putstr(str);
	sprintf_P(str, PSTR("current_ramp_rate=%d\r\n"), config.current_ramp_rate);
	uart_putstr(str);
}

int main(void)
{
	int x;
	unsigned tm_show_data;
	unsigned char cmdpos, cmdok;
	char cmd[32];
	char str[80];
	
#ifdef crc_address
	unsigned crc1, crc2;
	
	crc1 = pgm_read_word(crc_address);		// read program CRC
	crc2 = calc_prog_crc(crc_address);		// read program CRC
	if (crc1 != crc2) {
		// program CRC error
		while(1);							// do nothing for ever
	}
#endif

	wdt_enable(WDTO_250MS);					// enable watchdog
	
	PORTD = 0xff & ~PD_LED & ~PD_CONTACTOR;	// PORTD weak pullups, LED output pins low (LEDs off)
	DDRD = PD_LED & PD_CONTACTOR;			// two pins outputs
	PORTC = ~PC_ANALOGS_USED;				// weaks pull ups on _except_ for analog input pins
	PORTB = 0xff & ~PB_PWM;					// PWM output low, other outputs high, weak pullups on
	DDRB = PB_PWM | PB_OC_CLEAR;			// two pins outputs
	
	// External Vcc (5v) for analog reference
	ADMUX = (1 << REFS0);
	// enable ADC, prescale = 128, so conversion clock is 16M / 128 = 125KHz
	// a conversion take 13 cycles, at 125KHz equals 104uS
	// the fastest we can convert is 9.6 KHz
	// if we convert every other PWM cycle, that is 8KHz
	// see page 198 of ATMEG8 manual
	ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
	// do a conversion on channel 2 - the first conversion takes 25 cycles - so do it now
	ADMUX = ADMUX = (1 << REFS0) | 2;
	ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADSC);
	while (ADCSRA & (1 << ADSC));
	
	// set up input capture 1 interrupt at 976Hz
	// this is only for temporary timing until timer 1 is used for PWM
	// the reason for 976Hz instead of 1000Hz is explained below
	TCNT1 = 0;								// load 16 bit counter 1
	ICR1 = (long)F_OSC / 976;				// timer at 976 Hz
	TCCR1A = 0;								// no output action on match
	// let counter 1 run at fosc, reset to 0 at ICR1
	TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
	TIMSK = (1 << TICIE1);					// enable input capture 1 interrupt
	
	read_config();							// read config from EEprom
	config_pi();							// configure PI loop from config structure
	// interrups are now enabled by config_pi() - sei() instruction in config_pi()

	idle_loopcount = wait_time(100);		// wait 100mS and remember how many loops we did
	wdt_reset();							// kick watchdog
	// now that voltages have settled, measure Vref
	if (!measure_vref()) {
		// vref out of range
		while(1) {
			wdt_reset();					// kick watchdog
		}
	}
	// clear overcurrent fault (powerup in unknown state)
	clear_oc();
		
	// now configure timer 1 for PWM
	TIMSK = 0;								// no timer 1 interrupt
	TCCR1B = 0;								// stop counter 1
	TCNT1 = 0;								// load 16 bit counter 1
    OCR1A = 0;								// set initial PWM duty value to 0
	TCCR1A =  (1 << COM1A1) | (1 << WGM11);	// Pase Correct PWM mode, 9 bit
	TCCR1B = (1 << CS10);					// Pre-scaler = 1
    OCR1A = 0;								// again, just to be safe
	TIMSK = (1 << TOIE1);					// enable overflow 1 interrupt
	// now the PWM frequency = 16000000 / (1 << 9) / 2
	// so PWM frequency = 16000000 / 1024 = 15625Hz
	// now, counter_1k is incremented every 16 interrupt, so 15625 / 16 = 976.5625Hz
	// this is why we run SIG_INPUT_CAPTURE1 at 976Hz
	
	setup_uart(19200, PARITY_NONE, BITS_8_1);		// uart 19200,n,8,1
	show_menu(str);									// might as well
	// init some time variables
	tm_show_data = get_time();
	// now listen on serial port for commands
	memset(cmd, 0, sizeof(cmd)); cmdpos = 0;
	while (1) {
		wdt_reset();
		x = uart_getch();
		if (x >= 0) {
			if (x != 0x0d) {
				// not a CR
				uart_putch(x);							// echo the character back
				if (cmdpos < (sizeof(cmd) - 1)) {
					cmd[cmdpos++] = x;					// add character to command string
					cmd[cmdpos] = 0;					// and terminate command string
				}
			}
			else {
				// got a CR
				uart_putch(0x0a); uart_putch(0x0d);		// echo back LF and CR
				cmdok = 0;
				if (cmdpos > 0) {
					for (x = 0; x < (int)(cmdpos - 1); x++) {
						if (cmd[x] == ' ') {
							// have a space character, terminate at this position
							// and get numeric value
							cmd[x] = 0; x = atoi(&cmd[x + 1]); cmdok = 1;
						}
					}
				}
				if (cmdok) {
					// cmd is string, x is numeric value
					if (!strcmp_P(cmd, PSTR("save"))) {
						write_config();
						sprintf_P(str, PSTR("configuration written to EE\r\n"));
						uart_putstr(str);
					}
					else if (!strcmp_P(cmd, PSTR("idle"))) {
						sprintf_P(str, PSTR("AVR %lu%% idle\r\n"),
							wait_time(100) * (long)100 / idle_loopcount);
						uart_putstr(str);
					}
					else if (!strcmp_P(cmd, PSTR("kp"))) {
						if ((unsigned)x <= 500) {
							config.Kp = x; config_pi();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("ki"))) {
						if ((unsigned)x <= 500) {
							config.Ki = x; config_pi();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("t-min-rc"))) {
						if ((unsigned)x <= 1023) {
							cli(); config.throttle_min_raw_counts = x; sei();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("t-max-rc"))) {
						if ((unsigned)x <= 1023) {
							cli(); config.throttle_max_raw_counts = x; sei();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("t-pos-gain"))) {
						if ((unsigned)x <= 128) {
							cli(); config.throttle_pos_gain = x; sei();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("t-pwm-gain"))) {
						if ((unsigned)x <= 128) {
							cli(); config.throttle_pwm_gain = x; sei();
							show_config(str);
						}
					}
					else if (!strcmp_P(cmd, PSTR("c-rr"))) {
						if ((unsigned)x <= 100) {
							cli(); config.current_ramp_rate = x; sei();
							show_config(str);
						}
					}
				}
				else show_menu(str);
				// reset command string
				cmdpos = 0; cmd[0] = 0;
			}
		}
		/* add non time-critical code below */
		fetch_rt_data();
		if (diff_time(tm_show_data) >= 200) {
			// 200mS passed since last time, adjust tm_show_data to trigger in 200mS again
			tm_show_data += 200;
			sprintf_P(str, PSTR("TR=%03d CR=%03d CF=%03d HS=%04u RT=%04u\r\n"),
				rt_data.throttle_ref, rt_data.current_ref, rt_data.current_fb,
				rt_data.raw_hs_temp, rt_data.raw_throttle);
			uart_putstr(str);
		}
	}
	return(0);
}

Cougar.h

#define MAJOR_VERSION 0
#define MINOR_VERSION 1

#define F_OSC 16000000					// oscillator-frequency in Hz

#define PROGSTART 0x0000				// program start address

#define EE_CONFIG_ADDRESS 0				// address of config in EEprom

#define OC_CLEAR_ENABLED				// defin to enable AVR to clear OC fault

#define NUM_OC_CYCLES_OFF 4				// number of overcurrent cycles off (at 4KHz)

#define MAX_CURRENT_REF 511				// max current ref into PI loop

#define PINB_OC_STATE (1 << PINB0)		// OC state (high means fault)
#define PB_PWM (1 << PB1)				// PWM output pin (high to turn FETs on)
#define PB_OC_CLEAR (1 << PB2)			// OC clear (low to clear, high for normal operation)

#define PD_LED (1 << PD6)				// IDLE LED - high to light LED
#define PD_CONTACTOR (1 << PD7)			// Contactor Opto LED - high to light LED

// three analog inputs used, these pins must be inputs and weak pullups off
#define PC_ANALOGS_USED ((1 << PC0) | (1 << PC1) | (1 << PC2))

#define PARITY_NONE	0x00
#define PARITY_EVEN	0x02
#define PARITY_ODD	0x03

#define BITS_7_1	0x02
#define BITS_7_2	0x06
#define BITS_8_1	0x03
#define BITS_8_2	0x07

#define UART_RXBUF_SIZE 16
#define UART_TXBUF_SIZE 128

// function prototypes

// get character from uart fifo, return -1 if fifo empty
int uart_getch(void);

// put character to uart (return 1 if fifo full, else 0)
unsigned char uart_putch(char c);

// put string to uart
void uart_putstr(char *str);

// set up UART to specified baud, parity, and bits
void setup_uart(unsigned long baud, unsigned char parity, unsigned char bits);


Serial.c

/*
serial port support for cougar.c
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/iom8.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cougar.h"

// UART (serial)
typedef struct {
	unsigned char rxbuf[UART_RXBUF_SIZE];
	unsigned char txbuf[UART_TXBUF_SIZE];
	unsigned rxhead;
	unsigned rxtail;
	unsigned txhead;
	unsigned txtail;
} uart_fifo_type;

uart_fifo_type uart;

/* uart receive interrupt */
SIGNAL(SIG_UART_RECV)
{
	unsigned char c;
	unsigned i;
	
	c = UDR;
	i = uart.rxhead + 1;
	if (i >= UART_RXBUF_SIZE) i = 0;
	if (i != uart.rxtail) {
		// fifo not full
		uart.rxbuf[uart.rxhead] = c;
		uart.rxhead = i;
	}
}

/* uart UDR empty interrupt */
SIGNAL(SIG_UART_DATA)
{
	unsigned i;
	
	i = uart.txtail;
	if (i != uart.txhead) {
		UDR = uart.txbuf[i++];
		if (i >= UART_TXBUF_SIZE) i = 0;
		uart.txtail = i;
	}
	else {
		// disable TX buffer empty interrupt
		UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
	}
}

// get character from uart fifo, return -1 if fifo empty
int uart_getch(void)
{
	unsigned char c;
	unsigned i, j;
	
	i = uart.rxtail;
	cli(); j = uart.rxhead; sei();
	if (i != j) {
		c = uart.rxbuf[i++];
		if (i >= UART_RXBUF_SIZE) i = 0;
		cli(); uart.rxtail = i; sei();
		return(c);
	}
	return(-1);
}

// put character to uart (return 1 if fifo full, else 0)
unsigned char uart_putch(char c)
{
	unsigned i, j;
	
	i = uart.txhead + 1;
	if (i >= UART_TXBUF_SIZE) i = 0;
	cli(); j = uart.txtail; sei();
	if (i == j) {
		// fifo full
		return(1);
	}
	uart.txbuf[uart.txhead] = c;
	cli(); uart.txhead = i; sei();
	// enable TX buffer empty interrupt
	UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE) | (1 << UDRIE);
	return(0);
}

// put string to uart
void uart_putstr(char *str)
{
	char ch;
	
	while (1) {
		ch = *str++;
		if (ch == 0) break;
		while (uart_putch(ch));
	}
}

// set up UART to specified baud, parity, and bits
void setup_uart(unsigned long baud, unsigned char parity, unsigned char bits)
{
	unsigned int ubrr;

	ubrr = (F_OSC / ((unsigned long)16 * baud)) - 1;
	UBRRL = ubrr & 0xff;
	UBRRH = ubrr >> 8;
	UCSRC = (parity << 4) | (bits << 1) | (1 << URSEL);
	UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
}

--Adamj12b 19:30, 5 October 2009 (EDT)