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/io.h>
#include <avr/interrupt.h>
//#include <util/delay.h>
//#include

#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