2023-09-29 19:55:51 +02:00
|
|
|
|
#include "fan_controller.h"
|
|
|
|
|
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#include "eeprom_config.h"
|
|
|
|
|
|
2023-09-29 19:55:51 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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 corridor’s 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 corridor’s 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
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define CORRIDOR_MIN_TEMPERATURE EEPROM_CONFIG_CORRIDOR_MIN_TEMPERATURE
|
|
|
|
|
#define CORRIDOR_MAX_TEMPERATURE EEPROM_CONFIG_CORRIDOR_MAX_TEMPERATURE
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// The fan is started when temperature rises above this temperature.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define FAN_START_TEMPERATURE EEPROM_CONFIG_FAN_START_TEMPERATURE
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// The fan is stopped when the temperature falls below this point.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define FAN_OFF_TEMPERATURE EEPROM_CONFIG_FAN_OFF_TEMPERATURE
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// Emergency temperature level. If this is reached, the duty cycle is directly
|
|
|
|
|
// set to the maximum to skip a long ramp-up phase.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define EMERGENCY_TEMPERATURE EEPROM_CONFIG_EMERGENCY_TEMPERATURE
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// The fan PWM duty cycle cannot fall below this limit. It is held until
|
|
|
|
|
// temperature falls below the off-temperature.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define DUTY_CYCLE_MIN EEPROM_CONFIG_DUTY_CYCLE_MIN
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// The fan PWM duty cycle cannot go above this limit. Should be set to 100.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define DUTY_CYCLE_MAX EEPROM_CONFIG_DUTY_CYCLE_MAX
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// The fan is always started with this duty cycle. Do not set too low.
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define DUTY_CYCLE_START EEPROM_CONFIG_DUTY_CYCLE_START
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
// gain unit: percent duty cycle change per °C deviation per update cycle
|
2023-09-30 21:08:54 +02:00
|
|
|
|
#define GAIN_T_HIGH_P EEPROM_CONFIG_GAIN_T_HIGH_P
|
|
|
|
|
#define GAIN_T_HIGH_I EEPROM_CONFIG_GAIN_T_HIGH_I
|
|
|
|
|
#define GAIN_T_LOW_P EEPROM_CONFIG_GAIN_T_LOW_P
|
|
|
|
|
#define GAIN_T_LOW_I EEPROM_CONFIG_GAIN_T_LOW_I
|
2023-09-29 19:55:51 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|