From f5756a4f64bb8a55ec81f304d1630fa42634265f Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Fri, 29 Sep 2023 19:55:51 +0200 Subject: [PATCH] Implement control loop --- src/fan_controller.c | 131 +++++++++++++++++++++++++++++++++++++++++++ src/fan_controller.h | 15 +++++ src/main.c | 38 +++++++++++-- src/temp_sensor.c | 5 +- 4 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 src/fan_controller.c create mode 100644 src/fan_controller.h diff --git a/src/fan_controller.c b/src/fan_controller.c new file mode 100644 index 0000000..bf7990c --- /dev/null +++ b/src/fan_controller.c @@ -0,0 +1,131 @@ +#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); +} diff --git a/src/fan_controller.h b/src/fan_controller.h new file mode 100644 index 0000000..94e1c69 --- /dev/null +++ b/src/fan_controller.h @@ -0,0 +1,15 @@ +#ifndef FAN_CONTROLLER_H +#define FAN_CONTROLLER_H + +#include +#include + +void fan_controller_init(void); + +/**@brief Run one iteration of the control loop. + * @param temperature The newly measured temperature in °C. + * @returns The calculated duty cycle. + */ +uint8_t fan_controller_update(fxp_t temperature); + +#endif // FAN_CONTROLLER_H diff --git a/src/main.c b/src/main.c index 64ab14a..ebe55d9 100644 --- a/src/main.c +++ b/src/main.c @@ -3,8 +3,15 @@ #include #include "uart.h" +#include "temp_sensor.h" #include "fan_ctrl_pwm.h" #include "fan_ctrl_dc.h" +#include "fan_controller.h" + +#define SYSTICK_FREQ 10 // Hz + +static uint64_t timebase_ms = 0; +static bool systick_triggered = false; static void clock_setup(void) { @@ -31,8 +38,6 @@ static void init_systick(int freq) int main(void) { - uint64_t timebase_ms = 0; - // delay startup a bit to allow debugger to connect before debug pins are // reconfigured to UART. for(uint32_t i = 0; i < 1000000; i++) { @@ -40,16 +45,41 @@ int main(void) } clock_setup(); - init_systick(1000); + init_systick(SYSTICK_FREQ); uart_init(); + temp_sensor_init(); + fan_ctrl_pwm_init(); fan_ctrl_dc_init(); + fan_controller_init(); + uart_enqueue("TinyFanControl v" VERSION " initialized.\n"); + fan_ctrl_pwm_enable(); // TODO: make DC/PWM configurable + // triggered every 1 ms while (1) { + if(systick_triggered) { + systick_triggered = false; + + timebase_ms += 1000 / SYSTICK_FREQ; + + // start the measurement in the background. + temp_sensor_trigger_update(); + } + + // check for completion of a measurement + if(temp_sensor_has_new_values()) { + // if a temperature update is available, run the control loop. + fxp_t temperature = temp_sensor_get_ntc_temperature(); + + uint8_t duty = fan_controller_update(temperature); + + fan_ctrl_pwm_set_duty(duty); + } + __WFI(); } @@ -60,7 +90,7 @@ int main(void) /* Called when systick fires */ void sys_tick_handler(void) { - //wait_frame = 0; + systick_triggered = true; } diff --git a/src/temp_sensor.c b/src/temp_sensor.c index 49777ed..2990ef7 100644 --- a/src/temp_sensor.c +++ b/src/temp_sensor.c @@ -136,7 +136,8 @@ bool temp_sensor_has_new_values(void) if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL1, DMA_TCIF)) { dma_clear_interrupt_flags(DMA1, DMA_CHANNEL1, DMA_TCIF); - // TODO: calculate temperature values and cache them here. + // calculate temperature values and cache them. + m_temperature_ntc = calc_temperature_ntc(adc_values[0]); m_temperature_internal = calc_temperature(adc_values[1]); return true; } else { @@ -147,9 +148,11 @@ bool temp_sensor_has_new_values(void) fxp_t temp_sensor_get_ntc_temperature(void) { + return m_temperature_ntc; } fxp_t temp_sensor_get_internal_temperature(void) { + return m_temperature_internal; }