Implement control loop

This commit is contained in:
Thomas Kolb 2023-09-29 19:55:51 +02:00
parent 82d370bf83
commit f5756a4f64
4 changed files with 184 additions and 5 deletions

131
src/fan_controller.c Normal file
View file

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

15
src/fan_controller.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef FAN_CONTROLLER_H
#define FAN_CONTROLLER_H
#include <stdint.h>
#include <fxp.h>
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

View file

@ -3,8 +3,15 @@
#include <libopencmsis/core_cm3.h>
#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;
}

View file

@ -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;
}