/* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * */ /** dachboden front panel access control * @file * @author King Kévin * @date 2016-2020 */ /* standard libraries */ #include // standard integer types #include // standard utilities #include // string utilities #include // date/time utilities #include // utilities to check chars /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // vector table definition #include // interrupt utilities #include // general purpose input output library #include // real-time control clock library #include // external interrupt utilities #include // real time clock utilities #include // independent watchdog utilities #include // debug utilities #include // design utilities #include // flash utilities #include // backup domain utilities #include // timer utilities /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "uart.h" // USART utilities #include "usb_cdcacm.h" // USB CDC ACM utilities #include "terminal.h" // handle the terminal interface #include "menu.h" // menu utilities #include "led_ws2812b.h" // WS2812B RGB LED control /** 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 1 /** number of RTC ticks per second * @note use integer divider of oscillator to keep second precision */ #define RTC_TICKS_SECOND 1 #if defined(RTC_DATE_TIME) && RTC_DATE_TIME /** the start time from which to RTC ticks count * @note this allows the 32-bit value to reach further in time, particularly when there are several ticks per second */ const time_t rtc_offset = 1577833200; // We 1. Jan 00:00:00 CET 2020 #endif /** RTC time when device is started */ static time_t time_start = 0; /** @defgroup main_flags flag set in interrupts to be processed in main task * @{ */ volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */ /** @} */ /** GPIO pin connected to relay, used to control button connection to panel */ #define RELAY_PANEL_PIN PB6 /** GPIO pin connected to relay, used to simulate button press */ #define RELAY_BUTTON_PIN PB7 /** GPIO for button 1 */ #define BUTTON1_PIN PB9 /** GPIO for button 2 */ #define BUTTON2_PIN PB8 /** which button has been pressed */ volatile uint8_t button_pressed = 0; /** if we apply the opening policy */ bool opening_apply = false; uint8_t pattern_length = 0; static struct opening_settings_t { uint8_t days; /**< which days of the week it door access applies (bit 7 = Monday) */ uint16_t start_time; /**< at which minutes of the day to start */ uint16_t stop_time; /**< at which minutes of the day to stop */ uint8_t button_pattern[10]; /**< sequence of buttons to press to open the door */ } opening_settings; /** timer to generate the ticks for the button LED animations */ #define LED_ANIMATION_TIMER 2 /** number of timer ticks passed, for the LED animation */ static volatile uint8_t led_animation_ticks = 0; /** the button LED animation for the rust fade (duration in ticks, R, G, B) */ static const uint8_t rust_animation[][4] = { {0, 0, 0, 0}, {1, 0xb7 / 10 * 1, 0x41 / 10 * 1, 0x0e / 10 * 1}, {1, 0xb7 / 10 * 2, 0x41 / 10 * 2, 0x0e / 10 * 2}, {1, 0xb7 / 10 * 3, 0x41 / 10 * 3, 0x0e / 10 * 3}, {1, 0xb7 / 10 * 4, 0x41 / 10 * 4, 0x0e / 10 * 4}, {1, 0xb7 / 10 * 5, 0x41 / 10 * 5, 0x0e / 10 * 5}, {1, 0xb7 / 10 * 4, 0x41 / 10 * 4, 0x0e / 10 * 4}, {1, 0xb7 / 10 * 3, 0x41 / 10 * 3, 0x0e / 10 * 3}, {1, 0xb7 / 10 * 2, 0x41 / 10 * 2, 0x0e / 10 * 2}, {1, 0xb7 / 10 * 1, 0x41 / 10 * 1, 0x0e / 10 * 1}, {0, 0, 0, 0}, }; /** the button LED animation for the strobe (duration in ticks, R, G, B) */ static const uint8_t strobe_animation[][4] = { {0, 0, 0, 0}, {1, 0xff / 2, 0xff / 2, 0xff / 2}, {2, 0, 0, 0}, {1, 0xff / 2, 0xff / 2, 0xff / 2}, {0, 0, 0, 0}, }; /** save current opening_settings into SRAM */ static void save_opening_settings(void) { BKP_DR1 = 0; // invalid saved settings BKP_DR2 = opening_settings.days & 0x7f; BKP_DR3 = opening_settings.start_time; BKP_DR4 = opening_settings.stop_time; BKP_DR5 = opening_settings.button_pattern[0]; BKP_DR6 = opening_settings.button_pattern[1]; BKP_DR7 = opening_settings.button_pattern[2]; BKP_DR8 = opening_settings.button_pattern[3]; BKP_DR9 = opening_settings.button_pattern[4]; BKP_DR10 = opening_settings.button_pattern[5]; BKP_DR11 = opening_settings.button_pattern[6]; BKP_DR12 = opening_settings.button_pattern[7]; BKP_DR13 = opening_settings.button_pattern[8]; BKP_DR14 = opening_settings.button_pattern[9]; BKP_DR1 = 0x4223; //validate saved setting } 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 !defined(STLINKV2) uart_putchar_nonblocking('\r'); // send CR over USART #endif usb_cdcacm_putchar('\r'); // send CR over USB length++; // remember we printed 1 character } } #if !defined(STLINKV2) 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 } /** display available commands * @param[in] argument no argument required */ static void command_help(void* argument); /** show software and hardware version * @param[in] argument no argument required */ static void command_version(void* argument); /** show uptime * @param[in] argument no argument required */ static void command_uptime(void* argument); #if RTC_DATE_TIME /** show date and time * @param[in] argument date and time to set */ static void command_datetime(void* argument); #endif /** reset board * @param[in] argument no argument required */ static void command_reset(void* argument); /** switch to DFU bootloader * @param[in] argument no argument required */ static void command_bootloader_dfu(void* argument); /** switch to system memory / embedded USART bootloader * @param[in] argument no argument required */ static void command_bootloader_embedded(void* argument); /** show/set on which days the access policy applies * @param[in] argument 7x0/1 to enable day of the week, starting with Monday (optional) */ static void command_days(void* argument) { const char* days = (char*)argument; // argument is optional days if (NULL != argument) { // days are provided, parse and save them bool valid = (7 == strlen(days)); // verify input string for (uint8_t day = 0; day < 7 && valid; day++) { if (days[day] != '0' && days[day] != '1') { valid = false; } } if (valid) { // save provided settings // parse new days opening_settings.days = 0; for (uint8_t day = 0; day < 7; day++) { if ('1' == days[day]) { opening_settings.days |= (1 << (6 - day)); } } save_opening_settings(); // save days puts("days saved\n"); } else { puts("provide exactly 7 times 0 (off) or 1 (on). 1st digit for Monday, 7th digit for Sunday\n"); } } // display current days printf("opening days: %07b\n", opening_settings.days); const char* day_names[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; for (uint8_t day = 0; day < LENGTH(day_names); day++) { printf("- %s: %s\n", day_names[day], (opening_settings.days & (1 << (6 - day))) ? "on" : "off"); } } /** show/set on which time the access policy starts applying * @param[in] argument string with time of day, optional */ static void command_start(void* argument) { const char* time = (char*)argument; // argument is optional time if (NULL != argument) { // days are provided, parse and save them bool valid = (5 == strlen(time)); // verify input string if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) { valid = false; } if (valid) { // save provided settings opening_settings.start_time = 0; opening_settings.start_time += (time[4] - '0') * 1; opening_settings.start_time += (time[3] - '0') * 10; opening_settings.start_time += (time[1] - '0') * 60; opening_settings.start_time += (time[0] - '0') * 600; save_opening_settings(); // save days puts("start time saved\n"); } else { puts("provide time in HH:MM format\n"); } } printf("start time: %02u:%02u\n", opening_settings.start_time / 60, opening_settings.start_time % 60); } /** show/set on which time the access policy stops applying * @param[in] argument string with time of day, optional */ static void command_stop(void* argument) { const char* time = (char*)argument; // argument is optional time if (NULL != argument) { // days are provided, parse and save them bool valid = (5 == strlen(time)); // verify input string if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) { valid = false; } if (valid) { // save provided settings opening_settings.stop_time = 0; opening_settings.stop_time += (time[4] - '0') * 1; opening_settings.stop_time += (time[3] - '0') * 10; opening_settings.stop_time += (time[1] - '0') * 60; opening_settings.stop_time += (time[0] - '0') * 600; save_opening_settings(); // save days puts("stop time saved\n"); } else { puts("provide time in HH:MM format\n"); } } printf("stop time: %02u:%02u\n", opening_settings.stop_time / 60, opening_settings.stop_time % 60); } /** open door by simulating button press * @param[in] argument not used */ static void command_open(void* argument) { (void)argument; // we won't use the argument gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay and take control over the button gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay an simulate button press sleep_ms(1000); // hold button a bit gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and release button if (!opening_apply) { gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to deactivate relay and git control back to button } } /** show/set button pattern * @param[in] argument sequence of 1/2 */ static void command_pattern(void* argument) { const char* pattern = (char*)argument; // argument is optional pattern if (NULL != argument) { // pattern provided bool valid = (LENGTH(opening_settings.button_pattern) >= strlen(pattern)); // verify input string for (uint8_t i = 0; i < strlen(pattern) && valid; i++) { if ('1' != pattern[i] && '2' != pattern[i]) { valid = false; } } if (valid) { // save provided settings // reset pattern for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern); i++) { opening_settings.button_pattern[i] = 0; } // save new pattern for (uint8_t i = 0; i < strlen(pattern); i++) { opening_settings.button_pattern[i] = pattern[i] - '0'; } save_opening_settings(); // save days puts("button sequence saved\n"); } else { printf("provide buttons sequence of up to %u 1 or 2\n", LENGTH(opening_settings.button_pattern)); } for (pattern_length = 0; pattern_length < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[pattern_length]; pattern_length++); } if (0 == opening_settings.button_pattern[0]) { puts("no button sequence set\n"); } else { puts("button sequence: "); for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[i]; i++) { putc(opening_settings.button_pattern[i] + '0'); } putc('\n'); } } /** test LEDs * @param[in] argument "on" or "off" */ static void command_led(void* argument) { const char* onoff = (char*)argument; // if it should be switched on or off if (NULL == onoff || 0 == strlen(onoff)) { puts("say if the LEDs should be switched on or off\n"); } else if (0 == strcmp(onoff, "on")) { for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) { led_ws2812b_set_rgb(led, 0x20, 0x20 , 0x20); } } else if (0 == strcmp(onoff, "off")) { for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) { led_ws2812b_set_rgb(led, 0, 0 , 0); } } else { printf("unknown argument %s\n", onoff); } } /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { .shortcut = 'h', .name = "help", .command_description = "display help", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_help, }, { .shortcut = 'v', .name = "version", .command_description = "show software and hardware version", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_version, }, { .shortcut = 'u', .name = "uptime", .command_description = "show uptime", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_uptime, }, #if RTC_DATE_TIME { .shortcut = 'D', .name = "date", .command_description = "show/set date and time", .argument = MENU_ARGUMENT_STRING, .argument_description = "[YYYY-MM-DD HH:MM:SS]", .command_handler = &command_datetime, }, #endif { .shortcut = 'r', .name = "reset", .command_description = "reset board", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_reset, }, { .shortcut = 'b', .name = "bootloader", .command_description = "reboot into DFU bootloader", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_bootloader_dfu, }, { .shortcut = 'B', .name = "embedded", .command_description = "boot embedded USART bootloader", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_bootloader_embedded, }, { .shortcut = 'd', .name = "days", .command_description = "on which days to apply the access policy", .argument = MENU_ARGUMENT_STRING, .argument_description = "[0001000, 0/1 for Monday to Sunday]", .command_handler = &command_days, }, { .shortcut = 's', .name = "start", .command_description = "on which time to start the access policy", .argument = MENU_ARGUMENT_STRING, .argument_description = "[HH:MM]", .command_handler = &command_start, }, { .shortcut = 'S', .name = "stop", .command_description = "on which time to stop the access policy", .argument = MENU_ARGUMENT_STRING, .argument_description = "[HH:MM]", .command_handler = &command_stop, }, { .shortcut = 'o', .name = "open", .command_description = "open door", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_open, }, { .shortcut = 'l', .name = "led", .command_description = "test LEDs", .argument = MENU_ARGUMENT_STRING, .argument_description = "on|off", .command_handler = &command_led, }, { .shortcut = 'p', .name = "password", .command_description = "set/show password button sequence", .argument = MENU_ARGUMENT_STRING, .argument_description = "[sequence of 1/2]", .command_handler = &command_pattern, }, }; 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 } static void command_version(void* argument) { (void)argument; // we won't use the argument printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date const uint16_t dev_id = DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK; const uint16_t rev_id = DBGMCU_IDCODE >> 16; printf("chip: ID=0x%03x, rev=0x%04x\n", dev_id, rev_id); // show flash size puts("flash size: "); if (0xffff == DESIG_FLASH_SIZE) { puts("unknown (probably a defective micro-controller\n"); } else { printf("%u KB\n", DESIG_FLASH_SIZE); } // display device identity printf("device id: %08x%08x%04x%04x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0 & 0xffff, DESIG_UNIQUE_ID0 >> 16); } static void command_uptime(void* argument) { (void)argument; // we won't use the argument uint32_t uptime = (rtc_get_counter_val() - time_start) / RTC_TICKS_SECOND; // get time from internal RTC printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60); } #if RTC_DATE_TIME static void command_datetime(void* argument) { char* datetime = (char*)argument; // argument is optional date time if (NULL == argument) { // no date and time provided, just show the current day and time const time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND + rtc_offset; // get time from internal RTC const struct tm* time_tm = localtime(&time_rtc); // convert time const char* days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; // the days of the week printf("date: %s %d-%02d-%02d %02d:%02d:%02d\n", days[time_tm->tm_wday], 1900 + time_tm->tm_year, 1 + time_tm->tm_mon, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec); } else { // date and time provided, set it const char* malformed = "date and time malformed, expecting YYYY-MM-DD HH:MM:SS\n"; struct tm time_tm; // to store the parsed date time if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough printf(malformed); return; } if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && '-' == datetime[4] && isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && '-' == datetime[7] && isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && ' ' == datetime[10] && isdigit((int8_t)datetime[11]) && isdigit((int8_t)datetime[12]) && ':' == datetime[13] && isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && ':' == datetime[16] && isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]))) { // verify format (good enough to not fail parsing) printf(malformed); return; } time_tm.tm_year = strtol(&datetime[0], NULL, 10) - 1900; // parse year time_tm.tm_mon = strtol(&datetime[5], NULL, 10) - 1; // parse month time_tm.tm_mday = strtol(&datetime[8], NULL, 10); // parse day time_tm.tm_hour = strtol(&datetime[11], NULL, 10); // parse hour time_tm.tm_min = strtol(&datetime[14], NULL, 10); // parse minutes time_tm.tm_sec = strtol(&datetime[17], NULL, 10); // parse seconds time_t time_rtc = mktime(&time_tm); // get back seconds time_rtc -= rtc_offset; // remove start offset time_start = time_rtc * RTC_TICKS_SECOND + (rtc_get_counter_val() - time_start); // update uptime with current date rtc_set_counter_val(time_rtc * RTC_TICKS_SECOND); // save date/time to internal RTC printf("date and time saved: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm.tm_year, 1 + time_tm.tm_mon, time_tm.tm_mday, time_tm.tm_hour, time_tm.tm_min, time_tm.tm_sec); } } #endif 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 } static void command_bootloader_dfu(void* argument) { (void)argument; // we won't use the argument // disable relays gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set DFU magic to specific RAM location __dfu_magic[0] = 'D'; __dfu_magic[1] = 'F'; __dfu_magic[2] = 'U'; __dfu_magic[3] = '!'; scb_reset_system(); // reset system (core and peripherals) while (true); // wait for the reset to happen } static void command_bootloader_embedded(void* argument) { (void)argument; // we won't use the argument // disable relays gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set watchdog to exit system memory after some time iwdg_set_period_ms(25000); // set independent watchdog period (26214.4 ms if the max timeout) iwdg_start(); // start independent watchdog iwdg_reset(); // restart timer // start system memory const uint32_t address = 0x1FFFF000; // system memory address SCB_VTOR = (volatile uint32_t)(address); // set vector table to application vector table (store at the beginning of the application) __asm__ volatile ("MSR msp,%0" : :"r"(*(uint32_t*)address)); // set stack pointer to address provided in the beginning of the application (loaded into a register first) (*(void(**)(void))((uint32_t)address + 4))(); // start system memory (by jumping to the reset function which address is stored as second entry of the vector table) while (true); // this should not be reached } /** 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; } // don't handle empty lines if (!str || 0 == strlen(str)) { return; } bool command_handled = false; 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 board_setup(); // setup board #if !defined(STLINKV2) uart_setup(); // setup USART (for printing) #endif usb_cdcacm_setup(); // setup USB CDC ACM (for printing) puts("\nwelcome to the dachboden door panel\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 !(DEBUG) // show watchdog information printf("setup watchdog: %.2fs", WATCHDOG_PERIOD / 1000.0); if (FLASH_OBR & FLASH_OBR_OPTERR) { puts(" (option bytes not set in flash: software wachtdog used, not automatically started at reset)\n"); } else if (FLASH_OBR & FLASH_OBR_WDG_SW) { puts(" (software watchdog used, not automatically started at reset)\n"); } else { puts(" (hardware watchdog used, automatically started at reset)\n"); } #endif // setup RTC puts("setup internal RTC: "); // note: the blue pill LSE oscillator is affected when toggling the onboard LED -> DON'T USE THE ONBOARD LED since we want to use the LSE rtc_auto_awake(RCC_LSE, 32768 / RTC_TICKS_SECOND - 1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect) rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds" nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt time_start = rtc_get_counter_val(); // get start time from internal RTC puts("OK\n"); // setup relays puts("setup relays: "); rcc_periph_clock_enable(GPIO_RCC(RELAY_PANEL_PIN)); // enable clock for GPIO domain gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to leave per default gpio_set_mode(GPIO_PORT(RELAY_PANEL_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_PANEL_PIN)); // set as output to control the transistor controlling the relay rcc_periph_clock_enable(GPIO_RCC(RELAY_BUTTON_PIN)); // enable clock for GPIO domain gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to leave per default gpio_set_mode(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_BUTTON_PIN)); // set as output to control the transistor controlling the relay puts("OK\n"); // setup buttons puts("setup buttons: "); rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt rcc_periph_clock_enable(GPIO_RCC(BUTTON1_PIN)); // enable clock for button gpio_set(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN)); // pull up to be able to detect button push (go low) gpio_set_mode(GPIO_PORT(BUTTON1_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON1_PIN)); // set button pin to input exti_select_source(GPIO_EXTI(BUTTON1_PIN), GPIO_PORT(BUTTON1_PIN)); // mask external interrupt of this pin only for this port exti_set_trigger(GPIO_EXTI(BUTTON1_PIN), EXTI_TRIGGER_FALLING); // trigger when button is pressed exti_enable_request(GPIO_EXTI(BUTTON1_PIN)); // enable external interrupt nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON1_PIN)); // enable interrupt rcc_periph_clock_enable(GPIO_RCC(BUTTON2_PIN)); // enable clock for button gpio_set(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN)); // pull up to be able to detect button push (go low) gpio_set_mode(GPIO_PORT(BUTTON2_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON2_PIN)); // set button pin to input exti_select_source(GPIO_EXTI(BUTTON2_PIN), GPIO_PORT(BUTTON2_PIN)); // mask external interrupt of this pin only for this port exti_set_trigger(GPIO_EXTI(BUTTON2_PIN), EXTI_TRIGGER_FALLING); // trigger when button is pressed exti_enable_request(GPIO_EXTI(BUTTON2_PIN)); // enable external interrupt nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON2_PIN)); // enable interrupt puts("OK\n"); // read opening settings from SRAM puts("reading access settings: "); RCC_APB1ENR |= (RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN); // enable power PWR_CR |= PWR_CR_DBP; // enable access if (0x4223 == BKP_DR1) { // the magic header is present opening_settings.days = BKP_DR2 & 0x7f; opening_settings.start_time = BKP_DR3; opening_settings.stop_time = BKP_DR4; opening_settings.button_pattern[0] = BKP_DR5; opening_settings.button_pattern[1] = BKP_DR6; opening_settings.button_pattern[2] = BKP_DR7; opening_settings.button_pattern[3] = BKP_DR8; opening_settings.button_pattern[4] = BKP_DR9; opening_settings.button_pattern[5] = BKP_DR10; opening_settings.button_pattern[6] = BKP_DR11; opening_settings.button_pattern[7] = BKP_DR12; opening_settings.button_pattern[8] = BKP_DR13; opening_settings.button_pattern[9] = BKP_DR14; puts("loaded\n"); } else { // there are no settings saved memset(&opening_settings, 0, sizeof(struct opening_settings_t)); // clear all values puts("default\n"); } // figure out how many button need to be pressed for (pattern_length = 0; pattern_length < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[pattern_length]; pattern_length++); puts("setup bell LEDs: "); uint8_t animation_progress = 0; // index of the current animation led_ws2812b_setup(); for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) { led_ws2812b_set_rgb(led, 0x10, 0x10 , 0x10); } puts("OK\n"); puts("setup animation timer: "); // setup timer to wait for minimal time before next transmission (after previous transmission or reception) rcc_periph_clock_enable(RCC_TIM(LED_ANIMATION_TIMER)); // enable clock for timer block rcc_periph_reset_pulse(RST_TIM(LED_ANIMATION_TIMER)); // reset timer state timer_set_mode(TIM(LED_ANIMATION_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up timer_set_prescaler(TIM(LED_ANIMATION_TIMER), 1099 - 1); // set the prescaler so this 16 bits timer allows to wait for maximum 1s ( 1 / (72E6 / 1099 / (2**16)) = 1.0003s) timer_set_period(TIM(LED_ANIMATION_TIMER), 0xffff / 16); // the timing is not defined in the specification. I tested until the communication was reliable (all requests get an response) timer_clear_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF); // clear flag timer_enable_irq(TIM(LED_ANIMATION_TIMER), TIM_DIER_UIE); // enable update interrupt for timer nvic_enable_irq(NVIC_TIM_IRQ(LED_ANIMATION_TIMER)); // catch interrupt in service routine puts("OK\n"); // setup terminal terminal_prefix = ""; // set default prefix terminal_process = &process_command; // set central function to process commands terminal_setup(); // start terminal // start main loop bool action = false; // if an action has been performed don't go to sleep button_flag = false; // reset button flag uint32_t last_button_action = 0; // the last time a button has been pressed uint8_t button_pattern[LENGTH(opening_settings.button_pattern)]; // to store the input button pattern uint8_t button_input = 0; // how many buttons have been pressed bool rust_animated = false; // if the rust animation started bool strobe_animated = false; // if the strobe animation started while (true) { // infinite loop iwdg_reset(); // kick the dog if (user_input_available) { // user input is available action = true; // action has been performed led_toggle(); // toggle LED char c = user_input_get(); // store receive character terminal_send(c); // send received character to terminal } if (button_flag || button_pressed) { // user pressed button action = true; // action has been performed sleep_ms(200); // wait a bit to remove noise and double trigger if (!gpio_get(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN))) { button_pressed = 1; } if (!gpio_get(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN))) { button_pressed = 2; } if (button_pressed) { printf("button pressed: %u\n", button_pressed); led_toggle(); // toggle LED if (pattern_length > 0 && opening_apply) { // only check pattern if there is one to compare to // switch off LEDs for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) { led_ws2812b_set_rgb(led, 0, 0, 0); } // start LED animation if (1 == button_pressed) { rust_animated = true; // remember rust animation started led_ws2812b_set_rgb(3, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation led_ws2812b_set_rgb(4, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation led_ws2812b_set_rgb(5, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation strobe_animated = false; // stop strobe animation led_ws2812b_set_rgb(0, 0, 0, 0); // switch LED off led_ws2812b_set_rgb(1, 0, 0, 0); // switch LED off led_ws2812b_set_rgb(2, 0, 0, 0); // switch LED off } else if (2 == button_pressed) { strobe_animated = true; // remember strobe animation started led_ws2812b_set_rgb(0, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation led_ws2812b_set_rgb(1, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation led_ws2812b_set_rgb(2, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation rust_animated = false; // stop rust animation led_ws2812b_set_rgb(3, 0, 0, 0); // switch LED off led_ws2812b_set_rgb(4, 0, 0, 0); // switch LED off led_ws2812b_set_rgb(5, 0, 0, 0); // switch LED off } led_animation_ticks = 0; // reset timer counter animation_progress = 0; // reset animation timer_set_counter(TIM(LED_ANIMATION_TIMER), 0); // reset timer counter to get right duration timer_enable_counter(TIM(LED_ANIMATION_TIMER)); // start timer // store button if (button_input < LENGTH(button_pattern)) { button_pattern[button_input++] = button_pressed; printf("button sequence: %u/%u\n", button_input, pattern_length); last_button_action = rtc_get_counter_val(); // remember last button action } // compare pattern if (button_input >= pattern_length) { bool pattern_valid = true; for (uint8_t i = 0; i < pattern_length; i++) { if (button_pattern[i] != opening_settings.button_pattern[i]) { pattern_valid = false; break; } } // if the correct pattern has been input, press button if (pattern_valid) { puts("button sequence valid\n"); gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay an simulate button press sleep_ms(1000); // hold button a bit gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and release button } button_input = 0; // restart from scratch last_button_action = 0; // restart sequence } } else { // ignore all button entry when not within the opening hours button_input = 0; last_button_action = 0; } // wait until both buttons are released while (!gpio_get(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN)) || !gpio_get(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN))) { //iwdg_reset(); // kick the dog sleep_ms(100); } } // button_pressed button_pressed = 0; // reset button pressed button_flag = false; // reset flag } if (rtc_internal_tick_flag) { // the internal RTC ticked rtc_internal_tick_flag = false; // reset flag action = true; // action has been performed if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one second has passed led_toggle(); // toggle LED (good to indicate if main function is stuck) } if (last_button_action && last_button_action + 5 * RTC_TICKS_SECOND <= rtc_get_counter_val()) { // pattern entry timeout puts("button sequence entry timeout\n"); last_button_action = 0; // reset last button time button_input = 0; // reset pattern input } // always enforce the right state gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to not simulate button press // verify if day matches const time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND + rtc_offset; // get time from internal RTC const struct tm* time_tm = localtime(&time_rtc); // convert time const uint16_t current_time = time_tm->tm_hour * 60 + time_tm->tm_min; // get time of day in minutes const uint8_t day = 6 - ((time_tm->tm_wday + 6) % 7); // get bit for the current day of week if (opening_settings.stop_time > opening_settings.start_time) { // stop time is on same day opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time && current_time < opening_settings.stop_time); } else { // stop time is on next day opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time) || (opening_settings.days & (1 << (day + 1 % 7)) && current_time < opening_settings.stop_time); } if (opening_apply) { // we are in the opening hours //puts("apply\n"); gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay and disconnect button } else { gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to release relay and connect button } } if (led_animation_ticks) { // an LED animation is running if (rust_animated) { if (animation_progress < LENGTH(rust_animation)) { if (led_animation_ticks >= rust_animation[animation_progress][0]) { animation_progress++; // got to next animation step led_animation_ticks = 0; // reset time ticks if (animation_progress < LENGTH(rust_animation)) { // next step of animation reached led_ws2812b_set_rgb(3, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]); led_ws2812b_set_rgb(4, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]); led_ws2812b_set_rgb(5, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]); } else { // end of animation reached timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer led_ws2812b_set_rgb(3, 0, 0, 0); // switch off LED led_ws2812b_set_rgb(4, 0, 0, 0); // switch off LED led_ws2812b_set_rgb(5, 0, 0, 0); // switch off LED } } } else { // end of animation reached led_animation_ticks = 0; // disable check timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer } } if (strobe_animated) { if (animation_progress < LENGTH(strobe_animation)) { if (led_animation_ticks >= strobe_animation[animation_progress][0]) { animation_progress++; // got to next animation step led_animation_ticks = 0; // reset time ticks if (animation_progress < LENGTH(strobe_animation)) { // next step of animation reached led_ws2812b_set_rgb(0, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]); led_ws2812b_set_rgb(1, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]); led_ws2812b_set_rgb(2, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]); } else { // end of animation reached timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer led_ws2812b_set_rgb(3, 0, 0, 0); // switch off LED led_ws2812b_set_rgb(4, 0, 0, 0); // switch off LED led_ws2812b_set_rgb(5, 0, 0, 0); // switch off LED } } } else { // end of animation reached led_animation_ticks = 0; // disable check timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer } } } 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 rtc_internal_tick_flag = true; // notify to show new time } void GPIO_EXTI_ISR(BUTTON1_PIN)(void) // it's the same at BUTTON2_PIN: EXT9_5 { if (exti_get_flag_status(GPIO_EXTI(BUTTON1_PIN))) { exti_reset_request(GPIO_EXTI(BUTTON1_PIN)); // reset interrupt } if (exti_get_flag_status(GPIO_EXTI(BUTTON2_PIN))) { exti_reset_request(GPIO_EXTI(BUTTON2_PIN)); // reset interrupt } button_flag = true; // perform button action } /** interrupt service routine called on animation tick */ void TIM_ISR(LED_ANIMATION_TIMER)(void) { if (timer_get_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF)) { // update event happened timer_clear_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF); // clear flag led_animation_ticks++; // remember one tick passed } }