diff --git a/application.c b/application.c index 6b04e07..cc2b85f 100644 --- a/application.c +++ b/application.c @@ -38,6 +38,7 @@ #include // design utilities #include // flash utilities #include // backup domain utilities +#include // timer utilities /* own libraries */ #include "global.h" // board definitions @@ -46,6 +47,7 @@ #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 @@ -81,15 +83,16 @@ volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ti /** GPIO pin connected to relay, used to simulate button press */ #define RELAY_BUTTON_PIN PB7 /** GPIO for button 1 */ -#define BUTTON1_PIN PB8 +#define BUTTON1_PIN PB9 /** GPIO for button 2 */ -#define BUTTON2_PIN PB9 +#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) */ @@ -98,6 +101,36 @@ static struct opening_settings_t { 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) { @@ -270,14 +303,13 @@ static void command_stop(void* argument) static void command_open(void* argument) { (void)argument; // we won't use the argument - gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay and take control over the button - gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay an simulate button press + 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_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to deactivate relay and release button + 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_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and git control back to button + 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 @@ -307,6 +339,7 @@ static void command_pattern(void* argument) } 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"); @@ -319,6 +352,27 @@ static void command_pattern(void* argument) } } +/** 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[] = { { @@ -411,6 +465,14 @@ static const struct menu_command_t menu_commands[] = { .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", @@ -642,14 +704,14 @@ void main(void) 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_RISING); // trigger when button is released + 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_RISING); // trigger when button is released + 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"); @@ -678,9 +740,28 @@ void main(void) puts("default\n"); } // figure out how many button need to be pressed - uint8_t pattern_length; 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 @@ -692,6 +773,8 @@ void main(void) 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 @@ -702,38 +785,80 @@ void main(void) } if (button_flag || button_pressed) { // user pressed button action = true; // action has been performed - printf("button released: %u\n", button_pressed); - led_toggle(); // toggle LED - if (pattern_length > 0 && opening_apply) { // only check pattern if there is one to compare to - // store button - if (button_input < LENGTH(button_pattern)) { - button_pattern[button_input++] = button_pressed; - 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_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay an simulate button press - sleep_ms(1000); // hold button a bit - gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_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; + 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; } - sleep_ms(100); // wait a bit to remove noise and double trigger + 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 } @@ -749,7 +874,7 @@ void main(void) button_input = 0; // reset pattern input } // always enforce the right state - gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to not simulate button press + 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 @@ -761,9 +886,54 @@ void main(void) 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 - gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay and disconnect button + //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_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to release relay and connect button + 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 @@ -785,11 +955,18 @@ 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 - button_pressed = 1; // remember which button has been released } if (exti_get_flag_status(GPIO_EXTI(BUTTON2_PIN))) { exti_reset_request(GPIO_EXTI(BUTTON2_PIN)); // reset interrupt - button_pressed = 2; // remember which button has been released } 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 + } +}