TinyFanControl-Firmware/src/fan_controller.c

132 lines
3.9 KiB
C
Raw Normal View History

2023-09-29 19:55:51 +02:00
#include "fan_controller.h"
/*
* The control loop is based on a temperature corridor concept. While the
* temperature is inside the corridor, the PWM duty cycle will not be changed.
*
* If it is above the corridors maximum temperature, the duty cycle increases
* as defined by a PI controller, i.e. there is a dynamic/temporary part (P)
* that depends on how far outside the corridor the temperature is, and there
* is a persistent part (I) that is continuously increased while the
* temperature is outside the corridor.
*
* If the temperature is below the corridors minimum temperature, similar
* rules are applied to decrease the fan speed. However, the P part makes
* little sense here so its gain is usually set to 0.
*
* The fan starts in a stopped state and stays there until the temperature
* rises above the startup temperature. Once that happens, the PWM is set
* to a specific duty cycle (high enough to reliably start the fan) and
* regulation begins.
*/
/* Note:
* - all temperatures are in °C represented in fxp_t.
* - all duty cycles are in percent represented in fxp_t.
*/
// corridor definition
#define CORRIDOR_MIN_TEMPERATURE (40 << POINTPOS)
#define CORRIDOR_MAX_TEMPERATURE (45 << POINTPOS)
// The fan is started when temperature rises above this temperature.
#define FAN_START_TEMPERATURE (42 << POINTPOS)
// The fan is stopped when the temperature falls below this point.
#define FAN_OFF_TEMPERATURE (30 << POINTPOS)
// Emergency temperature level. If this is reached, the duty cycle is directly
// set to the maximum to skip a long ramp-up phase.
#define EMERGENCY_TEMPERATURE (60 << POINTPOS)
// The fan PWM duty cycle cannot fall below this limit. It is held until
// temperature falls below the off-temperature.
#define DUTY_CYCLE_MIN (5 << POINTPOS)
// The fan PWM duty cycle cannot go above this limit. Should be set to 100.
#define DUTY_CYCLE_MAX (100 << POINTPOS)
// The fan is always started with this duty cycle. Do not set too low.
#define DUTY_CYCLE_START (20 << POINTPOS)
// gain unit: percent duty cycle change per °C deviation per update cycle
#define GAIN_T_HIGH_P 0x00020000 // 2.000
#define GAIN_T_HIGH_I 0x00002000 // 0.125
#define GAIN_T_LOW_P 0x00000000 // 0.000
#define GAIN_T_LOW_I 0x00002000 // 0.125
// integer unit ^
static fxp_t m_duty;
void fan_controller_init(void)
{
m_duty = 0; // fan off by default
}
uint8_t fan_controller_update(fxp_t temperature)
{
fxp_t duty_out = m_duty;
// check emergency case. Directly starts the fan, if necessary.
if(temperature > EMERGENCY_TEMPERATURE) {
m_duty = duty_out = DUTY_CYCLE_MAX;
}
if(m_duty == 0) {
// we are currently in off state. Check whether we need to start.
if(temperature > FAN_START_TEMPERATURE) {
m_duty = duty_out = DUTY_CYCLE_START;
}
} else {
// fan is currently on
// corridor handling: too warm
if(temperature > CORRIDOR_MAX_TEMPERATURE) {
fxp_t delta_above = fxp_sub(temperature, CORRIDOR_MAX_TEMPERATURE);
fxp_t duty_p = fxp_mult(delta_above, GAIN_T_HIGH_P);
fxp_t duty_i = fxp_mult(delta_above, GAIN_T_HIGH_I);
m_duty = fxp_add(m_duty, duty_i);
if(m_duty > DUTY_CYCLE_MAX) {
m_duty = DUTY_CYCLE_MAX;
}
duty_out = fxp_add(m_duty, duty_p);
if(duty_out > DUTY_CYCLE_MAX) {
duty_out = DUTY_CYCLE_MAX;
}
}
// corridor handling: too cold
if(temperature < CORRIDOR_MIN_TEMPERATURE) {
fxp_t delta_below = fxp_sub(temperature, CORRIDOR_MIN_TEMPERATURE);
fxp_t duty_p = fxp_mult(delta_below, GAIN_T_LOW_P);
fxp_t duty_i = fxp_mult(delta_below, GAIN_T_LOW_I);
m_duty = fxp_add(m_duty, duty_i);
if(m_duty < DUTY_CYCLE_MIN) {
m_duty = DUTY_CYCLE_MIN;
}
duty_out = fxp_add(m_duty, duty_p);
if(duty_out < DUTY_CYCLE_MIN) {
duty_out = DUTY_CYCLE_MIN;
}
}
// fan stop handling
if(temperature < FAN_OFF_TEMPERATURE) {
m_duty = duty_out = 0;
}
}
return FXP_TO_INT(duty_out);
}