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.
232 lines
6.4 KiB
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));
|
|
}
|
|
}
|
|
|