#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 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 #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); }