Open ReVolt/Software

From EcoModder Forum Wiki
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)


Software Features and Revisions

An Archive of firmware versions v0.10 through v1.11b is available at :http://www.adambrunette.com/firmware/

version 0.1 - first version tested
version 0.2 - added PW (PWM) to realtime data
version 0.3 - added thermal cutback code (to be checked by Paul)
version 0.4 - added continous check of vref below 2V
version 0.5 - declared some variables volatile to enforce execution within bounds of cli() and sei()
version 0.6 - make ocr1a_lpf time constant longer - V0.5 oscillates if Kp,Ki are low (slow PI loop)
version 0.7 - fixed stupid bug in config_pi() that caused Joe's problems (thanks Joe)
version 0.8 - high pedal lockout added but not yet tested
version 0.9 - store mutiple copies of configuration in EE prom (not yet tested)
version 0.10 - code size reduction, correct thermal cutback starting point, selectable pwm low pass filter
version 1.0 - in pi_loop() added cli() and sei() around update of OCR1A, added cli() after pi_loop
version 1.1 - added battery amps and battery amp hour calculations (not yet tested)
version 1.2 - added motor overspeed logic (experimental and not yet tested)
version 1.3 - added battery amps limit (not yet tested) and changed motor overspeed parameters to 4 digit
version 1.4 - new 8KHz PWM compile to option, added battery_amps_limit, spare, and crc, default_config PROGMEM
version 1.5 - fixed ocr1a_lpf calculation - so battery amps limit should now work, added "restart" command
version 1.6 - added support for ATMEGA168 (not yet tested)
version 1.7 - moved motor overspeed logic (still not yet tested) from main() into interrupt code pi_loop()
version 1.8 - added precharge timer, removed not needed code that measures pi_loop() execution time
version 1.9 - forgot to calculate bat_amp_lim_510 when loading config from EEprom, now fixed (thanks Adam)
version 1.10 - added configurable PWM dead zone and motor overspeed detect time for motor overspeed logic, configuration menu a little nicer, new watchdog handling (mainly for ATMega168)
2010-01-10 fixed defines for PIND, DDRD, and PORTD in bootload168/misc.asm (no version change)
version 1.11 - added motor_speed_calc_amps - attempt to avoid overspeed tripping when pedal is "pumped"


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)


Serial Command List

Serial Hardware is NOT RS232, it is FTDI 5v logic serial. Beware!

Commands:

Format -

<Command><" "><INTEGER>

<SAVE>

Example:

pctime 50

save

saves a new precharge time of 5 seconds


save - Save setting

idle - ? Idle controller

restart - restart controller

reset-ah - reset Amp hours

pc-time - Precharge contactor/relay time

kp - PI loop proportional gain

ki - PI loop integreal gain

t-min-rc - config.throttle_min_raw_counts - throttle low voltage (pedal to metal)

t-max-rc - config.throttle_max_raw_counts - throttle high voltage (foot off pedal)

t-pos-gain - config.throttle_pos_gain - gain for actual throttle position

t-pwm-gain - config.throttle_pwm_gain - gain for pwm (voltage)

c-rr - config.current_ramp_rate - current ramp rate

rtd-period - Period between output of controller data to the serial port

pwm-filter - ?

motor-os-dt - Motor Overspeed detect time

pwm-deadzone - ?

motor-sc-amps



DATA:

If you set rtd-period # to a value in ms, this will be how often the controller spits out the data string with the real time parameters.

The break down is as follows:


TR = Controllers calculated throttle position. 0-511

CR = Controllers current reference based on throttle and other factors. 0-511

CF = Current feedback from LEM current sensor 0-511

PW = PWM duty cycle of controller 0-511

HS = Heat-sink Temp in raw ADC counts 0-1023

RT = Raw Throttle counts from the ADC 0-1023

FB = Fault Bits. Hex output with each fault and status code. Normal 00

BA = Calculated Battery amps 0-511

AH = Calculated Amp Hours consumed 0-999.9


Here is an example string:

TR=000 CR=000 CF=002 PW=000 HS=0315 RT=0716 FB=00 BA=000 AH=000.0


This shows:


TR=000 No throttle

CR=000 Controller is not commanding any current

CF=002 Current sensor reading of 002, it will fluctuate a few counts in increments of 002

PW=000 No pwm output

HS=0315 heatsink temp of 315 is about 76 ish F.

RT=0716 The throttle all the way up value

FB=00 No fault codes. this will change on HPL, pre-charge, overspeed, overtemp, and no throttle or current sensor

BA=000 no battery current

AH=000.0 no AH consumed because you havnt used any power yet. lol

Link to Adam Brunettes [1] page with his software - RTD explorer that graphs this all automatically for you.