The Lavolta BPS305 is a cheap bench power supply of Chinese origin. The device comes with a 24 V DC fan, but without any means to control it in any way. The fan will just run the whole time, which is pretty annoying. Because of that, I decided very build a fan-controller using a microcontroller and a sensor to measure the current temperature in the case.
The whole board is powered by 12 V DC from the buck converter above the fan.
I used the avr-libc and avr-gcc to write and compile the code.
At first I had a more sophisticated solution in mind. But after a while I decided to go with a simpler if-then-else kind of solution. For communicating with the DS18b20 sensor, I used a 3rd party library.
#ifndef __AVR_ATtiny85__
#define __AVR_ATtiny85__
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include "ds18b20/ds18b20.h"
#define P_LED PB0
#define PORT_LED PORTB0
#define DD_LED DDB0
#define P_PWM PB1
#define PORT_PWM PORTB1
#define DD_PWM DDB1
#define P_BUTTON PB2
#define PORT_BUTTON PORTB2
#define PIN_BUTTON PINB2
#define P_TACHO PB3
#define PORT_TACHO PORTB3
#define PIN_TACHO PINB3
#define PCINT_TACHO PCINT3
#define P_DS18B20 PB4
#define PORT_DS18B20 PORTB4
#define PIN_DS18B20 PINB4
/*
* Pin 1 - Reset
* Pin 2 - [PB3|PCINT3] Fan tacho signal
* Pin 3 - [PB4] DS18b20 temperature sensor
* Pin 4 - GND
* Pin 5 - [PB0] Status LED
* Pin 6 - [PB1|OC0B] Fan PWM signal wire
* Pin 7 - [PB2] Button
* Pin 8 - VCC
*/
const uint8_t PWM_BOTTOM = 2; // Lowest PWM value to actually use
const uint8_t PWM_TOP = 39; // 0-39 ==> 40 values
uint8_t volatile timer1_irq_counter;
uint8_t seconds_counter;
// uint8_t volatile fan_tacho_signals; // Fan half-revolutions per second
// uint16_t fan_rpm; // Fan rounds per minute
int16_t temperature_raw; // Temperature value from sensor
int8_t temperature; // Current temperature
int8_t temperature_1s_ago;
int8_t temperature_1m_ago;
// Timer1 Compare Match Interrupt ISR
// Call to ISR resets OCF1A flag in the TIFR register.
ISR(TIM1_COMPA_vect) {
timer1_irq_counter++;
}
void setupTimer0(void) {
// Set timer to Fast PWM mode (TOP = OCR0A)
// Non-inverted PWM
TCCR0A |= (1 << COM0B1) | (0 << COM0B0) | (1 << WGM01) | (1 << WGM00);
// Init counter variables
OCR0A = PWM_TOP;
OCR0B = PWM_BOTTOM; // Duty-Cycle
// Set prescaler to 8
// Formula for Fast PWM mode: F_CPU / (Prescaler * 256)
// 8,0 MHz: 8.000.000 / (8 * 40) = 25.000 Hz --> 25 kHz
TCCR0B |= (1 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00);
}
void setupTimer1(void) {
// Prescaler 4096
// 8.000.000 Hz / 4096 = 1953,125 Hz
// 1953,125 Hz / 217 = 9,000576 Compare Match Interrupts per second
// Clear timer after compare match with OCR1C(!)
TCCR1 |= (1 << CTC1) | (1 << CS13) | (1 << CS12) | (0 << CS11) | (1 << CS10);
TCNT1 = 0;
OCR1A = 217; // The match with OCR1A will fire the interrupt
OCR1C = OCR1A; // The match with OCR1C will reset the timer
TIMSK |= (1 << OCIE1A); // Enable compare match interrupt
// (There is no compare match interrupt for OCR1C and no CTC for OCR1A.)
}
int main(void) {
DDRB |= (1 << DD_PWM) | (1 << DD_LED); // Set to output
// PORTB |= (1 << PORT_BUTTON) | (1 << PORT_TACHO); // Enable pull-up
setupTimer0(); // PWM
setupTimer1(); // Timer for main loop
// setupTachoRead();
// Init temperature variables with current temperature
ds18b20convert(&PORTB, &DDRB, &PINB, (1 << PIN_DS18B20), NULL);
_delay_ms(1500);
if (ds18b20read(&PORTB, &DDRB, &PINB, (1 << PIN_DS18B20), NULL, &temperature_raw) == DS18B20_OK) {
temperature = ((temperature_raw + 8) / 16);
} else {
temperature = 40; // Set fail safe temperature if no/invalid value.
}
temperature_1s_ago = temperature;
temperature_1m_ago = temperature;
timer1_irq_counter = 0;
// fan_tacho_signals = 0;
seconds_counter = 0;
sei(); // Enable global interrupts
while(1) {
if (timer1_irq_counter >= 9) { // If one second has passed...
cli();
PORTB ^= (1 << PORT_LED); // Toggle LED (user feedback)
// fan_rpm = (fan_tacho_signals * 60) / 2;
// fan_tacho_signals = 0;
// Get temperature from sensor
static uint8_t conversion_wait_counter = 0;
if (conversion_wait_counter++ == 0) { // Start temperature conversion
ds18b20convert(&PORTB, &DDRB, &PINB, (1 << PIN_DS18B20), NULL);
} else if (conversion_wait_counter > 4) {
if (ds18b20read(&PORTB, &DDRB, &PINB, (1 << PIN_DS18B20), NULL, &temperature_raw) == DS18B20_OK) {
temperature = ((temperature_raw + 8) / 16);
} else {
temperature = 40; // Set fail safe temperature if no/invalid value.
}
conversion_wait_counter = 0;
}
// Temperature value calculation
int16_t tv = temperature_1m_ago * 15;
tv += temperature_1s_ago * 4;
tv += temperature;
tv = (tv + 5) / 20;
if (tv <= 23) {
OCR0B = PWM_BOTTOM; // Fan's slowest setting
} else if (tv == 24) {
OCR0B = 5;
} else if (tv == 25) {
OCR0B = 9;
} else if (tv == 26) {
OCR0B = 13;
} else if (tv == 27) {
OCR0B = 19;
} else if (tv == 28) {
OCR0B = 23;
} else if (tv == 29) {
OCR0B = 27;
} else if (tv == 30) {
OCR0B = 30;
} else if (tv == 31) {
OCR0B = 33;
} else if (tv == 34) {
OCR0B = 36;
} else {
OCR0B = PWM_TOP;
}
if (++seconds_counter >= 60) { // If ~60 seconds have passed...
seconds_counter = 0;
temperature_1m_ago = temperature;
}
temperature_1s_ago = temperature;
timer1_irq_counter = 0;
sei();
}
// If the button is pressed: Start a simple fan test
if (~PINB & (1 << PIN_BUTTON)) {
cli();
PORTB |= (1 << PORT_LED); // LED on
OCR0B = PWM_BOTTOM;
_delay_ms(5000);
for (uint8_t i = PWM_BOTTOM; i < PWM_TOP; i++) { // Spin up
OCR0B = i;
_delay_ms(500);
}
OCR0B = PWM_TOP;
_delay_ms(5000);
for (uint8_t i = PWM_TOP; i > PWM_BOTTOM; i--) { // Spin down
OCR0B = i;
_delay_ms(500);
}
PORTB &= ~(1 << PORT_LED); // LED off
sei();
}
}
}