Cooling Code


Here’s an early snapshot of the code to drive the radiator cooling fans with PWM output, using the harness fan-switch and starter motor switches to determine the mode of operation. The code (already) compiles to 610 bytes; out of the 1024 available in the ATtiny13A microcontroller.

Field programming operation, battery voltage sensing and the ability to use an NTC resistor for coolant temperature sensing are not yet supported.


 /*
* Author: Bernd Felsche (C) Copyright 2013, 2014
* Innovative Reckoning
* Western Australia
*
* A CTC test Set up all pins
*/

#define F_CPU 4800000UL // must run slower to get 25Hz

#include
 #include <avr/interrupt.h>

#include <avr/wdt.h>
 #include <avr/eeprom.h>
 #include <avr/power.h>
 #include <avr/sleep.h>

// Pin assignments
 #define NTC PB0 // Temperature sensor
 #define PWM PB1 // Output to J293
 #define LOF PB2 // Low-speed fan signal at car harness
 #define HIF PB3 // Low-speed fan signal at car harness
 #define KL50 PB4 // starter motor solenoid high
 #define AVB PB5 // Battery supply Analog input voltage at RESET pin
 // AVB has to be scaled to remain above the guaranteed !RESET

// FACTORY DEFAULTS
 // these will be in EEPROM but not field-programmable without ICSP
 #define PWMclk 186 // compare register for "accurate" 25Hz
 // PWM pulse varies between these values for J293
 #define PWMmin 30 // minimum - fans off but system alive
 #define PWMmax 175 // maximum PWM and default for high speed
 #define PWMlof 90 // PWM for low speed
 #define LOBAT 0 // low battery ADC voltage 0 == uncalibrated

/* Our MAGIC which must match the first byte of EEPROM
  * otherwise it'll get confuddled */
 #define MAGIC 0x42 // EEPROM layout matches this firmware

// EEPROM Addresses
 #define _RC_CAL 0 // reserved for avrdude RC calibration byte
 #define _MAGIC _RC_CAL + 1 // MAGIC byte
 #define _PWMclk _MAGIC + 1 // Counter for base frequency of PWM, etc
 #define _PWMmin _PWMclk + 1
 #define _PWMmax _PWMmin + 1
 #define _PWMlof _PWMmax + 1 // initial value for low fan speed
 // ... PWM parameters for PID, ...
 #define _LOBAT 15 // ADC value matching low battery condition
 // "tuned" values
 #define _setlof 16 // PWM width set for low fan speed
 #define _sethif 17 // PWM width set for high fan speed
 #define _setntc 18 // NTC target value in PID-mode

#define byte uint8_t // easier to type

// global values
 byte fan_lo;
 byte fan_hi;
 byte no_fan; // PWM tick-over for J293 == PWMmin
 byte ntc_target;
 byte battery_low;

volatile bool PWMen = false; // enable PWM output
 volatile byte fanspeed = 0; // Selected fan speed (0,1,2)
 volatile byte newPW = 0; // the required pulse width non-zero means change
 volatile byte snooze = 0; // ticks left to sleep

uint8_t tick(num) { // wait for "n" ticks, then return
 // set "alarm" at num ticks
 snooze = num;
 // go to sleep
 sleep_enable();
 sleep_cpu();
 return snooze;
 }

initCLKS() { // Initialise clocks

// Set up frequency-correct PWM
 TCCR0A |= (1 << WGM01) ; // CTC

OCR0A = eeprom_read_byte((uint8_t *)_PWMclk); // Should yield about 25Hz
 OCR0B = no_fan; // start off at idle

TIMSK0 |= (1 << OCIE0A) | (1 << OCIE0B); // Enable CTC interrupts

sei(); // Enable global interrupts
 TCCR0B |= (1 << CS00) | (1 << CS02); // Start timer at FCPU/1024

// TIM0_COMPA will also be used as the base timer interrupt.

// watchdog every ~0.5 s
 wdt_enable(WDTO_500MS);
 }

// INTERRUPT handlers /////////////////////////////////////
 /* This toggles PWM pin to get frequency-correct PWM signal */
 ISR(TIM0_COMPA_vect) {
 if (PWMen)
 PORTB |= (1 << PWM); // on at OCR0A
 else
 PORTB &= ~(1 << PWM); // off // set the fan pulse width if changed if ( newPW != 0 ) { OCR0B = newPW; newPW = 0; } // Feed the watchdog wdt_reset(); // this is where a custom pause() would terminate when expired if ( snooze > 0 )
 if ( --snooze == 0 ) sleep_disable();
 }
 ISR(TIM0_COMPB_vect) {
 PORTB &= ~(1 << PWM); // off at OCR0B
 }

ISR(WDT_vect) { // the watchdog tried to wake up a stalled process
 }

ISR(ADC_vect) { // Analog conversion complete
 }

ISR(EE_RDY_vect) { // EEPROM ready (for erase/write)
 }
 ISR(PCINT0_vect) { // a pin changed determining nominal fan speed
 byte port_now;

// read the port all the inputs.
 port_now = PINB;

// test the interesting bits of the vehicle's state
 // set fan speed according to command state
 if ( port_now & (1 << HIF) ) {
 fanspeed = 2;
 } else if ( port_now & (1 << LOF) ) {
 fanspeed = 1;
 } else
 fanspeed = 0; // rests fans when Hi/Lo both off
 if ( port_now & (1 << KL50) ) { // starter engaged
 // stop fan while cranking
 fanspeed = 0;
 }

switch (fanspeed) { // determine require pulse width
 case 0: newPW = no_fan; break;
 case 1: newPW = fan_lo; break;
 case 2: newPW = fan_hi; break;
 }
 // application of the fanspeed is in the timerCMPA ISR
 // to avoid glitching the pulses on changes
 }

// EEPROM handlers /////////////////////////////////////
 // if signature doesn't match, just twiddle thumbs
 void check_ee() {
 // if the EEPROM MAGIC doesn't match, it's not safe to
 // continue
 if ( eeprom_read_byte((uint8_t *)_MAGIC) != MAGIC ) {
 // PORTB &= ~(1 << PB0); // blink!?
 while(1) ;; // never return
 }
 }

// load factory defaults for PWM, etc and save them for operation
 back_to_factory() {
 (void)check_ee();
 // low speed
 (void)eeprom_write_byte((uint8_t *)_setlof,
 eeprom_read_byte((uint8_t *)_PWMlof));
 // high speed
 (void)eeprom_write_byte((uint8_t *)_sethif,
 eeprom_read_byte((uint8_t *)_PWMmax));
 // NTC setpoint default is OFF
 (void)eeprom_write_byte((uint8_t *)_setntc,(uint8_t)0);
 }

// load frequently-used, saved values from EEPROM
 load_saved() {
 (void)check_ee();
 fan_lo = eeprom_read_byte((uint8_t *)_setlof);
 fan_hi = eeprom_read_byte((uint8_t *)_sethif);
 no_fan = eeprom_read_byte((uint8_t *)_PWMmin);
 ntc_target = eeprom_read_byte((uint8_t *)_setntc);
 battery_low = eeprom_read_byte((uint8_t *)_LOBAT);
 }

// MAIN PROGRAM /////////////////////////////////////
 int main (void) {
 // define the use of our few pins
 MCUCR |= (1 << PUD); // turn off all pull-ups
 DDRB |= (1 << PWM); // this is the output for driver of J293
 DDRB |= (1 << PB0); // devel diag pattern: blink!?
 PINB |= (1 << KL50) | (1 << LOF) | (1 << HIF);
 //DIDR0 |= (1 << AVB) | (1 << NTC); // disable digital buffer on analog
 DIDR0 |= (1 << AVB); // disable digital buffer on analog

// set pins on which to watch for changes (interrupts)
 PCMSK = (1 << LOF) | (1 << HIF) | (1 << KL50); // inputs from harness
 GIMSK |= (1 << PCIE); // enable pin change interrupts
 // they all need to be detected because power may be provided at KL_15

// Load saved values from EEPROM
 (void)load_saved();
 if ( fan_lo == fan_hi ) { // if they don't make sense
 (void)back_to_factory();
 (void)load_saved();
 }

// Start base timer for PWM and other tasks at TIM0_COMPA
 (void)initCLKS();

// Initialise according to PORTB state re-using pin-change interrupt
 PCINT0_vect();
 // PWM output won't be activated unless PWMen is true

PWMen = true; // enable PWM output

// can spend most cycles having a kip
 set_sleep_mode(SLEEP_MODE_IDLE);

// go loopy
 while (1) {
 byte early;

// monitor battery voltage

// monitor NTC voltage

// wait for next tick
 early = tick(1);
 }
 }
Advertisements

Your say

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s