Compare commits
22 Commits
master
...
klo-assist
Author | SHA1 | Date | |
---|---|---|---|
9b8632c893 | |||
ae875daba7 | |||
630945b27c | |||
bc06b38260 | |||
dc822fd341 | |||
80d5a2b704 | |||
2758b5d4c4 | |||
51f78852e5 | |||
f81ec06f93 | |||
08134e0d88 | |||
a22de00ee8 | |||
cac967ba76 | |||
557c8777b1 | |||
a100716cfc | |||
fcafec7cc6 | |||
804ba7628b | |||
f72e790a95 | |||
b20c272c8e | |||
7dcfa18d8d | |||
f58d7c6da4 | |||
69402a5525 | |||
bd631f6c12 |
97
README.md
97
README.md
@ -1,55 +1,80 @@
|
||||
This firmware template is designed for development boards based around [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
|
||||
This is the firmware for the Dachboden Klo-Assistant.
|
||||
|
||||
project
|
||||
=======
|
||||
|
||||
summary
|
||||
-------
|
||||
This project's original purpose was to indicate the occupancy state of the toilet.
|
||||
If the toilet was in use, a red light on a shield would indicate it is occupied.
|
||||
Else a green light indicates it is free.
|
||||
|
||||
*describe project purpose*
|
||||
Next, a display has been added to show the time spent in the water room.
|
||||
This would put some pressure on the user to not spend too much time on the loo and hold up the queue.
|
||||
|
||||
technology
|
||||
----------
|
||||
Finally, it has been connected to speakers.
|
||||
A welcome message would greed the new comer, followed by a (random) pleasant music.
|
||||
Then the time passed inside is announced, followed by a (random) comical message.
|
||||
And the loop continues with another song.
|
||||
Once you exit the place, a short (random) message is played.
|
||||
|
||||
*described electronic details*
|
||||
Two secret modes have been added:
|
||||
|
||||
- if the door is closed, opened, and closed, each step within 2 seconds, then techno music is continuously played (replacing all toilet messages)
|
||||
- if the door is further opened and closed, sketches from the Känguru-Chroniken by Marc-Uwe Kling are played
|
||||
|
||||
board
|
||||
=====
|
||||
|
||||
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
|
||||
The devices is comprised by following components:
|
||||
|
||||
The underlying template also supports following board:
|
||||
|
||||
- [Maple Mini](http://leaflabs.com/docs/hardware/maple-mini.html), based on a STM32F103CBT6
|
||||
- [System Board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#system_board), based on a STM32F103C8T6
|
||||
- [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
|
||||
- [black pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#black_pill), based on a STM32F103C8T6
|
||||
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
|
||||
- [ST-LINK V2 mini](https://wiki.cuvoodoo.info/doku.php?id=jtag#mini_st-link_v2), a ST-LINK/V2 clone based on a STM32F101C8T6
|
||||
- [USB-Blaster](https://wiki.cuvoodoo.info/doku.php?id=jtag#armjishu_usb-blaster), an Altera USB-Blaster clone based on a STM32F101C8T6
|
||||
|
||||
**Which board is used is defined in the Makefile**.
|
||||
This is required to map the user LED and button provided on the board
|
||||
|
||||
The ST-LINK V2 mini clone has SWD test points on the board.
|
||||
Because read protection is enabled, you will first need to remove the protection to be able to flash the firmware.
|
||||
To remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
|
||||
|
||||
The Altera USB-Blaster clone has a pin header for SWD and UART1 on the board.
|
||||
SWD is disabled in the main firmware, and it has read protection.
|
||||
To be able to flash using SWD (or the serial port), the BOOT0 pin must be set to 1 to boot the system memory install of the flash memory.
|
||||
To set BOOT0 to 1, apply 3.3 V on R11, between the resistor and the reference designator, when powering the device.
|
||||
The red LED should stay off while the green LED is on.
|
||||
Now you can remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
|
||||
- a [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill) development board, based on a STM32F103C8T6 micro-controller
|
||||
- a GW1584 step down voltage converter module to lower the 12V input to 5V for all peripherals
|
||||
- a switch to cut the power, allowing other devices to connect to the Bluetooth speakers
|
||||
- a micro-switch in the door lock detects when a user closed the door to use the toilet in peace
|
||||
- a SK6812 RGBW LED strip illuminates the shield to indicate if the toilet is free or occupied
|
||||
- a Titan TM1637 4-digit 7-segment display shows the time is second passed since the door has been locked
|
||||
- a Catalex YX5300 MP3 player reads the songs and announcements from a micro-SD card and outputs it to a 3.5 mm stereo jack
|
||||
- a Taotronics TT-BA01 Bluetooth transmitter with input 3.5 mm stereo jack connects to the room speaker to send the audio
|
||||
|
||||
connections
|
||||
===========
|
||||
|
||||
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
|
||||
GW1584 step down voltage converter module:
|
||||
|
||||
- *list board to peripheral pin connections*
|
||||
- IN-: outer shell of 5.5/2.1 barrel connector
|
||||
- IN+: inner pin of 5.5/2.1 barrel connector, after power switch (6-12V)
|
||||
- OUT-: ground
|
||||
- OUT+: 5V
|
||||
|
||||
All pins are configured using `define`s in the corresponding source code.
|
||||
micro-switch in door lock:
|
||||
|
||||
- COM: ground
|
||||
- NO: PB8, use external pull-up resistor (10k to 5V)
|
||||
|
||||
SK6812 RGBW LED strip:
|
||||
|
||||
- 5V (green cable): 5V
|
||||
- DIN (yellow cable): PB15 (SPI2_MOSI), through voltage shifter (to get clean 5V signal)
|
||||
- GND (orange cable): GND
|
||||
|
||||
Titan TM1637 4-digit 7-segment display:
|
||||
|
||||
- GND: GND
|
||||
- VCC: 5V
|
||||
- DIO: PB7
|
||||
- CLK: PB6
|
||||
|
||||
Catalex YX5300 MP3 player:
|
||||
|
||||
- GND: GND
|
||||
- VCC: 5V
|
||||
- TX: PA3 (UART2_RX)
|
||||
- RX: PA2 (UART2_TX)
|
||||
|
||||
Taotronics TT-BA01:
|
||||
|
||||
- 3.5 mm stereo jack: to MP3 player
|
||||
- cable soldered to button: PA0 (this allows to switch on/off the transmitter, and put it into pairing mode)
|
||||
- cable soldered to blue: PA1 (this allow to know in which state the transmitter is)
|
||||
|
||||
code
|
||||
====
|
||||
@ -81,9 +106,9 @@ The `bootloader` is started first and immediately jumps to the `application` if
|
||||
The `application` image is the main application and is implemented in `application.c`.
|
||||
It is up to the application to advertise USB DFU support (i.e. as does the provided USB CDC ACM example).
|
||||
|
||||
The `bootlaoder` image will be flashed using SWD (Serial Wire Debug).
|
||||
The `bootloader` image will be flashed using SWD (Serial Wire Debug).
|
||||
For that you need an SWD adapter.
|
||||
The `Makefile` uses a Black Magic Probe (per default), or a ST-Link V2 along OpenOCD software.
|
||||
The `Makefile` uses a ST-Link V2 along OpenOCD software.
|
||||
To flash the `booltoader` using SWD run `rake flash_booloader`.
|
||||
|
||||
Once the `bootloader` is flashed it is possible to flash the `application` over USB using the DFU protocol by running `rake flash`.
|
||||
|
646
application.c
646
application.c
@ -1,8 +1,8 @@
|
||||
/** STM32F1 application example
|
||||
/** dachboden klo-assistant firmware
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2016-2020
|
||||
* @date 2016-2021
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
@ -24,6 +24,8 @@
|
||||
#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
|
||||
#include <libopencm3/stm32/adc.h> // ADC utilities for random seed
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // board definitions
|
||||
@ -34,6 +36,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 +45,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 +62,122 @@ 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)
|
||||
* @note use external pull-up resistor (10k to 5V) since the internal pull up does not cope for the long cable
|
||||
*/
|
||||
#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;
|
||||
|
||||
/** pin to simulate button press (go high) to switch on/off Bluetooth audio transmitter
|
||||
* - 2s press: on/off
|
||||
* - 5s 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.3s): pairing
|
||||
* - slow flash (double blink every 5.3s): unconnected
|
||||
* - very slow flash (single blink every 10s): 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;
|
||||
|
||||
/** 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,
|
||||
};
|
||||
|
||||
/** 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 */
|
||||
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) */
|
||||
} 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;
|
||||
|
||||
/** number of possible welcome tacks */
|
||||
#define WELCOME_TRACKS 15
|
||||
/** number of possible music tracks */
|
||||
#define MUSIC_TRACKS 30
|
||||
/** number of possible exit message tracks */
|
||||
#define EXIT_TRACKS 19
|
||||
/** number of possible talk tracks */
|
||||
#define TALK_TRACKS 29
|
||||
/** number of possible techno music tracks */
|
||||
#define TECHNO_TRACKS 237
|
||||
/** number of possible kanguru */
|
||||
#define KANGURU_TRACKS 81
|
||||
|
||||
/** 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
|
||||
/** folder number for techno music tracks */
|
||||
#define TECHNO_FOLDER 6
|
||||
/** folder number for kanguru tracks */
|
||||
#define KANGURU_FOLDER 7
|
||||
|
||||
size_t putc(char c)
|
||||
{
|
||||
size_t length = 0; // number of characters printed
|
||||
@ -122,6 +232,271 @@ static void command_reset(void* argument);
|
||||
*/
|
||||
static void command_bootloader(void* argument);
|
||||
|
||||
/** 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
|
||||
}
|
||||
|
||||
/** 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]);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
puts("MP3 response: ");
|
||||
/*
|
||||
// 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");
|
||||
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
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, track_random(MUSIC_FOLDER)); // play random music track
|
||||
playing_state = PLAYING_STATE_SONG; // remember we are playing a song (for the first time)
|
||||
break;
|
||||
case PLAYING_STATE_SONG: // the song finished
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + 65); // play time announcement
|
||||
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)) {
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + 60); // play number of minutes announcement
|
||||
} else {
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + (time_passed / 60)); // play number of minutes announcement
|
||||
}
|
||||
playing_state = PLAYING_STATE_TIMER_MINUTES; // remember we are playing the number of minutes
|
||||
break;
|
||||
case PLAYING_STATE_TIMER_MINUTES: // the minutes announcement finished
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + 62); // play minute announcement
|
||||
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)) {
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + 60); // play number of seconds announcement
|
||||
} else {
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + (time_passed % 60)); // play number of seconds announcement
|
||||
}
|
||||
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
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (TIME_FOLDER << 8) + 64); // play time outro announcement
|
||||
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
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, track_random(TALK_FOLDER)); // play random talk track
|
||||
playing_state = PLAYING_STATE_TALK; // remember we are playing the talk track
|
||||
break;
|
||||
case PLAYING_STATE_TALK: // the talk track finished
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, track_random(MUSIC_FOLDER)); // play random music track
|
||||
playing_state = PLAYING_STATE_SONG; // remember we are playing a song (again)
|
||||
break;
|
||||
case PLAYING_STATE_TECHNO: // techno song completed, play the next one
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, track_random(TECHNO_FOLDER)); // play random techno music track
|
||||
break;
|
||||
case PLAYING_STATE_KANGURU: // kanguru sketch completed, play the next one
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, track_random(KANGURU_FOLDER)); // play random techno music track
|
||||
break;
|
||||
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
|
||||
break;
|
||||
case 0x40:
|
||||
puts("error");
|
||||
led_tm1637_text("sder"); // show we have an issue with the SD card
|
||||
led_tm1637_on(); // switch on display
|
||||
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)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/** put Bluetooth audi transmitter into pairing mode
|
||||
* @param[in] argument no argument required
|
||||
*/
|
||||
static void command_pair(void* argument)
|
||||
{
|
||||
(void)argument; // argument not used
|
||||
// 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");
|
||||
}
|
||||
|
||||
/** list of all supported commands */
|
||||
static const struct menu_command_t menu_commands[] = {
|
||||
{
|
||||
@ -174,6 +549,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_UNSIGNED,
|
||||
.argument_description = "track folder+number fftt",
|
||||
.command_handler = &command_play,
|
||||
},
|
||||
{
|
||||
.shortcut = 'a',
|
||||
.name = "audio",
|
||||
.command_description = "put BT audio transmitter into pairing mode",
|
||||
.argument = MENU_ARGUMENT_NONE,
|
||||
.argument_description = NULL,
|
||||
.command_handler = &command_pair,
|
||||
},
|
||||
};
|
||||
|
||||
static void command_help(void* argument)
|
||||
@ -273,6 +664,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
|
||||
*/
|
||||
@ -299,7 +703,7 @@ void main(void)
|
||||
uart_setup(); // setup USART (for printing)
|
||||
#endif
|
||||
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
|
||||
puts("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
|
||||
puts("\nwelcome to the CuVoodoo Dachboden Klo-Assistant\n"); // print welcome message
|
||||
|
||||
#if DEBUG
|
||||
// show reset cause
|
||||
@ -352,6 +756,112 @@ 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(true);
|
||||
led_tm1637_setup_ok &= led_tm1637_brightness(LED_TM1637_14DIV16); // set maximum brightness
|
||||
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");
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
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)
|
||||
|
||||
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_FLOAT, 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
|
||||
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
|
||||
puts("OK\n");
|
||||
|
||||
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);
|
||||
|
||||
// setup terminal
|
||||
terminal_prefix = ""; // set default prefix
|
||||
terminal_process = &process_command; // set central function to process commands
|
||||
@ -359,7 +869,6 @@ void main(void)
|
||||
|
||||
// start main loop
|
||||
bool action = false; // if an action has been performed don't go to sleep
|
||||
button_flag = false; // reset button flag
|
||||
while (true) { // infinite loop
|
||||
iwdg_reset(); // kick the dog
|
||||
if (user_input_available) { // user input is available
|
||||
@ -368,19 +877,95 @@ 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 (0 == (rtc_get_counter_val() % (RTC_TICKS_SECOND * 11))) { // 11 seconds have passed, time to check BT audio state
|
||||
static bool bt_search = false; // set when searching audio device
|
||||
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
|
||||
led_tm1637_text("bton"); // show on display we are switching Bluetooth on
|
||||
led_tm1637_on(); // switch on display
|
||||
} else if (btaudio_status <= 2) {
|
||||
puts("BT audio connected\n");
|
||||
if (bt_search) { // we just searched before
|
||||
led_tm1637_off(); // stop displaying we are searching
|
||||
bt_search = false; // clear flag
|
||||
}
|
||||
} else if (btaudio_status <= 6) {
|
||||
puts("BT audio disconnected\n");
|
||||
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)
|
||||
} else {
|
||||
puts("BT audio searching\n");
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
const uint32_t change_timestamp = rtc_get_counter_val(); // save when the door has been open/closed
|
||||
action = true; // action has been performed
|
||||
if (closed && 0 == timer_door_closed) { // door has been closed
|
||||
puts("door closed\n");
|
||||
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
|
||||
// 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
|
||||
// depending on the open/close sequence, enter secret mode
|
||||
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
|
||||
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
|
||||
} 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
|
||||
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
|
||||
}
|
||||
} 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
|
||||
mp3_command(MP3_CMD_PLAY_FOLDER_FILE, (EXIT_FOLDER << 8) + (rtc_get_counter_val() % EXIT_TRACKS) + 1); // play random exit message track
|
||||
playing_state = PLAYING_STATE_EXIT; // we are playing the exit track
|
||||
// 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
|
||||
}
|
||||
}
|
||||
if (mp3_rx_flag) { // data from MP3 player received
|
||||
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 +981,30 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
/** door has been opened/closed */
|
||||
void GPIO_EXTI_ISR(DOOR_PIN)(void)
|
||||
{
|
||||
exti_reset_request(GPIO_EXTI(DOOR_PIN)); // reset interrupt/clear flag
|
||||
door_flag = true; // notify main loop
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
|
@ -1,598 +0,0 @@
|
||||
/** library to communicate with an SD card flash memory using the SPI mode (code)
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2017
|
||||
* @note peripherals used: SPI @ref flash_sdcard_spi
|
||||
* @warning all calls are blocking
|
||||
* @implements SD Specifications, Part 1, Physical Layer, Simplified Specification, Version 6.00, 10 April 10 2017
|
||||
* @todo use SPI unidirectional mode, use DMA, force/wait going to idle state when initializing, filter out reserved values, check sector against size
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/spi.h> // SPI library
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "flash_sdcard.h" // SD card header and definitions
|
||||
|
||||
/** @defgroup flash_sdcard_spi SPI used to communication with SD card
|
||||
* @{
|
||||
*/
|
||||
#define FLASH_SDCARD_SPI 1 /**< SPI peripheral */
|
||||
/** @} */
|
||||
|
||||
/** if the card has been initialized successfully */
|
||||
static bool initialized = false;
|
||||
/** maximum N_AC value (in 8-clock cycles) (time between the response token R1 and data block when reading data (see section 7.5.4)
|
||||
* @note this is set to N_CR until we can read CSD (see section 7.2.6)
|
||||
*/
|
||||
static uint32_t n_ac = 8;
|
||||
/** is it a Standard Capacity SD card (true), or High Capacity SD cards (false)
|
||||
* @note this is indicated in the Card Capacity Status bit or OCR (set for high capacity)
|
||||
* @note this is important for addressing: for standard capacity cards the address is the byte number, for high capacity cards it is the 512-byte block number
|
||||
*/
|
||||
static bool sdsc = false;
|
||||
/** size of card in bytes */
|
||||
static uint64_t sdcard_size = 0;
|
||||
/** size of an erase block bytes */
|
||||
static uint32_t erase_size = 0;
|
||||
|
||||
/** table for CRC-7 calculation for the command messages (see section 4.5)
|
||||
* @note faster than calculating the CRC and doesn't cost a lot of space
|
||||
* @note generated using pycrc --width=7 --poly=0x09 --reflect-in=false --reflect-out=false --xor-in=0x00 --xor-out=0x00 --generate=table
|
||||
*/
|
||||
static const uint8_t crc7_table[] = {
|
||||
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
||||
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
|
||||
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
|
||||
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
|
||||
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
|
||||
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
|
||||
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
|
||||
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
|
||||
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
|
||||
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
|
||||
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
|
||||
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
|
||||
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
|
||||
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
|
||||
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
|
||||
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
|
||||
};
|
||||
|
||||
/** wait one SPI round (one SPI word)
|
||||
*/
|
||||
static void flash_sdcard_spi_wait(void)
|
||||
{
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
|
||||
}
|
||||
|
||||
/** read one SPI word
|
||||
* @return SPI word read
|
||||
*/
|
||||
static uint16_t flash_sdcard_spi_read(void)
|
||||
{
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
|
||||
(void)SPI_DR(SPI(FLASH_SDCARD_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until Tx buffer is empty before clearing the (previous) RXNE flag
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_RXNE)); // wait for next data to be available
|
||||
return SPI_DR(SPI(FLASH_SDCARD_SPI)); // return received adat
|
||||
}
|
||||
|
||||
/** test if card is present
|
||||
* @return if card has been detected
|
||||
* @note this use the SD card detection mechanism (CD/CS is high card is inserted due to the internal 50 kOhm resistor)
|
||||
*/
|
||||
static bool flash_sdcard_card_detect(void)
|
||||
{
|
||||
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for NSS pin port peripheral for SD card CD signal
|
||||
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS pin as input to read CD signal
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // pull pin low to avoid false positive when card in not inserted
|
||||
return (0!=gpio_get(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI))); // read CD signal: is card is present the internal 50 kOhm pull-up resistor will override our 1 MOhm pull-down resistor and set the signal high (see section 6.2)
|
||||
}
|
||||
|
||||
/** transmit command token
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
*/
|
||||
static void flash_sdcard_send_command(uint8_t index, uint32_t argument)
|
||||
{
|
||||
uint8_t command[5] = { 0x40+(index&0x3f), argument>>24, argument>>16, argument>>8, argument>>0 }; // commands are 5 bytes long, plus 1 bytes of CRC (see section 7.3.1.1)
|
||||
uint8_t crc7 = 0x00; // CRC-7 checksum for command message
|
||||
// send command
|
||||
for (uint8_t i=0; i<LENGTH(command); i++) {
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), command[i]); // send data
|
||||
crc7 = (crc7_table[((crc7<<1)^command[i])])&0x7f; // update checksum
|
||||
}
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), (crc7<<1)+0x01); // send CRC value (see section 7.3.1.1)
|
||||
}
|
||||
|
||||
/** transmit command token and receive response token
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] response response data to read (if no error occurred)
|
||||
* @param[in] size size of response to read
|
||||
* @return response token R1 or 0xff if error occurred or card is not present
|
||||
*/
|
||||
static uint8_t flash_sdcard_command_response(uint8_t index, uint32_t argument, uint8_t* response, size_t size)
|
||||
{
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.1.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
if (0x00==(r1&0xfe) && 0!=size && NULL!=response) { // we have to read a response
|
||||
for (size_t i=0; i<size; i++) {
|
||||
response[i] = flash_sdcard_spi_read(); // get byte
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r1;
|
||||
}
|
||||
|
||||
/** read a data block
|
||||
* @param[out] data data block to read (if no error occurred)
|
||||
* @param[in] size size of response to read (a multiple of 2)
|
||||
* @return 0 if succeeded, else control token (0xff for other errors)
|
||||
*/
|
||||
static uint8_t flash_sdcard_read_block(uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint8_t token = 0xff; // to save the control block token (see section 7.3.3)
|
||||
for (uint32_t i=0; i<n_ac && token==0xff; i++) { // wait for N_AC before reading data block (see section 7.5.2.1)
|
||||
token = flash_sdcard_spi_read(); // get control token (see section 7.3.3)
|
||||
}
|
||||
if (0==(token&0xf0)) { // data error token received (see section 7.3.3.3)
|
||||
if (0==(token&0x0f)) { // unknown error
|
||||
token = 0xff;
|
||||
}
|
||||
} else if (0xfe==token) { // start block token received (see section 7.3.3.2)
|
||||
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
|
||||
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
|
||||
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
// get block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
|
||||
for (size_t i=0; i<size/2; i++) {
|
||||
uint16_t word = flash_sdcard_spi_read(); // get word
|
||||
data[i*2+0] = (word>>8); // save byte
|
||||
data[i*2+1] = (word>>0); // save byte
|
||||
}
|
||||
flash_sdcard_spi_read(); // read CRC (the CRC after the data block should clear the computed CRC)
|
||||
if (SPI_CRC_RXR(FLASH_SDCARD_SPI)) { // CRC is wrong
|
||||
token = 0xff;
|
||||
} else { // no error occurred
|
||||
token = 0;
|
||||
}
|
||||
// switch back to 8-bit SPI frames
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
|
||||
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
} else { // start block token not received
|
||||
token = 0xff;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/** write a data block
|
||||
* @param[in] data data block to write
|
||||
* @param[in] size size of response to read (a multiple of 2)
|
||||
* @return data response token (0xff for other errors)
|
||||
*/
|
||||
static uint8_t flash_sdcard_write_block(uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xfe); // send start block token (see section 7.3.3.2)
|
||||
|
||||
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
|
||||
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
|
||||
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
// send block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
|
||||
for (size_t i=0; i<size/2; i++) {
|
||||
uint16_t word = (data[i*2+0]<<8)+data[i*2+1]; // prepare SPI frame
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), word); // senf data frame
|
||||
}
|
||||
spi_set_next_tx_from_crc(SPI(FLASH_SDCARD_SPI)); // send CRC
|
||||
// switch back to 8-bit SPI frames
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_next_tx_from_buffer(SPI(FLASH_SDCARD_SPI)); // don't send CRC
|
||||
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
|
||||
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
|
||||
uint8_t token = 0xff;
|
||||
while (0x01!=(token&0x11)) {
|
||||
token = flash_sdcard_spi_read(); // get data response token (see section 7.3.3.1)
|
||||
}
|
||||
while (0==flash_sdcard_spi_read()); // wait N_EC while the card is busy programming the data
|
||||
|
||||
return token;
|
||||
}
|
||||
/** get card status
|
||||
* @param[out] status SD status (512 bits)
|
||||
* @return response token R2 or 0xffff if error occurred or card is not present
|
||||
*/
|
||||
static uint16_t flash_sdcard_status(uint8_t* status)
|
||||
{
|
||||
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
|
||||
uint8_t r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
|
||||
if ((r1&0xfe)) { // error occurred, not in idle state
|
||||
return false;
|
||||
}
|
||||
|
||||
// send ACMD13 command
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(13, 0); // send ACMD13 (SD_STATUS) (see table 7-4)
|
||||
|
||||
// get response token R2
|
||||
uint16_t r2 = 0xffff; // response token R2 (see section 7.3.2.3)
|
||||
for (uint8_t i=0; i<8 && r2&0x8000; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r2 = (flash_sdcard_spi_read()<<8); // get first byte of response (see section 7.3.2.1)
|
||||
}
|
||||
if (0==(r2&0x8000)) { // got the first byte
|
||||
r2 += flash_sdcard_spi_read(); // read second byte (see 7.3.2.3)
|
||||
}
|
||||
|
||||
// get data block
|
||||
if (0==r2) { // no error
|
||||
if (flash_sdcard_read_block(status, 64)) { // read 512 bits data block containing SD status
|
||||
r2 |= (1<<11); // set communication error
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r2;
|
||||
}
|
||||
|
||||
/** transmit command token, receive response token and data block
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] data data block to read (if no error occurred)
|
||||
* @param[in] size size of data to read (a multiple of 2)
|
||||
* @return response token R1 or 0xff if error occurred or card is not present
|
||||
*/
|
||||
static uint8_t flash_sdcard_data_read(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
|
||||
// get data block
|
||||
if (0x00==r1) { // we can read a data block
|
||||
if (flash_sdcard_read_block(data, size)) { // read data block
|
||||
r1 |= (1<<3); // set communication error
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r1;
|
||||
}
|
||||
|
||||
/** transmit command token, receive response token and write data block
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] data data block to write
|
||||
* @param[in] size size of data to write (a multiple of 2)
|
||||
* @return data response token, or 0xff if error occurred or card is not present
|
||||
* @note at the end of a write operation the SD status should be check to ensure no error occurred during programming
|
||||
*/
|
||||
static uint8_t flash_sdcard_data_write(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) write odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
|
||||
// write data block
|
||||
uint8_t drt = 0xff; // data response token (see section 7.3.3.1)
|
||||
if (0x00==r1) { // we have to write the data block
|
||||
drt = flash_sdcard_write_block(data, size); // write data block
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
|
||||
return drt;
|
||||
}
|
||||
|
||||
bool flash_sdcard_setup(void)
|
||||
{
|
||||
// reset values
|
||||
initialized = false;
|
||||
n_ac = 8;
|
||||
sdcard_size = 0;
|
||||
erase_size = 0;
|
||||
|
||||
// check if card is present
|
||||
if (!flash_sdcard_card_detect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure SPI peripheral
|
||||
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for clock signal
|
||||
gpio_set_mode(SPI_SCK_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(FLASH_SDCARD_SPI)); // set SCK as output (clock speed will be negotiated later)
|
||||
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MOSI signal
|
||||
gpio_set_mode(SPI_MOSI_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(FLASH_SDCARD_SPI)); // set MOSI as output
|
||||
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MISO signal
|
||||
gpio_set_mode(SPI_MISO_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(FLASH_SDCARD_SPI)); // set MISO as input
|
||||
gpio_set(SPI_MISO_PORT(FLASH_SDCARD_SPI), SPI_MISO_PIN(FLASH_SDCARD_SPI)); // pull pin high to detect when the card is not answering (or not present) since responses always start with MSb 0
|
||||
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
|
||||
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS (CS) as output
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
|
||||
rcc_periph_clock_enable(RCC_SPI(FLASH_SDCARD_SPI)); // enable clock for SPI peripheral
|
||||
spi_reset(SPI(FLASH_SDCARD_SPI)); // clear SPI values to default
|
||||
spi_init_master(SPI(FLASH_SDCARD_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_256, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 256 (72E6/256=281 kHz) since maximum SD card clock frequency (fOD, see section 7.8/6.6.6) during initial card-identification mode is 400 kHz (maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (from SD card spec, polarity depends on clock phase), use 8 bits frames (as per spec), use MSb first
|
||||
spi_set_full_duplex_mode(SPI(FLASH_SDCARD_SPI)); // ensure we are in full duplex mode
|
||||
spi_enable_software_slave_management(SPI(FLASH_SDCARD_SPI)); // control NSS (CS) manually
|
||||
spi_set_nss_high(SPI(FLASH_SDCARD_SPI)); // set NSS high (internally) so we can output
|
||||
spi_disable_ss_output(SPI(FLASH_SDCARD_SPI)); // disable NSS output since we control CS manually
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// sadly we can't use the interrupts as events to sleep (WFE) since sleep disables also communication (e.g. going to sleep until Rx buffer is not empty prevents transmission)
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI
|
||||
|
||||
// start card-identification (see section 7.2.1/4.2)
|
||||
uint8_t r1 = 0;
|
||||
// send CMD0 (GO_IDLE_START) to start the card identification (see section 7.2.1)
|
||||
r1 = flash_sdcard_command_response(0, 0, NULL, 0); // (see table 7-3)
|
||||
if (0x01!=r1) { // error occurred, not in idle state
|
||||
return false;
|
||||
}
|
||||
// send CMD8 (SEND_IF_COND) to inform about voltage (1: 2.7-3.6V, aa: recommended check pattern) (see section 7.2.1)
|
||||
uint8_t r7[4] = {0}; // to store response toke R7 (see section 7.3.2.6)
|
||||
r1 = flash_sdcard_command_response(8, 0x000001aa, r7, sizeof(r7)); // (see table 7-3)
|
||||
if (0x01==r1) { // command supported, in idle state
|
||||
if (!(r7[2]&0x1)) { // 2.7-3.6V not supported (see table 5-1)
|
||||
return false;
|
||||
} else if (0xaa!=r7[3]) { // recommended pattern not returned (see section 4.3.13)
|
||||
return false;
|
||||
}
|
||||
} else if (0x05!=r1) { // illegal command (cards < physical spec v2.0 don't support CMD8) (see section 7.2.1)
|
||||
return false;
|
||||
}
|
||||
// send CMD58 (READ_OCR) to read Operation Conditions Register (see section 7.2.1)
|
||||
uint8_t r3[4] = {0}; // to store response token R3 (see section |