/* 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 . */ /** * @file main.c * @brief Main file for firmware */ #include #include #include #include #include #include #include #include #include #include "fatfs/shimatta_sdio_driver/shimatta_sdio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void setup_nvic_priorities(void) { /* No sub priorities */ NVIC_SetPriorityGrouping(2); /* Setup Priorities */ NVIC_SetPriority(ADC_IRQn, 2); /* Measurement ADC DMA */ NVIC_SetPriority(DMA2_Stream0_IRQn, 1); /* Shelmatta UART TX */ NVIC_SetPriority(DMA2_Stream7_IRQn, 3); NVIC_SetPriority(DMA2_Stream4_IRQn, 2); } FATFS fs; #define fs_ptr (&fs) /** * @brief Configure UART GPIOs * In case the application is build in debug mode, use the TX/RX Pins on the debug header * else the Pins on the DIGIO header are configured in the digio module and this function does nothing. */ static inline void uart_gpio_config(void) { #if defined(DEBUGBUILD) || defined(UART_ON_DEBUG_HEADER) rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SHELL_UART_PORT_RCC_MASK)); SHELL_UART_PORT->MODER &= MODER_DELETE(SHELL_UART_TX_PIN) & MODER_DELETE(SHELL_UART_RX_PIN); SHELL_UART_PORT->MODER |= ALTFUNC(SHELL_UART_RX_PIN) | ALTFUNC(SHELL_UART_TX_PIN); SETAF(SHELL_UART_PORT, SHELL_UART_RX_PIN, SHELL_UART_RX_PIN_ALTFUNC); SETAF(SHELL_UART_PORT, SHELL_UART_TX_PIN, SHELL_UART_TX_PIN_ALTFUNC); /* Setup Pullup resistor at UART RX */ SHELL_UART_PORT->PUPDR |= PULLUP(SHELL_UART_RX_PIN); #endif } /** * @brief Mount the SD card if available and not already mounted * @param mounted The current mounting state of the SD card * @return true if mounted, false if an error occured or the SD is not inserted and cannot be mounted */ static bool mount_sd_card_if_avail(bool mounted) { FRESULT res; static uint8_t IN_SECTION(.ccm.bss) inserted_counter = 0; if (sdio_check_inserted() && mounted) { memset(fs_ptr, 0, sizeof(FATFS)); sdio_stop_clk(); inserted_counter = 0; return false; } if (!sdio_check_inserted() && inserted_counter < 255) inserted_counter++; if (!sdio_check_inserted() && !mounted && inserted_counter > 4) { inserted_counter = 0; res = f_mount(fs_ptr, "0:/", 1); if (res == FR_OK) { led_set(1, 1); return true; } else { return false; } } return mounted; } /** * @brief Process the boot status structure in the safety (backup) RAM * Depending on the flags set there, this function will: * - Reboot into the ram code for reflashing * - Display the PANIC message * - Display if the flash has been successfully updated */ static inline void handle_boot_status(void) { struct safety_memory_boot_status status; int res; res = safety_memory_get_boot_status(&status); if (res != 0) panic_mode(); if (status.reset_from_panic) { /* We've seen a panic */ gui_root_menu_message_set("!! PANIC !!", "Check error me- mory!"); } if (status.reboot_to_bootloader) { status.reboot_to_bootloader = 0UL; safety_memory_set_boot_status(&status); led_set(0, 1); led_set(1, 1); gui_lcd_write_direct_blocking(0, "Updating..."); start_updater_ram_code(); } if (status.code_updated) { status.code_updated = 0x0UL; safety_memory_set_boot_status(&status); /* Display notification on GUI */ gui_root_menu_message_set("Firmware updated", "[Press Key]"); } } /** * @brief Read out the option bytes of the STM32 and program them to the desired values. * * - This function currently forces the brown out level to Level 3. */ static void check_and_program_opt_bytes(void) { struct option_bytes opts; int err; /** - Read option bytes */ stm_option_bytes_read(&opts); if (opts.brown_out_level != 0) { /* Set the brown out level to level 3 => highest brown out limit. */ opts.brown_out_level = 0; /** - Program the option bytes if brown out level was not set correctly */ err = stm_option_bytes_program(&opts); /** - If programming failes, enter panic mode */ if (err) panic_mode(); /** - If programming is successful, reset the system to apply new settings */ NVIC_SystemReset(); } } /** * @brief Setup the system. * * This function does all basic initializations of the MCU and its peripherals */ static inline void setup_system(void) { float tmp; /** - Read the option bytes and if necessary program them to the desired values */ check_and_program_opt_bytes(); /** - Setup the NVIC priorities of the core peripherals using interrupts */ setup_nvic_priorities(); /** - Init safety controller and safety memory */ safety_controller_init(); /** - Setup the systick module generating the 100us tick fort the GUI and * the 1ms tick for the global systick timestamp */ systick_setup(); /** - Initialize the oven output driver outputting the wavepacket control signal for the SSR and */ oven_driver_init(); /** - Initialize all DIGIO Pins to their default state and pin functions */ digio_init(); /** - Set-up the LED outputs */ led_setup(); /** - Set-up the loudspeaker / beeper output */ loudspeaker_setup(); /** - Initialize the GUI */ gui_init(); /** - Initialize the pins for the uart interface. */ uart_gpio_config(); /** - Set-up the settings module */ settings_setup(); /** - Load the overtemperature limit from eeprom if available. Otherwise the default value will be used */ if (settings_load_overtemp_limit(&tmp) == SETT_LOAD_SUCCESS) safety_controller_set_overtemp_limit(tmp); /** - Handle the boot status struct in the safety memory */ handle_boot_status(); /** - Initialize the shell UART */ shell_uart_setup(); /** - Enable the ADC for PT1000 measurement */ adc_pt1000_setup_meas(); } /** * @brief Handle the input for the shell instance. * * This function will check if the RX ring buffer of the UART contains data. * If so, it will prowvide it to the shellmatta shell. * * @param shell_handle Handle to the shellmatta instance */ static void handle_shell_uart_input(shellmatta_handle_t shell_handle) { int uart_receive_status; const char *uart_input; size_t uart_input_len; /* Handle UART input for shell */ uart_receive_status = shell_uart_receive_data_with_dma(&uart_input, &uart_input_len); if (uart_receive_status >= 0) shell_handle_input(shell_handle, uart_input, uart_input_len); } /** * @brief This is the main function containing the initilizations and the cyclic main loop * @return Don't care. This function will never return. We're on an embedded device... */ int main(void) { bool cal_active; float offset; float sens; int status; bool sd_card_mounted = false; bool sd_old; shellmatta_handle_t shell_handle; int menu_wait_request; uint64_t quarter_sec_timestamp = 0ULL; enum config_weight worst_safety_flag = SAFETY_FLAG_CONFIG_WEIGHT_NONE; /** - Setup all the peripherals and external componets like LCD, EEPROM etc. and the safety controller */ setup_system(); /** - Try load the calibration. This will only succeed if there's an EEPROM */ status = settings_load_calibration(&sens, &offset); if (!status) adc_pt1000_set_resistance_calibration(offset, sens, true); /** - Initialize the shellmatta shell */ shell_handle = shell_init(shell_uart_write_callback); /** - Print motd to shell */ shell_print_motd(shell_handle); /** - Set the main cycle counter to 0 */ main_cycle_counter_init(); /** - Do a loop over the following */ while (1) { /** - If 250 ms have passed since the last time this step was reached, we try to initialize the * SD card. If the card has been mounted and there is no current resistance calibration, * it is tried to load it from SD card. */ if (systick_ticks_have_passed(quarter_sec_timestamp, 250)) { led_set(1u, 0); sd_old = sd_card_mounted; sd_card_mounted = mount_sd_card_if_avail(sd_card_mounted); if (sd_card_mounted && !sd_old) { adc_pt1000_get_resistance_calibration(NULL, NULL, &cal_active); if (!cal_active) { status = settings_load_calibration(&sens, &offset); if (!status) adc_pt1000_set_resistance_calibration(offset, sens, true); } } /* Check if any flags are present, that disable the PID controller. Blink * LED 0 in this case */ if (worst_safety_flag >= SAFETY_FLAG_CONFIG_WEIGHT_PID) led_set(0u, led_get(0u) ? 0 : 1); else led_set(0u, 0); quarter_sec_timestamp = systick_get_global_tick(); } /** - Handle the GUI */ menu_wait_request = gui_handle(); /** - Handle the uart input for the shell */ handle_shell_uart_input(shell_handle); /** - Execute current profile step, if a profile is active */ temp_profile_executer_handle(); /** - Handle the safety controller. This must be called! Otherwise a watchdog reset will occur */ worst_safety_flag = safety_controller_handle(); /** - If the Oven PID controller is running, we handle its sample function */ if (oven_pid_get_status() == OVEN_PID_RUNNING) oven_pid_handle(); /** - Apply the power level of the oven driver */ oven_driver_apply_power_level(); /** - Report the main loop timing to the timing monitor to detect a slowed down main loop */ safety_controller_report_timing(ERR_TIMING_MAIN_LOOP); /** - If the menu requests a directly following loop run, the main loop will continue. * Otherwise it will wait for the next interrupt */ if (menu_wait_request) __WFI(); else __NOP(); /** - Increment the main cycle counter */ main_cycle_counter_inc(); } return 0; } /** * @brief Callback function for the SDIO driver to wait \p ms milliseconds * @param ms * @warning This function relies on the systick and must not be used in interrupt context. */ void sdio_wait_ms(uint32_t ms) { systick_wait_ms(ms); }