application: add LED animation

This commit is contained in:
King Kévin 2020-06-21 09:59:52 +02:00
parent 284065b62f
commit a78d45e94b
1 changed files with 223 additions and 46 deletions

View File

@ -38,6 +38,7 @@
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/f1/bkp.h> // backup domain utilities
#include <libopencm3/stm32/timer.h> // 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
}
}