From f58d7c6da46fdceb4e8fc0649b4150a849419903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Thu, 20 Aug 2020 13:54:42 +0200 Subject: [PATCH] application: first test for pinkeluhr firmware --- application.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 319 insertions(+), 13 deletions(-) diff --git a/application.c b/application.c index 1ac0f63..5366e84 100644 --- a/application.c +++ b/application.c @@ -24,6 +24,7 @@ #include // debug utilities #include // design utilities #include // flash utilities +#include // USART utilities for MP3 player /* own libraries */ #include "global.h" // board definitions @@ -34,6 +35,8 @@ #include "usb_cdcacm.h" // USB CDC ACM utilities #include "terminal.h" // handle the terminal interface #include "menu.h" // menu utilities +#include "led_tm1637.h" // TM1637 7-segment display controller +#include "led_sk6812rgbw.h" // SK6812 RGBW LED controller /** watchdog period in ms */ #define WATCHDOG_PERIOD 10000 @@ -41,16 +44,12 @@ /** 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 */ -#if defined(CORE_BOARD) -#define RTC_DATE_TIME 1 -#else #define RTC_DATE_TIME 0 -#endif /** number of RTC ticks per second * @note use integer divider of oscillator to keep second precision */ -#define RTC_TICKS_SECOND 4 +#define RTC_TICKS_SECOND 16 #if defined(RTC_DATE_TIME) && RTC_DATE_TIME /** the start time from which to RTC ticks count @@ -62,12 +61,55 @@ const time_t rtc_offset = 1577833200; // We 1. Jan 00:00:00 CET 2020 /** RTC time when device is started */ static time_t time_start = 0; +/** time when the door has been closed/locked (RTC time) */ +static uint32_t timer_door_closed = 0; + /** @defgroup main_flags flag set in interrupts to be processed in main task * @{ */ static volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */ +static volatile bool mp3_rx_flag = false; /**< if data has been received from the MP3 player */ +static volatile bool door_flag = false; /**< set when the door lock switch changes */ /** @} */ +/** switch in the door to verify if its locked (closed when locked) */ +#define DOOR_PIN PB8 + +/** USART port used to communicate with catalex MP3 player */ +#define MP3_UART 2 +/** data received from MP3 player */ +static volatile uint8_t mp3_rx_data[10]; +/** number of byte received from MP3 player */ +static volatile uint8_t mp3_rx_len; + +/** catalex MP3 player commands */ +enum mp3_commands_t { + MP3_CMD_NEXT_SONG = 0x01, + MP3_CMD_PREV_SONG = 0x02, + MP3_CMD_PLAY_W_INDEX = 0x03, + MP3_CMD_VOLUME_UP = 0x04, + MP3_CMD_VOLUME_DOWN = 0x05, + MP3_CMD_SET_VOLUME = 0x06, + MP3_CMD_SINGLE_CYCLE_PLAY = 0x08, + MP3_CMD_SEL_DEV = 0x09, + MP3_CMD_SLEEP_MODE = 0x0A, + MP3_CMD_WAKE_UP = 0x0B, + MP3_CMD_RESET = 0x0C, + MP3_CMD_PLAY = 0x0D, + MP3_CMD_PAUSE = 0x0E, + MP3_CMD_PLAY_FOLDER_FILE = 0x0F, + MP3_CMD_STOP_PLAY = 0x16, + MP3_CMD_FOLDER_CYCLE = 0x17, + MP3_CMD_SHUFFLE_PLAY = 0x18, + MP3_CMD_SET_SINGLE_CYCLE = 0x19, + MP3_CMD_SET_DAC = 0x1A, + MP3_CMD_PLAY_W_VOL = 0x22, + MP3_CMD_QUERY_STATUS = 0x42, + MP3_CMD_QUERY_FLDR_TRACKS = 0x4e, + MP3_CMD_QUERY_TOT_TRACKS = 0x48, + MP3_CMD_QUERY_FLDR_COUNT = 0x4f, +}; + size_t putc(char c) { size_t length = 0; // number of characters printed @@ -122,6 +164,139 @@ static void command_reset(void* argument); */ static void command_bootloader(void* argument); +/** process response received from MP3 player + * @return if a valid response has been received + */ +static bool mp3_response(void) +{ + if (mp3_rx_len < 10) { // responses are always at least 10 bytes long + return false; + } + if (0x7e != mp3_rx_data[0]) { // response always starts with 0x7e + mp3_rx_len = 0; // reset message + return false; + } + if (0xff != mp3_rx_data[1]) { // version is always 0xff + mp3_rx_len = 0; // reset message + return false; + } + if (mp3_rx_data[2] > LENGTH(mp3_rx_data) - 3) { // message is longer than buffer + mp3_rx_len = 0; // reset message + return false; + } + if (mp3_rx_data[2] < mp3_rx_len - 2U - 2U) { // message is not complete + return false; + } + if (0xef != mp3_rx_data[3 + mp3_rx_data[2]]) { // end by is not correct + return false; + } +/* + // display message + puts("> "); + for (uint8_t i = 0; i < mp3_rx_len; i++) { + printf("%02x ", mp3_rx_data[i]); + } + putc('\n'); +*/ + // check checksum + int16_t checksum = 0; + for (uint8_t i = 1; i < mp3_rx_len && i < mp3_rx_data[2] + 1U; i++) { + checksum += mp3_rx_data[i]; + } + checksum = -checksum; + const int16_t expected = ((mp3_rx_data[mp3_rx_data[2] + 1] << 8) + mp3_rx_data[mp3_rx_data[2] + 2]); + if (expected != checksum) { + printf("wrong checksum: should=%+04x is=%+04x\n", expected, checksum); + mp3_rx_len = 0; // reset message + return false; + } + switch (mp3_rx_data[3]) { + case 0x3a: + puts("card inserted"); + break; + case 0x3b: + puts("card removed"); + break; + case 0x3d: + puts("track finished"); + break; + case 0x40: + puts("error"); + break; + case 0x41: + puts("ack"); + break; + case 0x4e: + puts("track count"); + break; + default: + printf("unknown: %+02x", mp3_rx_data[3]); + break; + } + putc('\n'); + mp3_rx_len = 0; // reset message + return true; +} +/** send command to MP3 playes + * @param[in] cmd command to send + * @param[in] data argument for command (such as track number) + */ +static void mp3_command(enum mp3_commands_t cmd, uint16_t data) +{ + puts("MP3 command: "); + switch (cmd) { + case MP3_CMD_PLAY: + puts("play"); + break; + case MP3_CMD_NEXT_SONG: + puts("next"); + break; + case MP3_CMD_RESET: + puts("reset"); + break; + default: + printf("%+02x"); + break; + } + putc(' '); + + uint8_t command[] = {0x7e, 0xff, 0x06, cmd, 0x01, data >> 8, data, 0xef}; // command template (with feedback) + for (uint8_t i = 0; i < LENGTH(command); i++) { + usart_send_blocking(USART(MP3_UART), command[i]); + } +} + +/** play MP3 + * @param[in] argument no argument required + */ +static void command_play(void* argument) +{ + (void)argument; // argument not used + puts("sending play command\n"); + mp3_command(MP3_CMD_PLAY, 0); +} + +/** open/close door + * @param[in] argument no argument required + */ +static void command_door(void* argument) +{ + (void)argument; // argument not used + if (0 == timer_door_closed) { + puts("closing door\n"); + timer_door_closed = rtc_get_counter_val(); + led_tm1637_time(0, 0); + led_tm1637_on(); + mp3_command(MP3_CMD_PLAY, 0); + } else { + puts("opening door\n"); + timer_door_closed = 0; + led_tm1637_off(); + mp3_command(MP3_CMD_STOP_PLAY, 0); // stop playing + } +} + + /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { @@ -174,6 +349,22 @@ static const struct menu_command_t menu_commands[] = { .argument_description = NULL, .command_handler = &command_bootloader, }, + { + .shortcut = 'p', + .name = "play", + .command_description = "play MP3 song", + .argument = MENU_ARGUMENT_NONE, + .argument_description = NULL, + .command_handler = &command_play, + }, + { + .shortcut = 'd', + .name = "door", + .command_description = "open/close door", + .argument = MENU_ARGUMENT_NONE, + .argument_description = NULL, + .command_handler = &command_door, + }, }; static void command_help(void* argument) @@ -273,6 +464,19 @@ static void process_command(char* str) } } +/** switch LED sign to besetzt/occupied + * @param[in] besetzt if the sign should light up besetzt/occupied (true) or frei/free(false) + */ +static void leds_sign(bool besetzt) +{ + for (uint8_t led = 0; led < LED_SK6812RGBW_LEDS / 2; led++) { + led_sk6812rgbw_set_rgb(led, 0, besetzt ? 0 : 0xff, 0, 0); + } + for (uint8_t led = LED_SK6812RGBW_LEDS / 2; led < LED_SK6812RGBW_LEDS; led++) { + led_sk6812rgbw_set_rgb(led, besetzt ? 0xff : 0 , 0, 0, 0); + } +} + /** program entry point * this is the firmware function started by the micro-controller */ @@ -352,6 +556,64 @@ void main(void) time_start = rtc_get_counter_val(); // get start time from internal RTC puts("OK\n"); + puts("setup TM1637 7-segment display: "); + bool led_tm1637_setup_ok = true; + led_tm1637_setup(); + led_tm1637_setup_ok &= led_tm1637_brightness(LED_TM1637_14DIV16); // set maximum brightess + led_tm1637_setup_ok &= led_tm1637_time(88, 88); // light up all segments + led_tm1637_setup_ok &= led_tm1637_on(); // switch on to test it + sleep_ms(1000); // let used check display + led_tm1637_setup_ok &= led_tm1637_off(); // switch off and let main loop handle it + if (led_tm1637_setup_ok) { + puts("OK\n"); + } else { + puts("error\n"); + } + + // setup LEDs + puts("setup SK6812RGBW LEDs: "); + led_sk6812rgbw_setup(); + for (uint8_t led = 0; led < LED_SK6812RGBW_LEDS; led++) { + led_sk6812rgbw_set_rgb(led, 0, 0, 0, 64); // switch white on + } + sleep_ms(1000); // give user time to verify + for (uint8_t led = 0; led < LED_SK6812RGBW_LEDS; led++) { + led_sk6812rgbw_set_rgb(led, 0, 0, 0, 0); // switch all off + } + leds_sign(0 != timer_door_closed); + puts("OK\n"); + + puts("setup catalex YX5300 MP3 player: "); + rcc_periph_clock_enable(RCC_USART_PORT(MP3_UART)); // enable clock for UART port peripheral + rcc_periph_clock_enable(RCC_USART(MP3_UART)); // enable clock for UART peripheral + rcc_periph_reset_pulse(RST_USART(MP3_UART)); // reset peripheral + rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (UART) + gpio_set_mode(USART_TX_PORT(MP3_UART), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_TX_PIN(MP3_UART)); // setup GPIO pin UART transmit + gpio_set_mode(USART_RX_PORT(MP3_UART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_RX_PIN(MP3_UART)); // setup GPIO pin UART receive + gpio_set(USART_RX_PORT(MP3_UART), USART_RX_PIN(MP3_UART)); // pull up to avoid noise when not connected + usart_set_baudrate(USART(MP3_UART), 9600); + usart_set_databits(USART(MP3_UART), 8); + usart_set_stopbits(USART(MP3_UART), USART_STOPBITS_1); + usart_set_mode(USART(MP3_UART), USART_MODE_TX_RX); + usart_set_parity(USART(MP3_UART), USART_PARITY_NONE); + usart_set_flow_control(USART(MP3_UART), USART_FLOWCONTROL_NONE); + usart_enable_rx_interrupt(USART(MP3_UART)); // enable receive interrupt + nvic_enable_irq(USART_IRQ(MP3_UART)); // enable the UART interrupt + usart_enable(USART(MP3_UART)); // enable UART + puts("OK\n"); + mp3_command(MP3_CMD_RESET, 0); // reset all settings + + puts("setup door lock switch: "); + rcc_periph_clock_enable(GPIO_RCC(DOOR_PIN)); // enable clock for button + gpio_set_mode(GPIO_PORT(DOOR_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(DOOR_PIN)); // set button pin to input + rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt + exti_select_source(GPIO_EXTI(DOOR_PIN), GPIO_PORT(DOOR_PIN)); // mask external interrupt of this pin only for this port + gpio_set(GPIO_PORT(DOOR_PIN), GPIO_PIN(DOOR_PIN)); // pull up to be able to detect button push (go low) + exti_set_trigger(GPIO_EXTI(DOOR_PIN), EXTI_TRIGGER_BOTH); // trigger on change + exti_enable_request(GPIO_EXTI(DOOR_PIN)); // enable external interrupt + nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(DOOR_PIN)); // enable interrupt + puts("OK\n"); + // setup terminal terminal_prefix = ""; // set default prefix terminal_process = &process_command; // set central function to process commands @@ -368,19 +630,44 @@ void main(void) char c = user_input_get(); // store receive character terminal_send(c); // send received character to terminal } - if (button_flag) { // user pressed button - action = true; // action has been performed - puts("button pressed\n"); - led_toggle(); // toggle LED - sleep_ms(100); // wait a bit to remove noise and double trigger - 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 seond has passed + 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 (timer_door_closed && 0 == ((rtc_get_counter_val() - timer_door_closed) % RTC_TICKS_SECOND)) { // on second has passed since since door closed + uint16_t time_passed = (rtc_get_counter_val() - timer_door_closed) / RTC_TICKS_SECOND; // in seconds + led_tm1637_time(time_passed / 60, time_passed % 60); + if (time_passed && 0 == (time_passed % 5)) { + mp3_command(MP3_CMD_NEXT_SONG, 0); + } + } + } + if (door_flag) { // door switch state changed + sleep_ms(100); // wait a bit to de-noise before we check the door lock state + const bool closed = (0 == gpio_get(GPIO_PORT(DOOR_PIN), GPIO_PIN(DOOR_PIN))); // get door lock state + door_flag = false; // clear flag + action = true; // action has been performed + if (closed && 0 == timer_door_closed) { + puts("door closed\n"); + timer_door_closed = rtc_get_counter_val(); + leds_sign(true); + led_tm1637_time(0, 0); + led_tm1637_on(); + mp3_command(MP3_CMD_PLAY, 0); + } else if (!closed && timer_door_closed) { + puts("door opned\n"); + timer_door_closed = 0; + leds_sign(false); + led_tm1637_off(); + mp3_command(MP3_CMD_STOP_PLAY, 0); // stop playing + } + } + if (mp3_rx_flag) { // data from MP3 player + mp3_response(); // check for response + mp3_rx_flag = false; // clear flag + action = true; // action has been performed } if (action) { // go to sleep if nothing had to be done, else recheck for activity action = false; @@ -396,3 +683,22 @@ void rtc_isr(void) rtc_clear_flag(RTC_SEC); // clear flag rtc_internal_tick_flag = true; // notify to show new time } + +/** UART interrupt service routine called when data has been received */ +void USART_ISR(MP3_UART)(void) +{ + if (usart_get_flag(USART(MP3_UART), USART_SR_RXNE)) { // data has been transmitted + if (mp3_rx_len < LENGTH(mp3_rx_data)) { + mp3_rx_data[mp3_rx_len++] = usart_recv(USART(MP3_UART)); // store received data + } else { + usart_recv(USART(MP3_UART)); // discard data + } + mp3_rx_flag = true; // notify user + } +} + +void GPIO_EXTI_ISR(DOOR_PIN)(void) +{ + exti_reset_request(GPIO_EXTI(DOOR_PIN)); // reset interrupt/clear flag + door_flag = true; // notify main loop +}