From 1ecd5edd933ade8fe4a9b1f441bb7d1e91e87d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20H=C3=BCttel?= Date: Fri, 19 Mar 2021 20:19:37 +0100 Subject: [PATCH] Add temperature profile executer and add shell command --- stm-firmware/Makefile | 2 + .../config-parser/temp-profile-parser.c | 28 ++- .../include/reflow-controller/oven-driver.h | 2 - .../reflow-controller/temp-profile-executer.h | 52 +++++ stm-firmware/main.c | 5 +- stm-firmware/shell.c | 63 ++++- stm-firmware/temp-profile-executer.c | 216 ++++++++++++++++++ 7 files changed, 359 insertions(+), 9 deletions(-) create mode 100644 stm-firmware/include/reflow-controller/temp-profile-executer.h create mode 100644 stm-firmware/temp-profile-executer.c diff --git a/stm-firmware/Makefile b/stm-firmware/Makefile index 9fa520f..ba55e1d 100644 --- a/stm-firmware/Makefile +++ b/stm-firmware/Makefile @@ -51,6 +51,8 @@ CFILES += safety/safety-adc.c safety/safety-controller.c safety/watchdog.c safet CFILES += hw-version-detect.c CFILES += config-parser/config-parser.c config-parser/temp-profile-parser.c CFILES += updater/updater.c +CFILES += temp-profile-executer.c + INCLUDEPATH += -Iconfig-parser/include CFILES += base64-lib/src/base64-lib.c diff --git a/stm-firmware/config-parser/temp-profile-parser.c b/stm-firmware/config-parser/temp-profile-parser.c index 72b3145..c8b0670 100644 --- a/stm-firmware/config-parser/temp-profile-parser.c +++ b/stm-firmware/config-parser/temp-profile-parser.c @@ -108,27 +108,37 @@ static int parse_line(char *line, struct pl_command *cmd) const char * const delim = " \t"; const struct pl_command_list_map *map; char *endptr; + struct pl_command c; if (!line || !cmd) return -1000; token = strtok(line, delim); + + if (!token) { + /* Empty line or command line */ + return 1; + } + while (token && token_idx <= PROFILE_LANG_MAX_NUM_ARGS) { switch (token_idx) { case 0: map = string_to_command(token); - cmd->cmd = map->command; + c.cmd = map->command; break; default: if (!map) { /* No valid command found */ return -1; } - cmd->params[token_idx - 1] = strtof(token, &endptr); + c.params[token_idx - 1] = strtof(token, &endptr); if (endptr == token) { /* Invalid parameter */ return -2; } + if (token_idx > map->expected_param_count) + return -3; + break; } @@ -136,6 +146,12 @@ static int parse_line(char *line, struct pl_command *cmd) token_idx++; } + if (token_idx - 1 < map->expected_param_count) { + return -3; + } + + memcpy(cmd, &c, sizeof(struct pl_command)); + return 0; } @@ -180,12 +196,14 @@ enum pl_ret_val temp_profile_parse_from_file(const char *filename, /* Parse the line */ res = parse_line(workbuff, &cmd_list[cmd_idx]); - if (res) { + if (res < 0) { ret = PL_RET_SCRIPT_ERR; goto exit_close; + } else if (res == 0) { + cmd_idx++; + *cmds_parsed= cmd_idx; } - cmd_idx++; - *cmds_parsed= cmd_idx; + } while (!f_eof(&script_file)); diff --git a/stm-firmware/include/reflow-controller/oven-driver.h b/stm-firmware/include/reflow-controller/oven-driver.h index 5d56021..b3d5367 100644 --- a/stm-firmware/include/reflow-controller/oven-driver.h +++ b/stm-firmware/include/reflow-controller/oven-driver.h @@ -35,8 +35,6 @@ void oven_driver_set_power(uint8_t power); void oven_driver_disable(void); -void oven_pid_ack_errors(void); - void oven_pid_init(struct pid_controller *controller_to_copy); void oven_pid_handle(void); diff --git a/stm-firmware/include/reflow-controller/temp-profile-executer.h b/stm-firmware/include/reflow-controller/temp-profile-executer.h new file mode 100644 index 0000000..67ad3e3 --- /dev/null +++ b/stm-firmware/include/reflow-controller/temp-profile-executer.h @@ -0,0 +1,52 @@ +/* Reflow Oven Controller +* +* Copyright (C) 2021 Mario Hüttel +* +* This file is part of the Reflow Oven Controller Project. +* +* The reflow oven controller is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* The Reflow Oven Control Firmware is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with the reflow oven controller project. +* If not, see . +*/ + +#ifndef __TEMP_PROFILE_EXECUTER_H__ +#define __TEMP_PROFILE_EXECUTER_H__ + +#include +#include + +#define MAX_PROFILE_LENGTH 50 + +enum tpe_status { + TPE_OFF, + TPE_RUNNING, + TPE_ABORT, +}; + +struct tpe_current_state { + enum tpe_status status; + float setpoint; + uint64_t start_timestamp; + uint32_t step; + uint32_t profile_steps; + enum pl_command_type current_command; +}; + +enum pl_ret_val temp_profile_executer_start(const char *filename); + +int temp_profile_executer_handle(void); + +const struct tpe_current_state *temp_profile_executer_status(void); + +int temp_profile_executer_stop(void); + +#endif /* __TEMP_PROFILE_EXECUTER_H__ */ diff --git a/stm-firmware/main.c b/stm-firmware/main.c index dc1f805..1d04926 100644 --- a/stm-firmware/main.c +++ b/stm-firmware/main.c @@ -46,7 +46,7 @@ #include #include #include - +#include #include static void setup_nvic_priorities(void) @@ -253,6 +253,9 @@ int main(void) menu_wait_request = gui_handle(); handle_shell_uart_input(shell_handle); + /* Execute current profile step, if a profile is active */ + temp_profile_executer_handle(); + safety_controller_handle(); if (oven_pid_get_status() == OVEN_PID_RUNNING) { oven_pid_handle(); diff --git a/stm-firmware/shell.c b/stm-firmware/shell.c index 9405c97..b1132fb 100644 --- a/stm-firmware/shell.c +++ b/stm-firmware/shell.c @@ -41,6 +41,7 @@ #include #include #include +#include #ifndef GIT_VER #define GIT_VER "VERSION NOT SET" @@ -683,6 +684,58 @@ shellmatta_retCode_t shell_cmd_overtemp_cfg(const shellmatta_handle_t handle, co return ret; } +shellmatta_retCode_t shell_cmd_execute(const shellmatta_handle_t handle, const char *args, uint32_t len) +{ + enum pl_ret_val res; + const struct tpe_current_state *state; + static bool running = false; + char *data; + uint32_t dlen; + + shellmatta_read(handle, &data, &dlen); + + if (!running) { + res = temp_profile_executer_start("profile.tpr"); + + if (res != PL_RET_SUCCESS) { + switch (res) { + case PL_RET_DISK_ERR: + shellmatta_printf(handle, "Error reading file\r\n"); + break; + case PL_RET_LIST_FULL: + shellmatta_printf(handle, "Script too long!\r\n"); + break; + case PL_RET_SCRIPT_ERR: + shellmatta_printf(handle, "Error in script\r\n"); + break; + default: + shellmatta_printf(handle, "Unspecified error occured\r\n"); + break; + } + + return SHELLMATTA_ERROR; + } + running = true; + } else { + state = temp_profile_executer_status(); + if (state->status != TPE_RUNNING) { + shellmatta_printf(handle, "Profile executed.\r\n"); + running = false; + if(state->status == TPE_ABORT) { + shellmatta_printf(handle, "Profile execution aborted!\r\n"); + } + return SHELLMATTA_OK; + } + + if (dlen > 0 && *data == '\x03') { + temp_profile_executer_stop(); + return SHELLMATTA_OK; + } + } + + return SHELLMATTA_CONTINUE; +} + //typedef struct shellmatta_cmd //{ // char *cmd; /**< command name */ @@ -692,7 +745,7 @@ shellmatta_retCode_t shell_cmd_overtemp_cfg(const shellmatta_handle_t handle, co // shellmatta_cmdFct_t cmdFct; /**< pointer to the cmd callack function */ // struct shellmatta_cmd *next; /**< pointer to next command or NULL */ //} shellmatta_cmd_t; -static shellmatta_cmd_t cmd[20] = { +static shellmatta_cmd_t cmd[21] = { { .cmd = "version", .cmdAlias = "ver", @@ -852,6 +905,14 @@ static shellmatta_cmd_t cmd[20] = { .helpText = "Overtemperature Config", .usageText = "", .cmdFct = shell_cmd_overtemp_cfg, + .next = &cmd[20], + }, + { + .cmd = "execute", + .cmdAlias = NULL, + .helpText = "Execute Temp Profile", + .usageText = "", + .cmdFct = shell_cmd_execute, .next = NULL, } }; diff --git a/stm-firmware/temp-profile-executer.c b/stm-firmware/temp-profile-executer.c new file mode 100644 index 0000000..d812602 --- /dev/null +++ b/stm-firmware/temp-profile-executer.c @@ -0,0 +1,216 @@ +/* Reflow Oven Controller +* +* Copyright (C) 2021 Mario Hüttel +* +* This file is part of the Reflow Oven Controller Project. +* +* The reflow oven controller is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* The Reflow Oven Control Firmware is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with the reflow oven controller project. +* If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +static struct tpe_current_state IN_SECTION(.ccm.data) state = { + .status = TPE_OFF, + .start_timestamp = 0, +}; +static bool IN_SECTION(.ccm.bss) pid_should_run; +struct pid_controller IN_SECTION(.ccm.bss) pid; + +static struct pl_command IN_SECTION(.ccm.bss) cmd_list[MAX_PROFILE_LENGTH]; + +static void abort(void) +{ + temp_profile_executer_stop(); + state.status = TPE_ABORT; +} + +enum pl_ret_val temp_profile_executer_start(const char *filename) +{ + uint32_t parsed_count = 0; + enum pl_ret_val res; + + state.setpoint = 0.0f; + state.start_timestamp = 0ULL; + state.setpoint = 0.0f; + state.step = 0; + state.profile_steps = 0; + + oven_pid_stop(); + pid_should_run = false; + + res = temp_profile_parse_from_file(filename, cmd_list, MAX_PROFILE_LENGTH, &parsed_count); + if (res == PL_RET_SUCCESS) { + state.profile_steps = parsed_count; + state.status = TPE_RUNNING; + state.start_timestamp = systick_get_global_tick(); + } + + return res; +} + +static bool cmd_wait_temp(struct pl_command *cmd, bool cmd_continue) +{ + (void)cmd_continue; + float resistance; + int res; + float temp; + + res = adc_pt1000_get_current_resistance(&resistance); + if (res < 0) { + abort(); + return false; + } + + (void)temp_converter_convert_resistance_to_temp(resistance, &temp); + + if (ABS(cmd->params[0] - temp) < 3.0f) + return true; + + return false; +} + +static bool cmd_wait_time(struct pl_command *cmd, bool cmd_continue) +{ + static uint64_t temp_tick = 0UL; + + if (cmd_continue) { + if (systick_ticks_have_passed(temp_tick, + (uint64_t)(cmd->params[0] * 1000.0f))) + return true; + } else { + temp_tick = systick_get_global_tick(); + } + + return false; +} + +static void reactivate_pid_if_suspended(void) +{ + if (oven_pid_get_status() == OVEN_PID_DEACTIVATED) + oven_pid_init(&pid); + + pid_should_run = true; +} + +static bool cmd_set_temp(struct pl_command *cmd, bool cmd_continue) +{ + (void)cmd_continue; + + reactivate_pid_if_suspended(); + oven_pid_set_target_temperature(cmd->params[0]); + return true; +} + +int temp_profile_executer_handle(void) +{ + struct pl_command *current_cmd; + static uint64_t last_tick = 0UL; + bool advance; + static bool cmd_continue = false; + uint32_t next_step; + + + if (state.status != TPE_RUNNING) + return -1; + if (oven_pid_get_status() == OVEN_PID_ABORTED && pid_should_run) { + abort(); + oven_pid_stop(); + return -1; + } + + if (systick_ticks_have_passed(last_tick, 100)) { + current_cmd = &cmd_list[state.step]; + next_step = state.step; + + switch (current_cmd->cmd) { + case PL_WAIT_FOR_TIME: + advance = cmd_wait_time(current_cmd, cmd_continue); + break; + case PL_WAIT_FOR_TEMP: + advance = cmd_wait_temp(current_cmd, cmd_continue); + break; + case PL_SET_TEMP: + advance = cmd_set_temp(current_cmd, cmd_continue); + break; + case PL_LOUDSPEAKER_SET: + loudspeaker_set((uint16_t)current_cmd->params[0]); + advance = true; + break; + case PL_OFF: + oven_pid_stop(); + pid_should_run = false; + advance = true; + break; + case PL_PID_CONF: + pid_init(&pid, current_cmd->params[0], /* Kd */ + current_cmd->params[1], /* Ki */ + current_cmd->params[2], /* Kp */ + 0.0f, 0.0f, + current_cmd->params[3], /* Int max */ + current_cmd->params[4], /* Kd tau */ + current_cmd->params[5]); /* Period */ + oven_pid_init(&pid); + advance = true; + pid_should_run = true; + break; + case PL_SET_RAMP: + advance = true; + break; + default: + abort(); + advance = true; + break; + } + + if (advance) + next_step++; + + if (next_step != state.step) { + state.step = next_step; + if (next_step >= state.profile_steps) { + (void)temp_profile_executer_stop(); + } else { + cmd_continue = false; + } + } else { + cmd_continue = true; + } + last_tick = systick_get_global_tick(); + } + + return 0; +} + +const struct tpe_current_state *temp_profile_executer_status(void) +{ + return &state; +} + +int temp_profile_executer_stop(void) +{ + if (state.status == TPE_RUNNING) { + state.status = TPE_OFF; + oven_pid_stop(); + } + + loudspeaker_set(0); + + return 0; +}