A simple Integral fan controller driving three PC fans.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "msp430G2452.h" | |
#define FAN_CNT (3) | |
#define PWM_FREQ (32) | |
#define PWM_RESS (128) | |
// Port 1 | |
#define LED1 (1<<0) | |
#define TMP_RX (1<<1) | |
static const int TACH[] = { (1<<3), (1<<4), (1<<5) }; | |
// Port 2 | |
static const int FAN[] = { (1<<0), (1<<1), (1<<2) }; | |
#define LED1_ON() (P1OUT &= ~LED1) | |
#define LED1_OFF() (P1OUT |= LED1) | |
volatile struct { | |
int second; | |
int fracofsec; | |
unsigned int rpmdata[2][FAN_CNT]; | |
unsigned int maxrpm[FAN_CNT]; | |
unsigned int fanpwr[FAN_CNT]; | |
unsigned int fanrpm[FAN_CNT]; | |
} fs; | |
void tempCTLAlg(void); | |
int bound(int low, int mid, int high); | |
int takeADCReading(int channel); | |
void main(void) { | |
// Kill Boris - we don't need a watchdog | |
WDTCTL = WDTPW + WDTHOLD; | |
// Clock system | |
DCOCTL = CALDCO_16MHZ; | |
BCSCTL1 = CALBC1_16MHZ; | |
BCSCTL2 = SELS; // DCO slave clock | |
// Timer A | |
TA0CTL = TASSEL_2 | MC_1; // SCLK, UP | |
TA0CCR0 = (32768 / PWM_FREQ / PWM_RESS) - 1; | |
TA0CCTL0 = CCIE; | |
// ADC10 | |
ADC10CTL0 = SREF_0 | ADC10ON; // Vcc reference | |
ADC10CTL1 = INCH0 | ADC10SSEL1; // Channel A1, MCLK | |
ADC10AE0 = (1<<1); | |
// IO Port 1 | |
P1DIR = LED1; | |
P1IE = TMP_RX | TACH[0] | TACH[1] | TACH[2]; // Enable ints | |
P1IES = 0xFF; // H-L transition int | |
P1IFG = 0x00; // Clear noise interrupts | |
// IO Port 2 | |
P2DIR = FAN[0] | FAN[1] | FAN[2]; | |
// Start interrupts | |
_BIS_SR(GIE); | |
int i; | |
// Three second startup at full power | |
for (i=0; i<FAN_CNT; i++) | |
fs.fanpwr[i] = PWM_RESS; | |
while (fs.second <3) ; | |
for (i=0; i<FAN_CNT; i++) { | |
fs.maxrpm[i] = fs.rpmdata[1][i]; | |
fs.fanrpm[i] = 2 * fs.maxrpm[i] / 3; | |
} | |
fs.fanrpm[0] = fs.fanrpm[0] / 2; | |
while (1) { | |
if (fs.second) { | |
fs.second--; | |
tempCTLAlg(); | |
} | |
} | |
} | |
// Temperature Control Algorithm | |
// Do the 1Hz work to try and keep the temperature on target | |
void tempCTLAlg(void) { | |
int tempsense = takeADCReading(1); | |
int lightvote = 0; | |
int i; | |
int tempDelta = tempsense - 280; | |
for (i=0; i<FAN_CNT; i++) { | |
fs.fanrpm[i] = bound(fs.maxrpm[i]>>2, fs.fanrpm[i] + tempDelta, fs.maxrpm[i]); | |
int rpmDelta = fs.rpmdata[1][i] - fs.fanrpm[i]; | |
fs.fanpwr[i] = bound(0, fs.fanpwr[i] - (rpmDelta/250), PWM_RESS); | |
if (rpmDelta < 0) { // too slow | |
lightvote++; | |
} else {// too fast | |
lightvote--; | |
} | |
} | |
if (tempDelta > 0) { | |
LED1_ON(); | |
} else { | |
LED1_OFF(); | |
} | |
} | |
int bound(int low, int mid, int high) { | |
if (low > mid) | |
mid = low; | |
if (high < mid) | |
mid = high; | |
return mid; | |
} | |
int takeADCReading(int channel) { | |
ADC10CTL1 = (ADC10CTL1 & 0x0FFF) | (channel << 12); | |
ADC10CTL0 |= ENC | ADC10SC; | |
while (ADC10CTL1 & ADC10BUSY) ; | |
return ADC10MEM; | |
} | |
#pragma vector=TIMER0_A0_VECTOR | |
__interrupt void Timer_A00 (void) { | |
int i, fracofpwm; | |
fs.fracofsec = (fs.fracofsec + 1) % (PWM_FREQ * PWM_RESS); | |
if (fs.fracofsec == 0) { | |
for (i=0; i<FAN_CNT; i++) { | |
fs.rpmdata[1][i] = 30 * fs.rpmdata[0][i]; | |
fs.rpmdata[0][i] = 0; | |
} | |
fs.second++; | |
} | |
fracofpwm = fs.fracofsec%PWM_RESS; | |
if (fracofpwm == 0) { | |
// Turn on enabled fans | |
P2OUT |= FAN[0] | FAN[1] | FAN[2]; | |
} | |
for (i=0; i<FAN_CNT; i++) { | |
if (fs.fanpwr[i] < fracofpwm) { | |
P2OUT &= ~(FAN[i]); | |
} | |
} | |
} | |
#pragma vector=PORT1_VECTOR | |
__interrupt void Port1 (void) { | |
unsigned int pins = P1IFG; | |
P1IFG = 0x00; | |
unsigned int i; | |
for (i=0; i<FAN_CNT; i++) { | |
if (pins & TACH[i]) { | |
fs.rpmdata[0][i]++; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment