You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
toaster-oven-bluepill/Core/Src/app_heater.c

232 lines
6.4 KiB

#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "app_temp.h"
#include "app_pid.h"
#include "app_heater.h"
#include "cmsis_os2.h"
#include "tim.h"
#include "queue.h"
#include "app_safety.h"
#include "Gui/gui_event.h"
#include "eeprom_emul.h"
#include "ee_addresses.h"
#include "transmute.h"
extern osMutexId_t heaterMutexHandle;
static void heater_pwm_init()
{
LL_TIM_OC_SetCompareCH1(TIM_HEATER, 0); // Off
LL_TIM_EnableCounter(TIM_HEATER);
LL_TIM_CC_EnableChannel(TIM_HEATER, LL_TIM_CHANNEL_CH1);
}
static void heater_pwm_set_perc(float perc)
{
uint16_t perc_u = (uint16_t) perc;
if (perc_u > 100) {
perc_u = 100;
}
// (TIM3->ARR / 100)
LL_TIM_OC_SetCompareCH1(TIM_HEATER, 640 * perc_u);
// TIM3->CCR1 = 640 * perc_u;
}
static struct {
float oven_temp;
float soc_temp;
// these will be loaded from flash and stored back
float tuning_p;
float tuning_i;
float tuning_d;
/// Manual PWM override (percent, -1 = override disable)
int manual_override;
// PID state
struct PID pid;
} state = {
.tuning_p = 10.0f,
.tuning_i = 0.052f,
.tuning_d = 100.0f,
.manual_override = -1,
.pid = {
.SampleTimeTicks = pdMS_TO_TICKS(1000),
.outMax = 100.0f,
.outMin = 0.0f,
.ctlMode = PID_MANUAL,
.controllerDirection = PID_DIRECT,
},
};
static inline void heaterEnterCritical() {
osMutexAcquire(heaterMutexHandle, portMAX_DELAY);
}
static inline void heaterExitCritical() {
osMutexRelease(heaterMutexHandle);
}
void app_heater_manual_override(int percent) {
PRINTF("Set manual override: %d\r\n", percent);
heaterEnterCritical();
PID_SetCtlMode(&state.pid, PID_MANUAL);
state.manual_override = percent;
heaterExitCritical();
}
void app_heater_manual_override_clear() {
app_heater_manual_override(-1);
}
void app_heater_set_tuning(float p, float i, float d) {
heaterEnterCritical();
state.tuning_p = p;
state.tuning_i = i;
state.tuning_d = d;
PID_SetTunings(&state.pid, p, i, d);
heaterExitCritical();
}
void app_heater_get_tuning(float *p, float *i, float *d) {
if (!p || !i || !d) return; // fail
*p = state.tuning_p;
*i = state.tuning_i;
*d = state.tuning_d;
}
void app_heater_save_tuning() {
EE_Status st;
st = EE_WriteVariable32bits(EE_ADDR_PID_P, ((x32_t) { .f = state.tuning_p }).u);
if (st == EE_CLEANUP_REQUIRED) {
EE_CleanUp();
} else if (st != EE_OK) {
PRINTF("EE write err %d!\r\n", st);
}
st = EE_WriteVariable32bits(EE_ADDR_PID_I, ((x32_t) { .f = state.tuning_i }).u);
if (st == EE_CLEANUP_REQUIRED) {
EE_CleanUp();
} else if (st != EE_OK) {
PRINTF("EE write err %d!\r\n", st);
}
st = EE_WriteVariable32bits(EE_ADDR_PID_D, ((x32_t) { .f = state.tuning_d }).u);
if (st == EE_CLEANUP_REQUIRED) {
EE_CleanUp();
} else if (st != EE_OK) {
PRINTF("EE write err %d!\r\n", st);
}
}
void app_heater_enable(bool enable) {
PRINTF("Set heater enabled = %d\r\n", (int) enable);
heaterEnterCritical();
PID_SetCtlMode(&state.pid, enable ? PID_AUTOMATIC : PID_MANUAL);
heaterExitCritical();
}
void app_heater_set_target(float target) {
PRINTF("Set heater target = %d\r\n", (int) target);
heaterEnterCritical();
PID_SetSetpoint(&state.pid, target);
heaterExitCritical();
}
bool app_heater_get_state() {
return state.pid.ctlMode == PID_AUTOMATIC;
}
float app_heater_get_percent() {
return state.pid.Output;
}
float app_heater_get_target() {
return state.pid.Setpoint;
}
// emergency shutdown, this must not block use RTOS since it can be called from fault handlers or interrupt
void app_heater_emergency_shutdown() {
// Stop pwm
LL_TIM_OC_SetCompareCH1(TIM_HEATER, 0);
LL_TIM_CC_DisableChannel(TIM_HEATER, LL_TIM_CHANNEL_CH1);
LL_TIM_DisableCounter(TIM_HEATER);
// Also kill the GPIO PWM output
LL_GPIO_InitTypeDef GPIO_InitStruct = {};
GPIO_InitStruct.Pin = PWM_HEATER_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(PWM_HEATER_GPIO_Port, &GPIO_InitStruct);
// Output zero
LL_GPIO_ResetOutputPin(PWM_HEATER_GPIO_Port, PWM_HEATER_Pin);
}
void app_task_heater(void *argument)
{
// Wait until inited
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
PUTS("Heater task starts\r\n");
uint32_t c = 0;
if (EE_OK == EE_ReadVariable32bits(EE_ADDR_PID_P, &c)) {
state.tuning_p = ((x32_t) { .u = c }).f;
PRINTF("Loaded Kp = %f\r\n", state.tuning_p);
}
if (EE_OK == EE_ReadVariable32bits(EE_ADDR_PID_I, &c)) {
state.tuning_i = ((x32_t) { .u = c }).f;
PRINTF("Loaded Ki = %f\r\n", state.tuning_i);
}
if (EE_OK == EE_ReadVariable32bits(EE_ADDR_PID_D, &c)) {
state.tuning_d = ((x32_t) { .u = c }).f;
PRINTF("Loaded Kd = %f\r\n", state.tuning_d);
}
heater_pwm_init();
heaterEnterCritical();
// TODO load from flash
PID_SetTunings(&state.pid, state.tuning_p, state.tuning_i, state.tuning_d);
PID_SetOutputLimits(&state.pid, 0, 100);
PID_SetITermLimits(&state.pid, 0, 100);
PID_Initialize(&state.pid);
heaterExitCritical();
uint32_t wake_time = xTaskGetTickCount();
while (1) {
app_temp_sample();
state.oven_temp = app_temp_read_oven();
state.soc_temp = app_temp_read_soc();
enum GuiEvent ev = GUI_EVENT_TEMP_CHANGE;
xQueueSend(guiEventQueHandle, &ev, pdMS_TO_TICKS(100));
heaterEnterCritical();
app_safety_pass_reg_loop_running();
if (state.manual_override >= 0 && state.manual_override <= 100) {
PRINTF("manual override %d%%\r\n", state.manual_override);
heater_pwm_set_perc(state.manual_override);
} else {
PID_Compute(&state.pid, state.oven_temp);
if (state.pid.ctlMode == PID_AUTOMATIC) {
PRINTF("temp %d, output %d\r\n", (int) state.oven_temp, (int) state.pid.Output);
heater_pwm_set_perc(state.pid.Output);
} else {
// turn it off
heater_pwm_set_perc(0);
}
}
heaterExitCritical();
// TODO notify UI thread of the new temperature and heating percent
vTaskDelayUntil(&wake_time, pdMS_TO_TICKS(100));
}
}