
Below is our code for the project. To skip to the transmitter code, click here.
To access the original .c files, click click here for the main code and click here for the transmit code
//
// Cornell 2005
// Edward Lo and Sihan Goi
// ECE476 final project
// Temperature sensor and fan controller
//
#include <mega32.h>
#include <stdio.h>
#include <string.h>
#include <delay.h>
//number of supported fans
#define MAXFAN 2
//wireless packet definitions (maintain DC balance)
#define start_packet 0b10100110
#define stop_packet 0b11010010
//timeout values for each task
#define t1 20000 //Update LCD
#define t2 20000 //Hyperterminal display
#define t3 1000 //Hyperterminal input
#define t4 500 //Read temperature
#define t5 100 //Adjust fan speed
#define t6 5000 //PWM pulse stretching
//values for temperature calibration
#define inittemp 0.76
#define roomtemp 72.0
#define opampGain 4.00
#define Arefvolt 5.09
//other thresholds
#define t_lockedrotor 5 //when a fault occurs for this many cycles, the user is notified
//conversion macros
#define getTemp(input) ((float)(roomtemp +( ((float)((float)input-fproomtemp)/255.0)*Arefvolt/(0.01 * opampGain) )))
#define getRpm(input) ((unsigned int)(60000.0 / (((float)input/10.0) * tachdivider)))
//I like these definitions
#define begin {
#define end }
#define LCDwidth 16 //characters
//lcd definitions
#asm
.equ __lcd_port=0x15
#endasm
#include <lcd.h> // LCD driver routines
enum { pwm, dac, off }; //fan mode
enum { user, auto }; //opmode
enum { wireless, computer }; //inputmode
enum { start, data, stop }; //wireless data packets
//variables
unsigned char i; //local counter
unsigned char j; //counter in ISR
unsigned char opmode, inputmode, fanmode[MAXFAN]; //operation modes
unsigned int counttime, time1, time2, time3, time4, time5, time6; //task scheduling timeout counters
//wireless stuff
unsigned char rstate; //receive state
unsigned char datain; //data from serial in
//lcd stuff
unsigned char lcddispnum; //remembers which fan we are displaying status for
char lcd_buffer[17]; //LCD display buffer
//alarm stuff
unsigned char f_alarm, f_alarmsound; //flags to determine if an alarm should light and sound
//temperature sensing stuff
unsigned char sensornum; //remembers which temperature sensor we are currently reading from
unsigned char tempsensor[MAXFAN]; //temperature reading from ADC register
unsigned char t_mintemp[MAXFAN]; //minimum temperature threshold when fan turns on
unsigned char t_alarmtemp[MAXFAN]; //maximum temperature threshold when alarm sounds
float fpcurrtemp[MAXFAN]; //current temperature
float t_fpmintemp[MAXFAN]; //min threshold temperature
float t_fpalarmtemp[MAXFAN]; //max threshold temperature
float fproomtemp; //room temperature (for calibration)
float fpmaxtemp; //temporary variable for calculating fan speed
//fan tachometer detection stuff
int tachhi[MAXFAN]; //counts time spent in high phase of tach pulse
int tachlo[MAXFAN]; //counts time spent in low phase of tach pulse
unsigned char rpmsensor[MAXFAN]; //holds current tach pulse value
unsigned char f_readrpm[MAXFAN]; //flag to read rpm
unsigned char f_tachphase[MAXFAN]; //flag to remember which phase of the tach pulse we are in
unsigned char f_lockedrotor[MAXFAN]; //flag to detect a locked rotor
unsigned int rpm[MAXFAN]; //holds the calculated rpm of a fan
float tachdivider; //adjust for each fan - pulses per revolution
//pwm and dac stuff
unsigned char dacout; //holds dac value (0-15)
unsigned char pwmspeed; //holds pwm duty cycle (0-255)
unsigned char pwmcnt; //specifically for fan 2 rpm detection
float fanspeed; //speed to drive fan at: 1.0 = 100% power, 0.0 = 0% power
//keyboard input stuff
char cmd;
unsigned int val1;
//RXC ISR variables
unsigned char r_index; //current string index
unsigned char r_buffer[90]; //input string
unsigned char r_ready; //flag for receive done
unsigned char r_char; //current character
//TX empth ISR variables
unsigned char t_index; //current string index
unsigned char t_buffer[90]; //output string
unsigned char t_ready; //flag for transmit done
unsigned char t_char; //current character
//the subroutines
void gets_int(void); //starts getting a string from serial line
void puts_int(void); //starts a send to serial line
void initialize(void); //all the usual mcu stuff
//scheduled tasks
void DispScreen(void);
void DispLCD(void);
void ReadTemp(void);
void Sysadmin(void);
void AdjustFanSpeed(void);
void PWMPulseStretch(void);
//**********************************************************
//timer 2 comparison ISR
// This ISR ticks down the timers used to run scheduled tasks.
// It generates the square wave for the piezo buzzer when an alarm should sound.
// It reads the tachometer pulses from the fans and detects rpm.
// It detects locked fan rotors based on the behaviour of the tach pulses
interrupt [TIM2_COMP] void timer2_compare(void)
begin
//Decrement the scheduler times if they are not already zero
if (time1>0) --time1;
if (time2>0) --time2;
if (time3>0) --time3;
if (time4>0) --time4;
if (time5>0) --time5;
if (time6>0) --time6;
//generates square wave for the alarm
if( counttime-- == 0 ) {
if( f_alarmsound ) PORTD.5 ^= 1;
counttime = 10;
}
//turn off pwm mode pulse stretching after getting tachometer reading
if( (fanmode[1] == pwm) && (pwmcnt == 3) ) {
TCCR0 = 0b01101011; //resume normal pwm mode
f_readrpm[1] = 0; //don't read rpm anymore
}
//cycle through each fan and get rpm data
for( j = 0; j < MAXFAN; j++ ) {
//get rpm sensor reading
rpmsensor[j] = (PINA >> (j+4)) & 0x1;
//sample tachometer pulses
if( f_readrpm[j] == 1 ) {
//tachometer pulse is HIGH
if (rpmsensor[j] == 1) {
f_tachphase[j] = 1;
if (tachhi[j] > 600) f_lockedrotor[j] = t_lockedrotor; //rotor is locked in place
tachhi[j]++; //counts in 0.1ms intervals
}
//tachnometer pulse is LOW
else {
//check for falling edge
if (f_tachphase[j] == 1) {
//check for locked rotor here! (check if time difference between phases is > factor of 3)
if( ((tachhi[j]-tachlo[j]) > (3*tachlo[j])) && (f_lockedrotor[j] < t_lockedrotor) ||
((tachlo[j]-tachhi[j]) > (3*tachhi[j])) && (f_lockedrotor[j] < t_lockedrotor) ) {
f_lockedrotor[j]++;
}
else {
//rotor operating normally
f_lockedrotor[j] = 0;
//pwm fan 2
if( fanmode[j] == pwm ) pwmcnt++; //for pwm pulse stretching, count number of cycles
}
if( fanmode[j] == pwm ) {
if( pwmcnt == 3 ) rpm[j] = getRpm(tachlo[j]); //converts ms to rpm
}
else {
rpm[j] = getRpm(tachlo[j]); //converts ms to rpm
}
tachhi[j] = 0; //reset for counting next cycle
tachlo[j] = 0; //reset for counting next cycle
}
f_tachphase[j] = 0;
if (tachlo[j] > 600) f_lockedrotor[j] = t_lockedrotor; //rotor is locked in place
tachlo[j]++; //counts in 0.1ms intervals
}
}
} //for loop
end
//**********************************************************
//UART character-ready ISR
interrupt [USART_RXC] void uart_rec(void)
begin
r_char=UDR; //get a char
//computer input mode
if( inputmode == computer ) {
UDR=r_char; //then print it
//build the input string
if (r_char != '\r') r_buffer[r_index++]=r_char;
else
begin
putchar('\n'); //use putchar to avoid overwrite
r_buffer[r_index]=0x00; //zero terminate
r_ready=1; //signal cmd processor
UCSRB.7=0; //stop rec ISR
end
}
//wireless input mode
else {
if( (rstate == start) && (r_char == start_packet) ) {
rstate = data;
}
else if( rstate == data ) {
datain = r_char;
rstate = stop;
}
else if( (rstate == stop) && (r_char == stop_packet) ) {
rstate = start;
}
else {
rstate = start;
}
}
end
/**********************************************************/
//UART xmit-empty ISR
interrupt [USART_DRE] void uart_send(void)
begin
t_char = t_buffer[++t_index];
if (t_char == 0)
begin
UCSRB.5 = 0; //kill isr
t_ready = 1; //transmit done
end
else UDR = t_char; //send the char
end
//**********************************************************
// -- non-blocking keyboard check initializes ISR-driven
// receive. This routine merely sets up the ISR, which then
// does all the work of getting a command.
void gets_int(void)
begin
memset(r_buffer,0x00,90);
r_ready = 0;
r_index = 0;
UCSRB.7 = 1;
end
//**********************************************************
// -- nonblocking print: initializes ISR-driven
// transmit. This routine merely sets up the ISR, then
// send one character. The ISR does all the work.
void puts_int(void)
begin
t_ready = 0;
t_index = 0;
if (t_buffer[0]>0)
begin
putchar(t_buffer[0]);
UCSRB.5=1;
end
end
//**********************************************************
//Entry point and task scheduler loop
// Handles mux selection, alarm and lights, and operation modes.
// Calls various task timers based on the timeout counters.
void main(void)
begin
initialize();
//main task scheduler loop -- never exits!
while(1)
begin
if (time1==0) DispLCD(); //display system status on LCD
if (time2==0) DispScreen(); //display information on terminal
if (time3==0) Sysadmin(); //gets input from keyboard
if (time4==0) ReadTemp(); //reads temperature sensors
if (time5==0) AdjustFanSpeed(); //adjusts fan speeds
if (time6==0) PWMPulseStretch(); //sets PWM to max for rpm detection
//wireless or computer mode?
inputmode = PINB.1;
//mux output
PORTB.0 = opmode;
PORTB.2 = inputmode;
//alarm system
f_alarm = 0;
for( i = 0; i < MAXFAN; i++ ) {
if( (fanmode[i] != off) &&
((fpcurrtemp[i] > t_fpalarmtemp[i]) ||
((fpcurrtemp[i] > t_fpmintemp[i]) && (f_lockedrotor[i] == t_lockedrotor))) ) {
f_alarm = 1;
break;
}
}
f_alarmsound = f_alarm;
PORTD.4 = f_alarm; //alarm status light
//status lights
PORTD.2 = (opmode == auto);
PORTD.3 = (inputmode == wireless);
//manual mode
if( opmode == user ) {
TCCR0 = 0;
PORTB.3 = 1; //drive PWM at full power to maintain rpm detection
PORTA.2 = 1; //enable fan 2 so it will spin
f_readrpm[0] = 1; //continue rpm detection
f_readrpm[1] = 1; //continue rpm detection
pwmcnt = 0; //so interrupt will always read rpm
}
//auto mode
if( opmode == auto ) {
//fan 2 (pwm fan)
if( (fanmode[1] != off) && (fpcurrtemp[1] > t_fpmintemp[1]) ) {
// turn on fan
if( f_readrpm[1] == 0 ) {
OCR0 = pwmspeed; //only set PWM speed if rpm is not being detected
}
TCCR0 = 0b01101011; //clear OC0 on upcount
}
else {
// turn off fan
OCR0 = 0;
TCCR0 = 0;
PORTB.3 = 0; //don't drive PWM
rpm[1] = 0;
f_lockedrotor[1] = 0;
}
//fan1 (dac fan)
if( (fanmode[0] != off) && (fpcurrtemp[0] > t_fpmintemp[0]) ) {
PORTA.2 = 1; //enable fan
}
else {
PORTA.2 = 0; //disable fan
rpm[0] = 0;
f_lockedrotor[0] = 0;
}
//output dac value to port B
PORTB = PINB & 0x0f | (dacout << 4);
}
//wireless input
if( inputmode == wireless ) {
switch( datain ) {
case 0b11000010: t_fpmintemp[0] += 1.0; break;
case 0b10100010: t_fpmintemp[0] -= 1.0; break;
case 0b10010010: t_fpmintemp[1] += 1.0; break;
case 0b10001010: t_fpmintemp[1] -= 1.0; break;
case 0b10000110: opmode ^= 1;
if( opmode == auto ) f_readrpm[1] = 0;
break;
}
datain = 0; //clears received data
}
end
end
//**********************************************************
//Adjusts fan speeds
// This function uses the temperature detected from the sensors
// and adjusts the fan speed accordingly. It assumes linear ramping.
// The percentage of power to use is stored in 'fanspeed'.
void AdjustFanSpeed(void)
begin
time5=t5;
for( i = 0; i < MAXFAN; i++ ) {
//get percentage of max speed to use
fpmaxtemp = (t_fpalarmtemp[i] - t_fpmintemp[i]) * 0.75;
fanspeed = (fpcurrtemp[i] - t_fpmintemp[i]) / fpmaxtemp;
//pwm adjustment (fan 2)
if( i == 1 ) {
if( fanspeed >= 1.0 ) {
pwmspeed = 255;
} else if( fanspeed <= 0 ) {
pwmspeed = 0;
} else {
pwmspeed = (unsigned char)( fanspeed * 255.0 );
}
}
//4 bit DAC adjustment (fan 1)
if( i != 1 ) {
if( fanspeed >= 1.0 ) {
dacout = 15;
} else if( fanspeed <= 0 ) {
dacout = 0;
} else {
dacout = (unsigned char)( fanspeed * 15.0 );
}
}
}
end
//**********************************************************
//Reads Temperature Sensors
// This function reads the ADC result from the temperature sensors.
// It continuously muxes between the different sensors and
// alternatingly gets their readings and sets up for the next one.
void ReadTemp(void)
begin
time4=t4; //reset the task timer
// reads current sensor
tempsensor[sensornum] = ADCH;
fpcurrtemp[sensornum] = getTemp(tempsensor[sensornum]);
//switch to next sensor and starts next conversion
if( sensornum++ == (MAXFAN-1) ) sensornum = 0;
ADMUX = 0b00100000 | sensornum;
ADCSR.6 = 1;
end
//**********************************************************
//PWM Pulse Stretching
// If fan is running in PWM mode, then we need to run it at full power
// periodically in order to get RPM information. It sets the f_readrpm
// flag to 1 so the interrupt will gather RPM for the PWM fan.
void PWMPulseStretch(void)
begin
time6=t6; //reset the task timer
//use pulse stretching to handle pwm rpm monitoring
if( (fanmode[1] == pwm) && (fpcurrtemp[1] > t_fpmintemp[1]) ) {
OCR0 = 255; //drive PWM at full power
TCCR0 = 0b01101011; //clear OC0 on upcount
f_readrpm[1] = 1; //read rpm signals flag
pwmcnt = 0;
}
end
//**********************************************************
//Displays to LCD
// This function outputs the fan + temperature status to the LCD in
// an alternating fashion.
void DispLCD(void)
begin
time1=t1; //reset the task timer
//toggle between different fan info
if( lcddispnum++ == (MAXFAN-1) ) lcddispnum = 0;
//current rpm + temperature
lcd_clear();
sprintf( lcd_buffer, "%d: %4d rpm %2.0fF", lcddispnum+1, rpm[lcddispnum], fpcurrtemp[lcddispnum] );
lcd_gotoxy(0,0);
lcd_puts( lcd_buffer );
end
//**********************************************************
//Displays fan controller information to the computer terminal
void DispScreen(void)
begin
time2=t2;
//prints out title and mode on the hyperterminal
while ( !t_ready );
sprintf(t_buffer, "\f\n\rFan Controller (");
puts_int();
while ( !t_ready );
if( opmode == auto ) sprintf( t_buffer, "auto, " );
else sprintf( t_buffer, "user, " );
puts_int();
while ( !t_ready );
if( inputmode == wireless ) sprintf( t_buffer, "wireless)\n\r" );
else sprintf( t_buffer, "terminal)\n\r" );
puts_int();
//prints fan status
for( i = 0; i < MAXFAN; i++ ) {
while ( !t_ready );
if( (f_lockedrotor[i] == t_lockedrotor) && (fpcurrtemp[i] > t_fpmintemp[i]) && ((fanmode[i] != off) ||
(opmode == user)) )
sprintf(t_buffer, "Fan%d: %4drpm %4.1fF min=%3.0f max=%3.0f - FAULT detected!\n\r",
i+1, rpm[i], fpcurrtemp[i], t_fpmintemp[i], t_fpalarmtemp[i]);
else
sprintf(t_buffer, "Fan%d: %4drpm %4.1fF min=%3.0f max=%3.0f\n\r",
i+1, rpm[i], fpcurrtemp[i], t_fpmintemp[i], t_fpalarmtemp[i]);
puts_int();
}
//prints user prompt
while ( !t_ready );
sprintf(t_buffer, "> %s", r_buffer);
puts_int();
end
//**********************************************************
//Serial port input from the computer
// Gets commands from the keyboard and parses them.
void Sysadmin(void)
begin
time3=t3;
//get user input
if ( r_ready )
begin
sscanf(r_buffer, "%c%d", &cmd, &val1);
gets_int();
switch( cmd ) begin
//set pwmspeed
case 'a': t_fpmintemp[0] = val1; break;
case 'b': t_fpalarmtemp[0] = val1; break;
case 'c': t_fpmintemp[1] = val1; break;
case 'd': t_fpalarmtemp[1] = val1; break;
case 'o': opmode = opmode ^ 1;
if( opmode == auto ) f_readrpm[1] = 0;
break;
case 'f': if( val1 == 1 ) {
if( fanmode[0] == off ) fanmode[0] = dac;
else fanmode[0] = off;
}
if( val1 == 2 ) {
if( fanmode[1] == off ) fanmode[1] = pwm;
else fanmode[1] = off;
}
break;
case 't': tachdivider = val1; break;
end
end
end
//**********************************************************
//Initialize program settings
// Sets up port pins, timers, flags, and interrupts.
void initialize(void)
begin
//temperature and rpm sensing
DDRA = 0x84; //A.2 = enable DAC fan
PORTA = 0xff;
//dac, pwm, mode output and wireless toggle input
DDRB = 0xfd;
PORTB = 0x00;
//lcd output
DDRC = 0xff;
PORTC = 0xff;
//serial and status lights and alarm
DDRD = 0xff;
PORTD = 0xff;
//serial setop for debugging using printf, etc.
UCSRB = 0x18;
UBRRL = 207; //4800
putsf("\r\nCornell Starting...\r\n");
//set up timer 0 for PWM
TCNT0 = 0;
OCR0 = 0;
TCCR0 = 0b01001011; //prescalar to CLK, CTC, PWM, init with no OC0 operation
//set up timer 2 for time based scheduler
TCNT2 = 0;
OCR2 = 24; //0.1 ms
TIMSK = 1<<7; //turn on timer 2 cmp-match ISR
TCCR2 = 0b00001011; //prescalar to CLK / 64
//init the task timers
time1=t1;
time2=t2;
time3=t3;
time4=t4;
time5=t5;
time6=t6;
counttime = 10;
//lcd stuff
lcd_init(LCDwidth); //initialize the display
lcd_clear(); //clear the display
putsf("\r\nLCD init complete...\r\n");
//temperature reading stuff
sensornum = 0;
ADMUX = 0b00100100; //use port A.4 for input (left aligned)
ADCSR = 0b11000111; //prescalar to 1/128*16Mhz = 125kHz, start conversion
fproomtemp = (inittemp * opampGain / Arefvolt) * 255.0; //calibrate roomtemp with sensor 1
for( i = 0; i < MAXFAN; i++ ) {
t_fpmintemp[i] = 72; //initial thresholds
t_fpalarmtemp[i] = 80;
t_mintemp[i] = 72;
t_alarmtemp[i] = 80;
}
//fan speed and failure detection stuff
for( i = 0; i < MAXFAN; i++ ) {
tachhi[i] = 0;
tachlo[i] = 0;
f_readrpm[i] = 1;
f_lockedrotor[i] = 0;
}
f_readrpm[1] = 0; //fan 2 is a pwm fan
//selects fan modes
opmode = auto;
inputmode = computer;
fanmode[0] = dac;
fanmode[1] = pwm;
//pwm and dac stuff
dacout = 0;
pwmspeed = 0;
pwmcnt = 0;
//transmit and receive flags
r_ready = 0;
t_ready = 1;
rstate = start;
datain = 0;
//crank up the ISRs
#asm
sei
#endasm
//serial port input
gets_int();
//our fan
tachdivider = 2;
end
Transmitter code
Below is the transmitter code:
//
// Cornell 2005
// Edward Lo and Sihan Goi
// ECE476 final project
// Temperature sensor and fan wireless transmitter
//
#include <Mega32.h>
#include <stdio.h>
#include <stdlib.h>
//I like these definitions
#define begin {
#define end }
//task scheduling timers
#define t1 30 //30 ms debounce state machine
//wireless packet definitions (maintain DC balance)
#define start_packet 0b10100110
#define stop_packet 0b11010010
enum { NoPush, MaybePush, Pushed, MaybeNoPush };
unsigned int time1; //task scheduling timeout counter
unsigned char buttons, buttons2, inbutton; //saves the button press
unsigned char butstate; //debounce state machine
unsigned char cdata; //data byte to transmit
void initialize(void); //set everything up
void getInput(void); //debounce buttons
//**********************************************************
//Timer0 compare ISR
// Simply ticks down the task timers.
interrupt [TIM0_COMP] void timer0_compare(void)
{
if (time1>0) --time1;
}
//**********************************************************
//Transmit the data byte
// Encapsulates the data packet with a start and end packet.
// Send 0xaa's to setup receiver gain.
void senddata(void)
{
//setup the receiver for proper gain and sync
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xaa);
putchar(0xff);
putchar(0x00);
//send data
putchar(start_packet);
putchar(cdata);
putchar(stop_packet);
cdata = 0;
}
//**********************************************************
//Button debouncing state machine
// Gets the button presses into "inbutton" and then encodes it
// into the "cdata" variable. This becomes the data packet.
void getInput(void)
{
time1=t1;
//button debounce
switch( butstate )
begin
case NoPush:
buttons = PINA;
if( ~buttons != 0 ) butstate = MaybePush; break;
case MaybePush:
buttons2 = PINA;
if( buttons2 == buttons )
begin
butstate = Pushed;
inbutton = ~buttons;
end
else butstate = NoPush;
break;
case Pushed:
buttons2 = PINA;
if( buttons2 == buttons ) butstate = Pushed;
else butstate = MaybeNoPush; break;
case MaybeNoPush:
buttons2 = PINA;
if( buttons2 == buttons ) butstate = Pushed;
else
begin
butstate = NoPush;
inbutton = 0;
end
break;
end
//parse input
switch( inbutton ) {
case 0b10000000:
case 0b01000000:
case 0b00100000:
case 0b00010000: cdata = 0b10000000 | (inbutton >> 1) | 0b10;
inbutton = 0;
break;
case 0b00000001: cdata = 0b10000110;
inbutton = 0;
break;
}
}
//**********************************************************
//Main program
// Debounce buttons and send presses to serial port.
void main(void)
{
initialize();
while(1)
{
if( time1 == 0 ) getInput(); //get debounced input
if( cdata != 0 ) senddata(); //transmits button press
}
}
//**********************************************************
//Get everyting started
void initialize(void)
{
//setup ports
DDRA = 0x00; // buttons
PORTA = 0xff; // pull-up resistors
DDRD = 0xff; // output for status light and transmitter
PORTD = 0xff; // power status on
//serial setop for debugging using printf, etc.
UCSRB = 0x18;
UBRRL = 207; //4800
//setup timer0 for 1ms
TIMSK = 2;
OCR0 = 249;
TCCR0 = 0b00001011;
//initialize timers
time1 = t1;
time2 = t2;
//setup debounce states
butstate = NoPush;
inbutton = 0;
cdata = 0;
#asm ("sei");
//turns on LED
PORTD.7 = 0;
}
