Compare commits

...

96 Commits

Author SHA1 Message Date
King Kévin ee4da1f5ef application: minor, fix typo 2020-12-17 12:42:46 +01:00
King Kévin 6cb95a6963 application: fix rtc_to_seconds 2020-12-17 12:42:30 +01:00
King Kévin b686985eac README: minor, rename yeast to levain 2020-12-17 12:41:58 +01:00
King Kévin 0b984d2db8 application: minor, rename yeast to levain 2020-12-17 12:41:21 +01:00
King Kévin 991921ed99 application: remove MLX90614 because of errounous readings 2020-12-16 20:05:50 +01:00
King Kévin bef5cf299f application: add peripheral error display 2020-12-16 19:58:26 +01:00
King Kévin 03ed02b82c application: increase heater temperature limit 2020-12-16 19:46:04 +01:00
King Kévin 5ab04af9b0 remove unused library 2020-12-16 19:40:13 +01:00
King Kévin f808195452 README: document project 2020-12-16 19:36:43 +01:00
King Kévin 78fff2ed0f application: complete first working firmware version 2020-12-16 19:36:15 +01:00
King Kévin f5bdc1bbfc application: finish writing first prototype 2020-12-16 02:46:14 +01:00
King Kévin e3dcd93f9e sensor_mlx90614: add library to read from MLX90614 IR-thermometer 2020-12-16 02:45:13 +01:00
King Kévin 6897b3fae3 smbus_master: add SMBus library 2020-12-16 02:44:02 +01:00
King Kévin 69787f1714 i2c_master: fix stop generation 2020-12-16 02:42:34 +01:00
King Kévin 99593b92d0 i2c_master: fix wait_stop call 2020-12-16 02:38:57 +01:00
King Kévin 0c723fee11 sensor_sr04: fix shadow counter value issue 2020-12-16 02:37:35 +01:00
King Kévin 25f0235192 global: add ADC macros 2020-12-14 13:34:52 +01:00
King Kévin 579aed6e39 USB: increase text buffer size for project 2020-12-14 13:34:18 +01:00
King Kévin 695394255a application: display uptime 2020-12-12 14:45:36 +01:00
King Kévin 492fd1e6a0 application: implement uptime 2020-12-12 14:45:21 +01:00
King Kévin b9ed5ca759 application: add OLED display setup 2020-12-12 14:12:21 +01:00
King Kévin 90ead50dff oled_ssd1306: adapt to ported I²C library 2020-12-12 14:09:35 +01:00
King Kévin d0e43a82c8 i2c_master: port to STM32F4 2020-12-12 14:08:05 +01:00
King Kévin 707355f161 global: add I²C macros 2020-12-12 14:05:41 +01:00
King Kévin f48408ba7a application: minor, fix printing 2020-12-11 21:01:11 +01:00
King Kévin 05ad975fe7 application: add DS18B20 temperature sensor 2020-12-11 20:59:39 +01:00
King Kévin 2976f59622 interrupt: port to STM32F4 2020-12-11 20:56:41 +01:00
King Kévin 4f89a6a8ef interrupt: minor, fix comment 2020-12-11 20:55:39 +01:00
King Kévin 491ee17c9a onewire_master: set project specific configuration 2020-12-11 20:54:21 +01:00
King Kévin 5425e861e8 onewire_master: port to STM32F4 2020-12-11 20:53:58 +01:00
King Kévin 1cbe029139 onewire_master: minor, fix spacing 2020-12-11 20:53:21 +01:00
King Kévin 8407ed80b0 application: use sensor_sr04 2020-12-11 00:57:28 +01:00
King Kévin b86846e945 sensor_sr04: add library for HC-SR04 ultrasonic range sensor 2020-12-11 00:57:28 +01:00
King Kévin 9089a40e21 global: add tim irq defines 2020-12-11 00:57:28 +01:00
King Kévin 92c33da652 global: improve sleep_us for STM32F4 2020-12-11 00:57:28 +01:00
King Kévin 374aaacea5 Rakefile: automatically get libopencm3 2020-12-11 00:57:28 +01:00
King Kévin 510c82d00f Merge branch 'stm32f4' of ssh://git.cuvoodoo.info/stm32f1 into stm32f4 2020-12-11 00:03:15 +01:00
King Kévin 26f6de3015 sensor_max1247: STM32F4 incompatible for now 2020-12-11 00:02:44 +01:00
King Kévin a9461b53f5 README: port to F4 2020-12-11 00:00:25 +01:00
King Kévin d7b6300a50 rakefile: fix remove protection for F4 2020-12-11 00:00:25 +01:00
King Kévin d0bd71b266 application: add periodis RTC wakeup 2020-12-11 00:00:25 +01:00
King Kévin a46b6a1630 Rakefile: add macro debugging information 2020-12-11 00:00:25 +01:00
King Kévin b100c4ae13 application: RTC + date/time added 2020-12-11 00:00:25 +01:00
King Kévin a0f9b4a530 application: port to STM32F4 (RTC is not working yet) 2020-12-11 00:00:25 +01:00
King Kévin e32e27100d USB CDC ACM: fix sending loop (and spacing) 2020-12-11 00:00:25 +01:00
King Kévin 5b0523f751 uart: port to STM32F4 2020-12-11 00:00:25 +01:00
King Kévin d6cac41b78 USB CDC ACM: minor fix spacing 2020-12-11 00:00:25 +01:00
King Kévin adc62ebb9a USB CDC ACM: port to STM32F4 2020-12-11 00:00:25 +01:00
King Kévin d9a15f2daa USB CDC ACM: match serial to STM32 bootloader 2020-12-11 00:00:25 +01:00
King Kévin c4af940975 dfu: minor, improve disconnect 2020-12-11 00:00:25 +01:00
King Kévin c58d27cf2e Rakefile: add method to flash bootloader over DFU 2020-12-11 00:00:25 +01:00
King Kévin c3d7711258 global: add synchronisation barrier commands 2020-12-11 00:00:25 +01:00
King Kévin 78cb85421a global: add common function to start DFU and systeme memory 2020-12-11 00:00:25 +01:00
King Kévin ff5fbc847d DFU: fix DP pull down 2020-12-11 00:00:25 +01:00
King Kévin 51e0bfd188 DFU: minor, remove unused/duplicate code 2020-12-11 00:00:25 +01:00
King Kévin c411d552a1 DFU: set serial to match STM32 DFU bootloader 2020-12-11 00:00:25 +01:00
King Kévin ceff33ea0e Rakefile: use derivated device properties 2020-12-11 00:00:25 +01:00
King Kévin 68955ddfec bootloader: update to work with F4 2020-12-11 00:00:25 +01:00
King Kévin 40ee01ce67 usb_dfu: update to work with F4 2020-12-11 00:00:25 +01:00
King Kévin 0b2bbf8c97 libopencm3: use branch with OTG fix
because the MINIF4 board does not have an optional pull-up resistor on D+, the device is not enumerated without this fix.
this fix is not yet in official libopencm3 master.
2020-12-11 00:00:25 +01:00
King Kévin 87af738378 flash_internal: remove F1 flash utilities, add F4 section utility
compared to the STM32F1, the STM32F4 does not used 1 KB flash pages.
F4 uses variable large (>= 16 KB) flash sections.
this makes using the last page (128 KB instead of 1KB) for EEPROM highly inefficient.
caching such large pages before reprogramming small portion is also no doable (there is not enough RAM).
thus almost all F1 utilities are not applicable anymore.
to help erasing the right section, a utility to get the section from an address is added.
2020-12-11 00:00:25 +01:00
King Kévin e4ce622f15 terminal: minor, fix doc 2020-12-11 00:00:25 +01:00
King Kévin dbd0ea4d27 global: remove macro pin definition since on F4 they are not unique 2020-12-11 00:00:25 +01:00
King Kévin a878a1ad9c global: define MINIF401 button/led pins 2020-12-11 00:00:25 +01:00
King Kévin aff4275478 lib: disable most libraries since they need tuning to be F4 compatible 2020-12-11 00:00:25 +01:00
King Kévin 609188d74e Rakefile: compile for STM32F4 2020-12-11 00:00:25 +01:00
King Kévin 63a2e5e5ff *.ld: set flash and RAM size for STM32F401xC 2020-12-11 00:00:25 +01:00
King Kévin ac1bea1d45 README: port to F4 2020-11-30 15:03:32 +01:00
King Kévin 7b7f26ee47 rakefile: fix remove protection for F4 2020-11-30 14:51:06 +01:00
King Kévin 3d00bdf3c0 application: add periodis RTC wakeup 2020-11-30 14:36:33 +01:00
King Kévin 319a02d2b4 Rakefile: add macro debugging information 2020-11-28 15:19:13 +01:00
King Kévin cc8be1f278 application: RTC + date/time added 2020-11-28 15:17:52 +01:00
King Kévin e255573b1e application: port to STM32F4 (RTC is not working yet) 2020-11-27 17:07:39 +01:00
King Kévin 0fe7e1fd39 USB CDC ACM: fix sending loop (and spacing) 2020-11-27 17:06:21 +01:00
King Kévin 2249f460e3 uart: port to STM32F4 2020-11-27 16:49:59 +01:00
King Kévin aae4009fbe USB CDC ACM: minor fix spacing 2020-11-27 16:44:17 +01:00
King Kévin a9284b7154 USB CDC ACM: port to STM32F4 2020-11-27 16:43:57 +01:00
King Kévin 777fd7afb9 USB CDC ACM: match serial to STM32 bootloader 2020-11-27 16:41:19 +01:00
King Kévin 31079d95dd dfu: minor, improve disconnect 2020-11-27 16:39:51 +01:00
King Kévin ced714129c Rakefile: add method to flash bootloader over DFU 2020-11-27 16:39:11 +01:00
King Kévin 4fcfd29d2b global: add synchronisation barrier commands 2020-11-27 16:38:32 +01:00
King Kévin 06de8d0be9 global: add common function to start DFU and systeme memory 2020-11-27 16:37:52 +01:00
King Kévin de36c7f3a2 DFU: fix DP pull down 2020-11-27 16:05:55 +01:00
King Kévin 8918b97618 DFU: minor, remove unused/duplicate code 2020-11-27 16:05:37 +01:00
King Kévin 0bb2be3727 DFU: set serial to match STM32 DFU bootloader 2020-11-27 16:04:07 +01:00
King Kévin a781fc5b3b Rakefile: use derivated device properties 2020-11-27 15:54:08 +01:00
King Kévin 00ef5d9344 bootloader: update to work with F4 2020-11-24 16:18:17 +01:00
King Kévin 9fbf5b4aad usb_dfu: update to work with F4 2020-11-24 16:17:37 +01:00
King Kévin 46083bdf5e libopencm3: use branch with OTG fix
because the MINIF4 board does not have an optional pull-up resistor on D+, the device is not enumerated without this fix.
this fix is not yet in official libopencm3 master.
2020-11-24 16:11:01 +01:00
King Kévin 8a165c4d71 flash_internal: remove F1 flash utilities, add F4 section utility
compared to the STM32F1, the STM32F4 does not used 1 KB flash pages.
F4 uses variable large (>= 16 KB) flash sections.
this makes using the last page (128 KB instead of 1KB) for EEPROM highly inefficient.
caching such large pages before reprogramming small portion is also no doable (there is not enough RAM).
thus almost all F1 utilities are not applicable anymore.
to help erasing the right section, a utility to get the section from an address is added.
2020-11-24 16:04:42 +01:00
King Kévin 9db9ea9dc1 terminal: minor, fix doc 2020-11-24 16:01:49 +01:00
King Kévin 4b514c6801 global: remove macro pin definition since on F4 they are not unique 2020-11-24 16:01:06 +01:00
King Kévin 6a34352914 global: define MINIF401 button/led pins 2020-11-24 15:59:42 +01:00
King Kévin 35c441355d lib: disable most libraries since they need tuning to be F4 compatible 2020-11-24 15:56:00 +01:00
King Kévin 9751880813 Rakefile: compile for STM32F4 2020-11-24 15:51:03 +01:00
King Kévin e58614002c *.ld: set flash and RAM size for STM32F401xC 2020-11-24 15:48:25 +01:00
74 changed files with 2225 additions and 9461 deletions

3
.gitmodules vendored
View File

@ -1,4 +1,5 @@
[submodule "libopencm3"]
path = libopencm3
url = https://github.com/libopencm3/libopencm3
url = https://github.com/manuelbl/libopencm3
ignore = all
branch = no-vbus-sensing

118
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).
This is the firmware for the elevainitor, a sourdough starter (aka. levain) elevator.
project
=======
@ -6,48 +6,93 @@ project
summary
-------
*describe project purpose*
The purpose is to grow and train a yeast culture in a sourdough started.
The yeast will multiply on its own, but the optimum temperature is 30 - 35 °C.
A heater will keep the temperature around 32.5 °C.
This will also allow to have similar condition to measure the yeast's efficiency over time.
The device with also monitor the sourdough starter rising under the activity of the yeast.
technology
----------
*described electronic details*
A small glass (e.g. 350 mL) is used to make a sourdough starter.
board
=====
To make a starter:
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
1. mix 50 g of flour and 50g of water (whole wheat flour contains generally more yeast and bacteria since there is more of the grain's shell)
2. leave it in the glass for 24h
3. mix 50 g of this starter with 50 g of flour and 50 g of water
4. repeat from step 2 until the starter is rising twice its original size after a couple of hours
5. mix 10 g of this starter with 50 g of flour and 50 g of water
6. leave it for a couple of hours until it's falling back in
7. you can keep it in the fridge for a couple of days (maximum until a gray liquid layer is forming)
8. repeat from step 5
The underlying template also supports following board:
A heater is used to keep the glass above 30 °C.
The heater is made out of a lid fitting the glass, so the glass fits perfectly on top of it.
A 5 Ohm 10 W ceramic resistor is epoxy glued in the lid.
A DS18B20 sensor is placed next to it to monitor it's temperature.
The thermometer is used to prevent the lid from over-heating.
- [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
The top lid of the glass if equipped with the following:
**Which board is used is defined in the Makefile**.
This is required to map the user LED and button provided on the board
- a 10 kOhm thermistor in a stainless steel tube, to measure the starter's temperature once plunged into it
- a HC-SR04 ultrasonic range sensor, to measure the starter's rising
- a n-channel MOSFET, to power the power resistor in the heater
- a SSD1306 0.96 inch OLED display, to show the starter's temperature and growth
- a STM32F401-based [WeAct MiniF4](https://github.com/WeActTC/MiniF4-STM32F4x1) development board, to control all peripherals
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 top lid also used to have a MLX90614 IR-thermometer, but the reading were wrong (24 °C instead of 29 °C).
This might be because of the high humidity when heating up, and droplets are forming on the sensor.
It can maybe corrected by changing the emissivity, but a thermistor is just the easier solution.
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.
To use the yeast raiser:
1. put the lid on the empty glass
2. power the board, through the USB-C, USB micro-B, or OD 5.5 mm/ID 2.1 mm barrel jack, using a 5 V 1 A power supply
3. the screen should display a message
4. press on the button, which will measure the height of the glass
5. prepare the sourdough starter in the glass
6. put back the lid on, inserting the thermistor in the starter
7. press on the button, which will measure the height of the initial started
8. the yeast raiser will continuously monitor and display the height and temperature of the sourdough starter
9. it will heat the starter to 30 - 35 °C, until it falls back
10. it will show when, and how high the starter was when it reached its peak
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
Connect the peripherals the following way.
- *list board to peripheral pin connections*
button (if not already present on the development board):
1. ground
2. PA0
power n-channel MOSFET:
1. gate: PB12, pulled up by 100 kO resistor to 5V
2. drain: power resistor for heated bed
3. source: ground
HC-SR04 ultrasonic range sensor:
1. ground
2. echo: PB9
3. trigger: PB8
4. VCC: 5V
DS18B20 1-Wire temperature sensor:
1. ground
2. 1-Wire: PB10, pulled up by 1 kO resistor to 5V
3. VCC: 5V
SSD1306 OLED display module:
1. gound
2. VDD: 3.3V
3. SCK: PB6/I2C1_SCL, pulled up by 10-47 kO resistor to 3.3V
4. SDA: PB7/I2C1_SDA, pulled up by 10-47 kO resistor to 3.3V
10 kOhm NTC thermistor:
1. ground
2. PA6, pulled to to 3.3V by 10 kOhm resistor
All pins are configured using `define`s in the corresponding source code.
@ -81,15 +126,20 @@ 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 `bootloader` image will be flashed using SWD (Serial Wire Debug).
The simplest way do flash the `bootloader` image is using the embedded bootloader.
By pressing the BOOT0 button (setting the pin low) while powering or resetting the device, the micro-controller boot its embedded UART/USB DFU bootloader.
Connect a USB cable and run `rake dfu_bootloader`.
Once the `bootloader` is flashed, it is possible to flash the `application` over USB using the DFU protocol by running `rake flash` (equivalent to `rake dfu_application`.
To force the bootloader to start the DFU mode press the user button or short a pin, depending on the board.
Note: I use my own DFU bootloader instead of the embedded bootloader because I was not able to start the embedded USB DFU bootloader from the application.
The images can also be flash using SWD (Serial Wire Debug) in case the firmware gets stuck and does not provide USB functionalities.
For that you need an SWD adapter.
The `Makefile` uses a ST-Link V2 programmer along OpenOCD software (default), or Black Magic Probe.
To flash the `booltoader` using SWD run `rake flash_booloader`.
If the development board uses the CKS32 chip STM32 alternative, use `CPUTAPID=0x2ba01477 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`.
To flash the `booltoader` using SWD run `rake swd_booloader` (this will also erase the application).
To flash the `application` using SWD run `rake swd_aplication` (or `rake swd`).
To erase all memory and unlock read/write protection, run `rake remove_protection`.
debug
-----

View File

@ -1,8 +1,8 @@
# encoding: utf-8
# ruby: 2.4.2
=begin
Rakefile to manage compile CuVoodoo STM32F1 firmware.
the firmware is for development board based around a STM32F1xx micro-controller.
Rakefile to manage compiling CuVoodoo STM32F4 firmware.
the firmware is for development boards based around a STM32F4xx micro-controller.
the firmware uses the libopencm3 library providing support for this micro-controller.
=end
require 'rake'
@ -14,15 +14,26 @@ APPLICATION = "application"
FIRMWARES = [BOOTLOADER, APPLICATION]
# which development board is used
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, BLACK_PILL, CORE_BOARD, STLINKV2, BLASTER, BUSVOODOO
BOARD = ENV["BOARD"] || "BLUE_PILL"
# supported are: WeAct MiniF4 with STM32F401
BOARD = ENV["BOARD"] || "MINIF401"
# get MCU from board
DEVICE = case BOARD
when "MINIF401"
"stm32f401cc"
else
raise "unknown MCU for board #{BOARD}"
end
# libopencm3 definitions
LIBOPENCM3_DIR = "libopencm3"
LIBOPENCM3_INC = LIBOPENCM3_DIR+"/include"
LIBOPENCM3_LIB = LIBOPENCM3_DIR+"/lib"
# STM32F1 library used for this project provided by libopencm3
STM32F1_LIB = "opencm3_stm32f1"
LIBOPENCM3_LIBS = LIBOPENCM3_DIR+"/lib"
# get libopencm3
unless File.file?("./#{LIBOPENCM3_DIR}/scripts/genlink.py") then
sh "git submodule init"
sh "git submodule update"
end
LIBOPENCM3_LIB = "opencm3_" + `./#{LIBOPENCM3_DIR}/scripts/genlink.py #{LIBOPENCM3_DIR}/ld/devices.data #{DEVICE} FAMILY`
# source code used by the firmware
SRC_DIRS = [".", "lib"]
@ -43,7 +54,7 @@ cflags = [ENV["CFLAGS"]]
# optimize for size
cflags << "-Os"
# add debug symbols (remove for smaller release)
cflags << "-ggdb"
cflags << "-ggdb3"
# use C99 (supported by most an sufficient)
cflags << "-std=c99"
# have strict warning (for better code)
@ -59,7 +70,8 @@ cflags += SRC_DIRS.collect {|srd_dir| "-I #{srd_dir}"}
# include libopencm3 library
cflags << "-I #{LIBOPENCM3_INC}"
# add defines for micro-controller and board
cflags << "-DSTM32F1 -D#{BOARD}"
cflags << "-D#{BOARD}"
cflags << `./#{LIBOPENCM3_DIR}/scripts/genlink.py #{LIBOPENCM3_DIR}/ld/devices.data #{DEVICE} CPPFLAGS`
# render cflags
cflags = cflags.compact*' '
@ -76,14 +88,17 @@ ldflags_linker = ["--gc-sections"]
# show memory usage
ldflags_linker << "--print-memory-usage"
# add libopencm3 libraries
library_paths = [LIBOPENCM3_LIB]
library_paths = [LIBOPENCM3_LIBS]
# project libraries
ldlibs = [STM32F1_LIB]
ldlibs = [LIBOPENCM3_LIB]
# general libraries (gcc provides the ARM ABI)
ldlibs_linker = ["m", "c", "nosys", "gcc"]
# target micro-controller information (ARM Cortex-M3 supports thumb and thumb2, but does not include a floating point unit)
archflags = "-mthumb -mcpu=cortex-m3 -msoft-float"
# target micro-controller information (ARM Cortex-M4 supports thumb and thumb2, but does not include a floating point unit)
archflags = "-mthumb"
archflags += " -mcpu=" + `./#{LIBOPENCM3_DIR}/scripts/genlink.py #{LIBOPENCM3_DIR}/ld/devices.data #{DEVICE} CPU`
archflags += " -mfloat-abi=" + `./#{LIBOPENCM3_DIR}/scripts/genlink.py #{LIBOPENCM3_DIR}/ld/devices.data #{DEVICE} FPU`.split("-")[0]
archflags += " -mfpu=" + `./#{LIBOPENCM3_DIR}/scripts/genlink.py #{LIBOPENCM3_DIR}/ld/devices.data #{DEVICE} FPU`.split("-")[1..-1]*"-"
desc "compile firmwares"
task :default => FIRMWARES
@ -127,14 +142,8 @@ def dependencies(source, done=[])
return done
end
desc "get libopencm3"
file LIBOPENCM3_DIR+"/Makefile" do
sh "git submodule init"
sh "git submodule update"
end
desc "compile libopencm3"
file "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a" => LIBOPENCM3_DIR+"/Makefile" do
file "#{LIBOPENCM3_LIBS}/lib#{LIBOPENCM3_LIB}.a" do
sh "make --directory #{LIBOPENCM3_DIR}"
end
@ -143,17 +152,17 @@ task :doc => ["Doxyfile", "README.md"] do |t|
end
desc "compile source into object"
rule '.o' => ['.c', proc{|f| File.file?(f.ext("h")) ? f.ext("h") : []}, proc{|f| dependencies(f).collect{|d| File.file?(d.ext("h")) ? d.ext("h") : []}}, "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a"] do |t|
rule '.o' => ['.c', proc{|f| File.file?(f.ext("h")) ? f.ext("h") : []}, proc{|f| dependencies(f).collect{|d| File.file?(d.ext("h")) ? d.ext("h") : []}}, "#{LIBOPENCM3_LIBS}/lib#{LIBOPENCM3_LIB}.a"] do |t|
sh "#{CC} #{cflags} #{archflags} -o #{t.name} -c #{t.prerequisites[0]}"
end
desc "generate dependencies"
rule '.d' => ['.c', "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a"] do |t|
rule '.d' => ['.c', "#{LIBOPENCM3_LIBS}/lib#{LIBOPENCM3_LIB}.a"] do |t|
sh "#{CC} #{cflags} #{archflags} -MM -MF #{t.name} -c #{t.prerequisites[0]}"
end
desc "link binary"
rule '.elf' => ['.o', proc{|f| dependencies(f)}, '.ld', "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a"] do |t|
rule '.elf' => ['.o', proc{|f| dependencies(f)}, '.ld', "#{LIBOPENCM3_LIBS}/lib#{LIBOPENCM3_LIB}.a"] do |t|
sh "#{LD} #{archflags} #{ldflags.join(' ')} #{t.prerequisites[0..-3].join(' ')} -T#{t.name.ext('ld')} #{ldflags_linker.collect{|flag| "-Wl,"+flag}.join(' ')} #{library_paths.collect{|path| "-L"+path}.join(' ')} #{ldlibs.collect{|lib| "-l"+lib}.join(' ')} -Wl,--start-group #{ldlibs_linker.collect{|lib| "-l"+lib}.join(' ')} -Wl,--end-group --output #{t.name}"
end
@ -190,42 +199,50 @@ OOCD = ENV["OOCD"] || "openocd"
# openOCD adapted name
OOCD_INTERFACE = ENV["OOCD_INTERFACE"] || (SWD_ADAPTER=="STLINKV2" ? "stlink" : "")
# openOCD target for the micro-controller
OOCD_TARGET = "stm32f1x"
OOCD_TARGET = "stm32f4x"
# Black Magic Probe port
BMP_PORT = ENV["BMP_PORT"] || "/dev/ttyACM0"
# set CPUTAPID (0x1ba01477 for STM32, 0x2ba01477 for CKS32/APM32)
CPUTAPID = ENV["CPUTAPID"] || "0x1ba01477"
desc "flash application"
task :flash => :dfu_application
desc "flash application using USB DFU"
task :flash => APPLICATION+".bin" do |t|
task :dfu_application => APPLICATION+".bin" do |t|
sh "dfu-util --device 1209:4356 --download #{t.source}"
end
desc "remove STM32F1 protection using SWD"
desc "flash application using USB DFU"
task :dfu_bootloader => BOOTLOADER+".bin" do |t|
sh "dfu-util --device 0483:df11 --cfg 1 --intf 0 --alt 0 --dfuse-address 0x08000000 --download #{t.source} --reset"
end
desc "remove STM32F4 protection using SWD"
task :remove_protection do
case SWD_ADAPTER
when "STLINKV2"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --command 'set CPUTAPID #{CPUTAPID}' --file target/#{OOCD_TARGET}.cfg --command 'init' --command 'halt' --command 'reset init' --command 'stm32f1x unlock 0' --command 'reset init' --command 'flash protect 0 0 last off' --command 'reset init' --command 'stm32f1x options_write 0 SWWDG NORSTSTNDBY NORSTSTOP' --command 'reset init' --command 'stm32f1x mass_erase 0' --command 'shutdown'"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --file target/#{OOCD_TARGET}.cfg --command 'init' --command 'halt' --command 'reset init' --command 'stm32f2x unlock 0' --command 'reset init' --command 'flash protect 0 0 last off' --command 'reset init' --command 'stm32f2x mass_erase 0' --command 'shutdown'"
when "BMP"
sh "#{GDB} --eval-command='target extended-remote #{BMP_PORT}' --eval-command='set confirm off' --eval-command='monitor swdp_scan' --eval-command='attach 1' --eval-command='monitor option erase' --eval-command='monitor erase_mass' --eval-command='kill' --eval-command='quit'"
end
end
desc "flash bootloader using SWD"
task :flash_bootloader => BOOTLOADER+".hex" do |t|
task :swd_bootloader => BOOTLOADER+".hex" do |t|
case SWD_ADAPTER
when "STLINKV2"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --command 'set CPUTAPID #{CPUTAPID}' --file target/#{OOCD_TARGET}.cfg --command 'init' --command 'halt' --command 'reset init' --command 'flash erase_sector 0 0 last' --command 'flash write_image erase #{t.source}' --command 'reset' --command 'shutdown'"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --file target/#{OOCD_TARGET}.cfg --command 'init' --command 'halt' --command 'reset init' --command 'flash erase_sector 0 0 last' --command 'flash write_image erase #{t.source}' --command 'reset' --command 'shutdown'"
when "BMP"
sh "#{GDB} --eval-command='target extended-remote #{BMP_PORT}' --eval-command='set confirm off' --eval-command='monitor swdp_scan' --eval-command='attach 1' --eval-command='monitor erase_mass' --eval-command='load' --eval-command='kill' --eval-command='quit' #{t.source}"
end
end
task :swd => :swd_application
desc "flash application using SWD"
task :flash_application => APPLICATION+".hex" do |t|
task :swd_application => APPLICATION+".hex" do |t|
case SWD_ADAPTER
when "STLINKV2"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --command 'set CPUTAPID #{CPUTAPID}' --file target/#{OOCD_TARGET}.cfg --command 'adapter speed 100' --command 'init' --command 'halt' --command 'reset init' --command 'flash write_image erase #{t.source}' --command 'reset' --command 'shutdown'"
sh "#{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command 'transport select hla_swd' --file target/#{OOCD_TARGET}.cfg --command 'adapter speed 100' --command 'init' --command 'halt' --command 'reset init' --command 'flash write_image erase #{t.source}' --command 'reset' --command 'shutdown'"
when "BMP"
sh "#{GDB} --eval-command='target extended-remote #{BMP_PORT}' --eval-command='set confirm off' --eval-command='monitor swdp_scan' --eval-command='attach 1' --eval-command='load' --eval-command='kill' --eval-command='quit' #{t.source}"
end
@ -237,7 +254,7 @@ task :debug => APPLICATION+".elf" do |t|
case SWD_ADAPTER
when "STLINKV2"
# for GDB to work with openOCD the firmware needs to be reloaded
exec("#{GDB} --eval-command='target remote | #{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command \"transport select hla_swd\" --command \"set CPUTAPID #{CPUTAPID}\" --file target/#{OOCD_TARGET}.cfg --command \"gdb_port pipe; log_output /dev/null; init\"' #{t.source}")
exec("#{GDB} --eval-command='target remote | #{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command \"transport select hla_swd\" --file target/#{OOCD_TARGET}.cfg --command \"gdb_port pipe; log_output /dev/null; init\"' #{t.source}")
when "BMP"
exec("#{GDB} --eval-command='target extended-remote #{BMP_PORT}' --eval-command='monitor version' --eval-command='monitor swdp_scan' --eval-command='attach 1' #{t.source}")
end
@ -249,7 +266,7 @@ task :debug_bootloader => BOOTLOADER+".elf" do |t|
case SWD_ADAPTER
when "STLINKV2"
# for GDB to work with openOCD the firmware needs to be reloaded
exec("#{GDB} --eval-command='target remote | #{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command \"transport select hla_swd\" --command \"set CPUTAPID #{CPUTAPID}\" --file target/#{OOCD_TARGET}.cfg --command \"gdb_port pipe; log_output /dev/null; init\"' --eval-command='monitor reset init' #{t.source}")
exec("#{GDB} --eval-command='target remote | #{OOCD} --file interface/#{OOCD_INTERFACE}.cfg --command \"transport select hla_swd\" --file target/#{OOCD_TARGET}.cfg --command \"gdb_port pipe; log_output /dev/null; init\"' --eval-command='monitor reset init' #{t.source}")
when "BMP"
exec("#{GDB} --eval-command='target extended-remote #{BMP_PORT}' --eval-command='monitor version' --eval-command='monitor swdp_scan' --eval-command='attach 1' #{t.source}")
end

View File

@ -1,4 +1,4 @@
/** STM32F1 application example
/** firmware to raise sourdough starter (aka. levain)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -11,6 +11,7 @@
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <ctype.h> // utilities to check chars
#include <math.h> // NaN definition
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
@ -24,72 +25,121 @@
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/adc.h> // ADC 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 "sensor_sr04.h" // range measurement utilities
#include "sensor_ds18b20.h" // 1-Wire temperature sensor utilities
#include "oled_text.h" // OLED display utilities
/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000
/** 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
#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;
/** wakeup frequency (i.e. least number of times per second to perform the main loop) */
#define WAKEUP_FREQ 16
/** @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 wakeup_flag = false; /**< flag set when wakeup timer triggered */
static volatile bool second_flag = false; /**< flag set when a second passed */
/** @} */
/** number of seconds since boot */
static uint32_t boot_time = 0;
// current state
static uint32_t levain_time = 0; /**< when we start heating the yeas */
static uint32_t max_time = 0; /**< when the sourdough starter has grown */
static uint16_t current_height = 0; /**< current height */
static uint8_t container_height = 0; /**< how height the container is */
static uint8_t starter_height = 0; /**< how height the sourdough starter starter is */
static uint8_t max_height = 0; /**< the maximum height the sourdough starter reached */
static float heater_temp = NAN; /**< heater temperature */
static float levain_temp = NAN; /**< sourdough starter temperature */
/** heater control pin
* @note connected to power nMOS gate, pulled up to 5V
*/
#define HEATER_PIN PB12
#define heater_on() gpio_set(GPIO_PORT(HEATER_PIN), GPIO_PIN(HEATER_PIN)) // switch transistor on to let resistor heat
#define heater_off() gpio_clear(GPIO_PORT(HEATER_PIN), GPIO_PIN(HEATER_PIN)) // switch transistor off
/** maximum heater temperature, in °C
* @note 50 °C only allowed the starter to go up to 29 °C
*/
#define HEATER_LIMIT 60.0
/** ADC channel connected to thermistor */
#define THERMISTOR_CHANNEL 6 // PA6
/** voltages to convert (channel 17 = internal voltage reference) */
const uint8_t channels[] = {ADC_CHANNEL17, ADC_CHANNEL(THERMISTOR_CHANNEL)};
/** pin to control buzzer (active high, piezo-element with driver circuit) */
#define BUZZER_PIN PB2
/** temperature to heat the sourdough starter to, in °C */
const float levain_target = 30.0;
size_t putc(char c)
{
size_t length = 0; // number of characters printed
static char last_c = 0; // to remember on which character we last sent
if ('\n' == c) { // send carriage return (CR) + line feed (LF) newline for each LF
if ('\r' != last_c) { // CR has not already been sent
#if !defined(STLINKV2)
uart_putchar_nonblocking('\r'); // send CR over USART
#endif
usb_cdcacm_putchar('\r'); // send CR over USB
length++; // remember we printed 1 character
}
}
#if !defined(STLINKV2)
uart_putchar_nonblocking(c); // send byte over USART
#endif
usb_cdcacm_putchar(c); // send byte over USB
length++; // remember we printed 1 character
last_c = c; // remember last character
return length; // return number of characters printed
}
/** get voltage of thermistor (in V)
* @return thermistor voltage
*/
static double thermistor_voltage(void)
{
// read thermistor resistance using ADC (using resistor divider)
ADC_SR(ADC1) = 0; // reset flags
uint16_t adc_values[LENGTH(channels)];
double voltages[LENGTH(channels)];
for (uint8_t i = 0; i < LENGTH(channels); i++) {
adc_start_conversion_regular(ADC1); // start conversion (using trigger)
while (!adc_eoc(ADC1)); // wait until conversion finished
adc_values[i] = adc_read_regular(ADC1); // read voltage value (clears flag)
voltages[i] = adc_values[i] * 1.21 / adc_values[0]; // use 1.21 V internal voltage reference to get ADC voltage
}
return voltages[1];
}
/** get temperature of thermistor (in °C)
* @return thermistor temperature
*/
static double thermistor_temperature(void)
{
// convert to °C
// calibrated using a TP101
#define TEMP1 20.5
#define VOLTAGE1 1.813
#define TEMP2 37.9
#define VOLTAGE2 1.197
#define THERMISTOR_SLOPE ((TEMP2 - TEMP1) / (VOLTAGE2 - VOLTAGE1))
#define THERMISTOR_OFFSET (TEMP1 - VOLTAGE1 * THERMISTOR_SLOPE)
return thermistor_voltage() * THERMISTOR_SLOPE + THERMISTOR_OFFSET;
}
// menu commands
/** display available commands
* @param[in] argument no argument required
*/
@ -98,29 +148,217 @@ static void command_help(void* argument);
/** show software and hardware version
* @param[in] argument no argument required
*/
static void command_version(void* argument);
static void command_version(void* argument)
{
(void)argument; // we won't use the argument
printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
printf("device serial: %08x%08x%08x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0); // show complete serial (different than the one used for USB)
}
/** convert RTC date/time to number of seconds
* @return number of seconds since 2000-01-01 00:00:00
* @warning for simplicity I consider every month to have 31 days
*/
static uint32_t rtc_to_seconds(void)
{
rtc_wait_for_synchro(); // wait until date/time is synchronised
const uint8_t year = ((RTC_DR >> RTC_DR_YT_SHIFT) & RTC_DR_YT_MASK) * 10 + ((RTC_DR >> RTC_DR_YU_SHIFT) & RTC_DR_YU_MASK); // get year
uint8_t month = ((RTC_DR >> RTC_DR_MT_SHIFT) & RTC_DR_MT_MASK) * 10 + ((RTC_DR >> RTC_DR_MU_SHIFT) & RTC_DR_MU_MASK); // get month
if ((RTC_ISR & RTC_ISR_INITS) && month > 0) { // month has been initialized, but starts with 1
month--; // fix for calculation
}
uint8_t day = ((RTC_DR >> RTC_DR_DT_SHIFT) & RTC_DR_DT_MASK) * 10 + ((RTC_DR >> RTC_DR_DU_SHIFT) & RTC_DR_DU_MASK); // get day
if ((RTC_ISR & RTC_ISR_INITS) && day > 0) { // day has been initialized, but starts with 1
day--; // fix for calculation
}
uint8_t hour = ((RTC_TR >> RTC_TR_HT_SHIFT) & RTC_TR_HT_MASK) * 10 + ((RTC_TR >> RTC_TR_HU_SHIFT) & RTC_TR_HU_MASK); // get hours
if (RTC_TR & RTC_TR_PM) { // PM notation is used
hour += 12;
}
const uint8_t minute = ((RTC_TR >> RTC_TR_MNT_SHIFT) & RTC_TR_MNT_MASK) * 10 + ((RTC_TR >> RTC_TR_MNU_SHIFT) & RTC_TR_MNU_MASK); // get minutes
const uint8_t second = ((RTC_TR >> RTC_TR_ST_SHIFT) & RTC_TR_ST_MASK) * 10 + ((RTC_TR >> RTC_TR_SU_SHIFT) & RTC_TR_SU_MASK); // get seconds
const uint32_t seconds = ((((((((year * 12) + month) * 31) + day) * 24) + hour) * 60) + minute) * 60 + second; // convert to number of seconds
return seconds;
}
/** show uptime
* @param[in] argument no argument required
*/
static void command_uptime(void* argument);
static void command_uptime(void* argument)
{
(void)argument; // we won't use the argument
const uint32_t uptime = rtc_to_seconds() - boot_time; // get time from internal RTC
printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60);
}
#if RTC_DATE_TIME
/** show date and time
* @param[in] argument date and time to set
*/
static void command_datetime(void* argument);
#endif
static void command_datetime(void* argument)
{
char* datetime = (char*)argument; // argument is optional date time
const char* days[] = { "??", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}; // the days of the week
// set date
if (datetime) { // date has been provided
// parse date
const char* malformed = "date and time malformed, expecting YYYY-MM-DD WD HH:MM:SS\n";
if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + 2 + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough
printf(malformed);
return;
}
if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && \
'-' == datetime[4] && \
isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && \
'-' == datetime[7] && \
isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && \
' ' == datetime[10] && \
isalpha((int8_t)datetime[11]) && isalpha((int8_t)datetime[12]) && \
' ' == datetime[13] && \
isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && \
':' == datetime[16] && \
isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]) && \
':' == datetime[19] && \
isdigit((int8_t)datetime[20]) && isdigit((int8_t)datetime[21]))) { // verify format (good enough to not fail parsing)
printf(malformed);
return;
}
const uint16_t year = strtol(&datetime[0], NULL, 10); // parse year
if (year <= 2000 || year > 2099) {
puts("year out of range\n");
return;
}
const uint8_t month = strtol(&datetime[5], NULL, 10); // parse month
if (month < 1 || month > 12) {
puts("month out of range\n");
return;
}
const uint8_t day = strtol(&datetime[8], NULL, 10); // parse day
if (day < 1 || day > 31) {
puts("day out of range\n");
return;
}
const uint8_t hour = strtol(&datetime[14], NULL, 10); // parse hour
if (hour > 24) {
puts("hour out of range\n");
return;
}
const uint8_t minute = strtol(&datetime[17], NULL, 10); // parse minutes
if (minute > 59) {
puts("minute out of range\n");
return;
}
const uint8_t second = strtol(&datetime[30], NULL, 10); // parse seconds
if (second > 59) {
puts("second out of range\n");
return;
}
uint8_t week_day = 0;
for (uint8_t i = 1; i < LENGTH(days) && 0 == week_day; i++) {
if (days[i][0] == toupper(datetime[11]) && days[i][1] == tolower(datetime[12])) {
week_day = i;
break;
}
}
if (0 == week_day) {
puts("unknown week day\n");
return;
}
uint32_t date = 0; // to build the date
date |= (((year - 2000) / 10) & RTC_DR_YT_MASK) << RTC_DR_YT_SHIFT; // set year tenth
date |= (((year - 2000) % 10) & RTC_DR_YU_MASK) << RTC_DR_YU_SHIFT; // set year unit
date |= ((month / 10) & RTC_DR_MT_MASK) << RTC_DR_MT_SHIFT; // set month tenth
date |= ((month % 10) & RTC_DR_MU_MASK) << RTC_DR_MU_SHIFT; // set month unit
date |= ((day / 10) & RTC_DR_DT_MASK) << RTC_DR_DT_SHIFT; // set day tenth
date |= ((day % 10) & RTC_DR_DU_MASK) << RTC_DR_DU_SHIFT; // set day unit
date |= (week_day & RTC_DR_WDU_MASK) << RTC_DR_WDU_SHIFT; // time day of the week
uint32_t time = 0; // to build the time
time = 0; // reset time
time |= ((hour / 10) & RTC_TR_HT_MASK) << RTC_TR_HT_SHIFT; // set hour tenth
time |= ((hour % 10) & RTC_TR_HU_MASK) << RTC_TR_HU_SHIFT; // set hour unit
time |= ((minute / 10) & RTC_TR_MNT_MASK) << RTC_TR_MNT_SHIFT; // set minute tenth
time |= ((minute % 10) & RTC_TR_MNU_MASK) << RTC_TR_MNU_SHIFT; // set minute unit
time |= ((second / 10) & RTC_TR_ST_MASK) << RTC_TR_ST_SHIFT; // set second tenth
time |= ((second % 10) & RTC_TR_SU_MASK) << RTC_TR_SU_SHIFT; // set second unit
// write date
pwr_disable_backup_domain_write_protect(); // disable backup protection so we can set the RTC clock source
rtc_unlock(); // enable writing RTC registers
RTC_ISR |= RTC_ISR_INIT; // enter initialisation mode
while (!(RTC_ISR & RTC_ISR_INITF)); // wait to enter initialisation mode
RTC_DR = date; // set date
RTC_TR = time; // set time
RTC_ISR &= ~RTC_ISR_INIT; // exit initialisation mode
rtc_lock(); // protect RTC register against writing
pwr_enable_backup_domain_write_protect(); // re-enable protection now that we configured the RTC clock
boot_time = rtc_to_seconds() - boot_time; // adjust boot time
}
// show date
if (!(RTC_ISR & RTC_ISR_INITS)) { // date has not been set yet
puts("date/time not initialized\n");
} else {
rtc_wait_for_synchro(); // wait until date/time is synchronised
const uint8_t year = ((RTC_DR >> RTC_DR_YT_SHIFT) & RTC_DR_YT_MASK) * 10 + ((RTC_DR >> RTC_DR_YU_SHIFT) & RTC_DR_YU_MASK); // get year
const uint8_t month = ((RTC_DR >> RTC_DR_MT_SHIFT) & RTC_DR_MT_MASK) * 10 + ((RTC_DR >> RTC_DR_MU_SHIFT) & RTC_DR_MU_MASK); // get month
const uint8_t day = ((RTC_DR >> RTC_DR_DT_SHIFT) & RTC_DR_DT_MASK) * 10 + ((RTC_DR >> RTC_DR_DU_SHIFT) & RTC_DR_DU_MASK); // get day
const uint8_t week_day = ((RTC_DR >> RTC_DR_WDU_SHIFT) & RTC_DR_WDU_MASK); // get week day
const uint8_t hour = ((RTC_TR >> RTC_TR_HT_SHIFT) & RTC_TR_HT_MASK) * 10 + ((RTC_TR >> RTC_TR_HU_SHIFT) & RTC_TR_HU_MASK); // get hours
const uint8_t minute = ((RTC_TR >> RTC_TR_MNT_SHIFT) & RTC_TR_MNT_MASK) * 10 + ((RTC_TR >> RTC_TR_MNU_SHIFT) & RTC_TR_MNU_MASK); // get minutes
const uint8_t second = ((RTC_TR >> RTC_TR_ST_SHIFT) & RTC_TR_ST_MASK) * 10 + ((RTC_TR >> RTC_TR_SU_SHIFT) & RTC_TR_SU_MASK); // get seconds
printf("date: 20%02d-%02d-%02d %s %02d:%02d:%02d\n", year, month, day, days[week_day], hour, minute, second);
}
}
/** reset board
* @param[in] argument no argument required
*/
static void command_reset(void* argument);
static void command_reset(void* argument)
{
(void)argument; // we won't use the argument
scb_reset_system(); // reset device
while (true); // wait for the reset to happen
}
/** switch to system memory (e.g. embedded bootloader)
* @param[in] argument no argument required
*/
static void command_system(void* argument)
{
(void)argument; // we won't use the argument
system_memory(); // jump to system memory
}
/** switch to DFU bootloader
* @param[in] argument no argument required
*/
static void command_bootloader(void* argument);
static void command_bootloader(void* argument)
{
(void)argument; // we won't use the argument
dfu_bootloader(); // start DFU bootloader
}
/** show current state, e.g. all current measurements
* @param[in] argument no argument required
*/
static void command_state(void* argument)
{
(void)argument; // we won't use the argument
puts("sourdough starter statistics:\n");
uint32_t height = ((container_height && starter_height) ? (container_height - starter_height) : 0);
printf("initial height: %u mm\n", height);
height = ((container_height && current_height) ? (container_height - current_height) : 0);
printf("current height: %u mm\n", height);
printf("sourdough temperature: %.02f °C\n", levain_temp);
printf("heater temperature: %.02f °C\n", heater_temp);
puts("heater: ");
puts(gpio_get(GPIO_PORT(HEATER_PIN), GPIO_PIN(HEATER_PIN)) ? "on\n" : "off\n");
uint32_t time = (levain_time ? (rtc_to_seconds() - levain_time) : 0);
printf("current time: %02u:%02u:%02u\n", time / (60 * 60), (time / 60) % 60, time % 60);
height = ((container_height && max_height) ? (container_height - max_height) : 0);
printf("maximum height: %u mm\n", height);
time = (max_time ? (max_time - levain_time) : 0);
printf("maximum height time: %02u:%02u:%02u\n", time / (60 * 60), (time / 60) % 60, time % 60);
}
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
@ -148,7 +386,6 @@ static const struct menu_command_t menu_commands[] = {
.argument_description = NULL,
.command_handler = &command_uptime,
},
#if RTC_DATE_TIME
{
.shortcut = 'd',
.name = "date",
@ -157,7 +394,6 @@ static const struct menu_command_t menu_commands[] = {
.argument_description = "[YYYY-MM-DD HH:MM:SS]",
.command_handler = &command_datetime,
},
#endif
{
.shortcut = 'r',
.name = "reset",
@ -167,13 +403,29 @@ static const struct menu_command_t menu_commands[] = {
.command_handler = &command_reset,
},
{
.shortcut = 'b',
.shortcut = 'S',
.name = "system",
.command_description = "reboot into system memory",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_system,
},
{
.shortcut = 'B',
.name = "bootloader",
.command_description = "reboot into DFU bootloader",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_bootloader,
},
{
.shortcut = 's',
.name = "state",
.command_description = "show state",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_state,
},
};
static void command_help(void* argument)
@ -183,74 +435,6 @@ static void command_help(void* argument)
menu_print_commands(menu_commands, LENGTH(menu_commands)); // print global commands
}
static void command_version(void* argument)
{
(void)argument; // we won't use the argument
printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
printf("device serial: %08x%08x%04x%04x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0 & 0xffff, DESIG_UNIQUE_ID0 >> 16); // not that the half-works are reversed in the first word
}
static void command_uptime(void* argument)
{
(void)argument; // we won't use the argument
const uint32_t uptime = (rtc_get_counter_val() - time_start) / RTC_TICKS_SECOND; // get time from internal RTC
printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60);
}
#if RTC_DATE_TIME
static void command_datetime(void* argument)
{
char* datetime = (char*)argument; // argument is optional date time
if (NULL == argument) { // no date and time provided, just show the current day and time
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
if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough
printf(malformed);
return;
}
if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && '-' == datetime[4] && isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && '-' == datetime[7] && isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && ' ' == datetime[10] && isdigit((int8_t)datetime[11]) && isdigit((int8_t)datetime[12]) && ':' == datetime[13] && isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && ':' == datetime[16] && isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]))) { // verify format (good enough to not fail parsing)
printf(malformed);
return;
}
time_tm.tm_year = strtol(&datetime[0], NULL, 10) - 1900; // parse year
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, 1 + time_tm.tm_mon, time_tm.tm_mday, time_tm.tm_hour, time_tm.tm_min, time_tm.tm_sec);
}
}
#endif
static void command_reset(void* argument)
{
(void)argument; // we won't use the argument
scb_reset_system(); // reset device
while (true); // wait for the reset to happen
}
static void command_bootloader(void* argument)
{
(void)argument; // we won't use the argument
// set DFU magic to specific RAM location
__dfu_magic[0] = 'D';
__dfu_magic[1] = 'F';
__dfu_magic[2] = 'U';
__dfu_magic[3] = '!';
scb_reset_system(); // reset system (core and peripherals)
while (true); // wait for the reset to happen
}
/** process user command
* @param[in] str user command string (\0 ended)
*/
@ -273,13 +457,28 @@ static void process_command(char* str)
}
}
/** use buzzer to beep
* @param[in] beeps number of times to beep
*/
static void beep(uint8_t beeps)
{
for (uint8_t i = 0; i < beeps; i++) {
gpio_set(GPIO_PORT(BUZZER_PIN), GPIO_PIN(BUZZER_PIN)); // enable buzzer
sleep_ms(100); // buzz to show it is working
gpio_clear(GPIO_PORT(BUZZER_PIN), GPIO_PIN(BUZZER_PIN)); // disable buzzer
if (i + 1U < beeps) {
sleep_ms(100); // buzz to show it is working
}
}
gpio_clear(GPIO_PORT(BUZZER_PIN), GPIO_PIN(BUZZER_PIN)); // ensure buzzer is disabled
}
/** program entry point
* this is the firmware function started by the micro-controller
*/
void main(void);
void main(void)
{
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
#if DEBUG
// enable functionalities for easier debug
@ -295,11 +494,9 @@ void main(void)
#endif
board_setup(); // setup board
#if !defined(STLINKV2)
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 elevainitor sourdough starter raiser\n"); // print welcome message
#if DEBUG
// show reset cause
@ -331,7 +528,7 @@ void main(void)
// show watchdog information
printf("setup watchdog: %.2fs", WATCHDOG_PERIOD / 1000.0);
if (FLASH_OBR & FLASH_OBR_OPTERR) {
puts(" (option bytes not set in flash: software wachtdog used, not automatically started at reset)\n");
puts(" (option bytes not set in flash: software watchdog used, not automatically started at reset)\n");
} else if (FLASH_OBR & FLASH_OBR_WDG_SW) {
puts(" (software watchdog used, not automatically started at reset)\n");
} else {
@ -340,16 +537,145 @@ void main(void)
#endif
// 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
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)
puts("setup RTC: ");
rcc_periph_clock_enable(RCC_RTC); // enable clock for RTC peripheral
if (!(RCC_BDCR && RCC_BDCR_RTCEN)) { // the RTC has not been configured yet
pwr_disable_backup_domain_write_protect(); // disable backup protection so we can set the RTC clock source
rtc_unlock(); // enable writing RTC registers
#if defined(MINIF401)
rcc_osc_on(RCC_LSE); // enable LSE clock
while (!rcc_is_osc_ready(RCC_LSE)); // wait until clock is ready
rtc_set_prescaler(256, 128); // set clock prescaler to 32768
RCC_BDCR = (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSE << RCC_BDCR_RTCSEL_SHIFT); // select LSE as RTC clock source
#else
rcc_osc_on(RCC_LSI); // enable LSI clock
while (!rcc_is_osc_ready(RCC_LSI)); // wait until clock is ready
rtc_set_prescaler(250, 128); // set clock prescaler to 32000
RCC_BDCR = (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSI << RCC_BDCR_RTCSEL_SHIFT); // select LSI as RTC clock source
#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
RCC_BDCR |= RCC_BDCR_RTCEN; // enable RTC
rtc_lock(); // protect RTC register against writing
pwr_enable_backup_domain_write_protect(); // re-enable protection now that we configured the RTC clock
}
boot_time = rtc_to_seconds(); // remember the start time
puts("OK\n");
// setup wakeup timer for periodic checks
puts("setup wakeup: ");
// RTC needs to be configured beforehand
pwr_disable_backup_domain_write_protect(); // disable backup protection so we can write to the RTC registers
rtc_unlock(); // enable writing RTC registers
rtc_clear_wakeup_flag(); // clear flag for fresh start
#if defined(MINIF401)
rtc_set_wakeup_time((32768 / 2) / WAKEUP_FREQ - 1, RTC_CR_WUCLKSEL_RTC_DIV2); // set wakeup time based on LSE (keep highest precision, also enables the wakeup timer)
#else
rtc_set_wakeup_time((32000 / 2) / WAKEUP_FREQ - 1, RTC_CR_WUCLKSEL_RTC_DIV2); // set wakeup time based on LSI (keep highest precision, also enables the wakeup timer)
#endif
rtc_enable_wakeup_timer_interrupt(); // enable interrupt
rtc_lock(); // disable writing RTC registers
// important: do not re-enable backup_domain_write_protect, since this will prevent clearing flags (but RTC registers do not need to be unlocked)
puts("OK\n");
puts("setup heater: ");
rcc_periph_clock_enable(GPIO_RCC(HEATER_PIN)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(HEATER_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(HEATER_PIN)); // set GPIO to input floating
const bool heater_ok = gpio_get(GPIO_PORT(HEATER_PIN), GPIO_PIN(HEATER_PIN)); // pin should be put high
gpio_mode_setup(GPIO_PORT(HEATER_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(HEATER_PIN)); // set pin as output
gpio_set_output_options(GPIO_PORT(HEATER_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN(HEATER_PIN)); // set pin output as open-drain
heater_off(); // set heater off
if (heater_ok) {
puts("OK\n");
} else {
puts("KO\n");
}
puts("setup HC-SR04 range sensor: ");
sensor_sr04_setup(); // setup peripheral
sensor_sr04_distance = 0; // clear flag
sensor_sr04_trigger(); // start measurement
while(!sensor_sr04_distance); // wait for measurement to complete
const bool sensor_sr04_ok = (1 != sensor_sr04_distance); // if no echo received, the module is not present
sensor_sr04_distance = 0; // clear flag
if (sensor_sr04_ok) {
puts("OK\n");
} else {
puts("KO\n");
}
puts("setup DS18B20 temperature sensor: ");
sensor_ds18b20_setup(); // setup peripheral
const bool sensor_ds18b20_present = (1 == sensor_ds18b20_number() && sensor_ds18b20_only()); // ensure there is only one ds18b20 sensor on the bus (so we don't have to use its ROM code)
if (sensor_ds18b20_present) {
sensor_ds18b20_precision(0, 12); // use highest resolution. it requires 750 ms to do the conversion, but we only check once a second
sensor_ds18b20_convert(0); // start the conversion
puts("OK\n");
} else {
puts("KO\n");
}
puts("setup ADC for thermistor: ");
rcc_periph_clock_enable(RCC_ADC1); // enable clock for ADC domain
adc_power_off(ADC1); // switch off ADC while configuring it
adc_set_right_aligned(ADC1); // ensure it is right aligned to get the actual value in the 16-bit register
adc_enable_scan_mode(ADC1); // use scan mode do be able to go to next discontinuous subgroup of the regular sequence
adc_enable_discontinuous_mode_regular(ADC1, 1); // use discontinuous mode (to go through all channels of the group, one after another)
adc_set_single_conversion_mode(ADC1); // ensure continuous mode is not used (that's not the same as discontinuous)
adc_eoc_after_each(ADC1); // set EOC after each conversion instead of each group
adc_set_sample_time_on_all_channels(ADC1, ADC_SMPR_SMP_28CYC); // use at least 15 cycles to be able to sample at 12-bit resolution
adc_set_regular_sequence(ADC1, LENGTH(channels), (uint8_t*)channels); // set channel to convert
adc_enable_temperature_sensor(); // enable internal voltage reference
adc_power_on(ADC1); // switch on ADC
sleep_us(3); // wait t_stab for the ADC to stabilize
rcc_periph_clock_enable(RCC_ADC1_IN(THERMISTOR_CHANNEL)); // enable clock for GPIO domain for thermistor channel
gpio_mode_setup(ADC1_IN_PORT(THERMISTOR_CHANNEL), GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC1_IN_PIN(THERMISTOR_CHANNEL)); // set thermistor channel as analog input for the ADC
const bool thermsitor_ok = (thermistor_voltage() > 1.0 && thermistor_voltage() < 2.0); // ensure thermistor is connected
if (thermsitor_ok) {
puts("OK\n");
} else {
puts("KO\n");
}
puts("setup SSD1306 OLED display: ");
const bool oled_text_ok = oled_text_setup(); // setup display
if (oled_text_ok) {
oled_text_clear();
oled_text_line("ELEVAINITOR", 0);
oled_text_update();
puts("OK\n");
} else {
puts("KO\n");
}
// show status
const bool all_ok = heater_ok && sensor_sr04_ok && sensor_ds18b20_present && thermsitor_ok && oled_text_ok;
if (oled_text_ok) {
if (all_ok) {
oled_text_line("press button", 1);
oled_text_line("no sourdough", 2);
oled_text_update();
} else {
oled_text_line("error", 1);
if (!heater_ok) {
oled_text_line("heater", 2);
} else if (!sensor_sr04_ok) {
oled_text_line("heater MOSFET", 2);
} else if (!sensor_ds18b20_present) {
oled_text_line("heater thermo.", 2);
} else if (!thermsitor_ok) {
oled_text_line("thermistor", 2);
} else {
oled_text_line("unknown", 2);
}
oled_text_update();
}
}
puts("setup buzzer: ");
rcc_periph_clock_enable(GPIO_RCC(BUZZER_PIN)); // enable clock for GPIO port domain
gpio_clear(GPIO_PORT(BUZZER_PIN), GPIO_PIN(BUZZER_PIN)); // disable buzzer
gpio_mode_setup(GPIO_PORT(BUZZER_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(BUZZER_PIN)); // set pin as output
gpio_set_output_options(GPIO_PORT(BUZZER_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(BUZZER_PIN)); // set pin output as push-pull
beep(1); // buzz to show it is working
puts("OK\n");
// setup terminal
@ -360,6 +686,8 @@ void main(void)
// start main loop