/** STM32F1 BusVoodoo application * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2016-2020 */ /* standard libraries */ #include // standard integer types #include // string utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // general purpose input output library #include // real-time control clock library #include // debug utilities #include // real time clock utilities #include // independent watchdog utilities /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "usb_cdcacm.h" // USB CDC ACM utilities #include "terminal.h" // handle the terminal interface #include "menu.h" // menu utilities #include "busvoodoo_global.h" // BusVoodoo definitions #include "busvoodoo_hiz.h" // BusVoodoo HiZ mode #include "busvoodoo_uart.h" // BusVoodoo UART mode #include "busvoodoo_i2c.h" // BusVoodoo I2C mode #include "busvoodoo_spi.h" // BusVoodoo SPI mode #include "busvoodoo_onewire.h" // BusVoodoo 1-wire mode #if BUSVOODOO_HARDWARE_VERSION != 2 #include "uart.h" // USART utilities #include "busvoodoo_oled.h" // OLED utilities #include "busvoodoo_rs232.h" // BusVoodoo RS-232 mode #include "busvoodoo_rs485.h" // BusVoodoo RS-485/422 mode #include "busvoodoo_sdio.h" // BusVoodoo SDIO mode #endif /** watchdog period in ms */ #define WATCHDOG_PERIOD 10000 /** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime * set to 1 if VBAT can keep the RTC running when the board is unpowered, indicating the date and time */ #define RTC_DATE_TIME 0 /** number of RTC ticks per second * @note use integer divider of oscillator to keep second precision */ #define RTC_TICKS_SECOND 4 /** all supported BusVoodoo modes */ static const struct busvoodoo_mode_t* busvoodoo_modes[] = { &busvoodoo_hiz_mode, &busvoodoo_uart_mode, &busvoodoo_i2c_mode, &busvoodoo_spi_mode, &busvoodoo_onewire_mode, #if BUSVOODOO_HARDWARE_VERSION != 2 &busvoodoo_rs232_mode, &busvoodoo_rs485_mode, &busvoodoo_sdio_mode, #endif }; /** current BusVoodoo mode */ static struct busvoodoo_mode_t const * busvoodoo_mode = NULL; /** is mode setup complete */ static bool busvoodoo_mode_complete = false; size_t putc(char c) { size_t length = 0; // number of characters printed static char last_c = 0; // to remember on which character we last sent if ('\n' == c) { // send carriage return (CR) + line feed (LF) newline for each LF if ('\r' != last_c) { // CR has not already been sent #if BUSVOODOO_HARDWARE_VERSION != 2 uart_putchar_nonblocking('\r'); // send CR over USART #endif usb_cdcacm_putchar('\r'); // send CR over USB length++; // remember we printed 1 character } } #if BUSVOODOO_HARDWARE_VERSION != 2 uart_putchar_nonblocking(c); // send byte over USART #endif usb_cdcacm_putchar(c); // send byte over USB length++; // remember we printed 1 character last_c = c; // remember last character return length; // return number of characters printed } /** switch BusVoddoo mode * @param[in] mode mode to switch to */ static void switch_mode(const struct busvoodoo_mode_t* mode) { if (busvoodoo_mode) { (*busvoodoo_mode->exit)(); // exit current mode } busvoodoo_leds_off(); // switch off LEDs busvoodoo_safe_state(); // return to safe state // reset pinout for (uint8_t i = 0; i < LENGTH(busvoodoo_global_pinout_io); i++) { busvoodoo_global_pinout_io[i] = NULL; } #if BUSVOODOO_HARDWARE_VERSION != 2 for (uint8_t i = 0; i < LENGTH(busvoodoo_global_pinout_rscan); i++) { busvoodoo_global_pinout_rscan[i] = NULL; } busvoodoo_oled_clear(); // clear OLED display buffer busvoodoo_oled_update(); // update OLED display #endif if (NULL == mode) { // no mode provided busvoodoo_mode = &busvoodoo_hiz_mode; // use default mode } else { // mode provided busvoodoo_mode = mode; // set provided mode a current mode } busvoodoo_mode_complete = (*busvoodoo_mode->setup)(&terminal_prefix, NULL); // start setup terminal_send(0); // update the terminal prompt } /** command to show help * @param[in] argument no argument required */ static void command_help(void* argument); /** command to select mode * @param[in] argument mode to select */ static void command_mode(void* argument); /** command to quit current BusVoodoo mode * @param[in] argument no argument required */ static void command_quit(void* argument); /** command to reset board * @param[in] argument no argument required */ static void command_reset(void* argument); /** command to reboot into bootloader * @param[in] argument no argument required */ /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { .shortcut = 'm', .name = "mode", .command_description = "select mode", .argument = MENU_ARGUMENT_STRING, .argument_description = "[mode]", .command_handler = &command_mode, }, { .shortcut = 'q', .name = "quit", .command_description = "quit current mode", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_quit, }, { .shortcut = 'R', .name = "reset", .command_description = "reset board", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_reset, }, { .shortcut = 'h', .name = "help", .command_description = "display help", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_help, }, }; static void command_help(void* argument) { (void)argument; // we won't use the argument printf("available commands:\n"); menu_print_commands(menu_commands, LENGTH(menu_commands)); // print global commands menu_print_commands(busvoodoo_global_commands, busvoodoo_global_commands_nb); // print BusVoodoo global commands #if BUSVOODOO_HARDWARE_VERSION != 2 if (busvoodoo_full) { menu_print_commands(busvoodoo_global_full_commands, busvoodoo_global_full_commands_nb); // print BusVoodoo global commands } #endif if (!busvoodoo_mode->full_only || busvoodoo_full) { menu_print_commands(busvoodoo_mode->commands, busvoodoo_mode->commands_nb); // print BusVoodoo mode commands } } static void command_mode(void* argument) { if (NULL == argument || 0 == strlen(argument)) { // no mode provided: list all modes printf("available modes:\n"); for (uint8_t i = 0; i < LENGTH(busvoodoo_modes); i++) { // go through all modes if (!busvoodoo_modes[i]->full_only || busvoodoo_full) { printf("%s\t%s\n", busvoodoo_modes[i]->name, busvoodoo_modes[i]->description); // display mode information } } } else { // mode provided bool mode_found = false; // to know if we found the matching mode for (uint8_t i = 0; i < LENGTH(busvoodoo_modes); i++) { // go through all modes if (0 == strcmp(argument, busvoodoo_modes[i]->name)) { // check for corresponding mode if (!busvoodoo_mode->full_only || busvoodoo_full) { switch_mode(busvoodoo_modes[i]); // switch to mode } else { printf("mode only available for BusVoodoo full\n"); } mode_found = true; // remember we found the mode break; // stop searching for mode } } if (!mode_found) { printf("unknown mode: %s\n", argument); } } } static void command_quit(void* argument) { (void)argument; // we won't use the argument switch_mode(NULL); // switch do default mode } static void command_reset(void* argument) { (void)argument; // we won't use the argument scb_reset_system(); // reset device while (true); // wait for the reset to happen } /** process user command * @param[in] str user command string (\0 ended) */ static void process_command(char* str) { // ensure actions are available if (NULL == menu_commands || 0 == LENGTH(menu_commands)) { return; } // handle user input if (NULL == busvoodoo_mode) { // no mode set switch_mode(NULL); // set default mode } if (!busvoodoo_mode_complete) { // mode setup is not complete busvoodoo_mode_complete = (*busvoodoo_mode->setup)(&terminal_prefix, str); // continue setup terminal_send(0); // update the terminal prompt } else { // mode setup is complete // don't handle empty lines if (!str || 0 == strlen(str)) { return; } bool command_handled = false; if (!busvoodoo_mode->full_only || busvoodoo_full) { command_handled = menu_handle_command(str, busvoodoo_mode->commands, busvoodoo_mode->commands_nb); // try if the mode can handle this command } #if BUSVOODOO_HARDWARE_VERSION != 2 if (!command_handled && busvoodoo_full) { command_handled = menu_handle_command(str, busvoodoo_global_full_commands, busvoodoo_global_full_commands_nb); // try if full BusVoodoo can handle this command } #endif if (!command_handled) { command_handled = menu_handle_command(str, busvoodoo_global_commands, busvoodoo_global_commands_nb); // try if the base BusVoodoo can handle this command } if (!command_handled) { command_handled = menu_handle_command(str, menu_commands, LENGTH(menu_commands)); // try if this is not a global command } if (!command_handled) { printf("command not recognized. enter help to list commands\n"); } } } /** program entry point * this is the firmware function started by the micro-controller */ void main(void); void main(void) { rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock #if DEBUG // enable functionalities for easier debug DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted DBGMCU_CR |= DBGMCU_CR_WWDG_STOP; // stop window watchdog counter when code is halted DBGMCU_CR |= DBGMCU_CR_STANDBY; // allow debug also in standby mode (keep digital part and clock powered) DBGMCU_CR |= DBGMCU_CR_STOP; // allow debug also in stop mode (keep clock powered) DBGMCU_CR |= DBGMCU_CR_SLEEP; // allow debug also in sleep mode (keep clock powered) #else // setup watchdog to reset in case we get stuck (i.e. when an error occurred) iwdg_set_period_ms(WATCHDOG_PERIOD); // set independent watchdog period iwdg_start(); // start independent watchdog #endif // setup board board_setup(); // setup board #if BUSVOODOO_HARDWARE_VERSION != 2 uart_setup(); // setup USART (for printing) #endif busvoodoo_setup(); // setup BusVoodoo board usb_cdcacm_setup(); // setup USB CDC ACM (for printing) puts("\nwelcome to \x1b[32mBus\x1b[35mVoodoo\x1b[0m\n"); // print welcome message #if DEBUG // show reset cause if (RCC_CSR & (RCC_CSR_LPWRRSTF | RCC_CSR_WWDGRSTF | RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF | RCC_CSR_PORRSTF | RCC_CSR_PINRSTF)) { puts("reset cause(s):"); if (RCC_CSR & RCC_CSR_LPWRRSTF) { puts(" low-power"); } if (RCC_CSR & RCC_CSR_WWDGRSTF) { puts(" window-watchdog"); } if (RCC_CSR & RCC_CSR_IWDGRSTF) { puts(" independent-watchdog"); } if (RCC_CSR & RCC_CSR_SFTRSTF) { puts(" software"); } if (RCC_CSR & RCC_CSR_PORRSTF) { puts(" POR/PDR"); } if (RCC_CSR & RCC_CSR_PINRSTF) { puts(" pin"); } putc('\n'); RCC_CSR |= RCC_CSR_RMVF; // clear reset flags } #endif #if BUSVOODOO_HARDWARE_VERSION == 2 // on the STLINK/V2 PB7 is pulled high, while on the BusVoodoo dongle it is floating #define BUSVOODOO_STLINK_PIN PB7 gpio_clear(GPIO_PORT(BUSVOODOO_STLINK_PIN), GPIO_PIN(BUSVOODOO_STLINK_PIN)); gpio_set_mode(GPIO_PORT(BUSVOODOO_STLINK_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUSVOODOO_STLINK_PIN)); sleep_ms(100); // wait for voltage to settle if (gpio_get(GPIO_PORT(BUSVOODOO_STLINK_PIN), GPIO_PIN(BUSVOODOO_STLINK_PIN))) { while (true) { puts("solder the micro-controller on a BusVoodoo dongle\n"); iwdg_reset(); // kick the dog sleep_ms(1000); } } #endif // setup RTC rtc_auto_awake(RCC_HSE, 8000000 / 128 / RTC_TICKS_SECOND - 1); // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running) rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds" nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt // setup terminal terminal_prefix = "BV"; // set default prefix terminal_process = &process_command; // set central function to process commands terminal_setup(); // start terminal #if BUSVOODOO_HARDWARE_VERSION != 2 // setup OLED display sleep_ms(10); // wait a bit until the display is ready busvoodoo_oled_setup(); // setup OLED display // display version busvoodoo_oled_clear(); busvoodoo_oled_text_left("BusVoodoo"); char str[20]; snprintf(str, sizeof(str), "fl: %s", busvoodoo_full ? "full" : "light"); busvoodoo_oled_text_pos(1, 16+(fonts[FONT_KING10].height+2)*1, FONT_KING10, str); snprintf(str, sizeof(str), "hw: %c", busvoodoo_version); busvoodoo_oled_text_pos(1, 16+(fonts[FONT_KING10].height+2)*2, FONT_KING10, str); snprintf(str, sizeof(str), "fw: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); busvoodoo_oled_text_pos(1, 16+(fonts[FONT_KING10].height+2)*3, FONT_KING10, str); snprintf(str, sizeof(str), "bus.cuvoodoo.info"); busvoodoo_oled_text_pos((127-((fonts[FONT_KING8].width+1)*strlen(str)))/2, 63, FONT_KING8, str); busvoodoo_oled_update(); sleep_ms(1000); busvoodoo_oled_clear(); #endif // setup default mode switch_mode(NULL); // main loop bool action = false; // if an action has been performed don't go to sleep button_flag = false; // reset button flag while (true) { // infinite loop iwdg_reset(); // kick the dog while (user_input_available) { // user input received action = true; // action has been performed char c = user_input_get(); // get user input if (0x04 == c) { // CTRL+D is used to quit the mode printf("quit\n"); // acknowledge quitting command_quit(NULL); // quit current mode } else { terminal_send(c); // send received character to terminal } } if (action) { // go to sleep if nothing had to be done, else recheck for activity action = false; } else { __WFI(); // go to sleep } } // main loop } /** @brief interrupt service routine called when tick passed on RTC */ void rtc_isr(void) { rtc_clear_flag(RTC_SEC); // clear flag }