/* Reflow Oven Controller * * Copyright (C) 2020 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 . */ /** * @addtogroup safety-controller * @{ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define check_flag_persistent(flag) ((flag)->persistency && (flag)->persistency->persistency) #define get_flag_weight(flag) ((flag)->weight ? ((flag)->weight->weight) : SAFETY_FLAG_CONFIG_WEIGHT_NONE) struct safety_weight { uint32_t start_dummy; enum config_weight weight; enum safety_flag flag; volatile struct error_flag *flag_ptr; uint32_t end_dummy; }; struct safety_persistency { uint32_t start_dummy; bool persistency; enum safety_flag flag; volatile struct error_flag *flag_ptr; uint32_t end_dummy; }; struct error_flag { const char *name; enum safety_flag flag; bool error_state; bool error_state_inv; volatile struct safety_persistency *persistency; volatile struct safety_weight *weight; uint32_t key; }; struct timing_mon { const char *name; enum timing_monitor monitor; enum safety_flag associated_flag; uint64_t min_delta; uint64_t max_delta; uint64_t last; uint64_t calculated_delta; bool enabled; }; struct analog_mon { const char *name; enum analog_value_monitor monitor; enum safety_flag associated_flag; float min; float max; float value; bool valid; uint64_t timestamp; }; static volatile struct error_flag IN_SECTION(.ccm.data) flags[] = { ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OFF), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MEAS_ADC), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_PID), ERR_FLAG_ENTRY(ERR_FLAG_AMON_UC_TEMP), ERR_FLAG_ENTRY(ERR_FLAG_AMON_VREF), ERR_FLAG_ENTRY(ERR_FLAG_STACK), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_ADC), ERR_FLAG_ENTRY(ERR_FLAG_SYSTICK), ERR_FLAG_ENTRY(ERR_FLAG_WTCHDG_FIRED), ERR_FLAG_ENTRY(ERR_FLAG_UNCAL), ERR_FLAG_ENTRY(ERR_FLAG_DEBUG), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT), ERR_FLAG_ENTRY(ERR_FLAG_AMON_SUPPLY_VOLT), }; static volatile struct timing_mon IN_SECTION(.ccm.data) timings[] = { TIM_MON_ENTRY(ERR_TIMING_PID, 2, 5000, ERR_FLAG_TIMING_PID), TIM_MON_ENTRY(ERR_TIMING_MEAS_ADC, 0, 50, ERR_FLAG_TIMING_MEAS_ADC), TIM_MON_ENTRY(ERR_TIMING_SAFETY_ADC, 10, SAFETY_CONTROLLER_ADC_DELAY_MS + 1000, ERR_FLAG_SAFETY_ADC), TIM_MON_ENTRY(ERR_TIMING_MAIN_LOOP, 0, 1000, ERR_FLAG_TIMING_MAIN_LOOP), }; static volatile struct analog_mon IN_SECTION(.ccm.data) analog_mons[] = { ANA_MON_ENTRY(ERR_AMON_VREF, SAFETY_ADC_VREF_MVOLT - SAFETY_ADC_VREF_TOL_MVOLT, SAFETY_ADC_VREF_MVOLT + SAFETY_ADC_VREF_TOL_MVOLT, ERR_FLAG_AMON_VREF), ANA_MON_ENTRY(ERR_AMON_UC_TEMP, SAFETY_ADC_TEMP_LOW_LIM, SAFETY_ADC_TEMP_HIGH_LIM, ERR_FLAG_AMON_UC_TEMP), ANA_MON_ENTRY(ERR_AMON_SUPPLY_VOLT, SAFETY_ADC_SUPPLY_MVOLT - SAFETY_ADC_SUPPLY_TOL_MVOLT, SAFETY_ADC_SUPPLY_MVOLT + SAFETY_ADC_SUPPLY_TOL_MVOLT, ERR_FLAG_AMON_SUPPLY_VOLT), }; static const struct safety_weight default_flag_weights[] = { SAFETY_CONFIG_DEFAULT_WEIGHTS }; static const struct safety_persistency default_flag_persistencies[] = {SAFETY_CONFIG_DEFAULT_PERSIST}; static volatile struct safety_persistency IN_SECTION(.ccm.bss) flag_persistencies[COUNT_OF(default_flag_persistencies)]; static uint32_t IN_SECTION(.ccm.bss) flag_persistencies_crc; static volatile struct safety_weight IN_SECTION(.ccm.bss) flag_weights[COUNT_OF(default_flag_weights)]; static uint32_t IN_SECTION(.ccm.bss) flag_weight_crc; static uint8_t flag_enum_to_flag_no(enum safety_flag flag) { uint32_t flag_mask; uint8_t i; if (!is_power_of_two(flag)) return 0xFF; flag_mask = (uint32_t)flag; for (i = 0; i < 32; i++) { if ((flag_mask >> i) & 0x1U) break; } return i; } static enum safety_flag flag_no_to_flag_enum(uint8_t no) { if (no >= COUNT_OF(flags)) return ERR_FLAG_NO_FLAG; return (1U << no); } static int flag_weight_table_crc_check(void) { /* Check the flag weight table */ crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_weights, wordsize_of(flag_weights)); if (crc_unit_get_crc() != flag_weight_crc) return -1; return 0; } static int flag_persistency_table_crc_check(void) { crc_unit_reset(); crc_unit_input_array((uint32_t*)flag_persistencies, wordsize_of(flag_persistencies)); if (crc_unit_get_crc() != flag_persistencies_crc) return -1; return 0; } static volatile struct error_flag *find_error_flag(enum safety_flag flag) { uint32_t i; volatile struct error_flag *ret = NULL; for (i = 0; i < COUNT_OF(flags); i++) { if (flags[i].flag == flag) ret = &flags[i]; } return ret; } /** * @brief This function copies the safety weigths from flash ro RAM and computes the CRC */ static void init_safety_flag_weight_table_from_default(void) { uint32_t index; volatile struct safety_weight *current_weight; /* Copy the table */ memcpy((void *)flag_weights, default_flag_weights, sizeof(flag_weights)); /* Fill in the flag pointers */ for (index = 0; index < COUNT_OF(flag_weights); index++) { current_weight = &flag_weights[index]; current_weight->flag_ptr = find_error_flag(current_weight->flag); if (current_weight->flag_ptr) current_weight->flag_ptr->weight = current_weight; } crc_unit_reset(); crc_unit_input_array((uint32_t*)flag_weights, wordsize_of(flag_weights)); flag_weight_crc = crc_unit_get_crc(); } static void init_safety_flag_persistencies_from_default(void) { uint32_t index; volatile struct safety_persistency *current_persistency; /* Copy values */ memcpy((void *)flag_persistencies, default_flag_persistencies, sizeof(flag_persistencies)); /* Fill in flag pointers */ for (index = 0; index < COUNT_OF(flag_persistencies); index++) { current_persistency = &flag_persistencies[index]; current_persistency->flag_ptr = find_error_flag(current_persistency->flag); if (current_persistency->flag_ptr) current_persistency->flag_ptr->persistency = current_persistency; } crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); flag_persistencies_crc = crc_unit_get_crc(); } static void apply_config_overrides(void) { uint32_t count; uint32_t idx; struct config_override override; int res; enum safety_flag flag_enum; volatile struct error_flag *flag; res = safety_memory_get_config_override_count(&count); if (res) return; for (idx = 0; idx < count; idx++) { res = safety_memory_get_config_override(idx, &override); if (res) continue; switch (override.type) { case SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT: flag_enum = flag_no_to_flag_enum(override.entry.weight_override.flag); flag = find_error_flag(flag_enum); if (flag && flag->weight) { flag->weight->weight = override.entry.weight_override.weight; } break; case SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTANCE: flag_enum = flag_no_to_flag_enum(override.entry.persistance_override.flag); flag = find_error_flag(flag_enum); if (flag && flag->persistency) { flag->persistency->persistency = override.entry.persistance_override.persistance; } break; default: continue; } } /* Patch new CRCs */ crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); flag_persistencies_crc = crc_unit_get_crc(); crc_unit_reset(); crc_unit_input_array((uint32_t*)flag_weights, wordsize_of(flag_weights)); flag_weight_crc = crc_unit_get_crc(); } static bool error_flag_get_status(const volatile struct error_flag *flag) { if (!flag) return true; if (flag->error_state == flag->error_state_inv) { return true; } else { return flag->error_state; } } static volatile struct analog_mon *find_analog_mon(enum analog_value_monitor mon) { uint32_t i; volatile struct analog_mon *ret = NULL; for (i = 0; i < COUNT_OF(analog_mons); i++) { if (analog_mons[i].monitor == mon) ret = &analog_mons[i]; } return ret; } static volatile struct timing_mon *find_timing_mon(enum timing_monitor mon) { uint32_t i; volatile struct timing_mon *ret = NULL; for (i = 0; i < COUNT_OF(timings); i++) { if (timings[i].monitor == mon) ret = &timings[i]; } return ret; } static void safety_controller_process_active_timing_mons() { uint32_t i; volatile struct timing_mon *current_mon; uint64_t last; for (i = 0; i < COUNT_OF(timings); i++) { current_mon = &timings[i]; if (current_mon->enabled) { __disable_irq(); last = current_mon->last; __enable_irq(); if (systick_ticks_have_passed(last, current_mon->max_delta)) safety_controller_report_error(current_mon->associated_flag); } } } /** * @brief safety_controller_process_monitor_checks * Process the analog and timing monitors and set the relevant flags in case of a monitor outside its limits. * * The checking of the analog monitors will only be armed after a startup delay of 1000 ms to allow the values to stabilize. */ static void safety_controller_process_monitor_checks(void) { static bool startup_completed = false; enum analog_monitor_status amon_state; float amon_value; if (!startup_completed && systick_get_global_tick() >= 1000) startup_completed = true; if (startup_completed) { amon_state = safety_controller_get_analog_mon_value(ERR_AMON_VREF, &amon_value); if (amon_state != ANALOG_MONITOR_OK) safety_controller_report_error(ERR_FLAG_AMON_VREF); amon_state = safety_controller_get_analog_mon_value(ERR_AMON_UC_TEMP, &amon_value); if (amon_state != ANALOG_MONITOR_OK) safety_controller_report_error(ERR_FLAG_AMON_UC_TEMP); amon_state = safety_controller_get_analog_mon_value(ERR_AMON_SUPPLY_VOLT, &amon_value); if (amon_state != ANALOG_MONITOR_OK) safety_controller_report_error(ERR_FLAG_AMON_SUPPLY_VOLT); } safety_controller_process_active_timing_mons(); } /** * @brief Internal function for setting an error flag * * Multiple flags can be ored together to set them in one go. * The provided key will be set on all of the flags in order to prevent them from being reset by * unauthorized code. If nop key shall be used, set key to zero. * * @param flag Enum of the flags to set. This can be an ORed value of multiple error flags. * @param key The kex to set on the flag. * @param prevent_error_mem_enty Prevent the flag from being stored in the error memory. * @return 0 if successful. */ static int report_error(enum safety_flag flag, uint32_t key, bool prevent_error_mem_enty) { uint32_t i; int ret = -1; bool old_state; int res; struct error_memory_entry err_mem_entry; for (i = 0; i < COUNT_OF(flags); i++) { if (flags[i].flag & flag) { old_state = flags[i].error_state; flags[i].error_state = true; flags[i].error_state_inv = !flags[i].error_state; flags[i].key = key; if ((check_flag_persistent(&flags[i]) && !old_state && !prevent_error_mem_enty) || get_flag_weight(&flags[i]) == SAFETY_FLAG_CONFIG_WEIGHT_PANIC) { err_mem_entry.counter = 1; err_mem_entry.flag_num = flag_enum_to_flag_no(flags[i].flag); err_mem_entry.type = SAFETY_MEMORY_ERR_ENTRY_FLAG; res = safety_memory_insert_error_entry(&err_mem_entry); if (res) ret = -12; } else { ret = 0; } } } return ret; } int safety_controller_report_error(enum safety_flag flag) { return safety_controller_report_error_with_key(flag, 0x0UL); } int safety_controller_report_error_with_key(enum safety_flag flag, uint32_t key) { return report_error(flag, key, false); } void safety_controller_report_timing(enum timing_monitor monitor) { volatile struct timing_mon *tim; uint64_t timestamp; timestamp = systick_get_global_tick(); tim = find_timing_mon(monitor); if (tim) { if (tim->enabled) { if (!systick_ticks_have_passed(tim->last, tim->min_delta) && tim->min_delta > 0U) { safety_controller_report_error(tim->associated_flag); } } tim->calculated_delta = timestamp - tim->last; tim->last = timestamp; tim->enabled = true; } } void safety_controller_report_analog_value(enum analog_value_monitor monitor, float value) { volatile struct analog_mon *ana; /* Return if not a power of two */ if (!is_power_of_two(monitor)) return; ana = find_analog_mon(monitor); if (ana) { ana->valid = true; ana->value = value; ana->timestamp = systick_get_global_tick(); } } /** * @brief Return the flags, which are set in the error memory * @param flags Flags read from error memory * @return 0 if ok, != 0 if error */ static int get_safety_flags_from_error_mem(enum safety_flag *flags) { uint32_t count; uint32_t idx; int res; enum safety_flag return_flags = 0; struct error_memory_entry entry; if (!flags) return -1001; res = safety_memory_get_error_entry_count(&count); if (res) return -1; for (idx = 0; idx < count; idx++) { res = safety_memory_get_error_entry(idx, &entry); if (entry.type == SAFETY_MEMORY_ERR_ENTRY_FLAG) { return_flags |= flag_no_to_flag_enum(entry.flag_num); } } *flags = return_flags; return 0; } /** * @brief Initialize the GPIOs for the external hardware watchdog. * * The external harware watchdog has to be periodically reset or it will reset hte controller. * Because debugging is not possible, when the watchdog is active, it is only activated, if the application is * compiled in release mode. Any interruption of the main programm will then trigger the internal and/or the external watchdog. * * @note When enabled, execute the @ref external_watchdog_toggle function to reset the external watchdog. */ static void safety_controller_init_external_watchdog() { rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SAFETY_EXT_WATCHDOG_RCC_MASK)); SAFETY_EXT_WATCHDOG_PORT->MODER &= MODER_DELETE(SAFETY_EXT_WATCHDOG_PIN); #ifndef DEBUGBUILD SAFETY_EXT_WATCHDOG_PORT->MODER |= OUTPUT(SAFETY_EXT_WATCHDOG_PIN); SAFETY_EXT_WATCHDOG_PORT->ODR |= (1< 1000) safety_controller_report_error(ERR_FLAG_SYSTICK); } else { same_systick_cnt = 0UL; } last_systick = systick; } /** * @brief Handle weightet flags. * * This functions loops oer all weight entries and checks the corresponding flags. If a flag * is set, the appropriate action defined by the flag weight is executed. */ static void safety_controller_handle_weighted_flags() { uint32_t weight_index; volatile struct safety_weight *current_weight; for (weight_index = 0; weight_index < COUNT_OF(flag_weights); weight_index++) { current_weight = &flag_weights[weight_index]; if (error_flag_get_status(current_weight->flag_ptr)) { switch (current_weight->weight) { case SAFETY_FLAG_CONFIG_WEIGHT_NONE: break; case SAFETY_FLAG_CONFIG_WEIGHT_PID: oven_pid_abort(); break; case SAFETY_FLAG_CONFIG_WEIGHT_PANIC: /* Expected fallthrough */ default: oven_pid_abort(); panic_mode(); break; } } } } #ifndef DEBUGBUILD static void external_watchdog_toggle() { SAFETY_EXT_WATCHDOG_PORT->ODR ^= (1< 30) { external_watchdog_toggle(); watchdog_counter = 0UL; } } #endif return (ret ? -1 : 0); } int safety_controller_enable_timing_mon(enum timing_monitor monitor, bool enable) { volatile struct timing_mon *tim; if (enable) { safety_controller_report_timing(monitor); } else { tim = find_timing_mon(monitor); if (!tim) return -1; tim->enabled = false; } return 0; } enum analog_monitor_status safety_controller_get_analog_mon_value(enum analog_value_monitor monitor, float *value) { volatile struct analog_mon *mon; int ret = ANALOG_MONITOR_ERROR; if (!is_power_of_two(monitor)) goto go_out; if (!value) goto go_out; mon = find_analog_mon(monitor); if (mon) { if (!mon->valid) { ret = ANALOG_MONITOR_INACTIVE; goto go_out; } *value = mon->value; if (mon->value < mon->min) ret = ANALOG_MONITOR_UNDER; else if (mon->value > mon->max) ret = ANALOG_MONITOR_OVER; else ret = ANALOG_MONITOR_OK; } go_out: return ret; } int safety_controller_get_flag(enum safety_flag flag, bool *status, bool try_ack) { volatile struct error_flag *found_flag; int ret = -1; if (!status) return -1002; if (!is_power_of_two(flag)) return -1001; found_flag = find_error_flag(flag); if (found_flag) { *status = error_flag_get_status(found_flag); if (try_ack && !check_flag_persistent(found_flag)) { /* Flag is generally non persistent * If key is set, this function cannot remove the flag */ if (found_flag->key == 0UL) { found_flag->error_state = false; found_flag->error_state_inv = !found_flag->error_state; } } } return ret; } int safety_controller_ack_flag(enum safety_flag flag) { return safety_controller_ack_flag_with_key(flag, 0UL); } int safety_controller_ack_flag_with_key(enum safety_flag flag, uint32_t key) { int ret = -1; volatile struct error_flag *found_flag; if (!is_power_of_two(flag)) { return -1001; } found_flag = find_error_flag(flag); if (found_flag) { if (!check_flag_persistent(found_flag) && (found_flag->key == key || !found_flag->key)) { found_flag->error_state = false; found_flag->error_state_inv = true; ret = 0; } else { ret = -2; } } return ret; } bool safety_controller_get_flags_by_mask(enum safety_flag mask) { uint32_t i; bool ret = false; for (i = 0; i < COUNT_OF(flags); i++) { if ((flags[i].flag & mask) && error_flag_get_status(&flags[i])) { ret = true; break; } } return ret; } uint32_t safety_controller_get_flag_count() { return COUNT_OF(flags); } uint32_t safety_controller_get_analog_monitor_count() { return COUNT_OF(analog_mons); } uint32_t safety_controller_get_timing_monitor_count() { return COUNT_OF(timings); } int safety_controller_get_analog_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(analog_mons)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, analog_mons[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_flag_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(flags)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, flags[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(timings)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, timings[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_flag_by_index(uint32_t index, bool *status, enum safety_flag *flag_enum) { int ret = -1; if (!status && !flag_enum) return -1000; if (index < COUNT_OF(flags)) { if (status) *status = error_flag_get_status(&flags[index]); if (flag_enum) *flag_enum = flags[index].flag; ret = 0; } return ret; } int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_monitor_info *info) { volatile struct analog_mon *mon; if (!info) return -1002; if (index >= COUNT_OF(analog_mons)) { info->status = ANALOG_MONITOR_ERROR; return -1001; } mon = &analog_mons[index]; info->max = mon->max; info->min = mon->min; info->value = mon->value; info->timestamp = mon->timestamp; if (!mon->valid) { info->status = ANALOG_MONITOR_INACTIVE; } else { if (mon->value > mon->max) info->status = ANALOG_MONITOR_OVER; else if (mon->value < mon->min) info->status = ANALOG_MONITOR_UNDER; else info->status = ANALOG_MONITOR_OK; } return 0; } int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_monitor_info *info) { volatile struct timing_mon *mon; if (!info) return -1002; if (index >= COUNT_OF(timings)) { return -1001; } mon = &timings[index]; info->max = mon->max_delta; info->min = mon->min_delta; info->enabled = mon->enabled; info->last_run = mon->last; info->delta = mon->calculated_delta; return 0; } /** @} */