application: first test for pinkeluhr firmware
This commit is contained in:
parent
69402a5525
commit
f58d7c6da4
332
application.c
332
application.c
|
@ -24,6 +24,7 @@
|
|||
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
|
||||
#include <libopencm3/stm32/desig.h> // design utilities
|
||||
#include <libopencm3/stm32/flash.h> // flash utilities
|
||||
#include <libopencm3/stm32/usart.h> // 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue