|
|
|
#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"
|
|
|
|
|
|
|
|
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();
|
|
|
|
PID_SetTunings(&state.pid, p, i, d);
|
|
|
|
heaterExitCritical();
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
|
|
|
heater_pwm_init();
|
|
|
|
|
|
|
|
heaterEnterCritical();
|
|
|
|
// TODO load from flash
|
|
|
|
PID_SetTunings(&state.pid, state.tuning_p, state.tuning_i, state.tuning_d);
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|