/* 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 struct error_flag { const char *name; enum safety_flag flag; bool error_state; bool error_state_inv; bool persistent; 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; }; 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; }; static volatile struct error_flag IN_SECTION(.ccm.data) flags[] = { ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OFF, false), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG, false), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE, false), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW, true), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MEAS_ADC, false), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_PID, false), ERR_FLAG_ENTRY(ERR_FLAG_AMON_UC_TEMP, true), ERR_FLAG_ENTRY(ERR_FLAG_AMON_VREF, false), ERR_FLAG_ENTRY(ERR_FLAG_STACK, true), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_ADC, true), ERR_FLAG_ENTRY(ERR_FLAG_SYSTICK, true), ERR_FLAG_ENTRY(ERR_FLAG_WTCHDG_FIRED, true), ERR_FLAG_ENTRY(ERR_FLAG_UNCAL, false), ERR_FLAG_ENTRY(ERR_FLAG_DEBUG, true), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP, false), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT, true), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT, true), }; static volatile struct timing_mon IN_SECTION(.ccm.data) timings[] = { TIM_MON_ENTRY(ERR_TIMING_PID, 2, 1000, 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), }; 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 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); } 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); } crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); flag_persistencies_crc = crc_unit_get_crc(); } static bool error_flag_get_status(const volatile struct error_flag *flag) { 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); } } } static void safety_controller_process_monitor_checks() { 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); } safety_controller_process_active_timing_mons(); } 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); } 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) { 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 (flags[i].persistent && !old_state) { 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; } 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(); } } void safety_controller_init() { enum safety_memory_state found_memory_state; /* Init the safety memory */ if (safety_memory_init(&found_memory_state)) { /* Trigger panic mode! */ panic_mode(); } /* This is usually done by the safety memory already. But, since this module also uses the CRC... */ crc_unit_init(); stack_check_init_corruption_detect_area(); init_safety_flag_weight_table_from_default(); init_safety_flag_persistencies_from_default(); if (found_memory_state == SAFETY_MEMORY_INIT_CORRUPTED) safety_controller_report_error(ERR_FLAG_SAFETY_MEM_CORRUPT); /* Init default flag states */ safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY); safety_adc_init(); watchdog_setup(WATCHDOG_PRESCALER); if (watchdog_check_reset_source()) safety_controller_report_error(ERR_FLAG_WTCHDG_FIRED); #ifdef DEBUGBUILD safety_controller_report_error(ERR_FLAG_DEBUG); #endif } static void safety_controller_check_stack() { int32_t free_stack; free_stack = stack_check_get_free(); if (free_stack < SAFETY_MIN_STACK_FREE) safety_controller_report_error(ERR_FLAG_STACK); if (stack_check_corruption_detect_area()) { safety_controller_report_error(ERR_FLAG_STACK); } } static void safety_controller_handle_safety_adc() { static enum safety_adc_meas_channel current_channel = SAFETY_ADC_MEAS_TEMP; static uint64_t last_result_timestamp = 0; int poll_result; uint16_t result; float analog_value; poll_result = safety_adc_poll_result(&result); if (!systick_ticks_have_passed(last_result_timestamp, SAFETY_CONTROLLER_ADC_DELAY_MS) && poll_result != 1) return; if (poll_result) { if (poll_result == -1) { switch (current_channel) { case SAFETY_ADC_MEAS_TEMP: current_channel = SAFETY_ADC_MEAS_VREF; break; case SAFETY_ADC_MEAS_VREF: /* Expected fallthru */ default: current_channel = SAFETY_ADC_MEAS_TEMP; break; } safety_adc_trigger_meas(current_channel); } else if (poll_result == 1) { last_result_timestamp = systick_get_global_tick(); analog_value = safety_adc_convert_channel(current_channel, result); safety_controller_report_timing(ERR_TIMING_SAFETY_ADC); switch (current_channel) { case SAFETY_ADC_MEAS_TEMP: safety_controller_report_analog_value(ERR_AMON_UC_TEMP, analog_value); break; case SAFETY_ADC_MEAS_VREF: safety_controller_report_analog_value(ERR_AMON_VREF, analog_value); break; default: safety_controller_report_error(ERR_FLAG_SAFETY_ADC); break; } } } } /** * @brief Check the memory structures. */ static void safety_controller_handle_memory_checks(void) { static uint64_t ts = 0; enum safety_memory_state found_state; if (systick_ticks_have_passed(ts, 250)) { ts = systick_get_global_tick(); /* Check the safety memory */ if (safety_memory_check()) { (void)safety_memory_reinit(&found_state); if (found_state != SAFETY_MEMORY_INIT_VALID_MEMORY) { safety_controller_report_error(ERR_FLAG_SAFETY_MEM_CORRUPT); } } /* If flag weight table is broken, reinit to default and set flag */ if (flag_weight_table_crc_check()) { safety_controller_report_error(ERR_FLAG_SAFETY_TAB_CORRUPT); init_safety_flag_weight_table_from_default(); } /* If persistency table is broken, reinit to default and set flag */ if(flag_persistency_table_crc_check()) { safety_controller_report_error(ERR_FLAG_SAFETY_TAB_CORRUPT); init_safety_flag_persistencies_from_default(); } } } static void safety_controller_do_systick_checking() { static uint64_t last_systick; static uint32_t same_systick_cnt = 0UL; uint64_t systick; systick = systick_get_global_tick(); if (systick == last_systick) { same_systick_cnt++; if (same_systick_cnt > 1000) safety_controller_report_error(ERR_FLAG_SYSTICK); } else { same_systick_cnt = 0UL; } last_systick = systick; } int safety_controller_handle() { int ret = 0; safety_controller_check_stack(); safety_controller_handle_safety_adc(); safety_controller_handle_memory_checks(); safety_controller_do_systick_checking(); safety_controller_process_monitor_checks(); /* TODO: Check flag weights and trigger appropriate safety action */ ret |= watchdog_ack(WATCHDOG_MAGIC_KEY); 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 && !found_flag->persistent) { /* 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 (!found_flag->persistent && (found_flag->key == key || !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; } /** @} */