2020-11-06 10:00:46 +01:00
/** dachboden klo-assistant firmware
2020-02-17 18:08:17 +01:00
* @ file
2016-08-14 21:02:38 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2021-04-22 21:18:56 +02:00
* @ date 2016 - 2021
2016-08-14 21:02:38 +02:00
*/
2016-01-17 14:54:54 +01:00
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // standard utilities
2016-08-14 21:02:38 +02:00
# include <string.h> // string utilities
2019-06-18 17:43:43 +02:00
# include <time.h> // date/time utilities
# include <ctype.h> // utilities to check chars
2016-01-17 14:54:54 +01:00
/* STM32 (including CM3) libraries */
2016-01-18 16:23:35 +01:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/cm3/scb.h> // vector table definition
2016-01-29 11:25:30 +01:00
# include <libopencm3/cm3/nvic.h> // interrupt utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/stm32/rcc.h> // real-time control clock library
2016-01-29 11:25:30 +01:00
# include <libopencm3/stm32/exti.h> // external interrupt utilities
2016-08-14 21:02:38 +02:00
# include <libopencm3/stm32/rtc.h> // real time clock utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
# include <libopencm3/stm32/dbgmcu.h> // debug utilities
2018-04-06 17:37:17 +02:00
# include <libopencm3/stm32/desig.h> // design utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/flash.h> // flash utilities
2020-08-20 13:54:42 +02:00
# include <libopencm3/stm32/usart.h> // USART utilities for MP3 player
2021-06-24 21:49:32 +02:00
# include <libopencm3/stm32/adc.h> // ADC utilities for random seed
2016-01-17 14:54:54 +01:00
/* own libraries */
2016-01-28 21:21:50 +01:00
# include "global.h" // board definitions
2017-04-03 13:32:45 +02:00
# include "print.h" // printing utilities
2019-09-19 11:59:02 +02:00
# if !defined(STLINKV2)
2018-02-18 15:18:42 +01:00
# include "uart.h" // USART utilities
2018-10-29 15:03:29 +01:00
# endif
2016-01-18 16:23:35 +01:00
# include "usb_cdcacm.h" // USB CDC ACM utilities
2018-04-06 17:37:17 +02:00
# include "terminal.h" // handle the terminal interface
# include "menu.h" // menu utilities
2020-08-20 13:54:42 +02:00
# include "led_tm1637.h" // TM1637 7-segment display controller
# include "led_sk6812rgbw.h" // SK6812 RGBW LED controller
2016-01-17 14:54:54 +01:00
2020-02-02 21:19:03 +01:00
/** watchdog period in ms */
# define WATCHDOG_PERIOD 10000
2019-06-18 17:43:43 +02:00
/** 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
*/
2020-02-02 21:19:03 +01:00
# define RTC_DATE_TIME 0
/** number of RTC ticks per second
* @ note use integer divider of oscillator to keep second precision
*/
2020-08-20 13:54:42 +02:00
# define RTC_TICKS_SECOND 16
2019-06-18 17:43:43 +02:00
2020-03-05 14:04:51 +01:00
# 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
2019-06-18 17:43:43 +02:00
/** RTC time when device is started */
static time_t time_start = 0 ;
2016-10-23 17:42:27 +02:00
2020-08-20 13:54:42 +02:00
/** time when the door has been closed/locked (RTC time) */
static uint32_t timer_door_closed = 0 ;
2016-08-14 21:02:38 +02:00
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @ {
*/
2020-06-10 17:40:54 +02:00
static volatile bool rtc_internal_tick_flag = false ; /**< flag set when internal RTC ticked */
2020-08-20 13:54:42 +02:00
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 */
2016-08-14 21:02:38 +02:00
/** @} */
2020-09-27 12:00:22 +02:00
/** switch in the door to verify if its locked (closed when locked)
* @ note use external pull - up resistor ( 10 k to 5 V ) since the internal pull up does not cope for the long cable
*/
2020-08-20 13:54:42 +02:00
# 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 ;
2020-09-27 12:00:22 +02:00
/** pin to simulate button press (go high) to switch on/off Bluetooth audio transmitter
* - 2 s press : on / off
* - 5 s press when off : pair
*/
# define BTAUDIO_BUTTON_PIN PA0
/** status output (e.g. LED) of Bluetooth audio transmitter
* - off : off
* - fast flash ( periodic blink every 0.3 s ) : pairing
* - slow flash ( double blink every 5.3 s ) : unconnected
* - very slow flash ( single blink every 10 s ) : connected
*/
# define BTAUDIO_STATUS_PIN PA1
/** time (in ms) to press on the button to switch Bluetooth audio transmitter */
# define BTAUDIO_ON 3500
/** number of timer led Bluetooth audio transmitter blinked, to determine the status */
static uint8_t btaudio_status = 0 ;
2020-08-20 13:54:42 +02:00
/** 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 ,
} ;
2020-11-06 10:00:46 +01:00
/** which song group we are currently playing */
static enum playing_state_t {
PLAYING_STATE_OFF , /**< playing any song or track */
PLAYING_STATE_INTRO , /**< playing the welcome message */
PLAYING_STATE_SONG , /**< playing any song */
PLAYING_STATE_TIMER_INTRO , /**< playing the time announcement intro */
PLAYING_STATE_TIMER_MINUTES , /**< playing the number of minutes */
PLAYING_STATE_TIMER_MINUTE , /**< playing the minute announcement */
PLAYING_STATE_TIMER_SECONDS , /**< playing the number of seconds */
PLAYING_STATE_TIMER_OUTRO , /**< playing the timer announcement closing word */
PLAYING_STATE_TALK , /**< playing any talk track */
PLAYING_STATE_EXIT , /**< playing any exit message */
2021-04-23 14:59:41 +02:00
PLAYING_STATE_TECHNO , /**< continuously play techno songs (use secret open/close sequence to enter modus) */
PLAYING_STATE_KANGURU , /**< continuously play kanguru sketches (use secret open/close/open/close sequence to enter modus) */
2020-11-06 10:00:46 +01:00
} playing_state = PLAYING_STATE_OFF ; /**< which song group we are currently playing */
/** RTC timestamps when the last MP3 response track finished has been received */
static uint32_t last_finished = 0 ;
2021-04-22 21:21:26 +02:00
/** number of possible welcome tacks */
# define WELCOME_TRACKS 15
2020-09-27 12:00:22 +02:00
/** number of possible music tracks */
2021-04-23 14:57:35 +02:00
# define MUSIC_TRACKS 30
2020-11-06 10:00:46 +01:00
/** number of possible exit message tracks */
2021-04-23 14:57:35 +02:00
# define EXIT_TRACKS 19
2020-09-27 12:00:22 +02:00
/** number of possible talk tracks */
2021-04-22 21:20:37 +02:00
# define TALK_TRACKS 29
2021-04-23 14:59:41 +02:00
/** number of possible techno music tracks */
# define TECHNO_TRACKS 237
/** number of possible kanguru */
# define KANGURU_TRACKS 81
2020-09-27 12:00:22 +02:00
2021-04-22 21:28:24 +02:00
/** folder number for welcome tracks */
# define WELCOME_FOLDER 1
/** folder number for music tracks */
# define MUSIC_FOLDER 2
/** folder number for exit messages tracks */
# define EXIT_FOLDER 4
/** folder number for talk tracks */
# define TALK_FOLDER 3
/** folder number for time number announcement tracks */
# define TIME_FOLDER 5
2021-04-23 14:59:41 +02:00
/** folder number for techno music tracks */
# define TECHNO_FOLDER 6
/** folder number for kanguru tracks */
# define KANGURU_FOLDER 7
2017-04-03 13:32:45 +02:00
size_t putc ( char c )
2016-01-17 14:54:54 +01:00
{
2017-04-03 13:32:45 +02:00
size_t length = 0 ; // number of characters printed
2018-04-06 17:37:17 +02:00
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
2019-09-19 11:59:02 +02:00
# if !defined(STLINKV2)
2018-02-18 15:18:42 +01:00
uart_putchar_nonblocking ( ' \r ' ) ; // send CR over USART
2018-10-29 15:03:29 +01:00
# endif
2017-04-19 16:24:07 +02:00
usb_cdcacm_putchar ( ' \r ' ) ; // send CR over USB
2018-04-06 17:37:17 +02:00
length + + ; // remember we printed 1 character
2016-01-17 14:54:54 +01:00
}
}
2019-09-19 11:59:02 +02:00
# if !defined(STLINKV2)
2018-04-06 17:37:17 +02:00
uart_putchar_nonblocking ( c ) ; // send byte over USART
2018-10-29 15:03:29 +01:00
# endif
2018-04-06 17:37:17 +02:00
usb_cdcacm_putchar ( c ) ; // send byte over USB
length + + ; // remember we printed 1 character
last_c = c ; // remember last character
2019-03-26 19:27:40 +01:00
return length ; // return number of characters printed
2016-01-17 14:54:54 +01:00
}
2018-04-06 17:37:17 +02:00
/** 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 ) ;
2019-06-18 17:43:43 +02:00
# if RTC_DATE_TIME
/** show date and time
* @ param [ in ] argument date and time to set
*/
static void command_datetime ( void * argument ) ;
# endif
2018-04-06 17:37:17 +02:00
/** 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 ( void * argument ) ;
2021-06-24 21:49:57 +02:00
/** get random track from folder
* @ param [ in ] folder MP3 SD card folder number
* @ return a track within the folder ( 0 on invalid folder )
*/
static uint16_t track_random ( uint8_t folder )
{
uint8_t tracks ; // number of available tracks in folder
switch ( folder ) {
case WELCOME_FOLDER :
tracks = WELCOME_TRACKS ;
break ;
case MUSIC_FOLDER :
tracks = MUSIC_TRACKS ;
break ;
case EXIT_FOLDER :
tracks = EXIT_TRACKS ;
break ;
case TALK_FOLDER :
tracks = TALK_TRACKS ;
break ;
case TECHNO_FOLDER :
tracks = TECHNO_TRACKS ;
break ;
case KANGURU_FOLDER :
tracks = KANGURU_TRACKS ;
break ;
default : // invalid folder
return 0 ;
}
static uint16_t played [ 10 ] = { 0 } ; // the last tracks played
bool track_ok = false ; // when we found a valid track number
uint16_t track_nr ; // the track we will play
while ( ! track_ok ) {
track_nr = ( folder < < 8 ) + ( rand ( ) % tracks ) + 1 ; // generate random track number
track_ok = true ;
for ( uint8_t i = 0 ; i < LENGTH ( played ) ; i + + ) { // go though played list
if ( track_nr = = played [ i ] ) { // we already played the track
track_ok = false ; // the track we have is not OK
break ;
}
}
}
for ( uint8_t i = 0 ; i < LENGTH ( played ) - 1 ; i + + ) { // shift playlist
played [ i + 1 ] = played [ i ] ;
}
played [ 0 ] = track_nr ; // save the track we chose
return track_nr ; // return the random track within folder
}
2020-11-06 10:00:46 +01:00
/** 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_STOP_PLAY :
puts ( " stop " ) ;
break ;
case MP3_CMD_PLAY_FOLDER_FILE :
puts ( " playing " ) ;
const uint8_t folder = data > > 8 ;
const uint8_t track = data & 0xff ;
printf ( " %02u/%03u " , folder , track ) ;
if ( 0 = = folder ) {
puts ( " (invalid input folder 0) " ) ;
}
if ( 0 = = track ) {
puts ( " (invalid input track 0 " ) ;
}
break ;
case MP3_CMD_SET_VOLUME :
printf ( " set volume to %u " , data ) ;
break ;
case MP3_CMD_RESET :
puts ( " reset " ) ;
break ;
default :
printf ( " %+02x " ) ;
break ;
}
putc ( ' \n ' ) ;
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 ] ) ;
}
}
2020-08-20 13:54:42 +02:00
/** 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 ;
}
2020-09-27 12:00:22 +02:00
puts ( " MP3 response: " ) ;
2020-08-20 13:54:42 +02:00
/*
// display message
puts ( " > " ) ;
for ( uint8_t i = 0 ; i < mp3_rx_len ; i + + ) {
printf ( " %02x " , mp3_rx_data [ i ] ) ;
}
putc ( ' \n ' ) ;
*/
2020-09-27 12:00:22 +02:00
2020-08-20 13:54:42 +02:00
// 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 " ) ;
2020-11-06 10:00:46 +01:00
if ( ( rtc_get_counter_val ( ) - last_finished ) < 2 ) {
const uint16_t time_passed = ( rtc_get_counter_val ( ) - timer_door_closed ) / RTC_TICKS_SECOND ; // how many seconds have passed since door has been closed
switch ( playing_state ) {
case PLAYING_STATE_INTRO : // the welcome message finished
2021-06-24 21:49:57 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , track_random ( MUSIC_FOLDER ) ) ; // play random music track
2021-04-22 21:28:24 +02:00
playing_state = PLAYING_STATE_SONG ; // remember we are playing a song (for the first time)
2020-11-06 10:00:46 +01:00
break ;
case PLAYING_STATE_SONG : // the song finished
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + 65 ) ; // play time announcement
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_TIMER_INTRO ; // remember we are playing the timer announcement
break ;
case PLAYING_STATE_TIMER_INTRO : // the time intro finished
if ( 0 = = ( time_passed / 60 ) ) {
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + 60 ) ; // play number of minutes announcement
2020-11-06 10:00:46 +01:00
} else {
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + ( time_passed / 60 ) ) ; // play number of minutes announcement
2020-11-06 10:00:46 +01:00
}
playing_state = PLAYING_STATE_TIMER_MINUTES ; // remember we are playing the number of minutes
break ;
case PLAYING_STATE_TIMER_MINUTES : // the minutes announcement finished
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + 62 ) ; // play minute announcement
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_TIMER_MINUTE ; // remember we are playing the minute announcement
break ;
case PLAYING_STATE_TIMER_MINUTE : // the minute announcement finished
if ( 0 = = ( time_passed % 60 ) ) {
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + 60 ) ; // play number of seconds announcement
2020-11-06 10:00:46 +01:00
} else {
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + ( time_passed % 60 ) ) ; // play number of seconds announcement
2020-11-06 10:00:46 +01:00
}
playing_state = PLAYING_STATE_TIMER_SECONDS ; // remember we are playing the number of seconds
break ;
case PLAYING_STATE_TIMER_SECONDS : // the number of seconds finished
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TIME_FOLDER < < 8 ) + 64 ) ; // play time outro announcement
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_TIMER_OUTRO ; // remember we are playing the timer outro announcement
break ;
case PLAYING_STATE_TIMER_OUTRO : // the timer outro announcement finished
2021-06-24 21:49:57 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , track_random ( TALK_FOLDER ) ) ; // play random talk track
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_TALK ; // remember we are playing the talk track
break ;
case PLAYING_STATE_TALK : // the talk track finished
2021-06-24 21:49:57 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , track_random ( MUSIC_FOLDER ) ) ; // play random music track
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_SONG ; // remember we are playing a song (again)
break ;
2021-04-23 14:59:41 +02:00
case PLAYING_STATE_TECHNO : // techno song completed, play the next one
2021-06-24 21:49:57 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , track_random ( TECHNO_FOLDER ) ) ; // play random techno music track
2021-04-23 14:59:41 +02:00
break ;
case PLAYING_STATE_KANGURU : // kanguru sketch completed, play the next one
2021-06-24 21:49:57 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , track_random ( KANGURU_FOLDER ) ) ; // play random techno music track
2021-04-23 14:59:41 +02:00
break ;
2020-11-06 10:00:46 +01:00
default :
playing_state = PLAYING_STATE_OFF ; // we won't play anything else
break ;
}
}
last_finished = rtc_get_counter_val ( ) ; // remember last time we received the ack
2020-08-20 13:54:42 +02:00
break ;
case 0x40 :
puts ( " error " ) ;
2021-06-24 20:30:55 +02:00
led_tm1637_text ( " sder " ) ; // show we have an issue with the SD card
led_tm1637_on ( ) ; // switch on display
2020-08-20 13:54:42 +02:00
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 ;
}
/** play MP3
* @ param [ in ] argument no argument required
*/
static void command_play ( void * argument )
{
2020-09-27 12:00:22 +02:00
if ( NULL = = argument ) {
puts ( " playing first track \n " ) ;
mp3_command ( MP3_CMD_PLAY , 1 ) ; // play first song
} else {
const uint32_t track = * ( uint32_t * ) argument ; // argument not used
printf ( " playing track %u \n " , track ) ;
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( ( track / 100 ) < < 8 ) + ( track % 100 ) ) ; // play specific track
}
2020-08-20 13:54:42 +02:00
}
2020-09-27 12:00:22 +02:00
/** put Bluetooth audi transmitter into pairing mode
2020-08-20 13:54:42 +02:00
* @ param [ in ] argument no argument required
*/
2020-09-27 12:00:22 +02:00
static void command_pair ( void * argument )
2020-08-20 13:54:42 +02:00
{
( void ) argument ; // argument not used
2020-09-27 12:00:22 +02:00
// we assume the transmitter is on
puts ( " switching BT audio off \n " ) ;
gpio_set ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // press button to switch off
sleep_ms ( BTAUDIO_ON ) ; // keep pressed
gpio_clear ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // release button
puts ( " putting BT audio into pairing mode \n " ) ;
sleep_ms ( 1000 ) ; // wait a bit
gpio_set ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // press button to put into pairing mode
sleep_ms ( 6000 ) ; // keep pressed
gpio_clear ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // release button
puts ( " BT transmitter should be looking for speaker \n " ) ;
2020-08-20 13:54:42 +02:00
}
2018-04-06 17:37:17 +02:00
/** 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 ,
} ,
2019-06-18 17:43:43 +02:00
# if RTC_DATE_TIME
{
. shortcut = ' d ' ,
. name = " date " ,
2019-07-29 18:33:05 +02:00
. command_description = " show/set date and time " ,
2019-06-18 17:43:43 +02:00
. argument = MENU_ARGUMENT_STRING ,
. argument_description = " [YYYY-MM-DD HH:MM:SS] " ,
. command_handler = & command_datetime ,
} ,
# endif
2018-04-06 17:37:17 +02:00
{
. 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 ,
} ,
2020-08-20 13:54:42 +02:00
{
. shortcut = ' p ' ,
. name = " play " ,
. command_description = " play MP3 song " ,
2020-09-27 12:00:22 +02:00
. argument = MENU_ARGUMENT_UNSIGNED ,
. argument_description = " track folder+number fftt " ,
2020-08-20 13:54:42 +02:00
. command_handler = & command_play ,
} ,
{
2020-09-27 12:00:22 +02:00
. shortcut = ' a ' ,
. name = " audio " ,
. command_description = " put BT audio transmitter into pairing mode " ,
2020-08-20 13:54:42 +02:00
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
2020-09-27 12:00:22 +02:00
. command_handler = & command_pair ,
2020-08-20 13:54:42 +02:00
} ,
2018-04-06 17:37:17 +02:00
} ;
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
2020-06-24 11:54:33 +02:00
printf ( " device serial: %08x%08x%04x%04x \n " , DESIG_UNIQUE_ID2 , DESIG_UNIQUE_ID1 , DESIG_UNIQUE_ID0 & 0xffff , DESIG_UNIQUE_ID0 > > 16 ) ; // not that the half-works are reversed in the first word
2018-04-06 17:37:17 +02:00
}
2020-06-09 00:59:19 +02:00
static void command_uptime ( void * argument )
{
( void ) argument ; // we won't use the argument
const 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 ) ;
}
2019-06-18 17:43:43 +02:00
# 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
2020-03-05 14:04:51 +01:00
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 ) ;
2019-06-18 17:43:43 +02:00
} 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
2020-03-05 14:04:51 +01:00
time_tm . tm_mon = strtol ( & datetime [ 5 ] , NULL , 10 ) - 1 ; // parse month
2019-06-18 17:43:43 +02:00
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
2020-03-05 14:04:51 +01:00
time_rtc - = rtc_offset ; // remove start offset
2020-02-17 18:08:17 +01:00
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
2020-03-05 14:04:51 +01:00
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 ) ;
2019-06-18 17:43:43 +02:00
}
}
# endif
2018-04-06 17:37:17 +02:00
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 ( void * argument )
{
( void ) argument ; // we won't use the argument
2020-01-02 13:01:40 +01:00
// 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)
2018-04-06 17:37:17 +02:00
while ( true ) ; // wait for the reset to happen
}
2016-01-17 14:54:54 +01:00
2016-08-14 21:02:38 +02:00
/** process user command
* @ param [ in ] str user command string ( \ 0 ended )
*/
static void process_command ( char * str )
2016-01-17 14:54:54 +01:00
{
2018-04-06 17:37:17 +02:00
// ensure actions are available
2019-06-18 17:44:50 +02:00
if ( NULL = = menu_commands | | 0 = = LENGTH ( menu_commands ) ) {
2018-04-06 17:37:17 +02:00
return ;
2016-08-14 21:02:38 +02:00
}
2018-04-06 17:37:17 +02:00
// don't handle empty lines
2019-06-18 17:44:50 +02:00
if ( ! str | | 0 = = strlen ( str ) ) {
2018-04-06 17:37:17 +02:00
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 " ) ;
2016-08-14 21:02:38 +02:00
}
2016-01-17 14:54:54 +01:00
}
2020-08-20 13:54:42 +02:00
/** 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 ) ;
}
}
2016-08-14 21:02:38 +02:00
/** program entry point
* this is the firmware function started by the micro - controller
*/
2016-10-23 17:42:27 +02:00
void main ( void ) ;
void main ( void )
2018-04-06 17:37:17 +02:00
{
2016-01-29 00:24:49 +01:00
rcc_clock_setup_in_hse_8mhz_out_72mhz ( ) ; // use 8 MHz high speed external clock to generate 72 MHz internal clock
2016-08-14 21:02:38 +02:00
2016-10-23 17:42:27 +02:00
# if DEBUG
2017-01-30 09:44:51 +01:00
// enable functionalities for easier debug
2016-10-23 17:42:27 +02:00
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)
2017-02-06 17:40:28 +01:00
# else
2017-01-30 09:44:51 +01:00
// 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
2016-10-23 17:42:27 +02:00
# endif
2017-04-03 13:32:45 +02:00
board_setup ( ) ; // setup board
2019-09-19 11:59:02 +02:00
# if !defined(STLINKV2)
2018-02-18 15:18:42 +01:00
uart_setup ( ) ; // setup USART (for printing)
2018-10-29 15:03:29 +01:00
# endif
2017-04-19 16:26:40 +02:00
usb_cdcacm_setup ( ) ; // setup USB CDC ACM (for printing)
2020-09-27 12:00:22 +02:00
puts ( " \n welcome to the CuVoodoo Dachboden Klo-Assistant \n " ) ; // print welcome message
2016-01-29 00:24:49 +01:00
2019-12-21 19:26:27 +01:00
# 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
2017-01-30 09:44:51 +01:00
# if !(DEBUG)
// show watchdog information
2019-12-21 19:26:58 +01:00
printf ( " setup watchdog: %.2fs " , WATCHDOG_PERIOD / 1000.0 ) ;
if ( FLASH_OBR & FLASH_OBR_OPTERR ) {
2020-02-19 21:16:07 +01:00
puts ( " (option bytes not set in flash: software wachtdog used, not automatically started at reset) \n " ) ;
2019-12-21 19:26:58 +01:00
} else if ( FLASH_OBR & FLASH_OBR_WDG_SW ) {
2020-02-19 21:16:07 +01:00
puts ( " (software watchdog used, not automatically started at reset) \n " ) ;
2017-01-30 09:44:51 +01:00
} else {
2020-02-19 21:16:07 +01:00
puts ( " (hardware watchdog used, automatically started at reset) \n " ) ;
2017-01-30 09:44:51 +01:00
}
# endif
2016-08-14 21:02:38 +02:00
// setup RTC
2020-02-19 21:16:07 +01:00
puts ( " setup internal RTC: " ) ;
2020-01-03 19:41:39 +01:00
# if defined(BLUE_PILL) || defined(STLINKV2) || defined(BLASTER) // for boards without a Low Speed External oscillator
2018-10-29 14:14:32 +01:00
// note: the blue pill LSE oscillator is affected when toggling the onboard LED, thus prefer the HSE
2020-02-02 21:19:03 +01:00
rtc_auto_awake ( RCC_HSE , 8000000 / 128 / RTC_TICKS_SECOND - 1 ) ; // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running)
2018-10-28 22:44:53 +01:00
# else // for boards with an precise Low Speed External oscillator
2020-02-02 21:19:03 +01:00
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)
2018-10-28 22:44:53 +01:00
# endif
2016-08-14 21:02:38 +02:00
rtc_interrupt_enable ( RTC_SEC ) ; // enable RTC interrupt on "seconds"
nvic_enable_irq ( NVIC_RTC_IRQ ) ; // allow the RTC to interrupt
2019-06-18 17:43:43 +02:00
time_start = rtc_get_counter_val ( ) ; // get start time from internal RTC
2020-02-19 21:16:07 +01:00
puts ( " OK \n " ) ;
2016-08-14 21:02:38 +02:00
2020-08-20 13:54:42 +02:00
puts ( " setup TM1637 7-segment display: " ) ;
bool led_tm1637_setup_ok = true ;
2020-09-27 12:00:22 +02:00
led_tm1637_setup ( true ) ;
led_tm1637_setup_ok & = led_tm1637_brightness ( LED_TM1637_14DIV16 ) ; // set maximum brightness
2020-08-20 13:54:42 +02:00
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
if ( led_tm1637_setup_ok ) {
puts ( " OK \n " ) ;
} else {
puts ( " error \n " ) ;
}
2020-09-27 12:00:22 +02:00
puts ( " setup Bluetooth audio: " ) ;
rcc_periph_clock_enable ( GPIO_RCC ( BTAUDIO_BUTTON_PIN ) ) ; // enable clock for button
gpio_clear ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // set as unpressed
gpio_set_mode ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // set button pin as output
gpio_set ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // press button to switch on
sleep_ms ( BTAUDIO_ON ) ; // keep pressed
gpio_clear ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // release button
rcc_periph_clock_enable ( GPIO_RCC ( BTAUDIO_STATUS_PIN ) ) ; // enable clock for GPIO peripheral to read status input
gpio_set_mode ( GPIO_PORT ( BTAUDIO_STATUS_PIN ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_FLOAT , GPIO_PIN ( BTAUDIO_STATUS_PIN ) ) ; // set status pin as input
exti_set_trigger ( GPIO_EXTI ( BTAUDIO_STATUS_PIN ) , EXTI_TRIGGER_FALLING ) ; // trigger when LED goes on
exti_enable_request ( GPIO_EXTI ( BTAUDIO_STATUS_PIN ) ) ; // enable external interrupt
nvic_enable_irq ( GPIO_NVIC_EXTI_IRQ ( BTAUDIO_STATUS_PIN ) ) ; // enable interrupt
btaudio_status = 0 ; // number of LED blinks received, to determine the BT audio state
puts ( " OK \n " ) ;
led_tm1637_setup_ok & = led_tm1637_off ( ) ; // switch off and let main loop handle it
2020-08-20 13:54:42 +02:00
// 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
2020-09-27 12:00:22 +02:00
sleep_ms ( 500 ) ;
mp3_command ( MP3_CMD_SEL_DEV , 2 ) ; // set volume lower (BT audio transmitter saturates)
sleep_ms ( 500 ) ;
mp3_command ( MP3_CMD_SET_VOLUME , 15 ) ; // set volume lower (BT audio transmitter saturates)
2020-08-20 13:54:42 +02:00
puts ( " setup door lock switch: " ) ;
rcc_periph_clock_enable ( GPIO_RCC ( DOOR_PIN ) ) ; // enable clock for button
2020-09-27 12:00:22 +02:00
gpio_set_mode ( GPIO_PORT ( DOOR_PIN ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_FLOAT , GPIO_PIN ( DOOR_PIN ) ) ; // set button pin to input
2020-08-20 13:54:42 +02:00
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
2021-04-23 15:21:11 +02:00
uint32_t door_sequence [ 4 ] = { 0 } ; // duration of past door states, in RTC ticks, oldest first, updated every time the door is opened/closed
uint32_t door_timestamp = 0 ; // RTC timestamps when the door state has changed
2020-08-20 13:54:42 +02:00
puts ( " OK \n " ) ;
2021-06-24 21:49:32 +02:00
puts ( " setup RNG: " ) ;
rcc_periph_clock_enable ( RCC_ADC1 ) ; // enable clock for ADC peripheral
adc_power_off ( ADC1 ) ; // ensure ADC is off for configuring
rcc_periph_reset_pulse ( RST_ADC1 ) ; // reset configuration
rcc_set_adcpre ( RCC_CFGR_ADCPRE_PCLK2_DIV2 ) ; // set clock
adc_set_dual_mode ( ADC_CR1_DUALMOD_IND ) ; // not sure what it does
adc_disable_scan_mode ( ADC1 ) ; // we only do single conversion
adc_set_single_conversion_mode ( ADC1 ) ; // ensure we do single conversion
adc_set_sample_time ( ADC1 , ADC_CHANNEL_TEMP , ADC_SMPR_SMP_1DOT5CYC ) ; // we will read the temperature
adc_enable_external_trigger_regular ( ADC1 , ADC_CR2_EXTSEL_SWSTART ) ; // conversion is triggered by software
adc_power_on ( ADC1 ) ; // start ADC
adc_reset_calibration ( ADC1 ) ; // reset calibration value
adc_calibrate ( ADC1 ) ; // calibrate ADC
unsigned int seed = 1 ; // the seed we need to generate
uint8_t seed_rounds = 25 ; // get plenty of sample
while ( seed_rounds - - ) { // go through all rounds
adc_start_conversion_regular ( ADC1 ) ; // do conversion
while ( ! adc_eoc ( ADC1 ) ) ; // wait for conversion
const uint16_t temp = adc_read_regular ( ADC1 ) ; // read temperature
if ( temp > 2 ) {
seed * = temp ; // calculate the seed
}
sleep_ms ( 10 ) ; // wait a bit for the temperature to change
}
srand ( seed ) ; // set the seed
printf ( " %u \n " , seed ) ;
2018-04-06 17:37:17 +02:00
// setup terminal
terminal_prefix = " " ; // set default prefix
terminal_process = & process_command ; // set central function to process commands
terminal_setup ( ) ; // start terminal
2016-10-23 17:42:27 +02:00
2018-04-06 17:37:17 +02:00
// start main loop
2016-02-18 10:39:08 +01:00
bool action = false ; // if an action has been performed don't go to sleep
2016-01-29 00:24:49 +01:00
while ( true ) { // infinite loop
2016-10-23 17:42:27 +02:00
iwdg_reset ( ) ; // kick the dog
2019-06-18 17:45:53 +02:00
if ( user_input_available ) { // user input is available
2016-02-18 10:39:08 +01:00
action = true ; // action has been performed
2016-01-29 00:24:49 +01:00
led_toggle ( ) ; // toggle LED
2018-04-06 17:37:17 +02:00
char c = user_input_get ( ) ; // store receive character
terminal_send ( c ) ; // send received character to terminal
2016-08-14 21:02:38 +02:00
}
2019-06-18 17:45:53 +02:00
if ( rtc_internal_tick_flag ) { // the internal RTC ticked
2016-08-14 21:02:38 +02:00
rtc_internal_tick_flag = false ; // reset flag
2017-06-27 15:44:03 +02:00
action = true ; // action has been performed
2020-08-20 13:54:42 +02:00
if ( 0 = = ( rtc_get_counter_val ( ) % RTC_TICKS_SECOND ) ) { // one second has passed
2020-02-02 21:19:03 +01:00
led_toggle ( ) ; // toggle LED (good to indicate if main function is stuck)
}
2020-09-27 12:00:22 +02:00
if ( 0 = = ( rtc_get_counter_val ( ) % ( RTC_TICKS_SECOND * 11 ) ) ) { // 11 seconds have passed, time to check BT audio state
2021-06-24 20:26:34 +02:00
static bool bt_search = false ; // set when searching audio device
2020-09-27 12:00:22 +02:00
if ( 0 = = btaudio_status ) {
puts ( " BT audio off, switching on \n " ) ;
gpio_set ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // press button to switch on
sleep_ms ( BTAUDIO_ON ) ; // keep pressed
gpio_clear ( GPIO_PORT ( BTAUDIO_BUTTON_PIN ) , GPIO_PIN ( BTAUDIO_BUTTON_PIN ) ) ; // release button
2021-06-24 20:26:34 +02:00
led_tm1637_text ( " bton " ) ; // show on display we are switching Bluetooth on
led_tm1637_on ( ) ; // switch on display
2020-09-27 12:00:22 +02:00
} else if ( btaudio_status < = 2 ) {
puts ( " BT audio connected \n " ) ;
2021-06-24 20:26:34 +02:00
if ( bt_search ) { // we just searched before
led_tm1637_off ( ) ; // stop displaying we are searching
bt_search = false ; // clear flag
}
2020-09-27 12:00:22 +02:00
} else if ( btaudio_status < = 6 ) {
puts ( " BT audio disconnected \n " ) ;
2021-06-24 20:26:34 +02:00
led_tm1637_text ( " btof " ) ; // show on display we are disconnected
led_tm1637_on ( ) ; // switch on display
bt_search = true ; // remember we are searching (no actively re-pairing)
2020-09-27 12:00:22 +02:00
} else {
puts ( " BT audio searching \n " ) ;
2021-06-24 20:26:34 +02:00
led_tm1637_text ( " btsc " ) ; // show on display we are searching
led_tm1637_on ( ) ; // switch on display
bt_search = true ; // remember we are searching (with re-pairing)
2020-09-27 12:00:22 +02:00
}
btaudio_status = 0 ; // reset counter
}
if ( timer_door_closed & & 0 = = ( ( rtc_get_counter_val ( ) - timer_door_closed ) % ( RTC_TICKS_SECOND / 4 ) ) ) { // 1/4 second has passed since since door closed
const uint16_t time_passed = ( rtc_get_counter_val ( ) - timer_door_closed ) / RTC_TICKS_SECOND ; // how many seconds have passed since door has been closed
led_tm1637_time ( time_passed / 60 , time_passed % 60 ) ; // show time passed
2020-08-20 13:54:42 +02:00
}
}
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
2021-04-23 15:21:11 +02:00
const uint32_t change_timestamp = rtc_get_counter_val ( ) ; // save when the door has been open/closed
2020-08-20 13:54:42 +02:00
action = true ; // action has been performed
2020-09-27 12:00:22 +02:00
if ( closed & & 0 = = timer_door_closed ) { // door has been closed
2020-08-20 13:54:42 +02:00
puts ( " door closed \n " ) ;
2020-09-27 12:00:22 +02:00
timer_door_closed = rtc_get_counter_val ( ) ; // remember when the door has been closed
leds_sign ( true ) ; // show on the sign that the toilet is occupied
led_tm1637_time ( 0 , 0 ) ; // start showing time on display
led_tm1637_on ( ) ; // ensure the display is on
2021-04-23 15:21:11 +02:00
// update door change sequence
for ( uint8_t i = 0 ; i < LENGTH ( door_sequence ) - 1 ; i + + ) {
door_sequence [ i ] = door_sequence [ i + 1 ] ; // shift previous changes
}
door_sequence [ LENGTH ( door_sequence ) - 1 ] = change_timestamp - door_timestamp ; // not underflow safe
door_timestamp = change_timestamp ; // remember the change
2021-04-23 14:59:41 +02:00
// depending on the open/close sequence, enter secret mode
2021-04-23 15:21:11 +02:00
if ( door_sequence [ LENGTH ( door_sequence ) - 1 ] < ( 2 * RTC_TICKS_SECOND ) & & door_sequence [ LENGTH ( door_sequence ) - 2 ] < ( 2 * RTC_TICKS_SECOND ) & & door_sequence [ LENGTH ( door_sequence ) - 3 ] < ( 2 * RTC_TICKS_SECOND ) & & door_sequence [ LENGTH ( door_sequence ) - 4 ] < ( 2 * RTC_TICKS_SECOND ) ) { // sequence detected CLOSED->OPENED->CLOSED->OPEN->CLOSED, each within 2 seconds, then enter secret mode 2
2021-04-23 14:59:41 +02:00
puts ( " entering secret kanguru mode \n " ) ;
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( KANGURU_FOLDER < < 8 ) + ( rtc_get_counter_val ( ) % KANGURU_TRACKS ) + 1 ) ; // play random kanguru sketch track
playing_state = PLAYING_STATE_KANGURU ; // remember we are playing kanguru sketches
2021-04-23 15:21:11 +02:00
} else if ( door_sequence [ LENGTH ( door_sequence ) - 1 ] < ( 2 * RTC_TICKS_SECOND ) & & door_sequence [ LENGTH ( door_sequence ) - 2 ] < ( 2 * RTC_TICKS_SECOND ) ) { // sequence detected CLOSED->OPENED->CLOSED, each within 2 seconds, then enter secret mode 1
2021-04-23 14:59:41 +02:00
puts ( " entering secret techno mode \n " ) ;
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( TECHNO_FOLDER < < 8 ) + ( rtc_get_counter_val ( ) % TECHNO_TRACKS ) + 1 ) ; // play random techno music track
playing_state = PLAYING_STATE_TECHNO ; // remember we are playing techno music
} else { // no secret mode sequence detected
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( WELCOME_FOLDER < < 8 ) + ( rtc_get_counter_val ( ) % WELCOME_TRACKS ) + 1 ) ; // play random welcome track
playing_state = PLAYING_STATE_INTRO ; // remember we are playing the welcome message
}
2020-09-27 12:00:22 +02:00
} else if ( ! closed & & timer_door_closed ) { // door has been opened
puts ( " door opened \n " ) ;
timer_door_closed = 0 ; // remember door is now open
leds_sign ( false ) ; // show on sign the toilet is free
led_tm1637_off ( ) ; // stop showing time
2021-04-22 21:28:24 +02:00
mp3_command ( MP3_CMD_PLAY_FOLDER_FILE , ( EXIT_FOLDER < < 8 ) + ( rtc_get_counter_val ( ) % EXIT_TRACKS ) + 1 ) ; // play random exit message track
2020-11-06 10:00:46 +01:00
playing_state = PLAYING_STATE_EXIT ; // we are playing the exit track
2021-04-23 15:21:11 +02:00
// update door change sequence
for ( uint8_t i = 0 ; i < LENGTH ( door_sequence ) - 1 ; i + + ) {
door_sequence [ i ] = door_sequence [ i + 1 ] ; // shift previous changes
}
door_sequence [ LENGTH ( door_sequence ) - 1 ] = change_timestamp - door_timestamp ; // not underflow safe
door_timestamp = change_timestamp ; // remember the change
2020-08-20 13:54:42 +02:00
}
}
2020-11-06 10:00:46 +01:00
if ( mp3_rx_flag ) { // data from MP3 player received
2020-08-20 13:54:42 +02:00
mp3_response ( ) ; // check for response
mp3_rx_flag = false ; // clear flag
action = true ; // action has been performed
2016-08-14 21:02:38 +02:00
}
if ( action ) { // go to sleep if nothing had to be done, else recheck for activity
2016-02-18 10:39:08 +01:00
action = false ;
} else {
__WFI ( ) ; // go to sleep
}
2016-10-23 17:42:27 +02:00
} // main loop
2016-01-17 14:54:54 +01:00
}
2016-01-29 11:25:30 +01:00
2016-08-14 21:02:38 +02:00
/** @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
}
2020-08-20 13:54:42 +02:00
/** 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
}
}
2020-09-27 12:00:22 +02:00
/** door has been opened/closed */
2020-08-20 13:54:42 +02:00
void GPIO_EXTI_ISR ( DOOR_PIN ) ( void )
{
exti_reset_request ( GPIO_EXTI ( DOOR_PIN ) ) ; // reset interrupt/clear flag
door_flag = true ; // notify main loop
}
2020-09-27 12:00:22 +02:00
/** Bluetooth audio transmitter switched on LED */
void GPIO_EXTI_ISR ( BTAUDIO_STATUS_PIN ) ( void )
{
exti_reset_request ( GPIO_EXTI ( BTAUDIO_STATUS_PIN ) ) ; // reset interrupt/clear flag
btaudio_status + + ; // count for main loop
}