Open ReVolt/Software
The Open ReVolt Project: Wiki main page | Paul's website | Forum thread | Donate
svn repository (browse only)
Current Controller Software--ZeroGasoline 19:44, 21 May 2009 (EDT)
Contents
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.