// // Cornell 2005 // Edward Lo and Sihan Goi // ECE476 final project // Temperature sensor and fan controller // #include #include #include #include //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 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