Compare commits

...

20 Commits

Author SHA1 Message Date
King Kévin 940fa5b9b1 remove unused library 2020-06-21 10:10:54 +02:00
King Kévin 09dfd6c6f5 README: describe project 2020-06-21 10:08:02 +02:00
King Kévin a78d45e94b application: add LED animation 2020-06-21 09:59:52 +02:00
King Kévin 284065b62f application: remove lengthy version description 2020-06-21 09:59:25 +02:00
King Kévin ac9eb9bac9 ws2812b: use push-pull output for noisy cable 2020-06-21 09:54:56 +02:00
King Kévin 4f91856445 ws2812b: only 6 LEDs are used fot this project 2020-06-21 09:54:38 +02:00
King Kévin 26382c9712 usb: increase tx buffer for debugging long messages 2020-06-21 09:44:31 +02:00
King Kévin 367ff5d8b5 README: update flashing over BT details 2020-05-31 23:54:31 +02:00
King Kévin eff94f9841 global: disable debug to enable watchdog 2020-05-31 23:54:31 +02:00
King Kévin 3ce0eaea91 uart: add one data bit for parity 2020-05-31 23:54:31 +02:00
King Kévin 660d351bcb ld: enforce 128 KiB flash space 2020-05-31 23:54:31 +02:00
King Kévin 972f768512 README: add battery note 2020-05-31 23:54:31 +02:00
King Kévin ad72c18556 add script to configure HC-05 bluetooth module 2020-05-31 23:54:31 +02:00
King Kévin af8aca9218 README: document dachtuer project 2020-05-31 23:54:31 +02:00
King Kévin 7d425f6cfb application: commit dachtuer application 2020-05-31 23:54:31 +02:00
King Kévin aaeed65c18 application: use LSE for oscillator (and remove other board ifdef 2020-05-31 23:54:31 +02:00
King Kévin 4229353d93 uart: change baudrate and parity to match bluetooth module, which matches USART bootloader 2020-05-31 23:54:31 +02:00
King Kévin b83ede23ef global: change LED pin so we can use the LSE oscillator 2020-05-31 23:54:31 +02:00
King Kévin ebbea376a6 application: add action to start embedded bootloader 2020-05-31 23:54:31 +02:00
King Kévin fdef34e663 application: fix date display and add offset 2020-05-31 23:54:31 +02:00
56 changed files with 817 additions and 10311 deletions

204
README.md
View File

@ -1,4 +1,4 @@
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).
The dachtür is a device to restrict access to the dachboden.
project
=======
@ -6,51 +6,161 @@ project
summary
-------
*describe project purpose*
Previously a button allowed to open the building's entrance door and granted access to the dachboden.
Since the dachboden got popular, it is now flooded.
The idea behind the dachtür it to disable this button and replace it with a secret sequence of button presses during certain periods (e.g. when parties are taking place in the dachboden).
It must be installed in the door panel, as described under `connections`.
technology
----------
configure
---------
*described electronic details*
To program the days, times, and button sequence, connect to the dachtür.
It should appear as "dachtuer" Bluetooth (classic) device.
A PIN is required to connect to it.
Once connected, you can use a terminal (such as the "Bluetooth terminal" android application).
Interact with it by entering commands.
- from time to time, update the date (it does not take into account summer and winter time):
~~~
date YYYY-MM-DD HH:MM
~~~
- enter the days on which the access should be restricted (starting with Monday, here only Thursday is active):
~~~
days 0001000
~~~
- enter at which time the restriction should start:
~~~
start 20:00
~~~
- enter at which time the restriction should stop:
~~~
stop 06:00
~~~
- enter the secret button sequence to open the door (here press button 1 then 2):
~~~
password 12
~~~
board
=====
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
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.
WARNING: If the internal backup battery gets empty and the device looses power, the configuration and date get lost (since there are stored in SRAM).
In this case, replace the battery and reconfigure the device.
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
On one side there are three XH-2.54 connectors.
- *list board to peripheral pin connections*
2-pin connector: 6-25 AC/DC power input
3-pin connector: opening button (the one already wired)
- 1 (left-most): common side of button (leave the bus bar connected)
- 2 (center): other side of the button (remove the cable)
- 3 (right-most): the cable which was connected to the button
4-pin connector: ring buttons (yet unused)
- 2 left-most pins: to button 1 of door panel
- 2 right-most pins: to button 2 of door panel
On the other side is a USB connector.
This is used to flash and configure the micro-controller.
peripherals
===========
The XH-2.54 2P connector is for the 15V AC power input.
A full bridge rectifier and smoothing capacitor make a DC power supply out of it.
A buck converter module steps it down to 5V.
Under 50 mA of power consumption it is very inefficient.
With no load it has a quiescent current of 37 mA.
The 5V rail provides power to the blue pill, Bluetooth module, and boost converter.
The MT3608-based boost converter steps the voltage up to 9V for the omrom G6E-134P relays.
It is more efficient to re-use the existing 5V than stepping down the 15V because the buck converters drawn a large current under no load.
The XH-2.54 3P is to be connected to door opening button (the installer will know what I mean).
Two omrom G6E-134P relays will take control over the button.
This allows the door button to be used.
At the specified time intervals, this is disconnected.
When the right code is entered, the second relay simulates the button press and opens the door.
The relays are controlled by the board using two 2N7000 n-channel MOSFETs.
The XH-2.54 4P is to be connected with the two buttons.
They are used to enter the secret sequence to open the door.
The [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6 micro-controller, will handle the logic.
A CR2032 battery is connected through a diode to VBAT.
This keeps the real time clock (RTC) running when there is no power from the panel.
A 3.3V pin is also connected through a diode to VBAT.
This powered the RTC when the board is powered, saving the battery.
The HC-05 Bluetooth module allows to remotely connect to the UART port.
It has been configured (using `hc-05_porg.rb`) to appear as "dachtuer", and requires a PIN to pair.
It offers the Serial Port Profile (SPP) from Bluetooth classic (Bluetooth Low Energy makes little sense).
This allows to communicate with the board without having to remove the panel.
It can be used to configure the opening time slots, days, and button sequence.
It is also possible to update the firmware through it.
A WS2812B LED strip is also connected to the board.
The LEDs should be placed behind the button name shield.
An animation with be shown when pressing the button (only during opening hours).
wiring
======
HC-05 Bluetooth module:
- STATE: no connect
- TXD: PA10/USART1_RX
- RXD: PA9/USART1_TX
- GND: ground
- VCC: 5V
- EN: no connect
CR2032 coin cell battery:
- +: VBAT, though diode (VBAT should also be connected to 3.3V through diode)
- -: ground
LED (because the on-board LED screws the LSE):
- anode: 3.3V, though resistor (1K)
- cathode: PB11
omrom G6E-134P relay (bottom, for button):
- 1 +: 9V
- 6 -: 2N7000 drain
- 7 COM: door button bar
- 10 NC: no connect
- 12 NO: other relay NO
2N7000 p-channel MOSFET (for bottom relay):
- 1 source: ground
- 2 gate: PB6 (pulled low)
- 3 drain: relay -
omrom G6E-134P relay (top, for panel):
- 1 +: 9V
- 6 -: 2N7000 drain
- 7 COM: wire to panel
- 10 NC: door button (where the wire was)
- 12 NO: other relay NO
2N7000 p-channel MOSFET (for top relay):
- 1 source: ground
- 2 gate: PB7 (pulled low)
- 3 drain: relay -
WS2812B RGB LED strip:
- VCC: 5V
- DIN: PB15
- GND: ground
XH-4P:
- 1: ground (for button 1)
- 2: PB8 (for button 1)
- 3: ground (for button 2)
- 4: PB9 (for button 2)
All pins are configured using `define`s in the corresponding source code.
The prototype uses a SZOMK AK-N-01 enclosures salvaged from a J-Link clone.
code
====
@ -83,13 +193,33 @@ It is up to the application to advertise USB DFU support (i.e. as does the provi
The `bootlaoder` 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`.
To force the bootloader to start the DFU mode press the user button or short a pin, depending on the board.
It is also possible to flash the `application` image using SWD by running `rake flash_application`.
It is also possible to flash the application over Bluetooth as follows:
~~~
# start bluetooth
sudo systemctl restart bluetooth
# pair device (you only need to do it once, and you need the PIN)
bluetoothctl
power on
pair 20:15:02:02:16:28
quit
# connect to device
sudo rfcomm bind rfcomm0 20:15:02:02:16:28
# switch to bootloader
echo "embedded" > /dev/rfcomm0
# flash firmware (it always fails the first time)
stm32flash /dev/rfcomm0
stm32flash /dev/rfcomm0 -b 115200 -S 0x08002000 -w application.bin -R
# disconnect from device
sudo rfcomm release rfcomm0
~~~
debug
-----
@ -99,4 +229,4 @@ To start the debugging session run `rake debug`.
USB
---
The firmware offers serial communication over USART1 and USB (using the CDC ACM device class).
The firmware offers serial communication over USART1 (where the Bluetooth module is connected) and USB (using the CDC ACM device class).

View File

@ -12,7 +12,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** STM32F1 application example
/** dachboden front panel access control
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
@ -37,16 +37,17 @@
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/f1/bkp.h> // backup domain utilities
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#if !defined(STLINKV2)
#include "uart.h" // USART utilities
#endif
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities
#include "led_ws2812b.h" // WS2812B RGB LED control
/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000
@ -54,16 +55,19 @@
/** 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 1
#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
/** RTC time when device is started */
static time_t time_start = 0;
@ -74,6 +78,79 @@ static time_t time_start = 0;
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */
/** GPIO pin connected to relay, used to control button connection to panel */
#define RELAY_PANEL_PIN PB6
/** GPIO pin connected to relay, used to simulate button press */
#define RELAY_BUTTON_PIN PB7
/** GPIO for button 1 */
#define BUTTON1_PIN PB9
/** GPIO for button 2 */
#define BUTTON2_PIN PB8
/** which button has been pressed */
volatile uint8_t button_pressed = 0;
/** if we apply the opening policy */
bool opening_apply = false;
uint8_t pattern_length = 0;
static struct opening_settings_t {
uint8_t days; /**< which days of the week it door access applies (bit 7 = Monday) */
uint16_t start_time; /**< at which minutes of the day to start */
uint16_t stop_time; /**< at which minutes of the day to stop */
uint8_t button_pattern[10]; /**< sequence of buttons to press to open the door */
} opening_settings;
/** timer to generate the ticks for the button LED animations */
#define LED_ANIMATION_TIMER 2
/** number of timer ticks passed, for the LED animation */
static volatile uint8_t led_animation_ticks = 0;
/** the button LED animation for the rust fade (duration in ticks, R, G, B) */
static const uint8_t rust_animation[][4] = {
{0, 0, 0, 0},
{1, 0xb7 / 10 * 1, 0x41 / 10 * 1, 0x0e / 10 * 1},
{1, 0xb7 / 10 * 2, 0x41 / 10 * 2, 0x0e / 10 * 2},
{1, 0xb7 / 10 * 3, 0x41 / 10 * 3, 0x0e / 10 * 3},
{1, 0xb7 / 10 * 4, 0x41 / 10 * 4, 0x0e / 10 * 4},
{1, 0xb7 / 10 * 5, 0x41 / 10 * 5, 0x0e / 10 * 5},
{1, 0xb7 / 10 * 4, 0x41 / 10 * 4, 0x0e / 10 * 4},
{1, 0xb7 / 10 * 3, 0x41 / 10 * 3, 0x0e / 10 * 3},
{1, 0xb7 / 10 * 2, 0x41 / 10 * 2, 0x0e / 10 * 2},
{1, 0xb7 / 10 * 1, 0x41 / 10 * 1, 0x0e / 10 * 1},
{0, 0, 0, 0},
};
/** the button LED animation for the strobe (duration in ticks, R, G, B) */
static const uint8_t strobe_animation[][4] = {
{0, 0, 0, 0},
{1, 0xff / 2, 0xff / 2, 0xff / 2},
{2, 0, 0, 0},
{1, 0xff / 2, 0xff / 2, 0xff / 2},
{0, 0, 0, 0},
};
/** save current opening_settings into SRAM */
static void save_opening_settings(void)
{
BKP_DR1 = 0; // invalid saved settings
BKP_DR2 = opening_settings.days & 0x7f;
BKP_DR3 = opening_settings.start_time;
BKP_DR4 = opening_settings.stop_time;
BKP_DR5 = opening_settings.button_pattern[0];
BKP_DR6 = opening_settings.button_pattern[1];
BKP_DR7 = opening_settings.button_pattern[2];
BKP_DR8 = opening_settings.button_pattern[3];
BKP_DR9 = opening_settings.button_pattern[4];
BKP_DR10 = opening_settings.button_pattern[5];
BKP_DR11 = opening_settings.button_pattern[6];
BKP_DR12 = opening_settings.button_pattern[7];
BKP_DR13 = opening_settings.button_pattern[8];
BKP_DR14 = opening_settings.button_pattern[9];
BKP_DR1 = 0x4223; //validate saved setting
}
size_t putc(char c)
{
size_t length = 0; // number of characters printed
@ -126,7 +203,175 @@ static void command_reset(void* argument);
/** switch to DFU bootloader
* @param[in] argument no argument required
*/
static void command_bootloader(void* argument);
static void command_bootloader_dfu(void* argument);
/** switch to system memory / embedded USART bootloader
* @param[in] argument no argument required
*/
static void command_bootloader_embedded(void* argument);
/** show/set on which days the access policy applies
* @param[in] argument 7x0/1 to enable day of the week, starting with Monday (optional)
*/
static void command_days(void* argument)
{
const char* days = (char*)argument; // argument is optional days
if (NULL != argument) { // days are provided, parse and save them
bool valid = (7 == strlen(days)); // verify input string
for (uint8_t day = 0; day < 7 && valid; day++) {
if (days[day] != '0' && days[day] != '1') {
valid = false;
}
}
if (valid) { // save provided settings
// parse new days
opening_settings.days = 0;
for (uint8_t day = 0; day < 7; day++) {
if ('1' == days[day]) {
opening_settings.days |= (1 << (6 - day));
}
}
save_opening_settings(); // save days
puts("days saved\n");
} else {
puts("provide exactly 7 times 0 (off) or 1 (on). 1st digit for Monday, 7th digit for Sunday\n");
}
}
// display current days
printf("opening days: %07b\n", opening_settings.days);
const char* day_names[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
for (uint8_t day = 0; day < LENGTH(day_names); day++) {
printf("- %s: %s\n", day_names[day], (opening_settings.days & (1 << (6 - day))) ? "on" : "off");
}
}
/** show/set on which time the access policy starts applying
* @param[in] argument string with time of day, optional
*/
static void command_start(void* argument)
{
const char* time = (char*)argument; // argument is optional time
if (NULL != argument) { // days are provided, parse and save them
bool valid = (5 == strlen(time)); // verify input string
if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) {
valid = false;
}
if (valid) { // save provided settings
opening_settings.start_time = 0;
opening_settings.start_time += (time[4] - '0') * 1;
opening_settings.start_time += (time[3] - '0') * 10;
opening_settings.start_time += (time[1] - '0') * 60;
opening_settings.start_time += (time[0] - '0') * 600;
save_opening_settings(); // save days
puts("start time saved\n");
} else {
puts("provide time in HH:MM format\n");
}
}
printf("start time: %02u:%02u\n", opening_settings.start_time / 60, opening_settings.start_time % 60);
}
/** show/set on which time the access policy stops applying
* @param[in] argument string with time of day, optional
*/
static void command_stop(void* argument)
{
const char* time = (char*)argument; // argument is optional time
if (NULL != argument) { // days are provided, parse and save them
bool valid = (5 == strlen(time)); // verify input string
if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) {
valid = false;
}
if (valid) { // save provided settings
opening_settings.stop_time = 0;
opening_settings.stop_time += (time[4] - '0') * 1;
opening_settings.stop_time += (time[3] - '0') * 10;
opening_settings.stop_time += (time[1] - '0') * 60;
opening_settings.stop_time += (time[0] - '0') * 600;
save_opening_settings(); // save days
puts("stop time saved\n");
} else {
puts("provide time in HH:MM format\n");
}
}
printf("stop time: %02u:%02u\n", opening_settings.stop_time / 60, opening_settings.stop_time % 60);
}
/** open door by simulating button press
* @param[in] argument not used
*/
static void command_open(void* argument)
{
(void)argument; // we won't use the argument
gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay and take control over the button
gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay an simulate button press
sleep_ms(1000); // hold button a bit
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and release button
if (!opening_apply) {
gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to deactivate relay and git control back to button
}
}
/** show/set button pattern
* @param[in] argument sequence of 1/2
*/
static void command_pattern(void* argument)
{
const char* pattern = (char*)argument; // argument is optional pattern
if (NULL != argument) { // pattern provided
bool valid = (LENGTH(opening_settings.button_pattern) >= strlen(pattern)); // verify input string
for (uint8_t i = 0; i < strlen(pattern) && valid; i++) {
if ('1' != pattern[i] && '2' != pattern[i]) {
valid = false;
}
}
if (valid) { // save provided settings
// reset pattern
for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern); i++) {
opening_settings.button_pattern[i] = 0;
}
// save new pattern
for (uint8_t i = 0; i < strlen(pattern); i++) {
opening_settings.button_pattern[i] = pattern[i] - '0';
}
save_opening_settings(); // save days
puts("button sequence saved\n");
} else {
printf("provide buttons sequence of up to %u 1 or 2\n", LENGTH(opening_settings.button_pattern));
}
for (pattern_length = 0; pattern_length < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[pattern_length]; pattern_length++);
}
if (0 == opening_settings.button_pattern[0]) {
puts("no button sequence set\n");
} else {
puts("button sequence: ");
for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[i]; i++) {
putc(opening_settings.button_pattern[i] + '0');
}
putc('\n');
}
}
/** test LEDs
* @param[in] argument "on" or "off"
*/
static void command_led(void* argument)
{
const char* onoff = (char*)argument; // if it should be switched on or off
if (NULL == onoff || 0 == strlen(onoff)) {
puts("say if the LEDs should be switched on or off\n");
} else if (0 == strcmp(onoff, "on")) {
for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) {
led_ws2812b_set_rgb(led, 0x20, 0x20 , 0x20);
}
} else if (0 == strcmp(onoff, "off")) {
for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) {
led_ws2812b_set_rgb(led, 0, 0 , 0);
}
} else {
printf("unknown argument %s\n", onoff);
}
}
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
@ -156,7 +401,7 @@ static const struct menu_command_t menu_commands[] = {
},
#if RTC_DATE_TIME
{
.shortcut = 'd',
.shortcut = 'D',
.name = "date",
.command_description = "show/set date and time",
.argument = MENU_ARGUMENT_STRING,
@ -178,7 +423,63 @@ static const struct menu_command_t menu_commands[] = {
.command_description = "reboot into DFU bootloader",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_bootloader,
.command_handler = &command_bootloader_dfu,
},
{
.shortcut = 'B',
.name = "embedded",
.command_description = "boot embedded USART bootloader",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_bootloader_embedded,
},
{
.shortcut = 'd',
.name = "days",
.command_description = "on which days to apply the access policy",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "[0001000, 0/1 for Monday to Sunday]",
.command_handler = &command_days,
},
{
.shortcut = 's',
.name = "start",
.command_description = "on which time to start the access policy",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "[HH:MM]",
.command_handler = &command_start,
},
{
.shortcut = 'S',
.name = "stop",
.command_description = "on which time to stop the access policy",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "[HH:MM]",
.command_handler = &command_stop,
},
{
.shortcut = 'o',
.name = "open",
.command_description = "open door",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_open,
},
{
.shortcut = 'l',
.name = "led",
.command_description = "test LEDs",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "on|off",
.command_handler = &command_led,
},
{
.shortcut = 'p',
.name = "password",
.command_description = "set/show password button sequence",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "[sequence of 1/2]",
.command_handler = &command_pattern,
},
};
@ -192,132 +493,10 @@ static void command_help(void* argument)
static void command_version(void* argument)
{
(void)argument; // we won't use the argument
bool fake = false; // if details indicate it's not an STM32
printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
puts("chip family: ");
const uint16_t dev_id = DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK;
switch (dev_id) {
case 0: // DBGMCU_IDCODE is only accessible in debug mode (this is a known issue documented in STM32F10xxC/D/E Errata sheet, without workaround)
puts("not readable, retry with debug attached");
break;
// from RM0008 STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx
case 0x412:
puts("STM32F10x low-density");
break;
case 0x410:
puts("STM32F10x medium-density");
break;
case 0x414:
puts("STM32F10x high-density");
break;
case 0x430:
puts("STM32F10x XL-density");
break;
case 0x418:
puts("STM32F10xconnectivity");
break;
// from RM0091 STM32F0x8
case 0x444:
puts("STM32F03x");
break;
case 0x445:
puts("STM32F04x");
break;
case 0x440:
puts("STM32F05x");
break;
case 0x448:
puts("STM32F07x");
break;
case 0x442:
puts("STM32F09x");
break;
// from RM0444 STM32G0x1
case 0x460:
puts("STM32G071xx/STM32G081xx");
break;
case 0x466:
puts("STM32G031xx/STM32G041xx");
break;
// from RM0090 STM32F4x5/STM32F4x7
case 0x413:
puts("STM32F405/STM32F407/STM32F415/STM32F417");
break;
case 0x419:
puts("STM32F42x/STM32F43x");
break;
// from RM0368
case 0x423:
puts("STM32F401xB/C");
break;
case 0x433:
puts("STM32F401xD/E");
break;
// from RM0383
case 0x431:
puts("STM32F411xC/E");
break;
default:
puts("unknown");
fake = true;
break;
}
printf(" (DEV_ID=0x%03x)\n", dev_id);
puts("chip revision: ");
const uint16_t rev_id = DBGMCU_IDCODE >> 16;
switch (DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK) {
case 0x412:
if (0x1000 == rev_id) {
putc('A');
} else {
puts("unknown");
}
break;
case 0x410:
if (0x0000 == rev_id) {
putc('A');
} else if (0x2000 == rev_id) {
putc('B');
} else if (0x2001 == rev_id) {
putc('Z');
} else if (0x2003 == rev_id) {
puts("1/2/3/X/Y");
} else {
puts("unknown");
}
break;
case 0x414:
if (0x1000 == rev_id) {
puts("A/1");
} else if (0x1001 == rev_id) {
putc('Z');
} else if (0x1003 == rev_id) {
puts("1/2/3/X/Y");
} else {
puts("unknown");
}
break;
case 0x430:
if (0x1003 == rev_id) {
puts("A/1");
} else {
puts("unknown");
}
break;
case 0x418:
if (0x1000 == rev_id) {
putc('A');
} else if (0x1001 == rev_id) {
putc('Z');
} else {
puts("unknown");
}
break;
default:
printf("unknown");
break;
}
printf(" (REV_ID=0x%04x)\n", rev_id);
printf("chip: ID=0x%03x, rev=0x%04x\n", dev_id, rev_id);
// show flash size
puts("flash size: ");
if (0xffff == DESIG_FLASH_SIZE) {
@ -327,100 +506,6 @@ static void command_version(void* argument)
}
// display device identity
printf("device id: %08x%08x%04x%04x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0 & 0xffff, DESIG_UNIQUE_ID0 >> 16);
// from RM0091 STM32F0x8 reference manual (not sure if it applies to F1)
printf("- X,Y wafer coordinate: %08x\n", DESIG_UNIQUE_ID0);
printf("- lot number: %c%c%c%c%c%c%c\n", DESIG_UNIQUE_ID2 >> 24, DESIG_UNIQUE_ID2 >> 16, DESIG_UNIQUE_ID2 >> 8, DESIG_UNIQUE_ID2 >> 0, DESIG_UNIQUE_ID1 >> 24, DESIG_UNIQUE_ID1 >> 16, DESIG_UNIQUE_ID1 >> 8);
printf("- wafer number: %u\n", DESIG_UNIQUE_ID1 & 0xff);
// from ARMv7-M and Cortex-M3 TRM
// ARMv7-M B3.2.3
printf("CPUID: 0x%08x\n", SCB_CPUID);
const uint8_t cpuid_implementer = (SCB_CPUID & SCB_CPUID_IMPLEMENTER) >> SCB_CPUID_IMPLEMENTER_LSB;
printf("- implementer: %s (0x%02x)\n", 0x41 == cpuid_implementer ? "ARM" : "unknown", cpuid_implementer);
const uint8_t cpuid_architecture = (SCB_CPUID & SCB_CPUID_CONSTANT) >> SCB_CPUID_CONSTANT_LSB;
puts("- architecture: ");
switch (cpuid_architecture) {
case 0xc:
puts("ARMv6-M");
break;
case 0xf:
puts("ARMv7-M");
break;
default:
fake = true;
puts("unknown");
}
printf(" (0x%x)\n", cpuid_architecture);
const uint16_t cpuid_partno = (SCB_CPUID & SCB_CPUID_PARTNO) >> SCB_CPUID_PARTNO_LSB;
puts("- part number: ");
switch (cpuid_partno) {
case 0xC60:
puts("Cortex-M0+");
break;
case 0xC20:
puts("CortexM0");
break;
case 0xC23: // the ARM spec actually mentions 0xC24
puts("CortexM3");
break;
case 0xC24:
puts("CortexM4");
break;
case 0xC27:
puts("CortexM7");
break;
default:
fake = true;
puts("unknown");
}
printf(" (0x%03x)\n", cpuid_partno);
const uint8_t cpuid_variant = (SCB_CPUID & SCB_CPUID_VARIANT) >> SCB_CPUID_VARIANT_LSB;
printf("- variant: %u\n", cpuid_variant);
const uint8_t cpuid_revision = (SCB_CPUID & SCB_CPUID_REVISION) >> SCB_CPUID_REVISION_LSB;
printf("- revision: %u\n", cpuid_revision);
// ARM CoreSight B2.2.2
const uint8_t jep106_continuation = *(uint32_t*)0xE00FFFD0 & 0x0f; // DES_2, PIDR4 bits[3:0]
const uint8_t jep106_identification = ((*(uint32_t*)0xE00FFFE8 & 0x7) << 4) + ((*(uint32_t*)0xE00FFFE4 >> 4) & 0xf); // DES_0, PIDR1 bits[7:4] JEP106 identification code bits[3:0], DES_1, PIDR2 bits[2:0] JEP106 identification code bits[6:4]
const uint16_t pidr_partno = ((*(uint32_t*)0xE00FFFE4 & 0xf) << 8) + (*(uint32_t*)0xE00FFFE0 & 0xff); // PART_0, PIDR0 bits[7:0] Part number bits[7:0], PART_1, PIDR1 bits[3:0] Part number bits[11:8]
puts("JEP106 ID: ");
if (0 == jep106_continuation && 0x20 == jep106_identification) {
puts("STM");
} else if (7 == jep106_continuation && 0x51 == jep106_identification) {
puts("GigaDevice");
} else if (4 == jep106_continuation && 0x3b == jep106_identification) {
puts("ARM");
} else {
puts("unknown");
}
printf(" (cont.=%u, ID=0x%02x), part=0x%03x\n", jep106_continuation, jep106_identification, pidr_partno);
// guess the micro-controller
puts("MCU: ");
if (1 == cpuid_variant && 1 == cpuid_revision && 0 == jep106_continuation && 0x20 == jep106_identification) { // STM32 uses Cortex-M3 r1p1 and the right JEP106 ID
puts("STM32");
} else if (2 == cpuid_variant && 1 == cpuid_revision && 7 == jep106_continuation && 0x51 == jep106_identification) { // GD32 uses Cortex-M3 r2p1 and the right JEP106 ID
puts("GD32");
fake = true;
} else if (2 == cpuid_variant && 1 == cpuid_revision && 4 == jep106_continuation && 0x3b == jep106_identification) { // GD32 uses Cortex-M3 r2p1 and ARM JEP106 ID
puts("CS32");
fake = true;
} else {
puts("unknown");
fake = true;
}
putc('\n');
// detect fake STM32
if (0x412 == dev_id || 0x410 == dev_id || 0x414 == dev_id || 0x430 == dev_id || 0x418 == dev_id) { // STM32F10x
// the original STM32F10x uses a Cortex-M3 r1p1
if (0xC23 != cpuid_partno) { // Cortex-M3
fake = true;
}
if (1 != cpuid_variant) { // r1
fake = true;
}
if (1 != cpuid_revision) { // p1
fake = true;
}
}
printf("this %s to be a genuine STM32\n", fake ? "does not seem" : "seems");
}
static void command_uptime(void* argument)
@ -435,9 +520,10 @@ 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
time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND; // get time from internal RTC
struct tm* time_tm = localtime(&time_rtc); // convert time
printf("date: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm->tm_year, time_tm->tm_mon, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
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);
} 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
@ -450,15 +536,16 @@ static void command_datetime(void* argument)
return;
}
time_tm.tm_year = strtol(&datetime[0], NULL, 10) - 1900; // parse year
time_tm.tm_mon = strtol(&datetime[5], NULL, 10); // parse month
time_tm.tm_mon = strtol(&datetime[5], NULL, 10) - 1; // parse month
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
time_rtc -= rtc_offset; // remove start offset
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
printf("date and time saved: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm.tm_year, time_tm.tm_mon, time_tm.tm_mday, time_tm.tm_hour, time_tm.tm_min, time_tm.tm_sec);
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);
}
}
#endif
@ -470,9 +557,12 @@ static void command_reset(void* argument)
while (true); // wait for the reset to happen
}
static void command_bootloader(void* argument)
static void command_bootloader_dfu(void* argument)
{
(void)argument; // we won't use the argument
// disable relays
gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN));
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN));
// set DFU magic to specific RAM location
__dfu_magic[0] = 'D';
__dfu_magic[1] = 'F';
@ -482,6 +572,24 @@ static void command_bootloader(void* argument)
while (true); // wait for the reset to happen
}
static void command_bootloader_embedded(void* argument)
{
(void)argument; // we won't use the argument
// disable relays
gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN));
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN));
// set watchdog to exit system memory after some time
iwdg_set_period_ms(25000); // set independent watchdog period (26214.4 ms if the max timeout)
iwdg_start(); // start independent watchdog
iwdg_reset(); // restart timer
// start system memory
const uint32_t address = 0x1FFFF000; // system memory address
SCB_VTOR = (volatile uint32_t)(address); // set vector table to application vector table (store at the beginning of the application)
__asm__ volatile ("MSR msp,%0" : :"r"(*(uint32_t*)address)); // set stack pointer to address provided in the beginning of the application (loaded into a register first)
(*(void(**)(void))((uint32_t)address + 4))(); // start system memory (by jumping to the reset function which address is stored as second entry of the vector table)
while (true); // this should not be reached
}
/** process user command
* @param[in] str user command string (\0 ended)
*/
@ -530,7 +638,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 dachboden door panel\n"); // print welcome message
#if DEBUG
// show reset cause
@ -572,17 +680,88 @@ void main(void)
// setup RTC
puts("setup internal RTC: ");
#if defined(BLUE_PILL) || defined(STLINKV2) || defined(BLASTER) // for boards without a Low Speed External oscillator
// note: the blue pill LSE oscillator is affected when toggling the onboard LED, thus prefer the HSE
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)
#else // for boards with an precise Low Speed External oscillator
// note: the blue pill LSE oscillator is affected when toggling the onboard LED -> DON'T USE THE ONBOARD LED since we want to use the LSE
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)
#endif
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
time_start = rtc_get_counter_val(); // get start time from internal RTC
puts("OK\n");
// setup relays
puts("setup relays: ");
rcc_periph_clock_enable(GPIO_RCC(RELAY_PANEL_PIN)); // enable clock for GPIO domain
gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to leave per default
gpio_set_mode(GPIO_PORT(RELAY_PANEL_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_PANEL_PIN)); // set as output to control the transistor controlling the relay
rcc_periph_clock_enable(GPIO_RCC(RELAY_BUTTON_PIN)); // enable clock for GPIO domain
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to leave per default
gpio_set_mode(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_BUTTON_PIN)); // set as output to control the transistor controlling the relay
puts("OK\n");
// setup buttons
puts("setup buttons: ");
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
rcc_periph_clock_enable(GPIO_RCC(BUTTON1_PIN)); // enable clock for button
gpio_set(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN)); // pull up to be able to detect button push (go low)
gpio_set_mode(GPIO_PORT(BUTTON1_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON1_PIN)); // set button pin to input
exti_select_source(GPIO_EXTI(BUTTON1_PIN), GPIO_PORT(BUTTON1_PIN)); // mask external interrupt of this pin only for this port
exti_set_trigger(GPIO_EXTI(BUTTON1_PIN), EXTI_TRIGGER_FALLING); // trigger when button is pressed
exti_enable_request(GPIO_EXTI(BUTTON1_PIN)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON1_PIN)); // enable interrupt
rcc_periph_clock_enable(GPIO_RCC(BUTTON2_PIN)); // enable clock for button
gpio_set(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN)); // pull up to be able to detect button push (go low)
gpio_set_mode(GPIO_PORT(BUTTON2_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON2_PIN)); // set button pin to input
exti_select_source(GPIO_EXTI(BUTTON2_PIN), GPIO_PORT(BUTTON2_PIN)); // mask external interrupt of this pin only for this port
exti_set_trigger(GPIO_EXTI(BUTTON2_PIN), EXTI_TRIGGER_FALLING); // trigger when button is pressed
exti_enable_request(GPIO_EXTI(BUTTON2_PIN)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON2_PIN)); // enable interrupt
puts("OK\n");
// read opening settings from SRAM
puts("reading access settings: ");
RCC_APB1ENR |= (RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN); // enable power
PWR_CR |= PWR_CR_DBP; // enable access
if (0x4223 == BKP_DR1) { // the magic header is present
opening_settings.days = BKP_DR2 & 0x7f;
opening_settings.start_time = BKP_DR3;
opening_settings.stop_time = BKP_DR4;
opening_settings.button_pattern[0] = BKP_DR5;
opening_settings.button_pattern[1] = BKP_DR6;
opening_settings.button_pattern[2] = BKP_DR7;
opening_settings.button_pattern[3] = BKP_DR8;
opening_settings.button_pattern[4] = BKP_DR9;
opening_settings.button_pattern[5] = BKP_DR10;
opening_settings.button_pattern[6] = BKP_DR11;
opening_settings.button_pattern[7] = BKP_DR12;
opening_settings.button_pattern[8] = BKP_DR13;
opening_settings.button_pattern[9] = BKP_DR14;
puts("loaded\n");
} else { // there are no settings saved
memset(&opening_settings, 0, sizeof(struct opening_settings_t)); // clear all values
puts("default\n");
}
// figure out how many button need to be pressed
for (pattern_length = 0; pattern_length < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[pattern_length]; pattern_length++);
puts("setup bell LEDs: ");
uint8_t animation_progress = 0; // index of the current animation
led_ws2812b_setup();
for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) {
led_ws2812b_set_rgb(led, 0x10, 0x10 , 0x10);
}
puts("OK\n");
puts("setup animation timer: ");
// setup timer to wait for minimal time before next transmission (after previous transmission or reception)
rcc_periph_clock_enable(RCC_TIM(LED_ANIMATION_TIMER)); // enable clock for timer block
rcc_periph_reset_pulse(RST_TIM(LED_ANIMATION_TIMER)); // reset timer state
timer_set_mode(TIM(LED_ANIMATION_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
timer_set_prescaler(TIM(LED_ANIMATION_TIMER), 1099 - 1); // set the prescaler so this 16 bits timer allows to wait for maximum 1s ( 1 / (72E6 / 1099 / (2**16)) = 1.0003s)
timer_set_period(TIM(LED_ANIMATION_TIMER), 0xffff / 16); // the timing is not defined in the specification. I tested until the communication was reliable (all requests get an response)
timer_clear_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF); // clear flag
timer_enable_irq(TIM(LED_ANIMATION_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
nvic_enable_irq(NVIC_TIM_IRQ(LED_ANIMATION_TIMER)); // catch interrupt in service routine
puts("OK\n");
// setup terminal
terminal_prefix = ""; // set default prefix
terminal_process = &process_command; // set central function to process commands
@ -591,6 +770,11 @@ 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
uint32_t last_button_action = 0; // the last time a button has been pressed
uint8_t button_pattern[LENGTH(opening_settings.button_pattern)]; // to store the input button pattern
uint8_t button_input = 0; // how many buttons have been pressed
bool rust_animated = false; // if the rust animation started
bool strobe_animated = false; // if the strobe animation started
while (true) { // infinite loop
iwdg_reset(); // kick the dog
if (user_input_available) { // user input is available
@ -599,19 +783,158 @@ 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
if (button_flag || button_pressed) { // 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
sleep_ms(200); // wait a bit to remove noise and double trigger
if (!gpio_get(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN))) {
button_pressed = 1;
}
if (!gpio_get(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN))) {
button_pressed = 2;
}
if (button_pressed) {
printf("button pressed: %u\n", button_pressed);
led_toggle(); // toggle LED
if (pattern_length > 0 && opening_apply) { // only check pattern if there is one to compare to
// switch off LEDs
for (uint8_t led = 0; led < LED_WS2812B_LEDS; led++) {
led_ws2812b_set_rgb(led, 0, 0, 0);
}
// start LED animation
if (1 == button_pressed) {
rust_animated = true; // remember rust animation started
led_ws2812b_set_rgb(3, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation
led_ws2812b_set_rgb(4, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation
led_ws2812b_set_rgb(5, rust_animation[0][1], rust_animation[0][2], rust_animation[0][3]); // start LED animation
strobe_animated = false; // stop strobe animation
led_ws2812b_set_rgb(0, 0, 0, 0); // switch LED off
led_ws2812b_set_rgb(1, 0, 0, 0); // switch LED off
led_ws2812b_set_rgb(2, 0, 0, 0); // switch LED off
} else if (2 == button_pressed) {
strobe_animated = true; // remember strobe animation started
led_ws2812b_set_rgb(0, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation
led_ws2812b_set_rgb(1, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation
led_ws2812b_set_rgb(2, strobe_animation[0][1], strobe_animation[0][2], strobe_animation[0][3]); // start LED animation
rust_animated = false; // stop rust animation
led_ws2812b_set_rgb(3, 0, 0, 0); // switch LED off
led_ws2812b_set_rgb(4, 0, 0, 0); // switch LED off
led_ws2812b_set_rgb(5, 0, 0, 0); // switch LED off
}
led_animation_ticks = 0; // reset timer counter
animation_progress = 0; // reset animation
timer_set_counter(TIM(LED_ANIMATION_TIMER), 0); // reset timer counter to get right duration
timer_enable_counter(TIM(LED_ANIMATION_TIMER)); // start timer
// store button
if (button_input < LENGTH(button_pattern)) {
button_pattern[button_input++] = button_pressed;
printf("button sequence: %u/%u\n", button_input, pattern_length);
last_button_action = rtc_get_counter_val(); // remember last button action
}
// compare pattern
if (button_input >= pattern_length) {
bool pattern_valid = true;
for (uint8_t i = 0; i < pattern_length; i++) {
if (button_pattern[i] != opening_settings.button_pattern[i]) {
pattern_valid = false;
break;
}
}
// if the correct pattern has been input, press button
if (pattern_valid) {
puts("button sequence valid\n");
gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay an simulate button press
sleep_ms(1000); // hold button a bit
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and release button
}
button_input = 0; // restart from scratch
last_button_action = 0; // restart sequence
}
} else { // ignore all button entry when not within the opening hours
button_input = 0;
last_button_action = 0;
}
// wait until both buttons are released
while (!gpio_get(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN)) || !gpio_get(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN))) {
//iwdg_reset(); // kick the dog
sleep_ms(100);
}
} // button_pressed
button_pressed = 0; // reset button pressed
button_flag = false; // reset flag
}
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 (last_button_action && last_button_action + 5 * RTC_TICKS_SECOND <= rtc_get_counter_val()) { // pattern entry timeout
puts("button sequence entry timeout\n");
last_button_action = 0; // reset last button time
button_input = 0; // reset pattern input
}
// always enforce the right state
gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to not simulate button press
// verify if day matches
const time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND + rtc_offset; // get time from internal RTC
const struct tm* time_tm = localtime(&time_rtc); // convert time
const uint16_t current_time = time_tm->tm_hour * 60 + time_tm->tm_min; // get time of day in minutes
const uint8_t day = 6 - ((time_tm->tm_wday + 6) % 7); // get bit for the current day of week
if (opening_settings.stop_time > opening_settings.start_time) { // stop time is on same day
opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time && current_time < opening_settings.stop_time);
} else { // stop time is on next day
opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time) || (opening_settings.days & (1 << (day + 1 % 7)) && current_time < opening_settings.stop_time);
}
if (opening_apply) { // we are in the opening hours
//puts("apply\n");
gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay and disconnect button
} else {
gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to release relay and connect button
}
}
if (led_animation_ticks) { // an LED animation is running
if (rust_animated) {
if (animation_progress < LENGTH(rust_animation)) {
if (led_animation_ticks >= rust_animation[animation_progress][0]) {
animation_progress++; // got to next animation step
led_animation_ticks = 0; // reset time ticks
if (animation_progress < LENGTH(rust_animation)) { // next step of animation reached
led_ws2812b_set_rgb(3, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]);
led_ws2812b_set_rgb(4, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]);
led_ws2812b_set_rgb(5, rust_animation[animation_progress][1], rust_animation[animation_progress][2], rust_animation[animation_progress][3]);
} else { // end of animation reached
timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer
led_ws2812b_set_rgb(3, 0, 0, 0); // switch off LED
led_ws2812b_set_rgb(4, 0, 0, 0); // switch off LED
led_ws2812b_set_rgb(5, 0, 0, 0); // switch off LED
}
}
} else { // end of animation reached
led_animation_ticks = 0; // disable check
timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer
}
}
if (strobe_animated) {
if (animation_progress < LENGTH(strobe_animation)) {
if (led_animation_ticks >= strobe_animation[animation_progress][0]) {
animation_progress++; // got to next animation step
led_animation_ticks = 0; // reset time ticks
if (animation_progress < LENGTH(strobe_animation)) { // next step of animation reached
led_ws2812b_set_rgb(0, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]);
led_ws2812b_set_rgb(1, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]);
led_ws2812b_set_rgb(2, strobe_animation[animation_progress][1], strobe_animation[animation_progress][2], strobe_animation[animation_progress][3]);
} else { // end of animation reached
timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer
led_ws2812b_set_rgb(3, 0, 0, 0); // switch off LED
led_ws2812b_set_rgb(4, 0, 0, 0); // switch off LED
led_ws2812b_set_rgb(5, 0, 0, 0); // switch off LED
}
}
} else { // end of animation reached
led_animation_ticks = 0; // disable check
timer_disable_counter(TIM(LED_ANIMATION_TIMER)); // stop timer
}
}
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
@ -627,3 +950,23 @@ void rtc_isr(void)
rtc_clear_flag(RTC_SEC); // clear flag
rtc_internal_tick_flag = true; // notify to show new time
}
void GPIO_EXTI_ISR(BUTTON1_PIN)(void) // it's the same at BUTTON2_PIN: EXT9_5
{
if (exti_get_flag_status(GPIO_EXTI(BUTTON1_PIN))) {
exti_reset_request(GPIO_EXTI(BUTTON1_PIN)); // reset interrupt
}
if (exti_get_flag_status(GPIO_EXTI(BUTTON2_PIN))) {
exti_reset_request(GPIO_EXTI(BUTTON2_PIN)); // reset interrupt
}
button_flag = true; // perform button action
}
/** interrupt service routine called on animation tick */
void TIM_ISR(LED_ANIMATION_TIMER)(void)
{
if (timer_get_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF)) { // update event happened
timer_clear_flag(TIM(LED_ANIMATION_TIMER), TIM_SR_UIF); // clear flag
led_animation_ticks++; // remember one tick passed
}
}

View File

@ -10,7 +10,7 @@
/* define memory regions. */
MEMORY
{
rom (rx) : ORIGIN = 0x08000000 + 8K, LENGTH = 64K - 8K
rom (rx) : ORIGIN = 0x08000000 + 8K, LENGTH = 128K - 8K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 20K - 4
}
PROVIDE(__application_beginning = ORIGIN(rom));

View File

@ -21,8 +21,8 @@ PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + 128K);
PROVIDE(__flash_end = ORIGIN(rom) + 128K);
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);

View File

@ -20,7 +20,7 @@
#pragma once
/** enable debugging functionalities */
#define DEBUG true
#define DEBUG false
/** get the length of an array */
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
@ -620,7 +620,7 @@
#define LED_PIN PA1 /**< GPIO pin (pin 11) */
#define LED_ON 0 /**< LED is on when pin is low */
#elif defined(BLUE_PILL)
#define LED_PIN PC13 /**< GPIO pin */
#define LED_PIN PB11 /**< GPIO pin */
#define LED_ON 0 /**< LED is on when pin is low */
#elif defined(BLACK_PILL)
#define LED_PIN PB12 /**< GPIO pin */

49
hc-05_porg.rb Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env ruby
# encoding: utf-8
# ruby: 2.4.1
require 'serialport'
DEBUG = true # show communication
unless ARGV.length > 0 and File.exist?(ARGV[0]) then
puts "this script will configure HC-05 bluetooth modules"
puts "provide path to serial port as argument"
exit
end
def send(at)
puts "< "+at if DEBUG # output request
@serial.write(at) # send request
@serial.write("\r\n") unless at.end_with?("\r\n")
@serial.flush
sleep 0.1
response = ""
until response.end_with?("OK") do
activity = IO.select([@serial],nil,nil,3) # wait for response
return nil unless activity # no response received
response += @serial.read(1) # get response
end
puts "> "+response if DEBUG # output response
return false unless response.end_with?("OK")
return response.chomp.gsub(/OK$/, "").chomp
end
@serial = SerialPort.open(ARGV[0],{ baud: 38400, databits: 8, parity: SerialPort::NONE, stop_bit: 1, flow_control: SerialPort::NONE})
@serial.baud = 38400 # sometimes the baud rate does not get set
@serial.read_timeout = 3000 # don't wait forever for an answer (in ms)
# commands to send
# get some general information
commands = ["AT", "AT+VERSION?", "AT+ADDR?", "AT+ROLE?", "AT+UART?", "AT+PSWD?", "AT+BIND?"]
#commands << "AT+NAME?" # somehow this does not work
commands << "AT+ORGL" # reset of factory settings
commands << 'AT+NAME="dachtuer"' # set name
commands << "AT+PSWD=1234" # set pin (1234 by default)
commands << "AT+UART=115200,0,2" # set usual baud rate (even parity, to be compatible with the bootloader)
commands << "AT+RESET" # reset to take effect (goes into non ART command mode)
# send all commands
commands.each do |command|
raise "error communicating will HC-05. ensure it is in AT mode by pressing on button while powering up" unless send(command)
end