/* 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 struct error_flag { const char *name; enum safety_flag flag; bool error_state; 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; 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; }; #ifdef COUNT_OF #undef COUNT_OF #endif #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) #define ERR_FLAG_ENTRY(errflag, persistency) {.name=#errflag, .flag = (errflag), .error_state = false, .persistent = (persistency), .key = 0UL} #define TIM_MON_ENTRY(mon, min, max, flag) {.name=#mon, .monitor = (mon), .associated_flag=(flag), .min_delta = (min), .max_delta = (max), .last = 0ULL, .enabled= false} #define ANA_MON_ENTRY(mon, min_value, max_value, flag) {.name=#mon, .monitor = (mon), .associated_flag=(flag), .min = (min_value), .max = (max_value), .value = 0.0f, .valid = false} static volatile struct error_flag 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), }; static volatile struct timing_mon 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 + 70, ERR_FLAG_SAFETY_ADC), }; static volatile struct analog_mon 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 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 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; } static void safety_controller_process_active_timing_mons() { uint32_t i; volatile struct timing_mon *current_mon; for (i = 0; i < COUNT_OF(timings); i++) { current_mon = &timings[i]; if (current_mon->enabled) { if (systick_ticks_have_passed(current_mon->last, current_mon->max_delta)) safety_controller_report_error(current_mon->associated_flag); } } } static void safety_controller_process_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(); } 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; for (i = 0; i < COUNT_OF(flags); i++) { if (flags[i].flag & flag) { flags[i].error_state = true; flags[i].key = key; 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->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; } } void safety_controller_init() { /* 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); } 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; } } } } int safety_controller_handle() { static uint64_t last_systick; static uint32_t same_systick_cnt = 0UL; uint64_t systick; int ret = 0; safety_controller_check_stack(); safety_controller_handle_safety_adc(); 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; safety_controller_process_checks(); /* TODO: Check flags for PID and HALT */ 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 = found_flag->error_state; 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; } } 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; 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) { ret = true; break; } } return ret; } uint32_t safety_controller_get_flag_count() { return COUNT_OF(flags); } 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_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 = flags[index].error_state; if (flag_enum) *flag_enum = flags[index].flag; ret = 0; } return ret; } /** @} */