Implement control loop
This commit is contained in:
parent
82d370bf83
commit
f5756a4f64
131
src/fan_controller.c
Normal file
131
src/fan_controller.c
Normal 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 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);
|
||||||
|
}
|
15
src/fan_controller.h
Normal file
15
src/fan_controller.h
Normal 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
|
38
src/main.c
38
src/main.c
|
@ -3,8 +3,15 @@
|
||||||
#include <libopencmsis/core_cm3.h>
|
#include <libopencmsis/core_cm3.h>
|
||||||
|
|
||||||
#include "uart.h"
|
#include "uart.h"
|
||||||
|
#include "temp_sensor.h"
|
||||||
#include "fan_ctrl_pwm.h"
|
#include "fan_ctrl_pwm.h"
|
||||||
#include "fan_ctrl_dc.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)
|
static void clock_setup(void)
|
||||||
{
|
{
|
||||||
|
@ -31,8 +38,6 @@ static void init_systick(int freq)
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
uint64_t timebase_ms = 0;
|
|
||||||
|
|
||||||
// delay startup a bit to allow debugger to connect before debug pins are
|
// delay startup a bit to allow debugger to connect before debug pins are
|
||||||
// reconfigured to UART.
|
// reconfigured to UART.
|
||||||
for(uint32_t i = 0; i < 1000000; i++) {
|
for(uint32_t i = 0; i < 1000000; i++) {
|
||||||
|
@ -40,16 +45,41 @@ int main(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
clock_setup();
|
clock_setup();
|
||||||
init_systick(1000);
|
init_systick(SYSTICK_FREQ);
|
||||||
|
|
||||||
uart_init();
|
uart_init();
|
||||||
|
temp_sensor_init();
|
||||||
|
|
||||||
fan_ctrl_pwm_init();
|
fan_ctrl_pwm_init();
|
||||||
fan_ctrl_dc_init();
|
fan_ctrl_dc_init();
|
||||||
|
|
||||||
|
fan_controller_init();
|
||||||
|
|
||||||
uart_enqueue("TinyFanControl v" VERSION " initialized.\n");
|
uart_enqueue("TinyFanControl v" VERSION " initialized.\n");
|
||||||
|
|
||||||
|
fan_ctrl_pwm_enable(); // TODO: make DC/PWM configurable
|
||||||
|
|
||||||
// triggered every 1 ms
|
// triggered every 1 ms
|
||||||
while (1) {
|
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();
|
__WFI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +90,7 @@ int main(void)
|
||||||
/* Called when systick fires */
|
/* Called when systick fires */
|
||||||
void sys_tick_handler(void)
|
void sys_tick_handler(void)
|
||||||
{
|
{
|
||||||
//wait_frame = 0;
|
systick_triggered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,8 @@ bool temp_sensor_has_new_values(void)
|
||||||
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL1, DMA_TCIF)) {
|
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL1, DMA_TCIF)) {
|
||||||
dma_clear_interrupt_flags(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]);
|
m_temperature_internal = calc_temperature(adc_values[1]);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,9 +148,11 @@ bool temp_sensor_has_new_values(void)
|
||||||
|
|
||||||
fxp_t temp_sensor_get_ntc_temperature(void)
|
fxp_t temp_sensor_get_ntc_temperature(void)
|
||||||
{
|
{
|
||||||
|
return m_temperature_ntc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fxp_t temp_sensor_get_internal_temperature(void)
|
fxp_t temp_sensor_get_internal_temperature(void)
|
||||||
{
|
{
|
||||||
|
return m_temperature_internal;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue