Compare commits

..

172 Commits

Author SHA1 Message Date
7854328a55 doc: add brief project description 2022-08-11 14:42:40 +02:00
90ec47d49a app: adjust project name welcome text 2022-08-11 14:42:15 +02:00
776569d7c1 doc: add artnet commands 2022-08-11 14:23:40 +02:00
f7e0d514dc app: fix artnet text display 2022-08-11 14:23:12 +02:00
7f06533fe7 app: add artnet command to set motor speed 2022-08-11 14:22:56 +02:00
dd09112a22 app: minor, remove artnet debug 2022-08-11 14:22:34 +02:00
dcd41db126 app: add code to debug reed trigger 2022-08-11 14:22:08 +02:00
63f7f0ece5 app: set default text displayed 2022-08-11 14:21:45 +02:00
938e2467a4 app: disable homing since reed switch code is buggy 2022-08-11 14:21:19 +02:00
aa1d7b1dde app: minor, fix dial action 2022-08-11 14:20:37 +02:00
bbc981fecb app: verify if ESP is connected 2022-08-11 14:20:12 +02:00
78bc3b97f3 app: fix motor speed 2022-08-11 14:19:23 +02:00
3f18fef34c app: clear pixels between characters 2022-08-11 14:17:56 +02:00
86fa08cb91 app: minor, increase WS2812 matrix brightness 2022-08-11 14:17:19 +02:00
886965ee80 app: minor, add flag to debug reed switch 2022-08-11 14:16:48 +02:00
f335044c77 app: minor, enable watchdog 2022-08-11 14:15:47 +02:00
af7477d971 app: allow setting matrix colors 2022-08-11 14:14:54 +02:00
b7670d1e24 app: minor, calibrate dial steps 2022-08-11 14:13:24 +02:00
55ac60e080 app: fix motor speed and direction 2022-08-11 14:11:13 +02:00
8d6d7f315f app: increase watchdog timeout to allow connecting to network 2022-08-11 14:08:54 +02:00
1d118ced08 esp8266: fix listen in case port is already opened 2022-08-11 14:07:10 +02:00
da57f5ee40 esp8266: add connected function 2022-08-11 14:06:39 +02:00
ac7b075b1c esp8266: add reset function 2022-08-11 14:06:22 +02:00
cfc6c509b7 esp8266: add simple at command function 2022-08-11 14:05:22 +02:00
ad1aa69180 esp8266: allow reading received data 2022-08-11 14:03:15 +02:00
ae394561e2 app: make rgb panel optional 2022-07-27 10:52:39 +02:00
851c25ffe0 app: add dials command 2022-07-27 10:41:36 +02:00
0e92b46c3e app: use offloaded RGB panel library 2022-07-27 10:40:36 +02:00
3f00d36f05 lib: put all RGB panel code in seperate library 2022-07-27 10:39:23 +02:00
b0ab623968 app: parse art-net packets 2022-07-27 10:06:53 +02:00
571038023c usb: adjust product name 2022-07-27 10:04:45 +02:00
b7da249c51 app: add dial target mechanism 2022-07-25 15:42:36 +02:00
8bfccb86a0 app: minor, make line select more secure 2022-07-25 15:41:51 +02:00
9d415ed449 app: remove ununsed code 2022-07-25 15:41:16 +02:00
85fdb1bbac app: fix dial homing 2022-07-25 15:40:56 +02:00
2dd6e09cd0 doc: describe reed switch 2022-07-25 15:39:47 +02:00
9af8516030 app: add test for WS2812b panel 2022-07-21 21:24:54 +02:00
479a1316c3 app: reuse matrix control for WS2812b 2022-07-21 21:24:13 +02:00
3cd3a869f4 ws2812b: set number of LEDs 2022-07-21 21:21:44 +02:00
3c4cabb483 doc: add pinouts and ESP config 2022-07-14 19:01:40 +02:00
32aa557a00 app: add Art-Net receiver 2022-07-14 19:00:46 +02:00
af9d0d685a app: add WS2812B 2022-07-14 19:00:02 +02:00
02f95f55ee app: remove USART printing 2022-07-14 18:58:23 +02:00
24cec3850d esp8266: make received data available 2022-07-14 18:56:56 +02:00
462a65cb92 esp8266: add listening to port 2022-07-14 18:56:36 +02:00
a8b7726c35 esp8266: minor, fix spacing 2022-07-14 18:56:04 +02:00
90dbc57166 lib: port ESP8266 to STM32F4 2022-07-14 18:54:06 +02:00
62650bd1b2 lib: port WS2812B to STM32F4 2022-07-14 14:59:24 +02:00
4c1ee2bc26 global: add TIM_OC macro 2022-06-03 10:10:49 +02:00
0250d81862 add: add RGBW LED strip control 2022-06-03 10:10:16 +02:00
beb6d33f85 app: add scrolling text animation 2022-05-26 10:39:03 +02:00
5e688d467d app: make position on RGB panel more flexibel 2022-05-26 10:38:23 +02:00
b0351bcaaf app: improve a tiny bit the ghosting on RGB panel 2022-05-25 20:21:37 +02:00
7675f73d0b app: remove RGB panel test animation 2022-05-25 20:15:53 +02:00
a1da462852 app: add function to draw text on RGB panel 2022-05-25 20:14:58 +02:00
0e91b71aae app: improve RGB panel refresh rate 2022-05-25 19:58:08 +02:00
759cfd577e app: add function to draw character on RGB panel 2022-05-25 19:57:44 +02:00
c73600c91f app: use timer to update RGB panel lines 2022-05-25 19:37:17 +02:00
34d44c2a88 app: improve/fix RGB panel control 2022-05-25 17:38:34 +02:00
884f007d19 README: fix RGB panel connections 2022-05-25 17:35:54 +02:00
71564dd4f1 app: implement stepper and RGB matrix control 2022-05-24 14:45:56 +02:00
4fba2fd7a4 global: disable button init (pin used by RGB matrix) 2022-05-24 14:17:04 +02:00
d208297aa3 bootloader: fix DFU_FORCE_PIN code 2022-05-24 14:11:19 +02:00
9d1a4006af README: document pin connections 2022-05-24 14:10:05 +02:00
6aa820690e switch back to libopenmc3 repo 2021-09-24 16:01:53 +02:00
ea30c7c879 application: disable VBUS sensing 2021-09-24 16:00:55 +02:00
a2f203a81f lib/usb_dfu: disable VBUS sensing 2021-09-24 16:00:23 +02:00
a74539ab4f bootloader: fix new compiler error 2021-09-24 15:59:47 +02:00
9c9893e1da application: minor, double ensure debug info is only show with DEBUG enabled 2021-05-14 14:50:02 +02:00
a716cb10cf flash_internal: add support for STM32F401CE 2021-05-14 14:30:15 +02:00
0a18d73197 .ld: minor, fix typo 2021-05-14 14:28:48 +02:00
8c01cbd918 sensor_max6675: mark as untested for STM32F4 2021-05-14 13:43:17 +02:00
019b82d384 sensor_max6675: add library for MAX6675 k-type thermocouple reader 2021-05-14 13:42:03 +02:00
a89d8abb38 lib: minor, simplify license 2021-05-14 13:42:03 +02:00
ced5582a1a application: minor, update copyright date 2021-05-14 13:18:20 +02:00
4d53d868fe usart_enhanced: mark STM32F4 compatible (no difference with STM32F1) 2021-05-14 13:14:58 +02:00
ce0848343d usart_enhanced: minor, fix spacing 2021-05-14 13:14:58 +02:00
863fd744d7 README: fix typo 2021-05-14 13:14:58 +02:00
d85d345ec4 application: make some text only output when DEBUG is set 2021-05-14 13:14:53 +02:00
35a614750d application: remove watchdog info (not F4 compatible) 2021-05-14 13:12:46 +02:00
b3cf0d0302 swd: improve documentation 2021-03-23 17:22:48 +01:00
3a6a64928d swd: minor, fix doc 2021-03-23 17:22:48 +01:00
99d66f4e4e swd: improve part number decoding 2021-03-23 17:22:48 +01:00
66521e1981 swd: minor, fix comment, add doc, make reset tiny bit longer for better reliability 2021-03-23 17:22:48 +01:00
210fab8eae swd: expose release pins 2021-03-23 17:22:48 +01:00
7318d70dcd swd: minor, fix space 2021-03-23 17:22:48 +01:00
0010c5e046 swd: provide function to set SWCLK/SWDIO pin 2021-03-23 17:22:48 +01:00
6b3b55839e swd: use variables for pins (for later dynamic change) 2021-03-23 17:22:48 +01:00
6bed3ab0fb Rakefile: ingnore .inc files 2021-03-23 17:22:48 +01:00
9a7c51f80e Rakefile: fix spacing 2021-03-23 17:22:48 +01:00
7a74f9709f add SWD library 2021-03-23 17:22:48 +01:00
fa29cfc29f application: minor, fix typo 2020-12-17 12:51:23 +01:00
2248ba1762 application: fix rtc_to_seconds 2020-12-17 12:51:23 +01:00
ac255816a1 sensor_mlx90614: add library to read from MLX90614 IR-thermometer 2020-12-17 12:51:23 +01:00
95b63a06f5 smbus_master: add SMBus library 2020-12-17 12:51:23 +01:00
7656c699bf i2c_master: fix stop generation 2020-12-17 12:51:23 +01:00
c6a4f58b93 i2c_master: fix wait_stop call 2020-12-17 12:51:23 +01:00
b82520fa9b sensor_sr04: fix shadow counter value issue 2020-12-17 12:51:23 +01:00
25fcf8fe0b global: add ADC macros 2020-12-17 12:51:23 +01:00
01eaa5cfab USB: increase text buffer size for project 2020-12-17 12:51:23 +01:00
793611d629 application: implement uptime 2020-12-17 12:48:37 +01:00
ad52abc26b oled_ssd1306: adapt to ported I²C library 2020-12-17 12:48:37 +01:00
b0f5f127f6 i2c_master: port to STM32F4 2020-12-17 12:48:37 +01:00
a449b9b7ff global: add I²C macros 2020-12-17 12:48:37 +01:00
4c6e9a4fda interrupt: port to STM32F4 2020-12-17 12:48:37 +01:00
789b36fc21 interrupt: minor, fix comment 2020-12-17 12:48:37 +01:00
c8861f40c4 onewire_master: port to STM32F4 2020-12-17 12:48:37 +01:00
77415cb41f onewire_master: minor, fix spacing 2020-12-17 12:48:37 +01:00
11f5bc9771 sensor_sr04: add library for HC-SR04 ultrasonic range sensor 2020-12-17 12:48:37 +01:00
8526dc084b global: add tim irq defines 2020-12-17 12:48:37 +01:00
fea286914b global: improve sleep_us for STM32F4 2020-12-17 12:48:37 +01:00
cfcc8a1bb6 Rakefile: automatically get libopencm3 2020-12-17 12:48:37 +01:00
510c82d00f Merge branch 'stm32f4' of ssh://git.cuvoodoo.info/stm32f1 into stm32f4 2020-12-11 00:03:15 +01:00
26f6de3015 sensor_max1247: STM32F4 incompatible for now 2020-12-11 00:02:44 +01:00
a9461b53f5 README: port to F4 2020-12-11 00:00:25 +01:00
d7b6300a50 rakefile: fix remove protection for F4 2020-12-11 00:00:25 +01:00
d0bd71b266 application: add periodis RTC wakeup 2020-12-11 00:00:25 +01:00
a46b6a1630 Rakefile: add macro debugging information 2020-12-11 00:00:25 +01:00
b100c4ae13 application: RTC + date/time added 2020-12-11 00:00:25 +01:00
a0f9b4a530 application: port to STM32F4 (RTC is not working yet) 2020-12-11 00:00:25 +01:00
e32e27100d USB CDC ACM: fix sending loop (and spacing) 2020-12-11 00:00:25 +01:00
5b0523f751 uart: port to STM32F4 2020-12-11 00:00:25 +01:00
d6cac41b78 USB CDC ACM: minor fix spacing 2020-12-11 00:00:25 +01:00
adc62ebb9a USB CDC ACM: port to STM32F4 2020-12-11 00:00:25 +01:00
d9a15f2daa USB CDC ACM: match serial to STM32 bootloader 2020-12-11 00:00:25 +01:00
c4af940975 dfu: minor, improve disconnect 2020-12-11 00:00:25 +01:00
c58d27cf2e Rakefile: add method to flash bootloader over DFU 2020-12-11 00:00:25 +01:00
c3d7711258 global: add synchronisation barrier commands 2020-12-11 00:00:25 +01:00
78cb85421a global: add common function to start DFU and systeme memory 2020-12-11 00:00:25 +01:00
ff5fbc847d DFU: fix DP pull down 2020-12-11 00:00:25 +01:00
51e0bfd188 DFU: minor, remove unused/duplicate code 2020-12-11 00:00:25 +01:00
c411d552a1 DFU: set serial to match STM32 DFU bootloader 2020-12-11 00:00:25 +01:00
ceff33ea0e Rakefile: use derivated device properties 2020-12-11 00:00:25 +01:00
68955ddfec bootloader: update to work with F4 2020-12-11 00:00:25 +01:00
40ee01ce67 usb_dfu: update to work with F4 2020-12-11 00:00:25 +01:00
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
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
e4ce622f15 terminal: minor, fix doc 2020-12-11 00:00:25 +01:00
dbd0ea4d27 global: remove macro pin definition since on F4 they are not unique 2020-12-11 00:00:25 +01:00
a878a1ad9c global: define MINIF401 button/led pins 2020-12-11 00:00:25 +01:00
aff4275478 lib: disable most libraries since they need tuning to be F4 compatible 2020-12-11 00:00:25 +01:00
609188d74e Rakefile: compile for STM32F4 2020-12-11 00:00:25 +01:00
63a2e5e5ff *.ld: set flash and RAM size for STM32F401xC 2020-12-11 00:00:25 +01:00
ac1bea1d45 README: port to F4 2020-11-30 15:03:32 +01:00
7b7f26ee47 rakefile: fix remove protection for F4 2020-11-30 14:51:06 +01:00
3d00bdf3c0 application: add periodis RTC wakeup 2020-11-30 14:36:33 +01:00
319a02d2b4 Rakefile: add macro debugging information 2020-11-28 15:19:13 +01:00
cc8be1f278 application: RTC + date/time added 2020-11-28 15:17:52 +01:00
e255573b1e application: port to STM32F4 (RTC is not working yet) 2020-11-27 17:07:39 +01:00
0fe7e1fd39 USB CDC ACM: fix sending loop (and spacing) 2020-11-27 17:06:21 +01:00
2249f460e3 uart: port to STM32F4 2020-11-27 16:49:59 +01:00
aae4009fbe USB CDC ACM: minor fix spacing 2020-11-27 16:44:17 +01:00
a9284b7154 USB CDC ACM: port to STM32F4 2020-11-27 16:43:57 +01:00
777fd7afb9 USB CDC ACM: match serial to STM32 bootloader 2020-11-27 16:41:19 +01:00
31079d95dd dfu: minor, improve disconnect 2020-11-27 16:39:51 +01:00
ced714129c Rakefile: add method to flash bootloader over DFU 2020-11-27 16:39:11 +01:00
4fcfd29d2b global: add synchronisation barrier commands 2020-11-27 16:38:32 +01:00
06de8d0be9 global: add common function to start DFU and systeme memory 2020-11-27 16:37:52 +01:00
de36c7f3a2 DFU: fix DP pull down 2020-11-27 16:05:55 +01:00
8918b97618 DFU: minor, remove unused/duplicate code 2020-11-27 16:05:37 +01:00
0bb2be3727 DFU: set serial to match STM32 DFU bootloader 2020-11-27 16:04:07 +01:00
a781fc5b3b Rakefile: use derivated device properties 2020-11-27 15:54:08 +01:00
00ef5d9344 bootloader: update to work with F4 2020-11-24 16:18:17 +01:00
9fbf5b4aad usb_dfu: update to work with F4 2020-11-24 16:17:37 +01:00
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
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
9db9ea9dc1 terminal: minor, fix doc 2020-11-24 16:01:49 +01:00
4b514c6801 global: remove macro pin definition since on F4 they are not unique 2020-11-24 16:01:06 +01:00
6a34352914 global: define MINIF401 button/led pins 2020-11-24 15:59:42 +01:00
35c441355d lib: disable most libraries since they need tuning to be F4 compatible 2020-11-24 15:56:00 +01:00
9751880813 Rakefile: compile for STM32F4 2020-11-24 15:51:03 +01:00
e58614002c *.ld: set flash and RAM size for STM32F401xC 2020-11-24 15:48:25 +01:00
67 changed files with 5960 additions and 1948 deletions

2
.gitmodules vendored
View File

@ -1,4 +1,4 @@
[submodule "libopencm3"]
path = libopencm3
url = https://github.com/libopencm3/libopencm3
url = https://github.com/libopencm3/libopencm3/
ignore = all

225
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).
firmware for the Bahn Uhr controller.
project
=======
@ -6,49 +6,201 @@ project
summary
-------
*describe project purpose*
This is the controller for a salvaged and modded Bahnsteig Uhr:
- the track is made of white translucent ceramic, and RGBW LED strips illuminate it
- a motor uses the dial drive to turn the dials (in theory with settable time)
- LED panels allow to show text on both sides
- controllable over the network using Art-Net
technology
----------
*described electronic details*
controller comprises:
- a [STM32 F4 series micro-controller](https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series.html)-based black pill
- a DRV8825 stepper motor allows turning the dials (hour is linked to minute)
- reed switch, to home dial
- P1 RGB LED panel (128x64), to display text on front side
- WS2812b panel (2x 32x8), to display text
- nMOS transistors to control 12V LED strips, to illuminate the track
- ESP8266-based ESP-01 to connect to network
- power connectors (0,5,12V-4pin and 12V-barrel input, 5V outputs for LED panels, 12V for motor and LED strips)
Art-Net
-------
The Bahn Uhr controller gets data over the network using Art-Net.
The mapping is as follows (universe without offset, channel, target).
clock illumination color (RGBW LED strip):
- 0, 0, red high byte
- 0, 1, red low byte
- 0, 2, green high byte
- 0, 3, green low byte
- 0, 4, blue high byte
- 0, 5, blue low byte
- 0, 4, white high byte
- 0, 5, white low byte
dials position:
- 1, 0, hours (0-11)
- 1, 1, minutes (0-59)
- 1, 2, seconds (0-59)
- 1, 3, direction
- 1, 4, speed
text display:
font display, line 1:
- 2, 0, red intensity (0 to use clock illumination color)
- 2, 1, green intensity (0 to use clock illumination color)
- 2, 2, blue intensity (0 to use clock illumination color)
- 2, 3, horizontal position (0 is left), high signed byte
- 2, 3, horizontal position (0 is left), low byte
- 2, 4+, text (NULL ended)
font display, line 2:
- 3, 0, red intensity (0 to use clock illumination color)
- 3, 1, green intensity (0 to use clock illumination color)
- 3, 2, blue intensity (0 to use clock illumination color)
- 3, 3, horizontal position (0 is left), high signed byte
- 3, 3, horizontal position (0 is left), low byte
- 3, 4+, text (NULL ended)
font display, line 3:
- 4, 0, red intensity (0 to use clock illumination color)
- 4, 1, green intensity (0 to use clock illumination color)
- 4, 2, blue intensity (0 to use clock illumination color)
- 4, 3, horizontal position (0 is left), high signed byte
- 4, 3, horizontal position (0 is left), low byte
- 4, 4+, text (NULL ended)
back display, line 1:
- 5, 0, red intensity (0 to use clock illumination color)
- 5, 1, green intensity (0 to use clock illumination color)
- 5, 2, blue intensity (0 to use clock illumination color)
- 5, 3, horizontal position (0 is left), high signed byte
- 5, 3, horizontal position (0 is left), low byte
- 5, 4+, text (NULL ended)
back display, line 2:
- 6, 0, red intensity (0 to use clock illumination color)
- 6, 1, green intensity (0 to use clock illumination color)
- 6, 2, blue intensity (0 to use clock illumination color)
- 6, 3, horizontal position (0 is left), high signed byte
- 6, 3, horizontal position (0 is left), low byte
- 6, 4+, text (NULL ended)
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.
- [WeAct MiniF4](https://github.com/WeActTC/MiniF4-STM32F4x1), based on a STM32F401CCU6
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
Connect the peripherals the following way (STM32F4xx signal; STM32F4xx pin; peripheral pin; peripheral signal; comment):
- *list board to peripheral pin connections*
DRV8825 stepper motor driver (using one timer for PWM output):
- STEP: PA15/TIM2_CH1
- DIRECTION: PB15
- nSLEEP: PB14, shorted to nRESET
- nRESET: PB14, with external 10kOhm pull-down resistor
- nENABLE: PB13, with external 10kOhm pull-up resistor
- FAULT: PB12, with external 10kOhm pull-up resistor
reed switch, to home dial position:
- 1: GND
- 2: PB4
LED driver (using one timer for PWM outputs):
- gate 0: PB6/TIM4_CH1
- gate 1: PB7/TIM4_CH2
- gate 2: PB8/TIM4_CH3
- gate 3: PB9/TIM4_CH4
WS2812b LED matrix:
- DOUT: PB5 (SPI1_MOSI)
RGB matrix (using one DMA)
- DR1: PA2
- DG1: PA3
- DB1: PA4
- DR2: PA5
- DG2: PA6
- DB2: PA7
- CLK: PA0
- LAT: PA1
- A: PB0
- B: PB1
- C: PB2
- D: PB3
- OE: PB10
ESP8266-based ESP-01 (using one UART):
- RX: PA9/USART1_TX
- TX: PA10/USART1_RX
EN is pulled to VCC using 10 kOhm for the ESP to start.
I replaced the 1 MB flash with a 4MB flash to be sure I won't be annoyed by the space limit.
I flashed the AT firmware from ESP8266_NONOS_SDK-3.0.5:
~~~
esptool.py --chip auto --port /dev/ttyUSB0 --baud 115200 erase_flash
esptool.py -p /dev/ttyUSB0 --chip esp8266 write_flash -fm dio -ff 26m --flash_size 2MB-c1 0x00000 ./bin/boot_v1.7.bin 0x01000 ./bin/at/1024+1024/user1.2048.new.5.bin 0x1fc000 ./bin/esp_init_data_default_v08.bin 0xfe000 ./bin/blank.bin 0x1fe000 ./bin/blank.bin 0x1fb000 ./bin/blank.bin
~~~
to test the firmware, issue AT commands:
~~~
picocom -b 115200 /dev/ttyUSB0 --omap crcrlf
AT
OK
AT+GMR
version information
~~~
configure the access point in flash:
~~~
# set station mode
AT+CWMODE_DEF=1
# set AP credentials
AT+CWJAP_DEF="essid","password"
~~~
free:
- PB4, PB5
- PA8/TIM1_CH1
used:
- PA12: USB DP
- PA11: USB DM
- PC13: LED
- PC14/PC15: 32kHz XTAL
All pins are configured using `define`s in the corresponding source code.
code
@ -81,15 +233,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 `bootloader` using SWD run `rake swd_bootloader` (this will also erase the application).
To flash the `application` using SWD run `rake swd_application` (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,27 @@ 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"
# the genuine MINIF401 with stm32f401cc is discontinued and has been replaced with stm32f401ce (just more RAM/flash), but knockoffs with stm32f401cc are still common
"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 +55,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 +71,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 +89,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
@ -101,17 +117,17 @@ end
# get dependencies of a file
# done is a list of already known dependencies
def dependencies(source, done=[])
def dependencies(source, done = [])
d_path = source.ext("d") # get the dependency file
Rake::Task[d_path].invoke # ensure the dependency file exists
d_file = IO.read(d_path) # read the dependencies from dependency file
d_file = d_file.split(': ')[1].gsub("\n",'').gsub('\\ ','').gsub(/\s+/,' ').split(' ') # get a list of dependencies
d_file = d_file.split(': ')[1].gsub("\n", '').gsub('\\ ', '').gsub(/\s+/, ' ').split(' ') # get a list of dependencies
d_list = [] # list of dependencies
# only save dependencies which are in our source directories
d_file.each do |d|
SRC_DIRS.each do |dir|
if File.dirname(d)==dir then
d_list << d
if File.dirname(d) == dir then
d_list << d unless d.end_with?(".inc")
end
end
end
@ -127,14 +143,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 +153,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 +200,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 +255,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 +267,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

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,18 @@
/* linker script for application running on STM32F103x8 micro-controller
* the STM32F103x8 has 64 KB of flash starting at 0x0800 0000, and 20 KB of RAM starting at 0x2000 0000
* the STM32F103xB has 128 KB of flash starting at 0x0800 0000, and 20 KB of RAM starting at 0x2000 0000
* STM32F103x8 most often have if fact 128 KB, instead of the specified and advertised 64 KB, like the STM32F103xB
* you can define the desired flash size here.
* the USB DFU bootloader will take the first 8 KB of flash, followed by the application
* the first 4 bytes of the RAM is reserved for the DFU magic word (DFU! to start DFU bootloader)
/* linker script for application running on STM32F401xC micro-controller
* the STM32F401xC has 256 KB of flash starting at 0x0800 0000, and 64 KB of RAM starting at 0x2000 0000
* the USB DFU bootloader will use the first sector, which is 16 KB large.
* this is followed by the application.
* the first 4 bytes of the RAM are reserved for the DFU magic word (DFU! to start DFU bootloader)
*/
/* define memory regions. */
MEMORY
{
rom (rx) : ORIGIN = 0x08000000 + 8K, LENGTH = 64K - 8K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 20K - 4
rom (rx) : ORIGIN = 0x08000000 + 16K, LENGTH = 256K - 16K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 64K - 4
}
PROVIDE(__application_beginning = ORIGIN(rom));
/* if you want the firmware to use the flash size advertised by the micro-controller itself, use the following:
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
if you want to enforce a flash size, because there is more flash than advertized by the micro-controller, use to following:
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);
/* include rest of the definitions for the ARM Cortex-M, including STM32F1 family */
/* include rest of the definitions for the ARM Cortex-M, including STM32F4 family */
INCLUDE cortex-m-generic.ld

View File

@ -2,7 +2,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2019
* @date 2017-2020
*/
/* standard libraries */
#include <stdint.h> // standard integer types
@ -17,6 +17,11 @@
#include "global.h" // board definitions
#include "usb_dfu.h" // USB DFU utilities
/** symbol for beginning of the application
* @note this symbol will be provided by the bootloader linker script
*/
extern uint32_t __application_beginning;
/** bootloader entry point */
void main(void);
void main(void)
@ -31,64 +36,41 @@ void main(void)
__dfu_magic[1] = 0;
__dfu_magic[2] = 0;
__dfu_magic[3] = 0;
} else if (0 == (RCC_CSR & 0xfc000000)) { // no reset flag present -> this was a soft reset using scb_reset_core() after clearing the flags using RCC_CSR_RMVF, this was the legacy way to start the DFU mode
dfu_force = true;
} else { // check if the force DFU mode input is set
// disable SWJ pin to use as GPIO
#if (defined(DFU_FORCE_PIN) && defined(DFU_FORCE_VALUE))
#if ((GPIO(B) == GPIO_PORT(DFU_FORCE_PIN)) && (GPIO(4) == GPIO_PIN(DFU_FORCE_PIN)))
// JNTRST pin is used as DFU pin
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function domain
gpio_primary_remap(AFIO_MAPR_SWJ_CFG_FULL_SWJ_NO_JNTRST, 0); // keep SWJ enable bit don't use JNTRST
#elif ((GPIO(B) == GPIO_PORT(DFU_FORCE_PIN)) && (GPIO(3) == GPIO_PIN(DFU_FORCE_PIN))) || ((GPIO(A) == GPIO_PORT(DFU_FORCE_PIN)) && (GPIO(15) == GPIO_PIN(DFU_FORCE_PIN)))
// JTAG but not SWD pin used as DFU pin
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function domain
gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0); // disable JTAG but keep SWD
#elif ((GPIO(A) == GPIO_PORT(DFU_FORCE_PIN)) && (GPIO(14) == GPIO_PIN(DFU_FORCE_PIN))) || ((GPIO(A) == GPIO_PORT(DFU_FORCE_PIN)) && (GPIO(13) == GPIO_PIN(DFU_FORCE_PIN)))
// JTAG and SWD pin used as DFU pin
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function domain
gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_OFF, 0); // disable JTAG and SWD
#endif // DFU_FORCE_PIN
rcc_periph_clock_enable(GPIO_RCC(DFU_FORCE_PIN)); // enable clock for GPIO domain
gpio_set_mode(GPIO_PORT(DFU_FORCE_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(DFU_FORCE_PIN)); // set GPIO to input
// pull on the opposite of the expected value
rcc_periph_clock_enable(GPIO_RCC(DFU_FORCE_PIN)); // enable clock for button
#if (DFU_FORCE_VALUE == 1)
gpio_clear(GPIO_PORT(DFU_FORCE_PIN), GPIO_PIN(DFU_FORCE_PIN)); // pull down to be able to detect when tied to high
gpio_mode_setup(GPIO_PORT(DFU_FORCE_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(DFU_FORCE_PIN)); // set GPIO to input
if (gpio_get(GPIO_PORT(DFU_FORCE_PIN), GPIO_PIN(DFU_FORCE_PIN))) { // check if output is set to the value to force DFU mode
#else
gpio_set(GPIO_PORT(DFU_FORCE_PIN), GPIO_PIN(DFU_FORCE_PIN)); // pull up to be able to detect when tied to low
gpio_mode_setup(GPIO_PORT(DFU_FORCE_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(DFU_FORCE_PIN)); // set GPIO to input
if (0 == gpio_get(GPIO_PORT(DFU_FORCE_PIN), GPIO_PIN(DFU_FORCE_PIN))) { // check if output is set to the value to force DFU mode
#endif // DFU_FORCE_VALUE
dfu_force = true; // DFU mode forced
}
#endif // defined(DFU_FORCE_PIN)
rcc_periph_clock_disable(RCC_AFIO); // disable alternate function domain to put it back to default
rcc_periph_reset_pulse(GPIO_RST(DFU_FORCE_PIN)); // reset pin GPIO domain
rcc_periph_clock_disable(GPIO_RCC(DFU_FORCE_PIN)); // disable pin GPIO domain
#endif // defined(DFU_FORCE_PIN)
}
// start application if valid
/* the application starts with the vector table
* the first entry in the vector table is the initial stack pointer (SP) address
* the stack will be placed in RAM
* on STM32F1xx SRAM begins at 0x2000 0000, and on STM32F103xx there is up to 96 KB of RAM (0x18000).
* since the stack grown "downwards" it should start at the end of the RAM: max 0x2001 8000
* on STM32F4 SRAM begins at 0x2000 0000, and on STM32F4xx there is up to 384 KiB of RAM (0x60000).
* since the stack grown "downwards" it should start at the end of the RAM: max 0x2006 0000
* if the SP is not in this range (e.g. flash has been erased) there is no valid application
* the second entry in the vector table is the reset address, corresponding to the application start
*/
volatile uint32_t* application = (uint32_t*)&__application_beginning; // get the value of the application address symbol (use a register instead on the stack since the stack pointer will be changed)
if (!dfu_force && (((*application) & 0xFFFE0000) == 0x20000000)) { // application at address seems valid
if (!dfu_force && (((*application) & 0xFFF80000) == 0x20000000)) { // application at address seems valid
SCB_VTOR = (volatile uint32_t)(application); // set vector table to application vector table (store at the beginning of the application)
__asm__ volatile ("MSR msp,%0" : :"r"(*application)); // set stack pointer to address provided in the beginning of the application (loaded into a register first)
(*(void(**)(void))((uint32_t)application + 4))(); // start application (by jumping to the reset function which address is stored as second entry of the vector table)
}
rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]); // start main clock
board_setup(); // setup board to control LED
led_on(); // indicate bootloader started
#if defined(BUSVOODOO)
led_toggle(); // switch from blue to red LED
#endif
usb_dfu_setup(); // setup USB DFU for firmware upload
usb_dfu_start(); // run DFU mode
}

View File

@ -1,30 +1,20 @@
/* linker script for application running on STM32F103x8 micro-controller
* the STM32F103x8 has 64 KB of flash starting at 0x0800 0000, and 20 KB of RAM starting at 0x2000 0000
* the STM32F103xB has 128 KB of flash starting at 0x0800 0000, and 20 KB of RAM starting at 0x2000 0000
* STM32F103x8 most often have if fact 128 KB, instead of the specified and advertised 64 KB, like the STM32F103xB
* you can define the desired flash size here.
* the USB DFU bootloader will take the first 8 KB of flash, followed by the application
* the first 4 bytes of the RAM is reserved for the DFU magic word (DFU! to start DFU bootloader)
/* linker script for application running on STM32F401xC micro-controller
* the STM32F401xC has 256 KB of flash starting at 0x0800 0000, and 64 KB of RAM starting at 0x2000 0000
* the USB DFU bootloader will use the first sector, which is 16 KB large.
* this is followed by the application.
* the first 4 bytes of the RAM are reserved for the DFU magic word (DFU! to start DFU bootloader)
*/
/* define memory regions. */
MEMORY
{
rom (rx) : ORIGIN = 0x08000000, LENGTH = 8K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 20K - 4
rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 64K - 4
}
/* where the main application starts */
PROVIDE(__application_beginning = ORIGIN(rom) + LENGTH(rom));
/* if you want the firmware to use the flash size advertised by the micro-controller itself, use the following:
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
if you want to enforce a flash size, because there is more flash than advertized by the micro-controller, use to following:
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);
/* include rest of the definitions for the ARM Cortex-M, including STM32F1 family */
/* include rest of the definitions for the ARM Cortex-M, including STM32F4 family */
INCLUDE cortex-m-generic.ld

110
global.c
View File

@ -16,6 +16,8 @@
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/exti.h> // external interrupt defines
#include <libopencm3/stm32/syscfg.h> // system definitions
#include <libopencm3/usb/dwc/otg_fs.h> // USB OTG utilities
#include "global.h" // common methods
@ -132,9 +134,6 @@ char* b2s(uint64_t binary, uint8_t rjust)
inline void led_on(void)
{
#if defined(LED_PIN)
#if defined(BUSVOODOO)
gpio_set_mode(GPIO_PORT(LED_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_PIN)); // set LED pin push-pull
#endif // BUSVOODOO
#if defined(LED_ON) && LED_ON
gpio_set(GPIO_PORT(LED_PIN), GPIO_PIN(LED_PIN));
#else
@ -147,14 +146,10 @@ inline void led_on(void)
inline void led_off(void)
{
#if defined(LED_PIN)
#if defined(BUSVOODOO)
gpio_set_mode(GPIO_PORT(LED_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LED_PIN)); // set LED pin to floating to disable LEDs
#else
#if defined(LED_ON) && LED_ON
gpio_clear(GPIO_PORT(LED_PIN), GPIO_PIN(LED_PIN));
#else
gpio_set(GPIO_PORT(LED_PIN), GPIO_PIN(LED_PIN));
#endif // BUSVOODOO
#endif // LED_ON
#endif // LED_PIN
}
@ -163,21 +158,18 @@ inline void led_off(void)
inline void led_toggle(void)
{
#if defined(LED_PIN)
#if defined(BUSVOODOO)
gpio_set_mode(GPIO_PORT(LED_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_PIN)); // set LED pin to push-pull
#endif // BUSVOODOO
gpio_toggle(GPIO_PORT(LED_PIN), GPIO_PIN(LED_PIN));
#endif // LED_PIN
}
void sleep_us(uint32_t duration)
{
if (duration <= 5) { // less than the setup time
for (volatile uint32_t nop = 0; nop < 5 * duration; nop++); // busy loop, approximate, hand tuned for 72 MHz system clock
if (duration <= 4) { // less than the setup time
for (volatile uint32_t nop = 0; nop < 5 * duration; nop++); // busy loop, approximate
return;
}
duration -= 5; // subtract setup time
duration -= 4; // subtract setup time
systick_counter_disable(); // disable SysTick to reconfigure it
if (!systick_set_frequency(1000000, rcc_ahb_frequency)) { // set SysTick frequency to microseconds
while (true); // unhandled error
@ -249,33 +241,41 @@ void user_input_store(char c)
void board_setup(void)
{
#if defined(LED_PIN)
// setup main clock
#if defined(MINIF401)
rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_84MHZ]); // the MINIF401 uses an STM32F401 which can go up to 84 MHz, and the board has a 25 MHz crystal
#else
rcc_clock_setup_pll(&rcc_hsi_configs[RCC_CLOCK_3V3_84MHZ]); // use HSI which is present on all boards, and limit to 84MHz (supported by all STM32F4
#endif
#if defined(LED_PIN) && defined(LED_ON)
// setup LED
rcc_periph_clock_enable(GPIO_RCC(LED_PIN)); // enable clock for LED
#if defined(BUSVOODOO)
gpio_set_mode(GPIO_PORT(LED_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LED_PIN)); // set LED pin to floating to disable LEDs
#else
gpio_set_mode(GPIO_PORT(LED_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_PIN)); // set LED pin to output push-pull do drive LED
gpio_mode_setup(GPIO_PORT(LED_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(LED_PIN)); // set LED pin as output
#if LED_ON // LED is on when sourcing
gpio_set_output_options(GPIO_PORT(LED_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(LED_PIN)); // set LED pin output as push-pull
#else // LED is on when sinking
gpio_set_output_options(GPIO_PORT(LED_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN(LED_PIN)); // set LED pin output as open-drain
#endif
led_off(); // switch off LED per default
#endif // LED_PIN
/*
// setup button
#if defined(BUTTON_PIN)
#if defined(BUTTON_PIN) && defined(BUTTON_PRESSED)
rcc_periph_clock_enable(GPIO_RCC(BUTTON_PIN)); // enable clock for button
gpio_set_mode(GPIO_PORT(BUTTON_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON_PIN)); // set button pin to input
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(GPIO_EXTI(BUTTON_PIN), GPIO_PORT(BUTTON_PIN)); // mask external interrupt of this pin only for this port
#if defined(BUTTON_PRESSED) && BUTTON_PRESSED
gpio_clear(GPIO_PORT(BUTTON_PIN), GPIO_PIN(BUTTON_PIN)); // pull down to be able to detect button push (go high)
#if BUTTON_PRESSED // level goes high when pressed
gpio_mode_setup(GPIO_PORT(BUTTON_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(BUTTON_PIN)); // set GPIO to input and pull down
exti_set_trigger(GPIO_EXTI(BUTTON_PIN), EXTI_TRIGGER_RISING); // trigger when button is pressed
#else
gpio_set(GPIO_PORT(BUTTON_PIN), GPIO_PIN(BUTTON_PIN)); // pull up to be able to detect button push (go low)
#else // level goes low when pressed
gpio_mode_setup(GPIO_PORT(BUTTON_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(BUTTON_PIN)); // set GPIO to input and pull up
exti_set_trigger(GPIO_EXTI(BUTTON_PIN), EXTI_TRIGGER_FALLING); // trigger when button is pressed
#endif
exti_enable_request(GPIO_EXTI(BUTTON_PIN)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON_PIN)); // enable interrupt
#endif
*/
// reset user input buffer
user_input_available = false;
@ -283,6 +283,66 @@ void board_setup(void)
user_input_used = 0;
}
/** disconnect USB by sending a reset condition */
static void usb_disconnect(void)
{
if (OTG_FS_GUSBCFG & OTG_GUSBCFG_FDMOD) { // USB configured as device
// pull USB D+ low for a short while
OTG_FS_DCTL |= OTG_DCTL_SDIS; // disconnect DP pull-up to simulate a disconnect
// in case there is an external pull-up resistor, pull DP low
// I have no idea why, but once USB is configured, I can't use PA12/DP back as GPIO
gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO12); // be sure the D+ pin can be used as GPIO output
gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO12); // use push-pull output
gpio_clear(GPIOA, GPIO12); // pull D+ low
for (volatile uint32_t i = 0; i < 0x2000; i++); // USB disconnected must be at least 10 ms long, at most 100 ms
}
}
void system_memory(void)
{
usb_disconnect(); // disconnect from USB (if necessary)
// for more details, see https://stm32f4-discovery.net/2017/04/tutorial-jump-system-memory-software-stm32/
// deinit RCC (according to STM32CubeF4 source code)
RCC_CR |= RCC_CR_HSION; // enable high speed internal clock
while (!(RCC_CR & RCC_CR_HSIRDY)); // wait until clock is ready
RCC_CR |= (0x10U << RCC_CR_HSITRIM_SHIFT); // set HSITRIM[4:0] bits to the reset value
RCC_CFGR = 0;// reset CFGR register
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS_HSI); // wait it till clock switch is ready
RCC_CR &= ~(RCC_CR_HSEON | RCC_CR_HSEBYP | RCC_CR_CSSON); // clear HSEON, HSEBYP and CSSON bits
while (RCC_CR & RCC_CR_HSERDY); // wait till HSE is disabled
RCC_CR &= ~RCC_CR_PLLON; // Clear PLLON bit
while (RCC_CR & RCC_CR_PLLRDY); // wait till PLL is disabled
//RCC_PLLCFGR = 0x24003010; // reset PLLCFGR register to default value (value for STM32F401)
RCC_CIR &= ~(RCC_CIR_LSIRDYIE | RCC_CIR_LSERDYIE | RCC_CIR_HSIRDYIE | RCC_CIR_HSERDYIE | RCC_CIR_PLLRDYIE); // disable all interrupts
RCC_CIR |= (RCC_CIR_LSIRDYC | RCC_CIR_LSERDYC | RCC_CIR_HSIRDYC | RCC_CIR_HSERDYC | RCC_CIR_PLLRDYC | RCC_CIR_CSSC); // clear all interrupt flags
RCC_CR &= ~RCC_CSR_LSION; // clear LSION bit
RCC_CSR |= RCC_CSR_RMVF; // reset all CSR flags
// switch to system memory
RCC_APB2ENR = RCC_APB2ENR_SYSCFGEN; // enable system configure clock (all others are not required)
cm_disable_interrupts(); // disable all interrupts
SYSCFG_MEMRM = 1; // map system memory to 0x0000 0000 (this bypasses the BOOT0 pin)
const uint32_t address = 0x1FFF0000; // system memory address
__asm__ volatile ("MSR msp,%0" : :"r"(*(uint32_t*)address)); // set stack pointer to address provided in the beginning of the bootloader (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)
// we should not reach this point
}
void dfu_bootloader(void)
{
usb_disconnect(); // disconnect from USB (if necessary)
// 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
}
#if defined(BUTTON_PIN)
/** interrupt service routine called when button is pressed */
void GPIO_EXTI_ISR(BUTTON_PIN)(void)

329
global.h
View File

@ -9,6 +9,11 @@
/** enable debugging functionalities */
#define DEBUG true
/** Data Synchronization Barrier, ensure all data is written */
#define __DSB() __asm__("dsb")
/** Instruction Synchronization Barrier, ensure all instruction are run */
#define __ISB() __asm__("isb")
/** get the length of an array */
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
/** concatenate 2 arguments */
@ -385,37 +390,22 @@ int32_t adds32_safe(int32_t a, int32_t b);
#define RST_TIM(x) CAT2(RST_TIM,x)
/** get NVIC IRQ for timer base on TIM identifier */
#define NVIC_TIM_IRQ(x) CAT3(NVIC_TIM,x,_IRQ)
/** NVIC IRQ for timer 9 */
#define NVIC_TIM9_IRQ NVIC_TIM1_BRK_TIM9_IRQ
/** NVIC IRQ for timer 10 */
#define NVIC_TIM10_IRQ NVIC_TIM1_UP_TIM10_IRQ
/** NVIC IRQ for timer 11 */
#define NVIC_TIM11_IRQ NVIC_TIM1_TRG_COM_TIM11_IRQ
/** get interrupt service routine for timer base on TIM identifier */
#define TIM_ISR(x) CAT3(tim,x,_isr)
/** get port based on TIMx_CHy identifier */
#define TIM_CH_PORT(x,y) CAT4(GPIO_BANK_TIM,x,_CH,y)
/** get pin based on TIMx_CHy identifier */
#define TIM_CH_PIN(x,y) CAT4(GPIO_TIM,x,_CH,y)
/** get RCC for port based on TIMx_CHy identifier */
#define RCC_TIM_CH(x,y) CAT4(RCC_TIM,x,_CH,y)
#define RCC_TIM1_CH1 RCC_GPIOA /**< RCC for port for on TIM1_CH1 */
#define RCC_TIM1_CH2 RCC_GPIOA /**< RCC for port for on TIM1_CH2 */
#define RCC_TIM1_CH3 RCC_GPIOA /**< RCC for port for on TIM1_CH3 */
#define RCC_TIM1_CH4 RCC_GPIOA /**< RCC for port for on TIM1_CH4 */
#define RCC_TIM1_CH1N RCC_GPIOB /**< RCC for port for on TIM1_CH1N */
#define RCC_TIM1_CH2N RCC_GPIOB /**< RCC for port for on TIM1_CH2N */
#define RCC_TIM1_CH3N RCC_GPIOB /**< RCC for port for on TIM1_CH3N */
#define RCC_TIM2_CH1_ETR RCC_GPIOA /**< RCC for port for on TIM2_CH1_ETR */
#define RCC_TIM2_CH2 RCC_GPIOA /**< RCC for port for on TIM2_CH2 */
#define RCC_TIM2_CH3 RCC_GPIOA /**< RCC for port for on TIM2_CH3 */
#define RCC_TIM2_CH4 RCC_GPIOA /**< RCC for port for on TIM2_CH4 */
#define RCC_TIM3_CH1 RCC_GPIOA /**< RCC for port for on TIM3_CH1 */
#define RCC_TIM3_CH2 RCC_GPIOA /**< RCC for port for on TIM3_CH2 */
#define RCC_TIM3_CH3 RCC_GPIOB /**< RCC for port for on TIM3_CH3 */
#define RCC_TIM3_CH4 RCC_GPIOB /**< RCC for port for on TIM3_CH4 */
#define RCC_TIM4_CH1 RCC_GPIOB /**< RCC for port for on TIM4_CH1 */
#define RCC_TIM4_CH2 RCC_GPIOB /**< RCC for port for on TIM4_CH2 */
#define RCC_TIM4_CH3 RCC_GPIOB /**< RCC for port for on TIM4_CH3 */
#define RCC_TIM4_CH4 RCC_GPIOB /**< RCC for port for on TIM4_CH4 */
#define RCC_TIM5_CH1 RCC_GPIOA /**< RCC for port for on TIM5_CH1 */
#define RCC_TIM5_CH2 RCC_GPIOA /**< RCC for port for on TIM5_CH2 */
#define RCC_TIM5_CH3 RCC_GPIOA /**< RCC for port for on TIM5_CH3 */
#define RCC_TIM5_CH4 RCC_GPIOA /**< RCC for port for on TIM5_CH4 */
/** interrupt service routine for timer 9 */
#define tim9_isr tim1_brk_tim9_isr
/** interrupt service routine for timer 10 */
#define tim10_isr tim1_up_tim10_isr
/** interrupt service routine for timer 11 */
#define tim11_isr tim1_trg_com_tim11_isr
/** get TIM_OC based on CHx identifier */
#define TIM_OC(x) CAT2(TIM_OC,x)
/** get TIM_IC based on CHx identifier */
#define TIM_IC(x) CAT2(TIM_IC,x)
/** get TIM_IC_IN_TI based on CHx identifier */
@ -472,123 +462,12 @@ int32_t adds32_safe(int32_t a, int32_t b);
#define USART_IRQ(x) CAT3(NVIC_USART,x,_IRQ)
/** get interrupt service routine for USART based on USART identifier */
#define USART_ISR(x) CAT3(usart,x,_isr)
/** get port for USART transmit pin based on USART identifier */
#define USART_TX_PORT(x) CAT3(GPIO_BANK_USART,x,_TX)
/** get port for USART receive pin based on USART identifier */
#define USART_RX_PORT(x) CAT3(GPIO_BANK_USART,x,_RX)
/** get port for USART RTS pin based on USART identifier */
#define USART_RTS_PORT(x) CAT3(GPIO_BANK_USART,x,_RTS)
/** get port for USART CTS pin based on USART identifier */
#define USART_CTS_PORT(x) CAT3(GPIO_BANK_USART,x,_CTS)
/** get pin for USART transmit pin based on USART identifier */
#define USART_TX_PIN(x) CAT3(GPIO_USART,x,_TX)
/** get pin for USART receive pin based on USART identifier */
#define USART_RX_PIN(x) CAT3(GPIO_USART,x,_RX)
/** get pin for USART RTS pin based on USART identifier */
#define USART_RTS_PIN(x) CAT3(GPIO_USART,x,_RTS)
/** get pin for USART CTS pin based on USART identifier */
#define USART_CTS_PIN(x) CAT3(GPIO_USART,x,_CTS)
/** get RCC for USART port based on USART identifier */
#define RCC_USART_PORT(x) CAT2(RCC_USART_PORT,x)
#define RCC_USART_PORT1 RCC_GPIOA /**< USART 1 is on port A */
#define RCC_USART_PORT2 RCC_GPIOA /**< USART 2 is on port A */
#define RCC_USART_PORT3 RCC_GPIOB /**< USART 3 is on port B */
/** get port based on ADC12_IN identifier */
#define ADC12_IN_PORT(x) CAT3(ADC12_IN,x,_PORT)
#define ADC12_IN0_PORT GPIOA /**< ADC12_IN0 is on PA0 */
#define ADC12_IN1_PORT GPIOA /**< ADC12_IN1 is on PA1 */
#define ADC12_IN2_PORT GPIOA /**< ADC12_IN2 is on PA2 */
#define ADC12_IN3_PORT GPIOA /**< ADC12_IN3 is on PA3 */
#define ADC12_IN4_PORT GPIOA /**< ADC12_IN4 is on PA4 */
#define ADC12_IN5_PORT GPIOA /**< ADC12_IN5 is on PA5 */
#define ADC12_IN6_PORT GPIOA /**< ADC12_IN6 is on PA6 */
#define ADC12_IN7_PORT GPIOA /**< ADC12_IN7 is on PA7 */
#define ADC12_IN8_PORT GPIOB /**< ADC12_IN8 is on PB0 */
#define ADC12_IN9_PORT GPIOB /**< ADC12_IN9 is on PB1 */
#define ADC12_IN10_PORT GPIOC /**< ADC12_IN10 is on PC0 */
#define ADC12_IN11_PORT GPIOC /**< ADC12_IN11 is on PC1 */
#define ADC12_IN12_PORT GPIOC /**< ADC12_IN12 is on PC2 */
#define ADC12_IN13_PORT GPIOC /**< ADC12_IN13 is on PC3 */
#define ADC12_IN14_PORT GPIOC /**< ADC12_IN14 is on PC4 */
#define ADC12_IN15_PORT GPIOC /**< ADC12_IN15 is on PC5 */
/** get pin based on ADC12_IN identifier */
#define ADC12_IN_PIN(x) CAT3(ADC12_IN,x,_PIN)
#define ADC12_IN0_PIN GPIO0 /**< ADC12_IN0 is on PA0 */
#define ADC12_IN1_PIN GPIO1 /**< ADC12_IN1 is on PA1 */
#define ADC12_IN2_PIN GPIO2 /**< ADC12_IN2 is on PA2 */
#define ADC12_IN3_PIN GPIO3 /**< ADC12_IN3 is on PA3 */
#define ADC12_IN4_PIN GPIO4 /**< ADC12_IN4 is on PA4 */
#define ADC12_IN5_PIN GPIO5 /**< ADC12_IN5 is on PA5 */
#define ADC12_IN6_PIN GPIO6 /**< ADC12_IN6 is on PA6 */
#define ADC12_IN7_PIN GPIO7 /**< ADC12_IN7 is on PA7 */
#define ADC12_IN8_PIN GPIO0 /**< ADC12_IN8 is on PB0 */
#define ADC12_IN9_PIN GPIO1 /**< ADC12_IN9 is on PB1 */
#define ADC12_IN10_PIN GPIO0 /**< ADC12_IN10 is on PC0 */
#define ADC12_IN11_PIN GPIO1 /**< ADC12_IN11 is on PC1 */
#define ADC12_IN12_PIN GPIO2 /**< ADC12_IN12 is on PC2 */
#define ADC12_IN13_PIN GPIO3 /**< ADC12_IN13 is on PC3 */
#define ADC12_IN14_PIN GPIO4 /**< ADC12_IN14 is on PC4 */
#define ADC12_IN15_PIN GPIO5 /**< ADC12_IN15 is on PC5 */
/** get RCC based on ADC12_IN identifier */
#define RCC_ADC12_IN(x) CAT2(RCC_ADC12_IN,x)
#define RCC_ADC12_IN0 RCC_GPIOA /**< ADC12_IN0 is on PA0 */
#define RCC_ADC12_IN1 RCC_GPIOA /**< ADC12_IN1 is on PA1 */
#define RCC_ADC12_IN2 RCC_GPIOA /**< ADC12_IN2 is on PA2 */
#define RCC_ADC12_IN3 RCC_GPIOA /**< ADC12_IN3 is on PA3 */
#define RCC_ADC12_IN4 RCC_GPIOA /**< ADC12_IN4 is on PA4 */
#define RCC_ADC12_IN5 RCC_GPIOA /**< ADC12_IN5 is on PA5 */
#define RCC_ADC12_IN6 RCC_GPIOA /**< ADC12_IN6 is on PA6 */
#define RCC_ADC12_IN7 RCC_GPIOA /**< ADC12_IN7 is on PA7 */
#define RCC_ADC12_IN8 RCC_GPIOB /**< ADC12_IN8 is on PB0 */
#define RCC_ADC12_IN9 RCC_GPIOB /**< ADC12_IN9 is on PB1 */
#define RCC_ADC12_IN10 RCC_GPIOC /**< ADC12_IN10 is on PC0 */
#define RCC_ADC12_IN11 RCC_GPIOC /**< ADC12_IN11 is on PC1 */
#define RCC_ADC12_IN12 RCC_GPIOC /**< ADC12_IN12 is on PC2 */
#define RCC_ADC12_IN13 RCC_GPIOC /**< ADC12_IN13 is on PC3 */
#define RCC_ADC12_IN14 RCC_GPIOC /**< ADC12_IN14 is on PC4 */
#define RCC_ADC12_IN15 RCC_GPIOC /**< ADC12_IN15 is on PC5 */
/** get channel based on ADC12_IN identifier */
/** get channel based on ADC1_IN identifier */
#define ADC_CHANNEL(x) CAT2(ADC_CHANNEL,x)
/** get SPI based on SPI identifier */
#define SPI(x) CAT2(SPI,x)
/** get RCC for SPI based on SPI identifier */
#define RCC_SPI(x) CAT2(RCC_SPI,x)
/** get RCC for GPIO port for SPI NSS signals */
#define RCC_SPI_NSS_PORT(x) CAT3(RCC_SPI,x,_NSS_PORT)
#define RCC_SPI1_NSS_PORT RCC_GPIOA /**< RCC for GPIO port for NSS for SPI1 */
#define RCC_SPI1_RE_NSS_PORT RCC_GPIOA /**< RCC for GPIO port for NSS for SPI1_RE */
#define RCC_SPI2_NSS_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI2 */
/** get RCC for GPIO port for SPI SCK signals */
#define RCC_SPI_SCK_PORT(x) CAT3(RCC_SPI,x,_SCK_PORT)
#define RCC_SPI1_SCK_PORT RCC_GPIOA /**< RCC for GPIO port for NSS for SPI1 */
#define RCC_SPI1_RE_SCK_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI1_RE */
#define RCC_SPI2_SCK_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI2 */
/** get RCC for GPIO port for SPI MISO signals */
#define RCC_SPI_MISO_PORT(x) CAT3(RCC_SPI,x,_MISO_PORT)
#define RCC_SPI1_MISO_PORT RCC_GPIOA /**< RCC for GPIO port for NSS for SPI1 */
#define RCC_SPI1_RE_MISO_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI1_RE */
#define RCC_SPI2_MISO_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI2 */
/** get RCC for GPIO port for SPI MOSI signals */
#define RCC_SPI_MOSI_PORT(x) CAT3(RCC_SPI,x,_MOSI_PORT)
#define RCC_SPI1_MOSI_PORT RCC_GPIOA /**< RCC for GPIO port for NSS for SPI1 */
#define RCC_SPI1_RE_MOSI_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI1_RE */
#define RCC_SPI2_MOSI_PORT RCC_GPIOB /**< RCC for GPIO port for NSS for SPI2 */
/** get SPI port for NSS signal based on SPI identifier */
#define SPI_NSS_PORT(x) CAT3(GPIO_BANK_SPI,x,_NSS)
/** get SPI port for SCK signal based on SPI identifier */
#define SPI_SCK_PORT(x) CAT3(GPIO_BANK_SPI,x,_SCK)
/** get SPI port for MISO signal based on SPI identifier */
#define SPI_MISO_PORT(x) CAT3(GPIO_BANK_SPI,x,_MISO)
/** get SPI port for MOSI signal based on SPI identifier */
#define SPI_MOSI_PORT(x) CAT3(GPIO_BANK_SPI,x,_MOSI)
/** get SPI pin for NSS signal based on SPI identifier */
#define SPI_NSS_PIN(x) CAT3(GPIO_SPI,x,_NSS)
/** get SPI pin for SCK signal based on SPI identifier */
#define SPI_SCK_PIN(x) CAT3(GPIO_SPI,x,_SCK)
/** get SPI pin for MISO signal based on SPI identifier */
#define SPI_MISO_PIN(x) CAT3(GPIO_SPI,x,_MISO)
/** get SPI pin for MOSI signal based on SPI identifier */
#define SPI_MOSI_PIN(x) CAT3(GPIO_SPI,x,_MOSI)
/** get SPI CRC polynomial register based on SPI identifier */
#define SPI_CRC_PR(x) CAT3(SPI,x,_CRCPR)
/** get SPI CRC transmit register based on SPI identifier */
@ -599,86 +478,82 @@ int32_t adds32_safe(int32_t a, int32_t b);
#define SPI_IRQ(x) CAT3(NVIC_SPI,x,_IRQ)
/** get SPI ISR based on SPI identifier */
#define SPI_ISR(x) CAT3(spi,x,_isr)
/** get DMA based on SPI identifier */
#define DMA_SPI(x) CAT2(DMA_SPI,x)
#define DMA_SPI1 DMA1 /**< SPI1 is on DMA1 */
#define DMA_SPI2 DMA1 /**< SPI2 is on DMA1 */
#define DMA_SPI3 DMA2 /**< SPI3 is on DMA2 */
/** get RCC for DMA based on SPI identifier */
#define RCC_DMA_SPI(x) CAT2(RCC_DMA_SPI,x)
#define RCC_DMA_SPI1 RCC_DMA1 /**< SPI1 is on DMA1 */
#define RCC_DMA_SPI2 RCC_DMA1 /**< SPI2 is on DMA1 */
#define RCC_DMA_SPI3 RCC_DMA2 /**< SPI3 is on DMA2 */
/** get DMA channel for SPI TX based on SPI identifier */
#define DMA_CHANNEL_SPI_TX(x) CAT3(DMA_CHANNEL_SPI,x,_TX)
#define DMA_CHANNEL_SPI1_TX DMA_CHANNEL3 /**< SPI1 TX is on DMA channel 3 */
#define DMA_CHANNEL_SPI2_TX DMA_CHANNEL5 /**< SPI2 TX is on DMA channel 5 */
#define DMA_CHANNEL_SPI3_TX DMA_CHANNEL2 /**< SPI3 TX is on DMA channel 2 */
/** get DMA channel for SPI RX based on SPI identifier */
#define DMA_CHANNEL_SPI_RX(x) CAT3(DMA_CHANNEL_SPI,x,_RX)
#define DMA_CHANNEL_SPI1_RX DMA_CHANNEL4 /**< SPI1 RX is on DMA channel 4 */
#define DMA_CHANNEL_SPI2_RX DMA_CHANNEL2 /**< SPI2 RX is on DMA channel 2 */
#define DMA_CHANNEL_SPI3_RX DMA_CHANNEL1 /**< SPI3 RX is on DMA channel 1 */
/** get DMA NVIC IRQ for SPI TX based on SPI identifier */
#define DMA_IRQ_SPI_TX(x) CAT3(NVIC_DMA_CHANNEL_IRQ_SPI,x,_TX)
#define NVIC_DMA_CHANNEL_IRQ_SPI1_TX NVIC_DMA1_CHANNEL3_IRQ /**< SPI1 TX is on DMA 1 channel 3 */
#define NVIC_DMA_CHANNEL_IRQ_SPI2_TX NVIC_DMA1_CHANNEL5_IRQ /**< SPI2 TX is on DMA 1 channel 5 */
#define NVIC_DMA_CHANNEL_IRQ_SPI3_TX NVIC_DMA2_CHANNEL2_IRQ /**< SPI3 TX is on DMA 2 channel 2 */
/** get DMA NVIC IRQ for SPI RX based on SPI identifier */
#define DMA_IRQ_SPI_RX(x) CAT3(NVIC_DMA_CHANNEL_IRQ_SPI,x,_RX)
#define NVIC_DMA_CHANNEL_IRQ_SPI1_RX NVIC_DMA1_CHANNEL4_IRQ /**< SPI1 RX is on DMA 1 channel 4 */
#define NVIC_DMA_CHANNEL_IRQ_SPI2_RX NVIC_DMA1_CHANNEL2_IRQ /**< SPI2 RX is on DMA 1 channel 2 */
#define NVIC_DMA_CHANNEL_IRQ_SPI3_RX NVIC_DMA2_CHANNEL1_IRQ /**< SPI3 RX is on DMA 2 channel 1 */
/** get DMA ISR for SPI TX based on SPI identifier */
#define DMA_ISR_SPI_TX(x) CAT3(DMA_CHANNEL_ISR_SPI,x,_TX)
#define DMA_CHANNEL_ISR_SPI1_TX dma1_channel3_isr /**< SPI1 TX is on DMA 1 channel 3 */
#define DMA_CHANNEL_ISR_SPI2_TX dma1_channel5_isr /**< SPI2 TX is on DMA 1 channel 5 */
#define DMA_CHANNEL_ISR_SPI3_TX dma2_channel2_isr /**< SPI3 TX is on DMA 2 channel 2 */
/** get DMA ISR for SPI RX based on SPI identifier */
#define DMA_ISR_SPI_RX(x) CAT3(DMA_CHANNEL_ISR_SPI,x,_RX)
#define DMA_CHANNEL_ISR_SPI1_RX dma1_channel4_isr /**< SPI1 RX is on DMA 1 channel 4 */
#define DMA_CHANNEL_ISR_SPI2_RX dma1_channel2_isr /**< SPI2 RX is on DMA 1 channel 2 */
#define DMA_CHANNEL_ISR_SPI3_RX dma2_channel1_isr /**< SPI3 RX is on DMA 2 channel 1 */
/** get I²C based on I²C identifier */
#define I2C(x) CAT2(I2C,x)
/** get RCC for I²C based on I²C identifier */
#define RCC_I2C(x) CAT2(RCC_I2C,x)
/** get port based on ADC1_IN identifier */
#define ADC1_IN_PORT(x) CAT3(ADC1_IN,x,_PORT)
#define ADC1_IN0_PORT GPIOA /**< ADC1_IN0 is on PA0 */
#define ADC1_IN1_PORT GPIOA /**< ADC1_IN1 is on PA1 */
#define ADC1_IN2_PORT GPIOA /**< ADC1_IN2 is on PA2 */
#define ADC1_IN3_PORT GPIOA /**< ADC1_IN3 is on PA3 */
#define ADC1_IN4_PORT GPIOA /**< ADC1_IN4 is on PA4 */
#define ADC1_IN5_PORT GPIOA /**< ADC1_IN5 is on PA5 */
#define ADC1_IN6_PORT GPIOA /**< ADC1_IN6 is on PA6 */
#define ADC1_IN7_PORT GPIOA /**< ADC1_IN7 is on PA7 */
#define ADC1_IN8_PORT GPIOB /**< ADC1_IN8 is on PB0 */
#define ADC1_IN9_PORT GPIOB /**< ADC1_IN9 is on PB1 */
#define ADC1_IN10_PORT GPIOC /**< ADC1_IN10 is on PC0 */
#define ADC1_IN11_PORT GPIOC /**< ADC1_IN11 is on PC1 */
#define ADC1_IN12_PORT GPIOC /**< ADC1_IN12 is on PC2 */
#define ADC1_IN13_PORT GPIOC /**< ADC1_IN13 is on PC3 */
#define ADC1_IN14_PORT GPIOC /**< ADC1_IN14 is on PC4 */
#define ADC1_IN15_PORT GPIOC /**< ADC1_IN15 is on PC5 */
/** get pin based on ADC1_IN identifier */
#define ADC1_IN_PIN(x) CAT3(ADC1_IN,x,_PIN)
#define ADC1_IN0_PIN GPIO0 /**< ADC1_IN0 is on PA0 */
#define ADC1_IN1_PIN GPIO1 /**< ADC1_IN1 is on PA1 */
#define ADC1_IN2_PIN GPIO2 /**< ADC1_IN2 is on PA2 */
#define ADC1_IN3_PIN GPIO3 /**< ADC1_IN3 is on PA3 */
#define ADC1_IN4_PIN GPIO4 /**< ADC1_IN4 is on PA4 */
#define ADC1_IN5_PIN GPIO5 /**< ADC1_IN5 is on PA5 */
#define ADC1_IN6_PIN GPIO6 /**< ADC1_IN6 is on PA6 */
#define ADC1_IN7_PIN GPIO7 /**< ADC1_IN7 is on PA7 */
#define ADC1_IN8_PIN GPIO0 /**< ADC1_IN8 is on PB0 */
#define ADC1_IN9_PIN GPIO1 /**< ADC1_IN9 is on PB1 */
#define ADC1_IN10_PIN GPIO0 /**< ADC1_IN10 is on PC0 */
#define ADC1_IN11_PIN GPIO1 /**< ADC1_IN11 is on PC1 */
#define ADC1_IN12_PIN GPIO2 /**< ADC1_IN12 is on PC2 */
#define ADC1_IN13_PIN GPIO3 /**< ADC1_IN13 is on PC3 */
#define ADC1_IN14_PIN GPIO4 /**< ADC1_IN14 is on PC4 */
#define ADC1_IN15_PIN GPIO5 /**< ADC1_IN15 is on PC5 */
/** get RCC based on ADC1_IN identifier */
#define RCC_ADC1_IN(x) CAT2(RCC_ADC1_IN,x)
#define RCC_ADC1_IN0 RCC_GPIOA /**< ADC1_IN0 is on PA0 */
#define RCC_ADC1_IN1 RCC_GPIOA /**< ADC1_IN1 is on PA1 */
#define RCC_ADC1_IN2 RCC_GPIOA /**< ADC1_IN2 is on PA2 */
#define RCC_ADC1_IN3 RCC_GPIOA /**< ADC1_IN3 is on PA3 */
#define RCC_ADC1_IN4 RCC_GPIOA /**< ADC1_IN4 is on PA4 */
#define RCC_ADC1_IN5 RCC_GPIOA /**< ADC1_IN5 is on PA5 */
#define RCC_ADC1_IN6 RCC_GPIOA /**< ADC1_IN6 is on PA6 */
#define RCC_ADC1_IN7 RCC_GPIOA /**< ADC1_IN7 is on PA7 */
#define RCC_ADC1_IN8 RCC_GPIOB /**< ADC1_IN8 is on PB0 */
#define RCC_ADC1_IN9 RCC_GPIOB /**< ADC1_IN9 is on PB1 */
#define RCC_ADC1_IN10 RCC_GPIOC /**< ADC1_IN10 is on PC0 */
#define RCC_ADC1_IN11 RCC_GPIOC /**< ADC1_IN11 is on PC1 */
#define RCC_ADC1_IN12 RCC_GPIOC /**< ADC1_IN12 is on PC2 */
#define RCC_ADC1_IN13 RCC_GPIOC /**< ADC1_IN13 is on PC3 */
#define RCC_ADC1_IN14 RCC_GPIOC /**< ADC1_IN14 is on PC4 */
#define RCC_ADC1_IN15 RCC_GPIOC /**< ADC1_IN15 is on PC5 */
/** get channel based on ADC1_IN identifier */
#define ADC_CHANNEL(x) CAT2(ADC_CHANNEL,x)
/** @} */
/** @defgroup board_led board LED GPIO
* @{
*/
#if defined(SYSTEM_BOARD) || defined(CORE_BOARD)
#define LED_PIN PA1 /**< GPIO pin (pin 11) */
#define LED_ON 0 /**< LED is on when pin is low */
#elif defined(BLUE_PILL)
#if defined(MINIF401)
#define LED_PIN PC13 /**< GPIO pin */
#define LED_ON 0 /**< LED is on when pin is low */
#elif defined(BLACK_PILL)
#define LED_PIN PB12 /**< GPIO pin */
#define LED_ON 0 /**< LED is on when pin is low */
#elif defined(MAPLE_MINI)
#define LED_PIN PB1 /**< GPIO pin (pin 19) */
#define LED_ON 1 /**< LED is on when pin is high */
#elif defined(STLINKV2) // it's sometimes a STM32F101, but it seems to have all STM32F103 features
/* on ST-Link V2 clone dongle in aluminum case LED is on pin PA9 (remap USART1_TX if used) */
#define LED_PIN PA9 /**< GPIO pin */
#define LED_ON 1 /**< the color and on level depends on the clone */
#elif defined(BLASTER)
#define LED_PIN PA5 /**< GPIO pin */
#define LED_ON 0 /**< red LED on when low (green LED is on when device is powered) */
#elif defined(BUSVOODOO)
#define LED_PIN PA8 /**< GPIO pin */
#define LED_ON 1 /**< blue LED is on when pin is high, red LED is on when pin is low, LED is off when LED is floating */
#endif
/** @} */
/** @defgroup board_button board user button GPIO
* @{
*/
#if defined(MAPLE_MINI)
/* on maple mini user button is on 32/PB8 */
#define BUTTON_PIN PB8 /**< GPIO pin (pin PB8 on maple mini) */
#define BUTTON_PRESSED 1 /**< pin is high when button is pressed */
#elif defined(CORE_BOARD)
/* on core board user button is on PA8 */
#define BUTTON_PIN PA8 /**< GPIO pin (pin PA8) */
#if defined(MINIF401)
#define BUTTON_PIN PA0 /**< GPIO pin */
#define BUTTON_PRESSED 0 /**< pin is low when button is pressed */
#endif
/** @} */
@ -689,39 +564,12 @@ int32_t adds32_safe(int32_t a, int32_t b);
#if defined(BUTTON_PIN) && defined(BUTTON_PRESSED)
#define DFU_FORCE_PIN BUTTON_PIN /**< button pin */
#define DFU_FORCE_VALUE BUTTON_PRESSED /**< start DFU when button is pressed on boot */
#elif defined(BLASTER)
#define DFU_FORCE_PIN PA8 /**< GPIO pin (pin PA8, not SWD and UART pin on debug port) */
#define DFU_FORCE_VALUE 0 /**< short to nearby ground connection to force DFU */
#elif defined(BUSVOODOO)
#if BUSVOODOO_HARDWARE_VERSION==0
/* on BusVoodoo v0 DFU input is on PC7 */
#define DFU_FORCE_PIN PC7 /**< GPIO pin (pin PC7) */
#define DFU_FORCE_VALUE 1 /**< pin is pulled low, and goes high when shorted with VUSB */
#else
/* on BusVoodoo vA DFU input is on PC4 */
#define DFU_FORCE_PIN PC4 /**< GPIO pin (pin PC4) */
#define DFU_FORCE_VALUE 1 /**< pin floating, set high when shorted with nearby VCC */
#endif
#else
#define DFU_FORCE_PIN PB2 /**< BOOT1 pin */
#define DFU_FORCE_VALUE 1 /**< often connected to 0 by default to start system memory when BOOT0 = 1 */
#endif
/** @} */
/** symbol for beginning of the application
* @note this symbol will be provided by the linker script
*/
extern uint32_t __application_beginning;
/** symbol for end of the application
* @note this symbol will be provided by the linker script
*/
extern char __application_end;
/** symbol for end of flash
* @note this symbol will be provided by the linker script
*/
extern char __flash_end;
/** flag set when board user button has been pressed/released */
extern volatile bool button_flag;
/** symbol for the DFU magic word
* @note this symbol will be provided by the linker script
*/
@ -763,4 +611,11 @@ char user_input_get(void);
void user_input_store(char c);
/** setup board peripherals */
void board_setup(void);
/** start embedded bootloader (e.g. system memory)
* @warning the USB DFU bootloader does not start
* @note after jumping to the bootloader, the USB device is detected, but does not enumerate. I have no idea about the cause. Not configuring the USB did not solve the issue. Re-initializing RCC did not help. Even the STM32CodeMX generated code was not able to have a working USB DFU reboot. Calibrating HSI according to https://community.st.com/s/question/0D50X0000BG0TfH/stm32f4-usb-dfu-bootloader-not-working-reliably did not solve the issue. Maybe there is something with the board. Booting the embedded USB DFU bootloader by hand by pressing the BOOT0 button works.
* @note the USART bootloader works fine though
*/
void system_memory(void);
/** start DFU bootloader */
void dfu_bootloader(void);

View File

@ -1,4 +1,4 @@
/** library to read/write internal flash
/** internal flash utilities
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -11,286 +11,167 @@
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/desig.h> // device signature definitions
#include <libopencm3/stm32/dbgmcu.h> // MCU definitions
/* own libraries */
#include "flash_internal.h" // flash storage library API
#include "global.h" // global definitions
#include "flash_internal.h" // own definitions
/** flash page size */
static uint16_t flash_internal_page = 0;
/** end address of flash */
static uint32_t flash_internal_end = 0;
/** start address of flash memory used for the emulated EEPROM */
static uint32_t flash_internal_eeprom_start = 0;
/** start address of emulated EEPROM */
static uint32_t flash_internal_eeprom_address = 0;
/** information about the MCU's flash sections */
struct flash_sections_info_s {
const uint16_t device_id; /**< the MCU DEV ID */
const uint8_t number; /**< number of sections */
const struct flash_internal_section_info_s* sections; /**< size of the sections, in KiB */
};
/** find out page size and flash end address */
static void flash_internal_init(void)
/** information about the STM42F401xB/C flash sections */
static const struct flash_internal_section_info_s sections_f401xbc[] = {
{
.number = 0,
.size = 16,
.start = FLASH_BASE + (0) * 1024,
.end = FLASH_BASE + (16) * 1024 - 1,
},
{
.number = 1,
.size = 16,
.start = FLASH_BASE + (0 + 16) * 1024,
.end = FLASH_BASE + (16 + 16) * 1024 - 1,
},
{
.number = 2,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16) * 1024 - 1,
},
{
.number = 3,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16) * 1024 - 1,
},
{
.number = 4,
.size = 64,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64) * 1024 - 1,
},
{
.number = 5,
.size = 128,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16 + 64) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64 + 128) * 1024 - 1,
},
};
/** information about the STM42F401xD/E flash sections */
static const struct flash_internal_section_info_s sections_f401xde[] = {
{
.number = 0,
.size = 16,
.start = FLASH_BASE + (0) * 1024,
.end = FLASH_BASE + (16) * 1024 - 1,
},
{
.number = 1,
.size = 16,
.start = FLASH_BASE + (0 + 16) * 1024,
.end = FLASH_BASE + (16 + 16) * 1024 - 1,
},
{
.number = 2,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16) * 1024 - 1,
},
{
.number = 3,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16) * 1024 - 1,
},
{
.number = 4,
.size = 64,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64) * 1024 - 1,
},
{
.number = 5,
.size = 128,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16 + 64) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64 + 128) * 1024 - 1,
},
{
.number = 6,
.size = 128,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16 + 64 + 128) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64 + 128 + 128) * 1024 - 1,
},
{
.number = 7,
.size = 128,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16 + 64 + 128 + 128) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64 + 128 + 128 + 128) * 1024 - 1,
},
};
static const struct flash_sections_info_s flash_sections_info[] = {
{
.device_id = 0x423,
.number = LENGTH(sections_f401xbc),
.sections = sections_f401xbc,
},
{
.device_id = 0x433,
.number = LENGTH(sections_f401xde),
.sections = sections_f401xde,
},
};
bool flash_internal_range(uint32_t address, size_t size)
{
if (0 == flash_internal_page) {
flash_internal_page_size(); // get page size
}
if (0 == flash_internal_end) {
if ((uint32_t)&__flash_end >= FLASH_BASE) {
flash_internal_end = (uint32_t)&__flash_end;
} else {
flash_internal_end = FLASH_BASE + desig_get_flash_size() * 1024;
}
}
}
/** verify if the data is in the internal flash area
* @param[in] address start address of the data to read
* @param[in] size how much data to read or write, in bytes
* @return if the data is in the internal flash area
*/
static bool flash_internal_range(uint32_t address, size_t size)
{
if (0 == flash_internal_page || 0 == flash_internal_end) {
flash_internal_init();
}
if (address > (UINT32_MAX - size)) { // on integer overflow will occur
return false;
}
if (address < FLASH_BASE) { // start address is before the start of the internal flash
return false;
}
if ((address + size) > flash_internal_end) { // end address is after the end of the internal flash
if (address > (UINT32_MAX - size)) { // an integer overflow will occur
return false;
}
if ((address + size) > FLASH_BASE + desig_get_flash_size() * 1024) { // end address is after the end of the internal flash
return false;
}
return true;
}
/** get flash page size
* @return flash page size (in bytes)
/** find out in which section is this address
* @param[in] address address to find the section for
* @return section in which this address is (NULL if not in flash, section has not been found, or the sections of this device are unknown)
*/
uint16_t flash_internal_page_size(void)
const struct flash_internal_section_info_s* flash_internal_section(uint32_t address)
{
if (0 == flash_internal_page) { // we don't know the page size yet
if (desig_get_flash_size() < 256) {
if ((*(uint32_t*)0x1FFFF000 & 0xFFFE0000) == 0x20000000) { // non-connectivity system memory start detected (MSP address pointing to SRAM
flash_internal_page = 1024;
} else { // connectivity system memory start is at 0x1FFFB000
flash_internal_page = 2048;
}
} else {
flash_internal_page = 2048;
if (!flash_internal_range(address, 0)) { // verify if this address is in flash
return NULL;
}
// find sections information for this device
uint8_t flash_sections_info_i;
for (flash_sections_info_i = 0; flash_sections_info_i < LENGTH(flash_sections_info); flash_sections_info_i++) {
if ((DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK) == flash_sections_info[flash_sections_info_i].device_id) {
break; // we found the device's section information
}
}
return flash_internal_page;
}
bool flash_internal_read(uint32_t address, uint8_t *buffer, size_t size)
{
// sanity checks
if (buffer == NULL || size == 0) {
return false;
}
if (!flash_internal_range(address, size)) {
return false;
if (flash_sections_info_i >= LENGTH(flash_sections_info)) { // we did not find the device's section information
return NULL;
}
// copy data byte per byte (a more efficient way would be to copy words, than the remaining bytes)
for (size_t i = 0; i < size; i++) {
buffer[i] = *((uint8_t*)address + i);
}
return true;
}
int32_t flash_internal_write(uint32_t address, const uint8_t *buffer, size_t size, bool preserve)
{
// sanity checks
if (buffer == NULL || size == 0 || size % 2) {
return -1;
} else if (address < FLASH_BASE) {
return -2;
} else if (!flash_internal_range(address, size)) {
return -3;
}
uint32_t written = 0; // number of bytes written
flash_unlock(); // unlock flash to be able to write it
while (size) { // write page by page until all data has been written
// verify of we need to erase the flash before writing it
uint32_t page_start = address - (address % flash_internal_page); // get start of the current page
bool erase = false; // verify if we need to erase the page
bool identical = true; // verify if we actually need to write data, or if the data to be written is the identical to the one already if flash
for (uint32_t i = 0; i < size && (address + i) < (page_start + flash_internal_page); i += 2) { // verify if any word in this page needs to be programmed
if (*(uint16_t*)(buffer + i) != (*(uint16_t*)(address + i))) { // verify if the data to be written is identical to the one already written
identical = false;
// in theory writing flash is only about flipping (individual) bits from 1 (erase state) to 0
// in practice the micro-controller will only allow to flip individual bits if the whole half-word is erased (set to 0xffff)
if (*(uint16_t*)(address + i) != 0xffff) { // flash is not erased
erase = true; // we need to erase it for it to be written
break; // no need to check further
}
}
}
if (identical) { // no data needs to be changed
// go to end of page, or size
uint32_t remaining = (page_start + flash_internal_page) - address;
if (remaining > size) {
remaining = size;
}
written += remaining;
buffer += remaining;
address += remaining;
size -= remaining;
} else if (erase && preserve) { // erase before
uint8_t page_data[flash_internal_page]; // a copy of the complete page before the erase it
uint16_t page_i = 0; // index for page data
// copy page before address
for (uint32_t flash = page_start; flash < address && flash < (page_start + flash_internal_page) && page_i <flash_internal_page; flash++) {
page_data[page_i++] = *(uint8_t*)(flash);
}
// copy data starting at address
while (size > 0 && page_i < flash_internal_page) {
page_data[page_i++] = *buffer;
buffer++;
address++;
size--;
}
// copy data after buffer until end of page
while (page_i < flash_internal_page) {
page_data[page_i] = *(uint8_t*)(page_start + page_i);
page_i++;
}
flash_erase_page(page_start); // erase current page
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -6;
}
for (uint16_t i = 0; i < flash_internal_page; i += 2) { // write whole page
if (*((uint16_t*)(page_data + i)) != 0xffff) { // after an erase the bits are set to one, no need to program them
flash_program_half_word(page_start + i, *((uint16_t*)(page_data + i)));
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -7;
}
}
if (*((uint16_t*)(page_data + i)) != *((uint16_t*)(page_start + i))) { // verify the programmed data is right
flash_lock(); // lock back flash to protect it
return -8;
}
written += 2;
}
} else { // simply copy data until end of page (or end of data)
if (erase) {
flash_erase_page(page_start); // erase current page
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -9;
}
}
while (size > 0 && address < (page_start + flash_internal_page)) {
if (*((uint16_t*)(buffer)) != *((uint16_t*)(address)) && *((uint16_t*)(buffer)) != 0xffff) { // only program when data is different and bits need to be set
flash_program_half_word(address, *((uint16_t*)(buffer))); // program the data
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -10;
}
if (*((uint16_t*)address) != *((uint16_t*)buffer)) { // verify the programmed data is right
flash_lock(); // lock back flash to protect it
return -11;
}
}
written += 2;
buffer += 2;
address += 2;
size -= 2;
}
// find in which section this address is
for (uint8_t i = 0; i < flash_sections_info[flash_sections_info_i].number; i++) {
if (address >= flash_sections_info[flash_sections_info_i].sections[i].start && address <= flash_sections_info[flash_sections_info_i].sections[i].end) {
return &flash_sections_info[flash_sections_info_i].sections[i];
}
}
flash_lock(); // lock back flash to protect it
return written;
}
/* the EEPROM allocated area is erased at first
* the EEPROM data starts at the end of the allocated memory
* each time it is written, the next data segment is placed before the existing one
* a data segment start with the size, which help detecting the segment since the data can be the same as erased data (0xffff)
*/
void flash_internal_eeprom_setup(uint16_t pages)
{
if (0 == flash_internal_page || 0 == flash_internal_end) {
flash_internal_init(); // get page size and flash end
}
flash_internal_eeprom_start = 0; // reset start address
flash_internal_eeprom_address = 0; // reset EEPROM address
if (pages > desig_get_flash_size() * 1024 / flash_internal_page) { // not enough pages are available
return;
}
flash_internal_eeprom_start = flash_internal_end - flash_internal_page * pages; // set EEPROM start (page aligned)
// find EEPROM in flash (first non-erased word)
for (flash_internal_eeprom_address = flash_internal_eeprom_start; flash_internal_eeprom_address < flash_internal_end && 0xffff == *(uint16_t*)flash_internal_eeprom_address; flash_internal_eeprom_address += 2);
}
bool flash_internal_eeprom_read(uint8_t *eeprom, uint16_t size)
{
// sanity checks
if (NULL == eeprom || 0 == size || 0xffff == size || 0 == flash_internal_eeprom_start || 0 == flash_internal_eeprom_address) {
return false;
}
if (size + 2U > flash_internal_end - flash_internal_eeprom_start) { // not enough space
return false;
}
if (size + 2U > flash_internal_end - flash_internal_eeprom_address) { // EEPROM size is too large
return false;
}
if (size != *(uint16_t*)flash_internal_eeprom_address) { // check if size match
return false;
}
return flash_internal_read(flash_internal_eeprom_address + 2, eeprom, size); // read data
}
int32_t flash_internal_eeprom_write(const uint8_t *eeprom, uint16_t size)
{
// sanity checks
if (NULL == eeprom || 0 == size || 0xffff == size || 0 == flash_internal_eeprom_start || 0 == flash_internal_eeprom_address) {
return -1;
}
if (size + 2U > flash_internal_end - flash_internal_eeprom_start) { // not enough space
return -2;
}
if (flash_internal_eeprom_start + size + 2U > flash_internal_eeprom_address) { // there is not enough free space
// erase all EEPROM allocated pages
flash_unlock(); // unlock flash to be able to erase it
for (uint32_t page_start = flash_internal_eeprom_start; page_start < flash_internal_end; page_start += flash_internal_page) {
flash_erase_page(page_start); // erase current page
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -3;
}
}
flash_internal_eeprom_address = flash_internal_end; // put address back as the end
}
flash_internal_eeprom_address -= (size + 2U); // get new start of data segment
if (flash_internal_eeprom_address % 2) { // have segment word aligned
flash_internal_eeprom_address--;
}
if (flash_internal_eeprom_address < flash_internal_eeprom_start) { // just to be sure
return -4;
}
int32_t rc = flash_internal_write(flash_internal_eeprom_address, (uint8_t*)&size, 2, false); // write size
if (2 != rc) {
return (-10 + rc);
}
rc = flash_internal_write(flash_internal_eeprom_address + 2, eeprom, size, false); // write data
if (size != rc) {
return (-10 + rc);
}
return rc;
return NULL; // we did not find the section
}
uint32_t flash_internal_probe_read_size(void)
@ -310,55 +191,3 @@ uint32_t flash_internal_probe_read_size(void)
return address - 1 - FLASH_BASE;
}
uint32_t flash_internal_probe_write_size(void)
{
if (0 == desig_get_flash_size()) { // no flash size advertised
return 0;
}
// prepare for reading the flash
cm_disable_faults(); // disable all faults, particularly BusFault
SCB_CFSR |= SCB_CFSR_BFARVALID; // clear bus fault flag
SCB_CCR |= SCB_CCR_BFHFNMIGN; // ignore bus faults (but still flag them)
// prepare for writing the flash
flash_unlock(); // unlock flash to be able to write it
// try reading and writing the flash, page per page
uint32_t start = FLASH_BASE + desig_get_flash_size() * 1024; // start with the end of the advertised flash
if ((uint32_t)&__flash_end >= FLASH_BASE) { // use linker flash size if provided
start = (uint32_t)&__flash_end;
}
uint32_t address = start; // address to test
const uint16_t test_data = 0x2342; // the data we will write and read to test page
while (address < 0x1FFFEFFF) { // this is where the system memory starts
// try reading the flash
(void)*(volatile uint32_t*)address; // access address
if (0 != (SCB_CFSR & SCB_CFSR_BFARVALID)) { // until a bus fault occurs
break; // page not readable
}
// try writing the flash
flash_erase_page(address); // erase current page
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
break;
}
flash_program_half_word(address, test_data); // writes test data
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
break;
}
if (test_data != *((uint16_t*)address)) { // verify data is written correctly
break;
}
flash_erase_page(address); // erase test data
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
break;
}
address += flash_internal_page_size(); // go to next page
}
flash_clear_status_flags(); // clear all flag
flash_lock(); // protect again from writing
SCB_CFSR |= SCB_CFSR_BFARVALID; // clear bus fault flag
SCB_CCR &= ~SCB_CCR_BFHFNMIGN; // re-enable bus fault
cm_enable_faults(); // re-enable faults
return address - start;
}

View File

@ -1,4 +1,4 @@
/** library to read/write internal flash
/** internal flash utilities
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -7,48 +7,33 @@
*/
#pragma once
/** read data from internal flash
* @param[in] address start address of the data to read
* @param[out] buffer where to store the read data
* @param[in] size how much data to read, in bytes
* @return if read succeeded
/** base address of the flash memory
* @note not sure if there is an STM32F4 with another address, or why this is not defined in libopencm3
*/
bool flash_internal_read(uint32_t address, uint8_t *buffer, size_t size);
/** write data to internal flash
* @param[in] address start address where to write data to
* @param[in] buffer data to be written
* @param[in] size how much data to write, in bytes
* @param[in] preserve keep the rest of the page if data needs to be erased
* @return number of bytes written (including preserved data), or negative in case of error
* @note the page will be erased if needed to write the data to the flash
*/
int32_t flash_internal_write(uint32_t address, const uint8_t *buffer, size_t size, bool preserve);
/** get flash page size
* @return flash page size (in bytes)
*/
uint16_t flash_internal_page_size(void);
#define FLASH_BASE (0x08000000U)
/** setup the emulated EEPROM area
* @param[in] pages number of flash pages to allocate for the emulated EEPROM
* @warn area must be at least 4 bytes larger than the structure to write
/** information about a flash section */
struct flash_internal_section_info_s {
const uint8_t number; /**< section number */
const uint8_t size; /**< section size, in KiB */
const uint32_t start; /**< section start address */
const uint32_t end; /**< section end address */
};
/** verify if the data is in the internal flash area
* @param[in] address start address of the data to read
* @param[in] size how much data to read or write, in bytes
* @return if the data is in the internal flash area
*/
void flash_internal_eeprom_setup(uint16_t pages);
/** read emulated EEPROM area
* @param[out] eeprom where to store the EEPROM data
* @param[in] size size of the EEPROM area (in bytes)
bool flash_internal_range(uint32_t address, size_t size);
/** find out in which section is this address
* @param[in] address address to find the section for
* @return section in which this address is (NULL if not in flash, section has not been found, or the sections of this device are unknown)
*/
bool flash_internal_eeprom_read(uint8_t *eeprom, uint16_t size);
/** write emulated EEPROM area
* @param[in] eeprom EEPROM data to be stored
* @param[in] size size of the EEPROM area (in bytes)
* @return number of bytes written (including preserved data), or negative in case of error
*/
int32_t flash_internal_eeprom_write(const uint8_t *eeprom, uint16_t size);
const struct flash_internal_section_info_s* flash_internal_section(uint32_t address);
/** probe the readable size of the internal flash
* @return tested size (in bytes)
*/
uint32_t flash_internal_probe_read_size(void);
/** probe the additional writable size of the internal flash, after the advertised size (and linker provided)
* @return tested size (in bytes)
*/
uint32_t flash_internal_probe_write_size(void);

View File

@ -7,6 +7,7 @@
* @warning all calls are blocking
*/
#pragma once
#error not converted for STM32F4
/** setup communication with SD card
* @return if card has been initialized correctly

View File

@ -1,4 +1,4 @@
/** library to communicate using I²C as master (code)
/** library to communicate using I²C as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -21,275 +21,100 @@
#include "global.h" // global utilities
#include "i2c_master.h" // I²C header and definitions
/** get RCC for I²C based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC address for I²C peripheral
/** @defgroup i2c_master_i2c I²C peripheral used for I²C communication
* @{
*/
static uint32_t RCC_I2C(uint32_t i2c)
#define I2C_MASTER_I2C 1 /**< I²C peripheral ID */
#define I2C_MASTER_SCL PB6 /**< GPIO pin for I²C SCL */
#define I2C_MASTER_SDA PB7 /**< GPIO pin for I²C SDA */
#define I2C_MASTER_AF GPIO_AF4 /**< alternate function for GPIO pin */
/** @} */
void i2c_master_setup(uint16_t frequency)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
return RCC_I2C1;
break;
case I2C2:
return RCC_I2C2;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_BANK_I2C1_RE_SCL;
} else {
return GPIO_BANK_I2C1_SCL;
}
break;
case I2C2:
return GPIO_BANK_I2C2_SCL;
break;
default:
return 0;
}
}
/** get GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_BANK_I2C1_RE_SDA;
} else {
return GPIO_BANK_I2C1_SDA;
}
break;
case I2C2:
return GPIO_BANK_I2C2_SDA;
break;
default:
return 0;
}
}
/** get GPIO pin for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_I2C1_RE_SCL;
} else {
return GPIO_I2C1_SCL;
}
break;
case I2C2:
return GPIO_I2C2_SCL;
break;
default:
return 0;
}
}
/** get GPIO pin for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_I2C1_RE_SDA;
} else {
return GPIO_I2C1_SDA;
}
break;
case I2C2:
return GPIO_I2C2_SDA;
break;
default:
return 0;
}
}
void i2c_master_setup(uint32_t i2c, uint16_t frequency)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// configure I²C peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // already put signal high to avoid small pulse
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // already put signal high to avoid small pulse
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I²C peripheral
i2c_reset(i2c); // reset peripheral domain
i2c_peripheral_disable(i2c); // I²C needs to be disable to be configured
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset peripheral
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // clear peripheral reset
if (0==frequency) { // don't allow null frequency
rcc_periph_clock_enable(GPIO_RCC(I2C_MASTER_SCL)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT(I2C_MASTER_SCL), GPIO_PIN(I2C_MASTER_SCL)); // already put signal high to avoid small pulse
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SCL), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(I2C_MASTER_SCL)); // set SCL pin to alternate function
gpio_set_output_options(GPIO_PORT(I2C_MASTER_SCL), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(I2C_MASTER_SCL)); // set SCL pin output as open-drain
gpio_set_af(GPIO_PORT(I2C_MASTER_SCL), I2C_MASTER_AF, GPIO_PIN(I2C_MASTER_SCL)); // set alternate function to I²C SCL pin
rcc_periph_clock_enable(GPIO_RCC(I2C_MASTER_SDA)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT(I2C_MASTER_SDA), GPIO_PIN(I2C_MASTER_SDA)); // already put signal high to avoid small pulse
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SDA), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(I2C_MASTER_SDA)); // set SDA pin to alternate function
gpio_set_output_options(GPIO_PORT(I2C_MASTER_SDA), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(I2C_MASTER_SDA)); // set SDA pin output as open-drain
gpio_set_af(GPIO_PORT(I2C_MASTER_SDA), I2C_MASTER_AF, GPIO_PIN(I2C_MASTER_SDA)); // set alternate function to I²C SDA pin
rcc_periph_clock_enable(RCC_I2C(I2C_MASTER_I2C)); // enable clock for I²C peripheral
i2c_reset(I2C(I2C_MASTER_I2C)); // reset peripheral domain
i2c_peripheral_disable(I2C(I2C_MASTER_I2C)); // I²C needs to be disable to be configured
I2C_CR1(I2C(I2C_MASTER_I2C)) |= I2C_CR1_SWRST; // reset peripheral
I2C_CR1(I2C(I2C_MASTER_I2C)) &= ~I2C_CR1_SWRST; // clear peripheral reset
if (0 == frequency) { // don't allow null frequency
frequency = 1;
} else if (frequency > 400) { // limit frequency to 400 kHz
frequency = 400;
}
i2c_set_clock_frequency(i2c, rcc_apb1_frequency / 1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
if (frequency>100) { // use fast mode for frequencies over 100 kHz
i2c_set_fast_mode(i2c); // set fast mode (Fm)
i2c_set_ccr(i2c, rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency (fast duty not used)
i2c_set_trise(i2c, (300 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Fm mode (< 400) kHz is 300 ns
i2c_set_clock_frequency(I2C(I2C_MASTER_I2C), rcc_apb1_frequency / 1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
if (frequency > 100) { // use fast mode for frequencies over 100 kHz
i2c_set_fast_mode(I2C(I2C_MASTER_I2C)); // set fast mode (Fm)
i2c_set_ccr(I2C(I2C_MASTER_I2C), rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency (fast duty not used)
i2c_set_trise(I2C(I2C_MASTER_I2C), (300 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Fm mode (< 400) kHz is 300 ns
} else { // use fast mode for frequencies below 100 kHz
i2c_set_standard_mode(i2c); // set standard mode (Sm)
i2c_set_ccr(i2c, rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency of 100 kHz
i2c_set_trise(i2c, (1000 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
i2c_set_standard_mode(I2C(I2C_MASTER_I2C)); // set standard mode (Sm)
i2c_set_ccr(I2C(I2C_MASTER_I2C), rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency of 100 kHz
i2c_set_trise(I2C(I2C_MASTER_I2C), (1000 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
}
i2c_peripheral_enable(i2c); // enable I²C after configuration completed
i2c_peripheral_enable(I2C(I2C_MASTER_I2C)); // enable I²C after configuration completed
}
void i2c_master_release(uint32_t i2c)
void i2c_master_release(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
i2c_reset(i2c); // reset I²C peripheral configuration
i2c_peripheral_disable(i2c); // disable I²C peripheral
rcc_periph_clock_disable(RCC_I2C(i2c)); // disable clock for I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SCL(i2c)); // put I²C I/O pins back to floating
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SDA(i2c)); // put I²C I/O pins back to floating
i2c_reset(I2C(I2C_MASTER_I2C)); // reset I²C peripheral configuration
i2c_peripheral_disable(I2C(I2C_MASTER_I2C)); // disable I²C peripheral
rcc_periph_clock_disable(RCC_I2C(I2C_MASTER_I2C)); // disable clock for I²C peripheral
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SCL), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(I2C_MASTER_SCL)); // set SCL pin back to input
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SDA), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(I2C_MASTER_SDA)); // set SDA pin back to input
}
bool i2c_master_check_signals(uint32_t i2c)
bool i2c_master_check_signals(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// enable GPIOs to read SDA and SCL
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C I/O peripheral
rcc_periph_clock_enable(GPIO_RCC(I2C_MASTER_SDA)); // enable clock for I²C I/O peripheral
rcc_periph_clock_enable(GPIO_RCC(I2C_MASTER_SCL)); // enable clock for I²C I/O peripheral
// pull SDA and SDC low to check if there are pull-up resistors
uint32_t sda_crl = GPIO_CRL(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_crh = GPIO_CRH(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_bsrr = GPIO_BSRR(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t scl_crl = GPIO_CRL(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_crh = GPIO_CRH(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_bsrr = GPIO_BSRR(GPIO_PORT_SCL(i2c)); // backup port configuration
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SDA(i2c)); // configure signal as pull down
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SCL(i2c)); // configure signal as pull down
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // pull down
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // pull down
bool to_return = (0 != gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) && 0 != gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // check if the signals are still pulled high by external stronger pull-up resistors
GPIO_CRL(GPIO_PORT_SDA(i2c)) = sda_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SDA(i2c)) = sda_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SDA(i2c)) = sda_bsrr; // restore port configuration
GPIO_CRL(GPIO_PORT_SCL(i2c)) = scl_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SCL(i2c)) = scl_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SCL(i2c)) = scl_bsrr; // restore port configuration
const uint32_t sda_moder = GPIO_MODER(GPIO_PORT(I2C_MASTER_SDA)); // backup port configuration
const uint32_t sda_pupdr = GPIO_PUPDR(GPIO_PORT(I2C_MASTER_SDA)); // backup port configuration
const uint32_t scl_moder = GPIO_MODER(GPIO_PORT(I2C_MASTER_SCL)); // backup port configuration
const uint32_t scl_pupdr = GPIO_PUPDR(GPIO_PORT(I2C_MASTER_SCL)); // backup port configuration
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SDA), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(I2C_MASTER_SDA)); // set SDA to input and pull down (weak) to check if there is an external pull up (strong)
gpio_mode_setup(GPIO_PORT(I2C_MASTER_SCL), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(I2C_MASTER_SCL)); // set SCL to input and pull down (weak) to check if there is an external pull up (strong)
sleep_us(100); // let signal settle
const bool to_return = (gpio_get(GPIO_PORT(I2C_MASTER_SCL), GPIO_PIN(I2C_MASTER_SCL)) && gpio_get(GPIO_PORT(I2C_MASTER_SDA), GPIO_PIN(I2C_MASTER_SDA))); // check if the signals are still pulled high by external stronger pull-up resistors
GPIO_MODER(GPIO_PORT(I2C_MASTER_SDA)) = sda_moder; // restore port configuration
GPIO_PUPDR(GPIO_PORT(I2C_MASTER_SDA)) = sda_pupdr; // restore port configuration
GPIO_MODER(GPIO_PORT(I2C_MASTER_SCL)) = scl_moder; // restore port configuration
GPIO_PUPDR(GPIO_PORT(I2C_MASTER_SCL)) = scl_pupdr; // restore port configuration
return to_return;
}
bool i2c_master_reset(uint32_t i2c)
void i2c_master_reset(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool to_return = true;
// follow procedure described in STM32F10xxC/D/E Errata sheet, Section 2.14.7
i2c_peripheral_disable(i2c); // disable I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SCL(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SDA(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is low
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is low
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // set I²C I/O pins back
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I²C I/O pins back
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset device
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(i2c); // re-enable device
return to_return;
i2c_peripheral_disable(I2C(I2C_MASTER_I2C)); // disable I²C peripheral
I2C_CR1(I2C(I2C_MASTER_I2C)) |= I2C_CR1_SWRST; // reset device
I2C_CR1(I2C(I2C_MASTER_I2C)) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(I2C(I2C_MASTER_I2C)); // re-enable device
}
enum i2c_master_rc i2c_master_start(uint32_t i2c)
enum i2c_master_rc i2c_master_start(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool retry = true; // retry after reset if first try failed
enum i2c_master_rc to_return; // return code
uint16_t sr1; // read register once, since reading/writing other registers or other events clears some flags
try:
to_return = I2C_MASTER_RC_NONE; // return code
// send (re-)start condition
if (I2C_CR1(i2c) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
if (I2C_CR1(I2C(I2C_MASTER_I2C)) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of I²C peripheral in master mode after a misplaced Stop)
@ -298,20 +123,20 @@ try:
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
i2c_send_start(i2c); // send start condition to start transaction
i2c_send_start(I2C(I2C_MASTER_I2C)); // send start condition to start transaction
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(i2c) & I2C_CR1_START) && !((sr1 = I2C_SR1(i2c)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until start condition has been accepted and cleared
while ((I2C_CR1(I2C(I2C_MASTER_I2C)) & I2C_CR1_START) && !((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until start condition has been accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(i2c); // be sure to get the current value
sr1 = I2C_SR1(I2C(I2C_MASTER_I2C)); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_SB | I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout && I2C_MASTER_RC_NONE == to_return) { // wait until start condition is transmitted
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_SB | I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout && I2C_MASTER_RC_NONE == to_return) { // wait until start condition is transmitted
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(i2c); // be sure to get the current value
sr1 = I2C_SR1(I2C(I2C_MASTER_I2C)); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
} else if (!(sr1 & I2C_SR1_SB)) { // the start bit has not been set although we the peripheral is not busy anymore
@ -324,8 +149,8 @@ try:
if (I2C_MASTER_RC_NOT_MASTER == to_return && retry) { // error happened
retry = false; // don't retry a second time
I2C_CR1(i2c) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // release peripheral reset
I2C_CR1(I2C(I2C_MASTER_I2C)) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(I2C(I2C_MASTER_I2C)) &= ~I2C_CR1_SWRST; // release peripheral reset
goto try;
}
systick_counter_disable(); // we don't need to timer anymore
@ -333,13 +158,10 @@ try:
}
/** wait until stop is sent and bus is released
* @param[in] i2c I²C base address
* @return I²C return code
*/
static enum i2c_master_rc i2c_master_wait_stop(uint32_t i2c)
static enum i2c_master_rc i2c_master_wait_stop(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc to_return = I2C_MASTER_RC_NONE; // return code
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of I²C peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
@ -349,25 +171,25 @@ static enum i2c_master_rc i2c_master_wait_stop(uint32_t i2c)
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(i2c) & I2C_CR1_STOP) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until stop condition is accepted and cleared
while ((I2C_CR1(I2C(I2C_MASTER_I2C)) & I2C_CR1_STOP) && !(I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until stop condition is accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
if (I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(i2c) & I2C_SR2_MSL) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until bus released (non master mode)
while ((I2C_SR2(I2C(I2C_MASTER_I2C)) & I2C_SR2_MSL) && !(I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until bus released (non master mode)
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
if (I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(i2c) & I2C_SR2_BUSY) && !(I2C_SR1(i2c) & (I2C_SR1_BERR)) && !timeout) { // wait until peripheral is not busy anymore
while ((I2C_SR2(I2C(I2C_MASTER_I2C)) & I2C_SR2_BUSY) && !(I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR)) && !timeout) { // wait until peripheral is not busy anymore
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
if (I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((0 == gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) || 0 == gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))) && !timeout) { // wait until lines are really high again
while ((0 == gpio_get(GPIO_PORT(I2C_MASTER_SCL), GPIO_PIN(I2C_MASTER_SCL)) || 0 == gpio_get(GPIO_PORT(I2C_MASTER_SDA), GPIO_PIN(I2C_MASTER_SDA))) && !timeout) { // wait until lines are really high again
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
@ -375,55 +197,53 @@ static enum i2c_master_rc i2c_master_wait_stop(uint32_t i2c)
if (I2C_MASTER_RC_NONE == to_return) {
to_return = I2C_MASTER_RC_TIMEOUT; // indicate timeout only when no more specific error has occurred
}
I2C_CR1(i2c) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // release peripheral reset
I2C_CR1(I2C(I2C_MASTER_I2C)) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(I2C(I2C_MASTER_I2C)) &= ~I2C_CR1_SWRST; // release peripheral reset
}
systick_counter_disable(); // we don't need to timer anymore
return to_return;
}
enum i2c_master_rc i2c_master_stop(uint32_t i2c)
enum i2c_master_rc i2c_master_stop(void)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (!(I2C_SR2(i2c) & I2C_SR2_BUSY)) { // release is not busy
if (!(I2C_SR2(I2C(I2C_MASTER_I2C)) & I2C_SR2_BUSY)) { // release is not busy
return I2C_MASTER_RC_NONE; // bus has probably already been released
}
if (I2C_CR1(i2c) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
if (I2C_CR1(I2C(I2C_MASTER_I2C)) & (I2C_CR1_START)) { // ensure start is not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
if (!((I2C_SR2(i2c) & I2C_SR2_TRA))) { // if we are in receiver mode
i2c_disable_ack(i2c); // disable ACK to be able to close the communication
if (!((I2C_SR2(I2C(I2C_MASTER_I2C)) & I2C_SR2_TRA))) { // if we are in receiver mode
i2c_disable_ack(I2C(I2C_MASTER_I2C)); // disable ACK to be able to close the communication
}
i2c_send_stop(i2c); // send stop to release bus
return i2c_master_wait_stop(i2c);
if (!(I2C_CR1(I2C(I2C_MASTER_I2C)) & (I2C_CR1_STOP))) { // ensure stop operations is not in progress
i2c_send_stop(I2C(I2C_MASTER_I2C)); // send stop to release bus
}
return i2c_master_wait_stop();
}
enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool address_10bit, bool write)
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
uint16_t sr1, sr2; // read register once, since reading/writing other registers or other events clears some flags
if (!((sr1 = I2C_SR1(i2c)) & I2C_SR1_SB)) { // start condition has not been sent
rc = i2c_master_start(i2c); // send start condition
if (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & I2C_SR1_SB)) { // start condition has not been sent
rc = i2c_master_start(); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
}
if (!((sr2 = I2C_SR2(i2c)) & I2C_SR2_MSL)) { // I²C device is not in master mode
if (!((sr2 = I2C_SR2(I2C(I2C_MASTER_I2C))) & I2C_SR2_MSL)) { // I²C device is not in master mode
return I2C_MASTER_RC_NOT_MASTER;
}
// select slave
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_SR1(I2C(I2C_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
if (!address_10bit) { // 7-bit address
i2c_send_7bit_address(i2c, slave, write ? I2C_WRITE : I2C_READ); // select slave, with read/write flag
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until address is transmitted
i2c_send_7bit_address(I2C(I2C_MASTER_I2C), slave, write ? I2C_WRITE : I2C_READ); // select slave, with read/write flag
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
@ -432,8 +252,8 @@ enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool ad
}
} else { // 10-bit address
// send first part of address
I2C_DR(i2c) = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADD10 | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until first part of address is transmitted
I2C_DR(I2C(I2C_MASTER_I2C)) = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_ADD10 | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until first part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
@ -441,9 +261,9 @@ enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool ad
return I2C_MASTER_RC_NAK;
}
// send second part of address
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(i2c) = (slave & 0xff); // send remaining of address
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
I2C_SR1(I2C(I2C_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(I2C(I2C_MASTER_I2C)) = (slave & 0xff); // send remaining of address
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
@ -452,14 +272,14 @@ enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool ad
}
// go into receive mode if necessary
if (!write) {
rc = i2c_master_start(i2c); // send start condition
rc = i2c_master_start(); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
// send first part of address with receive flag
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(i2c) = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
I2C_SR1(I2C(I2C_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(I2C(I2C_MASTER_I2C)) = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
@ -472,17 +292,15 @@ enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool ad
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_read(uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (NULL == data || 0 == data_size) { // no data to read
return I2C_MASTER_RC_NONE;
}
// I²C start condition check
uint16_t sr1 = I2C_SR1(i2c); // read once
uint16_t sr1 = I2C_SR1(I2C(I2C_MASTER_I2C)); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return I2C_MASTER_RC_NOT_READY;
}
@ -492,11 +310,11 @@ enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size
// prepare (N)ACK (EV6_3 in RM0008)
if (1 == data_size) {
i2c_disable_ack(i2c); // NACK after first byte
i2c_disable_ack(I2C(I2C_MASTER_I2C)); // NACK after first byte
} else {
i2c_enable_ack(i2c); // NAK after next byte
i2c_enable_ack(I2C(I2C_MASTER_I2C)); // NAK after next byte
}
uint16_t sr2 = I2C_SR2(i2c); // reading SR2 will also also clear ADDR in SR1 and start the transaction
uint16_t sr2 = I2C_SR2(I2C(I2C_MASTER_I2C)); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // I²C device is not master
return I2C_MASTER_RC_NOT_MASTER;
}
@ -508,36 +326,34 @@ enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size
for (size_t i = 0; i < data_size; i++) { // read bytes
// set (N)ACK (EV6_3, EV6_1)
if (1 == data_size - i) { // prepare to sent NACK for last byte
i2c_send_stop(i2c); // already indicate we will send a stop (required to not send an ACK, and this must happen before the byte is transferred, see errata)
i2c_nack_current(i2c); // (N)ACK current byte
i2c_disable_ack(i2c); // NACK received to stop slave transmission
i2c_send_stop(I2C(I2C_MASTER_I2C)); // already indicate we will send a stop (required to not send an ACK, and this must happen before the byte is transferred, see errata)
i2c_nack_current(I2C(I2C_MASTER_I2C)); // (N)ACK current byte
i2c_disable_ack(I2C(I2C_MASTER_I2C)); // NACK received to stop slave transmission
} else if (2 == data_size - i) { // prepare to sent NACK for second last byte
i2c_nack_next(i2c); // NACK next byte
i2c_disable_ack(i2c); // NACK received to stop slave transmission
i2c_nack_next(I2C(I2C_MASTER_I2C)); // NACK next byte
i2c_disable_ack(I2C(I2C_MASTER_I2C)); // NACK received to stop slave transmission
} else {
i2c_enable_ack(i2c); // ACK received byte to continue slave transmission
i2c_enable_ack(I2C(I2C_MASTER_I2C)); // ACK received byte to continue slave transmission
}
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_RxNE | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been received
while (!((sr1 = I2C_SR1(I2C(I2C_MASTER_I2C))) & (I2C_SR1_RxNE | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been received
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
data[i] = i2c_get_data(i2c); // read received byte
data[i] = i2c_get_data(I2C(I2C_MASTER_I2C)); // read received byte
}
return i2c_master_wait_stop(i2c);
return i2c_master_wait_stop();
}
enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_write(const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (NULL == data || 0 == data_size) { // no data to write
return I2C_MASTER_RC_NONE;
}
// I²C start condition check
uint16_t sr1 = I2C_SR1(i2c); // read once
uint16_t sr1 = I2C_SR1(I2C(I2C_MASTER_I2C)); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return I2C_MASTER_RC_NOT_READY;
}
@ -546,7 +362,7 @@ enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t da
}
// master check
uint16_t sr2 = I2C_SR2(i2c); // reading SR2 will also also clear ADDR in SR1 and start the transaction
uint16_t sr2 = I2C_SR2(I2C(I2C_MASTER_I2C)); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // I²C device is not master
return I2C_MASTER_RC_NOT_MASTER;
}
@ -556,13 +372,13 @@ enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t da
// write data
for (size_t i = 0; i < data_size; i++) { // write bytes
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
i2c_send_data(i2c, data[i]); // send byte to be written in memory
while (!(I2C_SR1(i2c) & (I2C_SR1_TxE | I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been transmitted
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
I2C_SR1(I2C(I2C_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
i2c_send_data(I2C(I2C_MASTER_I2C), data[i]); // send byte to be written in memory
while (!(I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_TxE | I2C_SR1_AF)) && !(I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been transmitted
if (I2C_SR1(I2C(I2C_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (I2C_SR1(i2c) & I2C_SR1_AF) { // data has not been acknowledged
if (I2C_SR1(I2C(I2C_MASTER_I2C)) & I2C_SR1_AF) { // data has not been acknowledged
return I2C_MASTER_RC_NAK;
}
}
@ -570,51 +386,47 @@ enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t da
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, false); // select slave to read
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
if (NULL != data && data_size > 0) { // only read data if needed
rc = i2c_master_read(i2c, data, data_size); // read data (includes stop)
rc = i2c_master_read(data, data_size); // read data (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(i2c); // sent stop condition
i2c_master_stop(); // sent stop condition
}
rc = I2C_MASTER_RC_NONE; // all went well
error:
if (I2C_MASTER_RC_NONE != rc) {
i2c_master_stop(i2c); // sent stop condition
i2c_master_stop(); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
if (NULL != data && data_size > 0) { // write data only is some is available
rc = i2c_master_write(i2c, data, data_size); // write data
rc = i2c_master_write(data, data_size); // write data
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
@ -622,60 +434,57 @@ enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool add
rc = I2C_MASTER_RC_NONE; // all went well
error:
i2c_master_stop(i2c); // sent stop condition
i2c_master_stop(); // sent stop condition
return rc;
}
enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
// write address
if (NULL != address && address_size > 0) {
rc = i2c_master_write(i2c, address, address_size); // send memory address
rc = i2c_master_write(address, address_size); // send memory address
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
}
// read data
if (NULL != data && data_size > 0) {
rc = i2c_master_start(i2c); // send re-start condition
rc = i2c_master_start(); // send re-start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, false); // select slave to read
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
rc = i2c_master_read(i2c, data, data_size); // read memory (includes stop)
rc = i2c_master_read(data, data_size); // read memory (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(i2c); // sent stop condition
i2c_master_stop(); // sent stop condition
}
rc = I2C_MASTER_RC_NONE;
error:
if (I2C_MASTER_RC_NONE != rc) { // only send stop on error
i2c_master_stop(i2c); // sent stop condition
i2c_master_stop(); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size)
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
if (SIZE_MAX - address_size < data_size) { // prevent integer overflow
return I2C_MASTER_RC_OTHER;
}
@ -691,11 +500,11 @@ enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool a
uint8_t buffer[address_size + data_size];
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
@ -711,12 +520,12 @@ enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool a
buffer[address_size + i] = data[i];
}
}
rc = i2c_master_write(i2c, buffer, address_size + data_size); // send memory address
rc = i2c_master_write(buffer, address_size + data_size); // send memory address
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
error:
rc = i2c_master_stop(i2c); // sent stop condition
rc = i2c_master_stop(); // sent stop condition
return rc;
}

View File

@ -3,7 +3,7 @@
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: I2C
* @note peripherals used: I²C @ref i2c_master_i2c
*/
#pragma once
@ -22,66 +22,55 @@ enum i2c_master_rc {
};
/** setup I²C peripheral
* @param[in] i2c I²C base address
* @param[in] frequency frequency to use in kHz (1-400)
* @note Standard mode (Sm) is used for frequencies up to 100 kHz, and Fast mode (Fm) is used for frequencies up to 400 kHz
*/
void i2c_master_setup(uint32_t i2c, uint16_t frequency);
void i2c_master_setup(uint16_t frequency);
/** release I²C peripheral
* @param[in] i2c I²C base address
*/
void i2c_master_release(uint32_t i2c);
void i2c_master_release(void);
/** reset I2C peripheral, fixing any locked state
* @warning the I2C peripheral needs to be re-setup
* @note to be used after failed start or stop, and bus error
* @param[in] i2c I2C base address
* @return true if complete reset is successful, false if the lines could not be set
*/
bool i2c_master_reset(uint32_t i2c);
/** check if SDA and SCL signals are high
* @param[in] i2c I²C base address
* @return SDA and SCL signals are high
void i2c_master_reset(void);
/** check if SDA and SCL signals are pulled up externally
* @return SDA and SCL signals are pulled up externally
*/
bool i2c_master_check_signals(uint32_t i2c);
bool i2c_master_check_signals(void);
/** send start condition
* @param[in] i2c I²C base address
* @return I2C return code
*/
enum i2c_master_rc i2c_master_start(uint32_t i2c);
enum i2c_master_rc i2c_master_start(void);
/** select I²C slave device
* @warning a start condition should be sent before this operation
* @param[in] i2c I²C base address
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
* @return I²C return code
*/
enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool address_10bit, bool write);
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write);
/** read data over I²C
* @param[in] i2c I²C base address
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return I²C return code
* @warning the slave device must be selected before this operation
* @note a stop condition will be sent at the end (I²C does not permit multiple reads, and this is necessary for 1-byte transfer)
*/
enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_read(uint8_t* data, size_t data_size);
/** write data over I²C
* @param[in] i2c I²C base address
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return I²C return code
* @warning the slave device must be selected before this operation
* @note no stop condition is sent at the end, allowing multiple writes
*/
enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_write(const uint8_t* data, size_t data_size);
/** sent stop condition
* @param[in] i2c I²C base address
* @return I²C return code
*/
enum i2c_master_rc i2c_master_stop(uint32_t i2c);
enum i2c_master_rc i2c_master_stop(void);
/** read data from slave device
* @param[in] i2c I²C base address
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[out] data array to store bytes read
@ -89,9 +78,8 @@ enum i2c_master_rc i2c_master_stop(uint32_t i2c);
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size);
/** write data to slave device
* @param[in] i2c I²C base address
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] data array of byte to write to slave
@ -99,9 +87,8 @@ enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool addr
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size);
/** read data at specific address from an I²C memory slave
* @param[in] i2c I²C base address
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] address memory address of slave to read from
@ -111,9 +98,8 @@ enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool add
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size);
/** write data at specific address on an I²C memory slave
* @param[in] i2c I²C base address
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] address memory address of slave to write to
@ -123,4 +109,4 @@ enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool ad
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size);
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size);

View File

@ -1,8 +1,8 @@
/** BusVoodoo runtime interrupt table
/** runtime interrupt table
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018
* @date 2018-2020
*/
#include "interrupt.h" // own definitions
@ -14,11 +14,11 @@ static void isr_handler(void)
// get current IRQ number
uint8_t irq = 0;
if (NVIC_IABR(0)) {
irq = __builtin_ffs(NVIC_IABR(0))-1;
irq = __builtin_ffs(NVIC_IABR(0)) - 1;
} else if (NVIC_IABR(1)) {
irq = __builtin_ffs(NVIC_IABR(1))+31;
irq = __builtin_ffs(NVIC_IABR(1)) + 31;
} else if (NVIC_IABR(2)) {
irq = __builtin_ffs(NVIC_IABR(2))+64;
irq = __builtin_ffs(NVIC_IABR(2)) + 64;
} else {
while (true);
}
@ -36,73 +36,96 @@ static void isr_handler(void)
/** use the isr_handler as default ISR
* @note the ISR can still point to other defined function
* @remark from libopencm3/stm32/f1/vector_nvic.c
* @remark from libopencm3/lib/stm32/f4/vector_nvic.c
*/
#pragma weak wwdg_isr = isr_handler
#pragma weak pvd_isr = isr_handler
#pragma weak tamper_isr = isr_handler
#pragma weak rtc_isr = isr_handler
#pragma weak flash_isr = isr_handler
#pragma weak rcc_isr = isr_handler
#pragma weak exti0_isr = isr_handler
#pragma weak exti1_isr = isr_handler
#pragma weak exti2_isr = isr_handler
#pragma weak exti3_isr = isr_handler
#pragma weak exti4_isr = isr_handler
#pragma weak dma1_channel1_isr = isr_handler
#pragma weak dma1_channel2_isr = isr_handler
#pragma weak dma1_channel3_isr = isr_handler
#pragma weak dma1_channel4_isr = isr_handler
#pragma weak dma1_channel5_isr = isr_handler
#pragma weak dma1_channel6_isr = isr_handler
#pragma weak dma1_channel7_isr = isr_handler
#pragma weak adc1_2_isr = isr_handler
#pragma weak usb_hp_can_tx_isr = isr_handler
#pragma weak usb_lp_can_rx0_isr = isr_handler
#pragma weak can_rx1_isr = isr_handler
#pragma weak can_sce_isr = isr_handler
#pragma weak exti9_5_isr = isr_handler
#pragma weak tim1_brk_isr = isr_handler
#pragma weak tim1_up_isr = isr_handler
#pragma weak tim1_trg_com_isr = isr_handler
#pragma weak tim1_cc_isr = isr_handler
#pragma weak tim2_isr = isr_handler
#pragma weak tim3_isr = isr_handler
#pragma weak tim4_isr = isr_handler
#pragma weak i2c1_ev_isr = isr_handler
#pragma weak i2c1_er_isr = isr_handler
#pragma weak i2c2_ev_isr = isr_handler
#pragma weak i2c2_er_isr = isr_handler
#pragma weak spi1_isr = isr_handler
#pragma weak spi2_isr = isr_handler
#pragma weak usart1_isr = isr_handler
#pragma weak usart2_isr = isr_handler
#pragma weak usart3_isr = isr_handler
#pragma weak exti15_10_isr = isr_handler
#pragma weak rtc_alarm_isr = isr_handler
#pragma weak usb_wakeup_isr = isr_handler
#pragma weak tim8_brk_isr = isr_handler
#pragma weak tim8_up_isr = isr_handler
#pragma weak tim8_trg_com_isr = isr_handler
#pragma weak tim8_cc_isr = isr_handler
#pragma weak adc3_isr = isr_handler
#pragma weak fsmc_isr = isr_handler
#pragma weak sdio_isr = isr_handler
#pragma weak tim5_isr = isr_handler
#pragma weak spi3_isr = isr_handler
#pragma weak uart4_isr = isr_handler
#pragma weak uart5_isr = isr_handler
#pragma weak tim6_isr = isr_handler
#pragma weak tim7_isr = isr_handler
#pragma weak dma2_channel1_isr = isr_handler
#pragma weak dma2_channel2_isr = isr_handler
#pragma weak dma2_channel3_isr = isr_handler
#pragma weak dma2_channel4_5_isr = isr_handler
#pragma weak dma2_channel5_isr = isr_handler
#pragma weak eth_isr = isr_handler
#pragma weak eth_wkup_isr = isr_handler
#pragma weak can2_tx_isr = isr_handler
#pragma weak can2_rx0_isr = isr_handler
#pragma weak can2_rx1_isr = isr_handler
#pragma weak can2_sce_isr = isr_handler
#pragma weak otg_fs_isr = isr_handler
void nvic_wwdg_isr(void) __attribute__((weak, alias("isr_handler")));
void pvd_isr(void) __attribute__((weak, alias("isr_handler")));
void tamp_stamp_isr(void) __attribute__((weak, alias("isr_handler")));
void rtc_wkup_isr(void) __attribute__((weak, alias("isr_handler")));
void flash_isr(void) __attribute__((weak, alias("isr_handler")));
void rcc_isr(void) __attribute__((weak, alias("isr_handler")));
void exti0_isr(void) __attribute__((weak, alias("isr_handler")));
void exti1_isr(void) __attribute__((weak, alias("isr_handler")));
void exti2_isr(void) __attribute__((weak, alias("isr_handler")));
void exti3_isr(void) __attribute__((weak, alias("isr_handler")));
void exti4_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream0_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream1_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream2_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream3_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream4_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream5_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream6_isr(void) __attribute__((weak, alias("isr_handler")));
void adc_isr(void) __attribute__((weak, alias("isr_handler")));
void can1_tx_isr(void) __attribute__((weak, alias("isr_handler")));
void can1_rx0_isr(void) __attribute__((weak, alias("isr_handler")));
void can1_rx1_isr(void) __attribute__((weak, alias("isr_handler")));
void can1_sce_isr(void) __attribute__((weak, alias("isr_handler")));
void exti9_5_isr(void) __attribute__((weak, alias("isr_handler")));
void tim1_brk_tim9_isr(void) __attribute__((weak, alias("isr_handler")));
void tim1_up_tim10_isr(void) __attribute__((weak, alias("isr_handler")));
void tim1_trg_com_tim11_isr(void) __attribute__((weak, alias("isr_handler")));
void tim1_cc_isr(void) __attribute__((weak, alias("isr_handler")));
void tim2_isr(void) __attribute__((weak, alias("isr_handler")));
void tim3_isr(void) __attribute__((weak, alias("isr_handler")));
void tim4_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c1_ev_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c1_er_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c2_ev_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c2_er_isr(void) __attribute__((weak, alias("isr_handler")));
void spi1_isr(void) __attribute__((weak, alias("isr_handler")));
void spi2_isr(void) __attribute__((weak, alias("isr_handler")));
void usart1_isr(void) __attribute__((weak, alias("isr_handler")));
void usart2_isr(void) __attribute__((weak, alias("isr_handler")));
void usart3_isr(void) __attribute__((weak, alias("isr_handler")));
void exti15_10_isr(void) __attribute__((weak, alias("isr_handler")));
void rtc_alarm_isr(void) __attribute__((weak, alias("isr_handler")));
void usb_fs_wkup_isr(void) __attribute__((weak, alias("isr_handler")));
void tim8_brk_tim12_isr(void) __attribute__((weak, alias("isr_handler")));
void tim8_up_tim13_isr(void) __attribute__((weak, alias("isr_handler")));
void tim8_trg_com_tim14_isr(void) __attribute__((weak, alias("isr_handler")));
void tim8_cc_isr(void) __attribute__((weak, alias("isr_handler")));
void dma1_stream7_isr(void) __attribute__((weak, alias("isr_handler")));
void fsmc_isr(void) __attribute__((weak, alias("isr_handler")));
void sdio_isr(void) __attribute__((weak, alias("isr_handler")));
void tim5_isr(void) __attribute__((weak, alias("isr_handler")));
void spi3_isr(void) __attribute__((weak, alias("isr_handler")));
void uart4_isr(void) __attribute__((weak, alias("isr_handler")));
void uart5_isr(void) __attribute__((weak, alias("isr_handler")));
void tim6_dac_isr(void) __attribute__((weak, alias("isr_handler")));
void tim7_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream0_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream1_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream2_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream3_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream4_isr(void) __attribute__((weak, alias("isr_handler")));
void eth_isr(void) __attribute__((weak, alias("isr_handler")));
void eth_wkup_isr(void) __attribute__((weak, alias("isr_handler")));
void can2_tx_isr(void) __attribute__((weak, alias("isr_handler")));
void can2_rx0_isr(void) __attribute__((weak, alias("isr_handler")));
void can2_rx1_isr(void) __attribute__((weak, alias("isr_handler")));
void can2_sce_isr(void) __attribute__((weak, alias("isr_handler")));
void otg_fs_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream5_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream6_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2_stream7_isr(void) __attribute__((weak, alias("isr_handler")));
void usart6_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c3_ev_isr(void) __attribute__((weak, alias("isr_handler")));
void i2c3_er_isr(void) __attribute__((weak, alias("isr_handler")));
void otg_hs_ep1_out_isr(void) __attribute__((weak, alias("isr_handler")));
void otg_hs_ep1_in_isr(void) __attribute__((weak, alias("isr_handler")));
void otg_hs_wkup_isr(void) __attribute__((weak, alias("isr_handler")));
void otg_hs_isr(void) __attribute__((weak, alias("isr_handler")));
void dcmi_isr(void) __attribute__((weak, alias("isr_handler")));
void cryp_isr(void) __attribute__((weak, alias("isr_handler")));
void hash_rng_isr(void) __attribute__((weak, alias("isr_handler")));
void fpu_isr(void) __attribute__((weak, alias("isr_handler")));
void uart7_isr(void) __attribute__((weak, alias("isr_handler")));
void uart8_isr(void) __attribute__((weak, alias("isr_handler")));
void spi4_isr(void) __attribute__((weak, alias("isr_handler")));
void spi5_isr(void) __attribute__((weak, alias("isr_handler")));
void spi6_isr(void) __attribute__((weak, alias("isr_handler")));
void sai1_isr(void) __attribute__((weak, alias("isr_handler")));
void lcd_tft_isr(void) __attribute__((weak, alias("isr_handler")));
void lcd_tft_err_isr(void) __attribute__((weak, alias("isr_handler")));
void dma2d_isr(void) __attribute__((weak, alias("isr_handler")));

View File

@ -1,8 +1,8 @@
/** BusVoodoo runtime interrupt table
/** runtime interrupt table
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018
* @date 2018-2020
*/
#pragma once
#include "libopencm3/cm3/nvic.h"

View File

@ -6,6 +6,7 @@
* @note peripherals used: timer channel @ref ir_nec_timer
*/
#pragma once
#error not converted for STM32F4
/** set when an IR NEC code has been received */
extern volatile bool ir_nec_code_received_flag;

1416
lib/jep106.inc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
* @note peripherals used: GPIO @ref lcd_hd44780_gpio, I²C @ref lcd_hd44780_i2c
*/
#pragma once
#error not converted for STM32F4
#include <libopencm3/stm32/i2c.h> // I²C definitions
/** @defgroup lcd_hd44780_i2c I²C peripheral used to control backpack adapter for the HD44780

View File

@ -7,6 +7,7 @@
* @warning all calls are blocking
*/
#pragma once
#error not converted for STM32F4
/** setup communication with MAX7219 IC
* @param[in] displays number of displays in the chain

213
lib/led_rgbpanel.c Normal file
View File

@ -0,0 +1,213 @@
/** control RGB LED matrix panels through shift registers
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2022
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/dma.h> // DMA library
#include <libopencm3/stm32/timer.h> // timer library
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include "led_rgbpanel.h" // library API
#include "global.h" // common methods
// RGB matrix pins (warning: hard coded)
#define RGBPANEL_OE_PIN PB10 /**< pin to enable output (active low) */
#define RGBPANEL_A_PIN PB0 /**< pin to select line */
#define RGBPANEL_B_PIN PB1 /**< pin to select line */
#define RGBPANEL_C_PIN PB2 /**< pin to select line */
#define RGBPANEL_D_PIN PB3 /**< pin to select line */
#define RGBPANEL_CLK_PIN PA0 /**< pin to generate clock for serial data */
#define RGBPANEL_LAT_PIN PA1 /**< pin to latch data on rising edge */
#define RGBPANEL_R1_PIN PA2 /**< pin to enable red color on top half */
#define RGBPANEL_G1_PIN PA3 /**< pin to enable green color on top half */
#define RGBPANEL_B1_PIN PA4 /**< pin to enable blue color on top half */
#define RGBPANEL_R2_PIN PA5 /**< pin to enable red color on bottom half */
#define RGBPANEL_G2_PIN PA6 /**< pin to enable green color on bottom half */
#define RGBPANEL_B2_PIN PA7 /**< pin to enable blue color on bottom half */
#define RGBPANEL_DMA DMA2 /**< DMA used to send data to the RGB matrix (only DMA2 can be used for memory-to-memory transfer) */
#define RGBPANEL_RCC_DMA RCC_DMA2 /**< RCC for DMA used for the RGB matrix */
#define RGBPANEL_STREAM DMA_STREAM1 /**< stream used to send data to the RGB matrix (any stream can be used for memory-to-memory transfer) */
#define RGBPANEL_CHANNEL DMA_SxCR_CHSEL_0 /**< channel used to send data to the RGB matrix (any channel can be used for memory-to-memory transfer) */
#define RGBPANEL_IRQ NVIC_DMA2_STREAM1_IRQ /**< IRQ for when a line transfer is complete */
#define RGBPANEL_ISR dma2_stream1_isr /**< ISR for when a line transfer is complete */
#define RGBPANEL_TIMER 3 /**< timer to update lines */
static uint8_t rgbpanel_data[RGBPANEL_HEIGHT / 2][RGBPANEL_WIDTH * 2]; /**< data to be sent to RGB matrix (one byte includes upper and lower half values, each byte has 2 clock edges) */
static volatile uint8_t rgbpanel_line = 0; // line being transferred
void rgbpanel_setup(void)
{
// configure pin for output enable
rcc_periph_clock_enable(GPIO_RCC(RGBPANEL_OE_PIN)); // enable clock for GPIO port peripheral
gpio_set(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_PIN(RGBPANEL_OE_PIN)); // disable output
gpio_set_output_options(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN(RGBPANEL_OE_PIN)); // set fast edge
gpio_mode_setup(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_OE_PIN)); // set pin as output
// configure pins for data and clock lines
const uint32_t rgbpanel_serial_port = GPIO_PORT(RGBPANEL_LAT_PIN); // common port for pins controlling the serial data
const uint16_t rgbpanel_serial_pins = GPIO_PIN(RGBPANEL_LAT_PIN) | GPIO_PIN(RGBPANEL_CLK_PIN) | GPIO_PIN(RGBPANEL_R1_PIN) | GPIO_PIN(RGBPANEL_G1_PIN) | GPIO_PIN(RGBPANEL_B1_PIN) | GPIO_PIN(RGBPANEL_R2_PIN) | GPIO_PIN(RGBPANEL_G2_PIN) | GPIO_PIN(RGBPANEL_B2_PIN); // pins controlling the serial data
rcc_periph_clock_enable(GPIO_RCC(RGBPANEL_LAT_PIN)); // enable clock for GPIO port peripheral
gpio_clear(rgbpanel_serial_port, rgbpanel_serial_pins); // disable LEDs
gpio_set_output_options(rgbpanel_serial_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, rgbpanel_serial_pins); // set fast edge
gpio_mode_setup(rgbpanel_serial_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, rgbpanel_serial_pins); // set pin as output
// configure pins for address lines
rcc_periph_clock_enable(RCC_GPIOB); // enable clock for GPIO port peripheral
gpio_clear(RCC_GPIOB, GPIO0 | GPIO1 | GPIO2 | GPIO3); // unselect line
gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set fast edge
gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set pin as output
// configure DMA to sent line data
// because there is no peripheral request for data, this is a memory to memory transfer
rcc_periph_clock_enable(RGBPANEL_RCC_DMA); // enable clock for DMA peripheral (any DMA and channel can be used)
dma_disable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // disable stream before re-configuring
while (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure
dma_stream_reset(RGBPANEL_DMA, RGBPANEL_STREAM); // use default values
dma_set_peripheral_size(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit
dma_enable_peripheral_increment_mode(RGBPANEL_DMA, RGBPANEL_STREAM); // increment address of memory to read
dma_set_memory_address(RGBPANEL_DMA, RGBPANEL_STREAM, (uint32_t) &GPIOA_ODR); // set GPIOA as destination (for memory-to-memory transfer, the destination is the memory)
dma_set_memory_size(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer
dma_disable_memory_increment_mode(RGBPANEL_DMA, RGBPANEL_STREAM); // don't increment GPIO address
dma_set_number_of_data(RGBPANEL_DMA, RGBPANEL_STREAM, LENGTH(rgbpanel_data[0])); // set transfer size (one line)
dma_channel_select(RGBPANEL_DMA, RGBPANEL_STREAM, RGBPANEL_CHANNEL); // set the channel for this stream
dma_set_transfer_mode(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_DIR_MEM_TO_MEM); // set transfer from memory to memory
dma_set_priority(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_PL_LOW); // there is no need to rush
dma_enable_transfer_complete_interrupt(RGBPANEL_DMA, RGBPANEL_STREAM); // interrupt when line transfer is complete
nvic_enable_irq(RGBPANEL_IRQ); // enable interrupt
rgbpanel_clear(); // clear matrix
gpio_clear(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_PIN(RGBPANEL_OE_PIN)); // enable output
// configure timer to go through rows/lines
rcc_periph_clock_enable(RCC_TIM(RGBPANEL_TIMER)); // enable clock for timer domain
rcc_periph_reset_pulse(RST_TIM(RGBPANEL_TIMER)); // reset timer state
timer_set_mode(TIM(RGBPANEL_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(RGBPANEL_TIMER), 2 - 1); // hand tuned prescale to minimize inter-line ghosting
timer_set_period(TIM(RGBPANEL_TIMER), 0x9fff - 1); // hand tuned period to minimize inter-line ghosting
timer_clear_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF); // clear update (overflow) flag
timer_update_on_overflow(TIM(RGBPANEL_TIMER)); // only use counter overflow as UEV source (use overflow as next line update indication)
timer_enable_irq(TIM(RGBPANEL_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
nvic_enable_irq(NVIC_TIM_IRQ(RGBPANEL_TIMER)); // catch interrupt in service routine
timer_enable_counter(TIM(RGBPANEL_TIMER)); // start timer to update RGB matrix
}
void rgbpanel_release(void)
{
// release timer
timer_disable_counter(TIM(RGBPANEL_TIMER)); // stop timer
nvic_disable_irq(NVIC_TIM_IRQ(RGBPANEL_TIMER)); // disable interrupt
rcc_periph_reset_pulse(RST_TIM(RGBPANEL_TIMER)); // reset timer state
// release DMA (it will stop onve completed
while (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN); // wait until DMA is complete
dma_disable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // disable stream
dma_stream_reset(RGBPANEL_DMA, RGBPANEL_STREAM); // use default values
nvic_disable_irq(RGBPANEL_IRQ); // disable interrupt
// release GPIO
gpio_mode_setup(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_OE_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_A_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_A_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_C_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_C_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_D_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_D_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_CLK_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_CLK_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_LAT_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_LAT_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_R1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_R1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_G1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_G1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_R2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_R2_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_G2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_G2_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B2_PIN));
}
void rgbpanel_clear(void)
{
for (uint8_t i = 0; i < LENGTH(rgbpanel_data); i++) { // for each line
for (uint8_t j = 0; j < LENGTH(rgbpanel_data[0]); j += 2) { // for each clock cycle
rgbpanel_data[i][j + 0] = 0; // create clock falling edge
rgbpanel_data[i][j + 1] = 1; // create clock rising edge
}
rgbpanel_data[i][LENGTH(rgbpanel_data[0]) - 1] |= (1 << 1); // latch data (next line will remove the latch)
}
}
void rgbpanel_set(int16_t x, int16_t y, bool r, bool g, bool b)
{
if (x < 0 || x >= RGBPANEL_WIDTH) {
return;
}
if (y < 0 || y >= RGBPANEL_HEIGHT) {
return;
}
const uint8_t row = y % (RGBPANEL_HEIGHT / 2); // get the actual line/row
const uint8_t col = x * 2; // get the actual column
uint8_t data = 0; // there we will set the color bits
if (y < (RGBPANEL_HEIGHT / 2)) {
data = rgbpanel_data[row][col] & 0xe0; // keep lower line colors
if (r) {
data |= (1 << 2);
}
if (g) {
data |= (1 << 3);
}
if (b) {
data |= (1 << 4);
}
} else {
data = rgbpanel_data[row][col] & 0x1c; // keep upper line colors
if (r) {
data |= (1 << 5);
}
if (g) {
data |= (1 << 6);
}
if (b) {
data |= (1 << 7);
}
}
// set data on low edge
rgbpanel_data[row][col + 0] &= 0x3; // clear color data (don't touch clock and latch data)
rgbpanel_data[row][col + 0] |= data; // set the LED data
// set data on high edge
rgbpanel_data[row][col + 1] &= 0x3; // clear color data (don't touch clock and latch data)
rgbpanel_data[row][col + 1] |= data; // set the LED data on clock high edge
}
/** ISR triggered when the data for the line of the RGB matrix has been transferred
* we switch line just after the data is latched, to reduce ghosting
*/
void RGBPANEL_ISR(void)
{
if (dma_get_interrupt_flag(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_TCIF)) {
dma_clear_interrupt_flags(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_TCIF);
GPIOB_ODR = (GPIOB_ODR & 0xfff0) | (rgbpanel_line & 0xf); // select line (line on lower and upper half are updated at once)
rgbpanel_line = (rgbpanel_line + 1) % (RGBPANEL_HEIGHT / 2); // go to next line (two lines are updated at once)
}
}
/** interrupt service routine called to update next line of RGB matrix
* @note ideally the next line should be updated when the current one is complete, but the DMA is too fast.
* @note switching lines too fast causes inter-line ghosting of the LEDs (on the same column), due to capacitance and driver switching limitations
*/
void TIM_ISR(RGBPANEL_TIMER)(void)
{
if (timer_get_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF)) { // update event happened
timer_clear_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF); // clear flag
if (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN) { // DMA is not complete
return;
}
dma_set_peripheral_address(RGBPANEL_DMA, RGBPANEL_STREAM, (uint32_t)&rgbpanel_data[rgbpanel_line]); // set memory containing line data to be transferred (for memory-to-memory transfer, the source is the peripheral)
dma_enable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // start sending next line
}
}

23
lib/led_rgbpanel.h Normal file
View File

@ -0,0 +1,23 @@
/** control RGB LED matrix panels through shift registers
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2022
*/
#define RGBPANEL_HEIGHT 32 /**< number of rows in the RGB matrix */
#define RGBPANEL_WIDTH 64 /**< number of columns in the RGB matrix */
/** setup peripheral to control RGB panel */
void rgbpanel_setup(void);
/** release peripheral used to control RGB panel */
void rgbpanel_release(void);
/** switch off all LEDs on the RGB panel */
void rgbpanel_clear(void);
/** set color of the LED on the RGB panel
* @param[in] x horizontal position (0 = left)
* @param[in] y vertical position (0 = top)
* @param[in] r if the red LED should be on
* @param[in] g if the green LED should be on
* @param[in] b if the blue LED should be on
*/
void rgbpanel_set(int16_t x, int16_t y, bool r, bool g, bool b);

View File

@ -6,6 +6,7 @@
* @note peripherals used: SPI @ref led_sk6812rgbw_spi, DMA
*/
#pragma once
#error not converted for STM32F4
/** number of LEDs on the SK6812RGBW strip */
#define LED_SK6812RGBW_LEDS (16 * 2)

View File

@ -7,6 +7,7 @@
* @warning all calls are blocking
*/
#pragma once
#error not converted for STM32F4
/** display brightness levels
*/

View File

@ -2,7 +2,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2020
* @date 2016-2022
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
@ -26,9 +26,16 @@
* @{
*/
/** SPI peripheral
* @note SPI2 is 5V tolerant and can be operated in open-drain mode will 1 kO pull-up resistor to 5V to get 5V signal level
* @note if pin is 5V tolerant, it can be operated in open-drain mode will 1 kOhm pull-up resistor to 5V to get 5V signal level
*/
#define LED_WS2812B_SPI 2 /**< SPI peripheral */
#define LED_WS2812B_SPI 1 /**< SPI peripheral */
#define LED_WS2812B_DR &SPI1_DR /**< SPI Data Register to transmit data */
#define LED_WS2812B_DOUT PB5 /**< SPI MOSI pin used for data output */
#define LED_WS2812B_AF GPIO_AF5 /**< alternate function for SPI pin */
#define LED_WS2812B_RCC_DMA RCC_DMA2 /**< RCC for DMA for SPI peripheral */
#define LED_WS2812B_DMA DMA2 /**< DMA for SPI peripheral */
#define LED_WS2812B_STREAM DMA_STREAM3 /**< DMA stream for SPI TX */
#define LED_WS2812B_CHANNEL DMA_SxCR_CHSEL_3 /**< DMA channel for SPI TX */
/** @} */
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
@ -40,8 +47,8 @@
*/
#define LED_WS2812B_SPI_TEMPLATE 0x924924
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 40 * 3 / 8 + 1] = {0};
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset */
uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 25] = {0};
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
{
@ -94,13 +101,14 @@ void led_ws2812b_setup(void)
}
// setup SPI to transmit data
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(LED_WS2812B_SPI)); // enable clock for SPI output peripheral
gpio_set_mode(SPI_MOSI_PORT(LED_WS2812B_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, SPI_MOSI_PIN(LED_WS2812B_SPI)); // set MOSI as output (push-pull needs to be shifted to 5V, open-drain consumes up to 5 mA with pull-up resistor of 1 kO)
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(GPIO_RCC(LED_WS2812B_DOUT)); // enable clock for SPI output peripheral
gpio_mode_setup(GPIO_PORT(LED_WS2812B_DOUT), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin to alternate function
gpio_set_output_options(GPIO_PORT(LED_WS2812B_DOUT), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin output as open-drain
gpio_set_af(GPIO_PORT(LED_WS2812B_DOUT), LED_WS2812B_AF, GPIO_PIN(LED_WS2812B_DOUT)); // set alternate function to SPI
rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default
spi_set_master_mode(SPI(LED_WS2812B_SPI)); // set SPI as master since we generate the clock
spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_16); // set clock to 0.44 us per bit
spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_32); // set clock to 0.30 us per bit
spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle
spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising)
spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
@ -109,20 +117,25 @@ void led_ws2812b_setup(void)
spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (so we can start transmission)
spi_set_nss_high(SPI(LED_WS2812B_SPI)); // set NSS high so we can output
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred
spi_enable(SPI(LED_WS2812B_SPI)); // enable SPI
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
rcc_periph_clock_enable(RCC_DMA_SPI(LED_WS2812B_SPI)); // enable clock for DMA peripheral
dma_channel_reset(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // start with fresh channel configuration
dma_set_read_from_memory(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // set direction from memory to peripheral
dma_set_memory_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data); // set bit pattern as source address
dma_enable_memory_increment_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // go through bit pattern
dma_set_number_of_data(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), LENGTH(led_ws2812b_data)); // set the size of the data to transmit
dma_set_peripheral_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)&SPI_DR(SPI(LED_WS2812B_SPI))); // set SPI as peripheral destination address
dma_set_peripheral_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
dma_set_priority(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
dma_enable_circular_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // always send the data
dma_enable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // enable DMA channel
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred
// configure DMA
rcc_periph_clock_enable(LED_WS2812B_RCC_DMA); // enable clock for DMA peripheral
dma_disable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // disable stream before re-configuring
while (DMA_SCR(LED_WS2812B_DMA, LED_WS2812B_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure
dma_stream_reset(LED_WS2812B_DMA, LED_WS2812B_STREAM); // use default values
dma_channel_select(LED_WS2812B_DMA, LED_WS2812B_STREAM, LED_WS2812B_CHANNEL); // set the channel for this stream
dma_set_transfer_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_DIR_MEM_TO_PERIPHERAL); // set transfer from memory to memory
dma_set_memory_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer
dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)led_ws2812b_data); // set memory address to read from
dma_enable_memory_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // increment address of memory to read
dma_set_peripheral_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)LED_WS2812B_DR); // set peripheral address to write data to
dma_set_peripheral_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit
dma_disable_peripheral_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always write to the SPI data register
dma_set_number_of_data(LED_WS2812B_DMA, LED_WS2812B_STREAM, LENGTH(led_ws2812b_data)); // set transfer size
dma_set_priority(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
dma_enable_circular_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always send the data
dma_enable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // enable DMA channel
}

View File

@ -2,13 +2,13 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2020
* @date 2016-2022
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
#pragma once
/** number of LEDs on the WS2812B strip */
#define LED_WS2812B_LEDS 48
#define LED_WS2812B_LEDS (2U * 8U * 32U)
/** setup WS2812B LED driver
* @note this starts the continuous transmission

View File

@ -1,4 +1,4 @@
/** library to communicate using microwore as master
/** library to communicate using microwire as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -9,6 +9,7 @@
* @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones.
*/
#pragma once
#error not converted for STM32F4
/** setup microwire peripheral
* @param[in] frequency clock frequency in Hz

View File

@ -2,8 +2,8 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018-2019
* @note peripherals used: I2C @ref oled_ssd1306_i2c
* @date 2018-2020
* @note peripherals used: I2C @ref i2c_master_i2c
*/
/* standard libraries */
#include <stdint.h> // standard integer types
@ -21,19 +21,13 @@
/** SSD1306 OLED display I²C slave address */
static uint8_t oled_ssd1306_slave_addr = 0x3c;
/** @defgroup oled_ssd1306_i2c I²C peripheral to communicate with the SSD1306 OLED
* @{
*/
#define OLED_SSD1306_I2C I2C1 /**< I²C peripheral */
/** @} */
bool oled_ssd1306_setup(uint8_t slave_addr)
{
if (!i2c_master_check_signals(OLED_SSD1306_I2C)) { // check if there are pull-ups to operator I²C
if (!i2c_master_check_signals()) { // check if there are pull-ups to operator I²C
return false;
}
oled_ssd1306_slave_addr = slave_addr; // save I²C slave address of SSD1306
i2c_master_setup(OLED_SSD1306_I2C, 400); // setup I²C bus (SSD1306 supports an I²C clock up to 400 kHz)
i2c_master_setup(400); // setup I²C bus (SSD1306 supports an I²C clock up to 400 kHz)
const uint8_t oled_init[] = {
0x00, // control byte: continuous (multiple byes), command
0xae, // Set Display ON/OFF: OFF
@ -56,7 +50,7 @@ bool oled_ssd1306_setup(uint8_t slave_addr)
// Addressing Setting
0x20, 0x00 // Set Memory Addressing Mode: Horizontal Addressing Mode
}; // command to initialize the display
return I2C_MASTER_RC_NONE == i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_init, LENGTH(oled_init)); // send command to initialize display
return I2C_MASTER_RC_NONE == i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_init, LENGTH(oled_init)); // send command to initialize display
}
void oled_ssd1306_on(void)
@ -65,7 +59,7 @@ void oled_ssd1306_on(void)
0x80, // control byte: no continuation, command
0xaf, // Set Display ON/OFF: ON
}; // command to switch on display
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_display_on, LENGTH(oled_display_on)); // sent command to switch on display
i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_display_on, LENGTH(oled_display_on)); // sent command to switch on display
}
void oled_ssd1306_off(void)
@ -74,7 +68,7 @@ void oled_ssd1306_off(void)
0x80, // control byte: no continuation, command
0xae, // Set Display ON/OFF: OFF
}; // command to switch off display
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_display_off, LENGTH(oled_display_off)); // sent command to switch font display
i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_display_off, LENGTH(oled_display_off)); // sent command to switch font display
}
void oled_ssd1306_test(void)
@ -83,7 +77,7 @@ void oled_ssd1306_test(void)
0x80, // control byte: no continuation, command
0xa5 // Entire Display ON: Entire display ON Output ignores RAM content
}; // command to set entire display on
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_entire_display_on, LENGTH(oled_entire_display_on)); // send command to switch entire display on
i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_entire_display_on, LENGTH(oled_entire_display_on)); // send command to switch entire display on
oled_ssd1306_on(); // set display on
sleep_ms(200); // let is on for a bit (enough for the user to see it is completely on
oled_ssd1306_off(); // set display off
@ -91,7 +85,7 @@ void oled_ssd1306_test(void)
0x80, // control byte: no continuation, command
0xa4 // Entire Display ON: Resume to RAM content display
}; // command to display RAM
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_entire_display_ram, LENGTH(oled_entire_display_ram)); // send command to display RAM
i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_entire_display_ram, LENGTH(oled_entire_display_ram)); // send command to display RAM
}
void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length)
@ -107,11 +101,11 @@ void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length)
0x00, // Set Lower Column Start Address for Page Addressing Mode: 0
0x10 // Set Higher Column Start Address for Page Addressing Mode: 0
}; // command to set addressing mode
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_start_page, LENGTH(oled_start_page)); // send command to set addressing mode
if (I2C_MASTER_RC_NONE != i2c_master_start(OLED_SSD1306_I2C)) { // send start condition
i2c_master_slave_write(oled_ssd1306_slave_addr, false, oled_start_page, LENGTH(oled_start_page)); // send command to set addressing mode
if (I2C_MASTER_RC_NONE != i2c_master_start()) { // send start condition
return;
}
if (I2C_MASTER_RC_NONE != i2c_master_select_slave(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, true)) { // select OLED display
if (I2C_MASTER_RC_NONE != i2c_master_select_slave(oled_ssd1306_slave_addr, false, true)) { // select OLED display
return;
}
uint8_t oled_data[display_length + 1]; // we have to copy the data because we need to send a single block since i2c_master_write sends a stop
@ -119,9 +113,9 @@ void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length)
for (uint16_t i = 0; i < display_length; i++) {
oled_data[i + 1] = display_data[i];
}
if (I2C_MASTER_RC_NONE != i2c_master_write(OLED_SSD1306_I2C, oled_data, display_length + 1)) { // send data header
if (I2C_MASTER_RC_NONE != i2c_master_write(oled_data, display_length + 1)) { // send data header
return;
}
i2c_master_stop(OLED_SSD1306_I2C); // send stop condition
i2c_master_stop(); // send stop condition
}

View File

@ -1,10 +1,11 @@
/** SSD1306 OLED library (API)
/** SSD1306 OLED library
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018-2019
* @note peripherals used: I2C @ref oled_ssd1306_i2c
* @date 2018-2020
* @note peripherals used: I2C @ref i2c_master_i2c
*/
#pragma once
/** setup OLED display
* @param[in] slave_addr I²C slave address of SSD1306 device (least significant 7-bit)

View File

@ -77,7 +77,7 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
case ONEWIRE_STATE_MASTER_RESET: // reset pulse has been started
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // enable compare interrupt for presence detection
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again for slaves to respond
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again for slaves to respond
onewire_master_state = ONEWIRE_STATE_SLAVE_PRESENCE; // set new state
break;
case ONEWIRE_STATE_SLAVE_PRESENCE: // waiting for slave presence but none received
@ -88,7 +88,7 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
case ONEWIRE_STATE_MASTER_READ: // end of time slot and recovery time for reading bit
case ONEWIRE_STATE_MASTER_WRITE: // end of time slot and recovery time for writing bit
if (buffer_bit < buffer_size) { // check if byte to read/write are remaining
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start next slot
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start next slot
} else { // all bytes read/written
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // disable compare interrupt for master pull low
@ -103,14 +103,14 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable all compare interrupt
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // disable all compare interrupt
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // disable all compare interrupt
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high (idle state)
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high (idle state)
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
}
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF)) { // compare event happened for master pull low end for read
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear flag
switch (onewire_master_state) {
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
break;
default: // unknown state for this stage
break; // let the overflow handle the error if any
@ -121,7 +121,7 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
case ONEWIRE_STATE_MASTER_WRITE: // master has to write a bit
if (buffer_bit < buffer_size) { // check if byte to send are remaining
if (buffer[buffer_bit / 8] & (1 << (buffer_bit % 8))) { // check bit (LSb first)
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again to write "1"
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again to write "1"
}
buffer_bit++; // got to next bit
} else {
@ -131,7 +131,7 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
break;
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit set by slave
if (buffer_bit<buffer_size) { // check if byte to send are remaining
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check if the slave kept it low
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check if the slave kept it low
buffer[buffer_bit / 8] |= (1 << (buffer_bit % 8)); // save bit "1"
} else {
buffer[buffer_bit / 8] &= ~(1 << (buffer_bit % 8)); // save bit "0"
@ -147,10 +147,10 @@ void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
}
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF)) { // compare event happened for end to time slot
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear flag
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF)) { // compare event happened for slave presence detection
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear flag
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check is a slave let its presence know by pulling low
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check is a slave let its presence know by pulling low
slave_presence = false; // remember no slave(s) responded
} else {
slave_presence = true; // remember slave(s) responded
@ -165,7 +165,8 @@ void onewire_master_setup(void)
// setup GPIO with external interrupt
rcc_periph_clock_enable(GPIO_RCC(ONEWIRE_MASTER_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // idle is high (using pull-up resistor)
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_mode_setup(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(ONEWIRE_MASTER_PIN)); // set pin as output, and expect external pull-up resistor
gpio_set_output_options(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN(ONEWIRE_MASTER_PIN)); // set pin output as open-drain for normal 1-Wire communication (no parasite power)
// setup timer to generate/measure signal timing
rcc_periph_clock_enable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // enable clock for timer peripheral
@ -205,7 +206,7 @@ void onewire_master_release(void)
rcc_periph_clock_disable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // disable clock for timer peripheral
// release GPIO
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(ONEWIRE_MASTER_PIN)); // put back to input floating
gpio_mode_setup(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(ONEWIRE_MASTER_PIN)); // put back to input floating
// disable timer ISR
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
@ -228,7 +229,7 @@ bool onewire_master_reset(void)
slave_presence = false; // reset state
onewire_master_state = ONEWIRE_STATE_MASTER_RESET; // set new state
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_set_output_options(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN(ONEWIRE_MASTER_PIN)); // set pin output as open-drain for normal 1-Wire communication (no parasite power)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start reset (it's not important if it was low in the first place since the reset pulse has no maximum time)
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
@ -261,8 +262,8 @@ static bool onewire_master_write(void)
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
// start writing
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
gpio_set_output_options(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN(ONEWIRE_MASTER_PIN)); // set pin output as open-drain for normal 1-Wire communication (no parasite power)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
while (onewire_master_state != ONEWIRE_STATE_DONE && onewire_master_state != ONEWIRE_STATE_ERROR) { // wait until write procedure completed
__WFI(); // go to sleep
@ -297,7 +298,7 @@ static bool onewire_master_read(void)
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
// start reading
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_set_output_options(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN(ONEWIRE_MASTER_PIN)); // set pin output as open-drain for normal 1-Wire communication (no parasite power)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
while (onewire_master_state != ONEWIRE_STATE_DONE && onewire_master_state != ONEWIRE_STATE_ERROR) { // wait until read procedure completed

View File

@ -7,6 +7,7 @@
* @note overdrive mode is not supported
*/
#pragma once
#error not converted for STM32F4
/** set when a function command code has been received
* @note needs to be cleared by user

View File

@ -1,18 +1,18 @@
/** library to send data using ESP8266 WiFi SoC
/** library to send data using ESP8266 WiFi SoC (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2021
* @date 2016-2022
* @note peripherals used: USART @ref radio_esp8266_usart
*/
// standard libraries
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // string and memory utilities
#include <stdio.h> // string utilities
// STM32 (including CM3) libraries
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
@ -25,33 +25,35 @@
/** @defgroup radio_esp8266_usart USART peripheral used for communication with radio
* @{
*/
#define RADIO_ESP8266_USART 2 /**< USART peripheral */
#define RADIO_ESP8266_TX PA2 /**< pin used for USART TX */
#define RADIO_ESP8266_RX PA3 /**< pin used for USART RX */
#define RADIO_ESP8266_USART 1 /**< USART peripheral */
#define RADIO_ESP8266_TX PA9 /**< pin used for USART TX */
#define RADIO_ESP8266_RX PA10 /**< pin used for USART RX */
#define RADIO_ESP8266_AF GPIO_AF7 /**< alternate function for UART pins */
/** @} */
// input and output buffers and used memory
static uint8_t rx_buffer[24] = {0}; /**< buffer for received data (we only expect AT responses) */
/* input and output buffers and used memory */
static uint8_t rx_buffer[24 + 530] = {0}; /**< buffer for received data */
static volatile uint16_t rx_used = 0; /**< number of byte in receive buffer */
static uint8_t tx_buffer[256] = {0}; /**< buffer for data to transmit */
static volatile uint16_t tx_used = 0; /**< number of bytes used in transmit buffer */
// response status
volatile bool radio_esp8266_response = false; /**< when a response has been received (OK or ERROR) */
volatile bool radio_esp8266_success = false; /**< if the response is OK (else ERROR), set when radio_esp8266_response is set to true */
volatile bool radio_esp8266_activity = false;
volatile bool radio_esp8266_success = false;
uint8_t radio_esp8266_received[512 + 18];
uint16_t radio_esp8266_received_len = 0;
/** transmit data to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmit(const uint8_t* data, uint8_t length) {
static void radio_esp8266_transmit(uint8_t* data, uint8_t length) {
while (tx_used || !usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // wait until ongoing transmission completed
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable transmit interrupt
__WFI(); // sleep until something happened
}
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // ensure transmit interrupt is disable to prevent index corruption (the ISR should already have done it)
radio_esp8266_response = false; // reset status because of new activity
rx_used = 0; // reset receive buffer
radio_esp8266_activity = false; // reset status because of new activity
for (tx_used = 0; tx_used < length && tx_used < LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length - 1 - tx_used]; // put character in buffer (in reverse order)
}
@ -60,7 +62,32 @@ static void radio_esp8266_transmit(const uint8_t* data, uint8_t length) {
}
}
bool radio_esp8266_setup(void)
/** transmit string to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmits(char* str) {
if (NULL == str) {
return;
}
const uint16_t length = strlen(str);
radio_esp8266_transmit((uint8_t*)str, length);
}
/** transmit string to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmit_at(char* at) {
if (NULL == at) {
return;
}
radio_esp8266_transmits("AT");
radio_esp8266_transmits(at);
radio_esp8266_transmits("\r\n");
}
void radio_esp8266_setup(void)
{
// configure pins
rcc_periph_clock_enable(GPIO_RCC(RADIO_ESP8266_TX)); // enable clock for USART TX pin port peripheral
@ -71,10 +98,10 @@ bool radio_esp8266_setup(void)
gpio_mode_setup(GPIO_PORT(RADIO_ESP8266_RX), GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN(RADIO_ESP8266_RX)); // set GPIO to alternate function, with pull up to avoid noise in case it is not connected
gpio_set_af(GPIO_PORT(RADIO_ESP8266_RX), RADIO_ESP8266_AF, GPIO_PIN(RADIO_ESP8266_RX)); // set alternate function to USART
// configure USART
// configure USART for ESP8266 AT firmware
rcc_periph_clock_enable(RCC_USART(RADIO_ESP8266_USART)); // enable clock for USART peripheral
rcc_periph_reset_pulse(RST_USART(RADIO_ESP8266_USART)); // reset peripheral
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200);
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200); // AT firmware 0.51 (SDK 1.5.0) uses 115200 bps
usart_set_databits(USART(RADIO_ESP8266_USART), 8);
usart_set_stopbits(USART(RADIO_ESP8266_USART), USART_STOPBITS_1);
usart_set_mode(USART(RADIO_ESP8266_USART), USART_MODE_TX_RX);
@ -85,133 +112,108 @@ bool radio_esp8266_setup(void)
usart_enable_rx_interrupt(USART(RADIO_ESP8266_USART)); // enable receive interrupt
usart_enable(USART(RADIO_ESP8266_USART)); // enable UART
// reset buffer states
/* reset buffer states */
rx_used = 0;
tx_used = 0;
radio_esp8266_activity = false;
radio_esp8266_success = false;
// verify if ESP8266 is reachable
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT\r\n", 4); // verify if module is present
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
radio_esp8266_transmit_at(""); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
radio_esp8266_transmit_at("E0"); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
}
void radio_esp8266_reset(void)
{
radio_esp8266_transmit_at("+RST"); // reset module
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
radio_esp8266_transmit_at("E0"); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
while(rx_used < 13 || memcmp((char*)&rx_buffer[rx_used - 13], "WIFI GOT IP\r\n", 13) != 0) { // wait to have IP
__WFI(); // sleep until something happened
}
}
bool radio_esp8266_connected(void)
{
// verify if we ware connect to access point
radio_esp8266_transmit_at("+CWJAP_CUR?");
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (rx_used < 5 || 0 == memcmp((char*)&rx_buffer[rx_used - 5], "No AP", 5)) {
rx_used = 0; // finished using the buffer
return false;
}
// reset module so it connects to AP
timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n", 8); // reset module
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
// check if we have an IP
radio_esp8266_transmit_at("+CIPSTA_CUR?"); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
return false;
}
timeout = 0; // reset timeout counter
while(rx_used < 13 || 0 != memcmp((char*)&rx_buffer[rx_used - 13], "WIFI GOT IP\r\n", 13)) { // wait to have IP
if (timeout > 10000) { // connection takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
// disable echo for better parsing
timeout = 0; // reset timeout counter
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
if (rx_used < 17 || '0' == rx_buffer[16]) { // check for +CIPSTA_CUR:ip:"0.0.0.0"
rx_used = 0;
return false;
}
rx_used = 0; // clear buffer
return true;
}
bool radio_esp8266_open(const char* host, uint16_t port, bool tcp)
void radio_esp8266_tcp_open(char* host, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"%s\",\"%s\",%u\r\n", tcp ? "TCP" : "UDP", host, port); // create AT command to establish a TCP connection
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"TCP\",\"%s\",%u\r\n", host, port); // create AT command to establish a TCP connection
if (length > 0) {
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)command, length);
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
}
}
bool radio_esp8266_listen(bool udp, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"%s\",\"0.0.0.0\",0,%u\r\n", udp ? "UDP" : "TCP", port); // create AT command to establish a listening connection
if (!length) {
return false;
}
radio_esp8266_transmit((uint8_t*)command, length);
while (!radio_esp8266_activity) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) { // send AT command did not succeed
return false;
}
return true;
}
bool radio_esp8266_send(const uint8_t* data, uint8_t length)
void radio_esp8266_send(uint8_t* data, uint8_t length)
{
char command[16 + 1] = {0}; // string to create command
int command_length = snprintf(command, LENGTH(command), "AT+CIPSEND=%u\r\n", length); // create AT command to send data
if (command_length > 0) {
// start sending
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)command, command_length); // transmit AT command
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
return false;
if (!radio_esp8266_success) { // send AT command did not succeed
return; // don't transmit data
}
// send actual data
timeout = 0; // reset timeout counter
radio_esp8266_transmit(data, length); // transmit data
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
}
return true;
}
bool radio_esp8266_close(void)
void radio_esp8266_close(void)
{
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT+CIPCLOSE\r\n", 13); // send AT command to close established connection
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
return true;
radio_esp8266_transmit_at("+CIPCLOSE"); // send AT command to close established connection
}
/** USART interrupt service routine called when data has been transmitted or received */
@ -231,15 +233,38 @@ void USART_ISR(RADIO_ESP8266_USART)(void)
rx_used--; // update used buffer information
}
rx_buffer[rx_used++] = usart_recv(USART(RADIO_ESP8266_USART)); // put character in buffer
/*
if ('\n' == rx_buffer[rx_used - 1]) {
rx_buffer[rx_used] = '\0';
printf((char*)rx_buffer);
printf("\n");
}
*/
// if the used send a packet with these strings during the commands detection the AT command response will break (AT commands are hard to handle perfectly)
if (rx_used >= 4 && 0 == memcmp((char*)&rx_buffer[rx_used - 4], "OK\r\n", 4)) { // OK received
radio_esp8266_response = true; // response received
if (rx_used >= 4 && memcmp((char*)&rx_buffer[rx_used - 4], "OK\r\n", 4) == 0) { // OK received
radio_esp8266_activity = true; // response received
radio_esp8266_success = true; // command succeeded
rx_used = 0; // reset buffer
} else if (rx_used >= 7 && 0 == memcmp((char*)&rx_buffer[rx_used - 7], "ERROR\r\n", 7)) { // ERROR received
radio_esp8266_response = true; // response received
} else if (rx_used >= 7 && memcmp((char*)&rx_buffer[rx_used - 7], "ERROR\r\n", 7) == 0) { // ERROR received
radio_esp8266_activity = true; // response received
radio_esp8266_success = false; // command failed
rx_used = 0; // reset buffer
} else if (rx_used >= 7 && memcmp((char*)&rx_buffer[0], "\r\n+IPD,", 7) == 0) { // IP data being received
// find if we received the length
int32_t data_length = 0;
uint32_t data_start = 0;
for (uint16_t i = 7; i < rx_used; i++) {
if (':' == rx_buffer[i]) {
data_start = i + 1;
data_length = atoi((char*)&rx_buffer[7]);
break;
}
}
if (data_length > 0 && rx_used >= data_start + data_length) {
if ((uint32_t)data_length <= LENGTH(radio_esp8266_received)) {
memcpy(radio_esp8266_received, &rx_buffer[data_start], data_length);
radio_esp8266_received_len = data_length;
}
rx_used = 0; // reset buffer
}
}
}
}

View File

@ -1,8 +1,8 @@
/** library to send data using ESP8266 WiFi SoC (API)
/** library to send data using ESP8266 WiFi SoC
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2021
* @date 2016-2022
* @note peripherals used: USART @ref radio_esp8266_usart
*/
#pragma once
@ -11,25 +11,40 @@
extern volatile bool radio_esp8266_activity;
/** the last command has succeeded */
extern volatile bool radio_esp8266_success;
/** data received (overwritten upon next receive) */
extern uint8_t radio_esp8266_received[];
/** length of receive data */
extern uint16_t radio_esp8266_received_len;
/** setup peripherals to communicate with radio
* @return if it connected to AP
* @note this is blocking to ensure we are connected to the WiFi network
*/
bool radio_esp8266_setup(void);
void radio_esp8266_setup(void);
/** reset ESP */
void radio_esp8266_reset(void);
/** check if connect to the network
* @return if connected to network
*/
bool radio_esp8266_connected(void);
/** establish TCP connection
* @param[in] host host to connect to
* @param[in] port port number to connect to
* @param[in] tcp if connect to a TCP port (else UDP)
* @param[in] port TCP port to connect to
* @note wait for activity to get success status
*/
void radio_esp8266_tcp_open(char* host, uint16_t port);
/** open listening connection
* @param[in] udp if it's an UDP or TCP connection
* @param[in] port port to listen to
* @return if operation succeeded
*/
bool radio_esp8266_open(const char* host, uint16_t port, bool tcp);
bool radio_esp8266_listen(bool udp, uint16_t port);
/** send data (requires established connection)
* @param[in] data data to send
* @param[in] length size of data to send
* @return if operation succeeded
* @note wait for activity to get success status
*/
bool radio_esp8266_send(const uint8_t* data, uint8_t length);
void radio_esp8266_send(uint8_t* data, uint8_t length);
/** close established connection
* @return if operation succeeded
* @note wait for activity to get success status
*/
bool radio_esp8266_close(void);
void radio_esp8266_close(void);

View File

@ -7,6 +7,7 @@
* @note the interrupts and corresponding DIO should be handled directly by the user
*/
#pragma once
#error not converted for STM32F4
/** register addresses */
enum radio_sx172x_register_t {

View File

@ -6,6 +6,7 @@
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
*/
#pragma once
#error not converted for STM32F4
/** set when time information has been received */
extern volatile bool rtc_dcf77_time_flag;

View File

@ -1,4 +1,4 @@
/** library to communicate with the Maxim DS1307 I2C RTC IC (API)
/** library to communicate with the Maxim DS1307 I2C RTC IC
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -6,6 +6,7 @@
* @note peripherals used: I²C
*/
#pragma once
#error not converted for STM32F4
/** setup communication with RTC IC
* configure the I2C port defined in the sources

View File

@ -6,6 +6,7 @@
* @note peripherals used: SPI @ref sensor_as3935_spi, GPIO @ref sensor_as3935_gpio
*/
#pragma once
#error not converted for STM32F4
/** a interrupt has been received */
extern volatile bool sensor_as3935_interrupt;

177
lib/sensor_dht11.c Normal file
View File

@ -0,0 +1,177 @@
/** library to query measurements from Aosong DHT11 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht11_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht11.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht11_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT11_TIMER 3 /**< timer peripheral */
#define SENSOR_DHT11_CHANNEL 1 /**< channel used as input capture */
#define SENSOR_DHT11_JITTER 0.1 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht11_measurement_received = false;
/** communication states */
volatile enum sensor_dht11_state_t {
SENSOR_DHT11_OFF, // no request has started
SENSOR_DHT11_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT11_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT11_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT11_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT11_MAX
} sensor_dht11_state = SENSOR_DHT11_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht11_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht11_bits[5] = {0};
/** reset all states */
static void sensor_dht11_reset(void)
{
// reset states
sensor_dht11_state = SENSOR_DHT11_OFF;
sensor_dht11_bit = 0;
sensor_dht11_measurement_received = false;
gpio_set(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT11_TIMER)); // disable timer
}
void sensor_dht11_setup(void)
{
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // enable clock for GPIO peripheral
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT11_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT11_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT11_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(SENSOR_DHT11_TIMER), 20 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/20/(2**16))=18.20ms )
timer_ic_set_input(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT11_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_FALLING); // capture on rising edge
timer_ic_set_prescaler(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT11_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_CCIE(SENSOR_DHT11_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT11_TIMER)); // catch interrupt in service routine
sensor_dht11_reset(); // reset state
}
bool sensor_dht11_measurement_request(void)
{
if (sensor_dht11_state != SENSOR_DHT11_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT11_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht11_reset(); // reset states
// send start signal (pull low for > 18 ms)
gpio_clear(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT11_TIMER)); // enable timer to wait for 18 ms until overflow
sensor_dht11_state = SENSOR_DHT11_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void)
{
struct sensor_dht11_measurement_t measurement = { 0xff, 0xff }; // measurement to return
if (sensor_dht11_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht11_bits[0] + sensor_dht11_bits[1] + sensor_dht11_bits[2] + sensor_dht11_bits[3]) != sensor_dht11_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
measurement.humidity = sensor_dht11_bits[0];
measurement.temperature = sensor_dht11_bits[2];
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT11_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht11_state == SENSOR_DHT11_HOST_START) { // start signal sent
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
sensor_dht11_state = SENSOR_DHT11_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht11_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT11_TIMER)) + 1)); // calculate time in us
switch (sensor_dht11_state) {
case (SENSOR_DHT11_HOST_STARTED): // the host query data and the slave is responding
sensor_dht11_state = SENSOR_DHT11_SLAVE_START; // set new state
break;
case (SENSOR_DHT11_SLAVE_START): // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT11_JITTER)) && time <= ((80 + 80)*(1 + SENSOR_DHT11_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht11_state = SENSOR_DHT11_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case (SENSOR_DHT11_SLAVE_BIT): // the slave sent a bit
if (sensor_dht11_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT11_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT11_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht11_bits[sensor_dht11_bit / 8] &= ~(1 << (7 - (sensor_dht11_bit % 8))); // clear bit
} else if (time >= ((50 + 70)*(1 - SENSOR_DHT11_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT11_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht11_bits[sensor_dht11_bit / 8] |= (1 << (7 - (sensor_dht11_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht11_bit++;
if (sensor_dht11_bit >= 40) { // all bits received
sensor_dht11_reset(); // reset states
sensor_dht11_bit = 40; // signal decoder all bits have been received
sensor_dht11_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht11_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

29
lib/sensor_dht11.h Normal file
View File

@ -0,0 +1,29 @@
/** library to query measurements from Aosong DHT11 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht11_timer (add external pull-up resistor)
*/
#pragma once
#error not converted for STM32F4
/** a measurement response has been received */
extern volatile bool sensor_dht11_measurement_received;
/** measurement returned by sensor */
struct sensor_dht11_measurement_t {
uint8_t humidity; /**< relative humidity in %RH (20-95) */
uint8_t temperature; /**< temperature in °C (0-50) */
};
/** setup peripherals to communicate with sensor */
void sensor_dht11_setup(void);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht11_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void);

View File

@ -1,206 +0,0 @@
/** library to query measurements from Aosong and DHT22/AM2302 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht1122_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht1122.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht1122_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT1122_PIN PB4 /**< MCU pin connected to DHT1122 I/O pin, pulled up externally */
#define SENSOR_DHT1122_TIMER 3 /**< timer peripheral for DHT1122 pin */
#define SENSOR_DHT1122_CHANNEL 1 /**< channel used as input capture */
#define SENSOR_DHT1122_AF GPIO_AF2 /**< pin alternate function to use as timer */
#define SENSOR_DHT1122_JITTER 0.2 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht1122_measurement_received = false;
/** remember if the sensor is a DHT11 or DHT22 */
static bool sensor_dht1122_dht22 = false;
/** communication states */
volatile enum sensor_dht1122_state_t {
SENSOR_DHT1122_OFF, // no request has started
SENSOR_DHT1122_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT1122_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT1122_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT1122_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT1122_MAX
} sensor_dht1122_state = SENSOR_DHT1122_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht1122_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht1122_bits[5] = {0};
/** reset all states */
static void sensor_dht1122_reset(void)
{
// reset states
sensor_dht1122_state = SENSOR_DHT1122_OFF;
sensor_dht1122_bit = 0;
sensor_dht1122_measurement_received = false;
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL)); // disable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT1122_TIMER)); // disable timer
}
void sensor_dht1122_setup(bool dht22)
{
sensor_dht1122_dht22 = dht22; // remember sensor type
// configure pin to use as timer input
rcc_periph_clock_enable(GPIO_RCC(SENSOR_DHT1122_PIN)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // already put signal high
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin to alternate function
gpio_set_output_options(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin output as open-drain
gpio_set_af(GPIO_PORT(SENSOR_DHT1122_PIN), SENSOR_DHT1122_AF, GPIO_PIN(SENSOR_DHT1122_PIN)); // set alternate function to pin
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT1122_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT1122_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT1122_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_enable_break_main_output(TIM(SENSOR_DHT1122_TIMER)); // required to enable some timer, even when no dead time is used
// one difference between DHT11 and DHT22 is the request pulse duration
if (!sensor_dht1122_dht22) { // for DHT11
timer_set_prescaler(TIM(SENSOR_DHT1122_TIMER), 24 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(84E6/24/(2**16)) = 18.7 ms )
} else { // for DHT22
timer_set_prescaler(TIM(SENSOR_DHT1122_TIMER), 2 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(84E6/2/(2**16)) = 1.56 ms )
}
timer_ic_set_input(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT1122_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_FALLING); // capture on falling edge (start of bit)
timer_ic_set_prescaler(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT1122_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT1122_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_CCIF(SENSOR_DHT1122_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT1122_TIMER), TIM_DIER_CCIE(SENSOR_DHT1122_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT1122_TIMER)); // catch interrupt in service routine
sensor_dht1122_reset(); // reset state
}
bool sensor_dht1122_measurement_request(void)
{
if (sensor_dht1122_state != SENSOR_DHT1122_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT1122_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht1122_reset(); // reset states
// send start signal (pull low for > 18 ms)
gpio_clear(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT1122_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT1122_TIMER)); // enable timer to wait for 18 ms until overflow
sensor_dht1122_state = SENSOR_DHT1122_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht1122_measurement_t sensor_dht1122_measurement_decode(void)
{
struct sensor_dht1122_measurement_t measurement = { 0xff, 0xff }; // measurement to return
if (sensor_dht1122_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht1122_bits[0] + sensor_dht1122_bits[1] + sensor_dht1122_bits[2] + sensor_dht1122_bits[3]) != sensor_dht1122_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
if (0 == sensor_dht1122_bits[0] && 0 == sensor_dht1122_bits[1] && 0 == sensor_dht1122_bits[2] && 0 == sensor_dht1122_bits[3] && 0 == sensor_dht1122_bits[4]) { // this is measurement is very unlikely, there must be an error
return measurement;
}
// the other difference between DHT11 and DHT22 is the encoding of the values
if (!sensor_dht1122_dht22) { // for DHT11
// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
measurement.humidity = sensor_dht1122_bits[0];
measurement.temperature = sensor_dht1122_bits[2];
} else { // for DHT22
measurement.humidity = (int16_t)((sensor_dht1122_bits[0] << 8) + sensor_dht1122_bits[1]) / 10.0;
measurement.temperature = (int16_t)((sensor_dht1122_bits[2] << 8) + sensor_dht1122_bits[3]) / 10.0;
}
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT1122_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht1122_state == SENSOR_DHT1122_HOST_START) { // start signal sent
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // let pin go so we can use it as input
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin to alternate function so the timer can read the pin
sensor_dht1122_state = SENSOR_DHT1122_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht1122_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_CCIF(SENSOR_DHT1122_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT1122_TIMER, SENSOR_DHT1122_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT1122_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT1122_TIMER)) + 1)); // calculate time in us
switch (sensor_dht1122_state) {
case SENSOR_DHT1122_HOST_STARTED: // the host query data and the slave is responding
sensor_dht1122_state = SENSOR_DHT1122_SLAVE_START; // set new state
break;
case SENSOR_DHT1122_SLAVE_START: // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT1122_JITTER)) && time <= ((80 + 80) * (1 + SENSOR_DHT1122_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht1122_state = SENSOR_DHT1122_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case SENSOR_DHT1122_SLAVE_BIT: // the slave sent a bit
if (sensor_dht1122_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT1122_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT1122_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht1122_bits[sensor_dht1122_bit / 8] &= ~(1 << (7 - (sensor_dht1122_bit % 8))); // clear bit
} else if (time >= ((50 + 70)*(1 - SENSOR_DHT1122_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT1122_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht1122_bits[sensor_dht1122_bit / 8] |= (1 << (7 - (sensor_dht1122_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht1122_bit++;
if (sensor_dht1122_bit >= 40) { // all bits received
sensor_dht1122_reset(); // reset states
sensor_dht1122_bit = 40; // signal decoder all bits have been received
sensor_dht1122_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht1122_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

View File

@ -1,30 +0,0 @@
/** library to query measurements from Aosong DHT11 and DHT22/AM2302 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2021
* @note peripherals used: timer channel @ref sensor_dht1122_timer (add external pull-up resistor)
*/
#pragma once
/** a measurement response has been received */
extern volatile bool sensor_dht1122_measurement_received;
/** measurement returned by sensor */
struct sensor_dht1122_measurement_t {
float humidity; /**< relative humidity in %RH (20-95) */
float temperature; /**< temperature in °C (0-50) */
};
/** setup peripherals to communicate with sensor
* @param[in] dht22 false for DHT11, true for DHT22/AM2302
*/
void sensor_dht1122_setup(bool dht22);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht1122_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht1122_measurement_t sensor_dht1122_measurement_decode(void);

181
lib/sensor_dht22.c Normal file
View File

@ -0,0 +1,181 @@
/** library to query measurements from Aosong DHT22 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: GPIO and timer @ref sensor_dht22_timer
* @note the DHT22 protocol is very similar but nit completely compatible with the DHT22 protocol: only 1 ms initial host pull low is required (vs. 18 ms), the data is encoded as int16_t (vs. uint8_t), and the signal has more jitter
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <math.h> // maths utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht22.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht22_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT22_TIMER 4 /**< timer peripheral */
#define SENSOR_DHT22_CHANNEL 3 /**< channel used as input capture */
#define SENSOR_DHT22_JITTER 0.2 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht22_measurement_received = false;
/** communication states */
volatile enum sensor_dht22_state_t {
SENSOR_DHT22_OFF, // no request has started
SENSOR_DHT22_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT22_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT22_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT22_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT22_MAX
} sensor_dht22_state = SENSOR_DHT22_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht22_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht22_bits[5] = {0};
/** reset all states */
static void sensor_dht22_reset(void)
{
// reset states
sensor_dht22_state = SENSOR_DHT22_OFF;
sensor_dht22_bit = 0;
sensor_dht22_measurement_received = false;
gpio_set(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT22_TIMER)); // disable timer
}
void sensor_dht22_setup(void)
{
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // enable clock for GPIO peripheral
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT22_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT22_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT22_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(SENSOR_DHT22_TIMER), 2 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/2/(2**16))=1.820ms )
timer_ic_set_input(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT22_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_FALLING); // capture on rising edge
timer_ic_set_prescaler(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT22_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_CCIE(SENSOR_DHT22_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT22_TIMER)); // catch interrupt in service routine
sensor_dht22_reset(); // reset state
}
bool sensor_dht22_measurement_request(void)
{
if (sensor_dht22_state != SENSOR_DHT22_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT22_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht22_reset(); // reset states
// send start signal (pull low for > 1 ms)
gpio_clear(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT22_TIMER)); // enable timer to wait for 1.8 ms until overflow
sensor_dht22_state = SENSOR_DHT22_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void)
{
struct sensor_dht22_measurement_t measurement = { NAN, NAN }; // measurement to return
if (sensor_dht22_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht22_bits[0] + sensor_dht22_bits[1] + sensor_dht22_bits[2] + sensor_dht22_bits[3]) != sensor_dht22_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
// calculate measured values (stored as uint16_t deci-value)
measurement.humidity = (int16_t)((sensor_dht22_bits[0] << 8) + sensor_dht22_bits[1]) / 10.0;
measurement.temperature = (int16_t)((sensor_dht22_bits[2] << 8) + sensor_dht22_bits[3]) / 10.0;
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT22_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht22_state==SENSOR_DHT22_HOST_START) { // start signal sent
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
sensor_dht22_state = SENSOR_DHT22_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht22_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT22_TIMER)) + 1)); // calculate time in us
switch (sensor_dht22_state) {
case (SENSOR_DHT22_HOST_STARTED): // the host query data and the slave is responding
sensor_dht22_state = SENSOR_DHT22_SLAVE_START; // set new state
break;
case (SENSOR_DHT22_SLAVE_START): // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT22_JITTER)) && time <= ((80 + 80) * (1 + SENSOR_DHT22_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht22_state = SENSOR_DHT22_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case (SENSOR_DHT22_SLAVE_BIT): // the slave sent a bit
if (sensor_dht22_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT22_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT22_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht22_bits[sensor_dht22_bit / 8] &= ~(1 << (7 - (sensor_dht22_bit % 8))); // clear bit
} else if (time >= ((50 + 70) * (1 - SENSOR_DHT22_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT22_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht22_bits[sensor_dht22_bit / 8] |= (1 << (7 - (sensor_dht22_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht22_bit++;
if (sensor_dht22_bit >= 40) { // all bits received
sensor_dht22_reset(); // reset states
sensor_dht22_bit = 40; // signal decoder all bits have been received
sensor_dht22_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht22_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

29
lib/sensor_dht22.h Normal file
View File

@ -0,0 +1,29 @@
/** library to query measurements from Aosong DHT22 (aka. AM2302) temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht22_timer (add external pull-up resistor)
*/
#pragma once
#error not converted for STM32F4
/** a measurement response has been received */
extern volatile bool sensor_dht22_measurement_received;
/** measurement returned by sensor */
struct sensor_dht22_measurement_t {
float humidity; /**< relative humidity in %RH (0-100) */
float temperature; /**< temperature in °C (-40-80) */
};
/** setup peripherals to communicate with sensor */
void sensor_dht22_setup(void);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht22_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void);

View File

@ -1,4 +1,4 @@
/** library for Maxim DS18B20 digital temperature sensor (using 1-Wire protocol) (API)
/** library for Maxim DS18B20 digital temperature sensor (using 1-Wire protocol)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -6,6 +6,7 @@
* @note peripherals used: SPI @ref sensor_max1247_spi
*/
#pragma once
#error not converted for STM32F4
/** setup peripherals to communicate with sensor
* @note the sensor configuration will be set to default and powered down

View File

@ -6,6 +6,7 @@
* @note peripherals used: SPI @ref sensor_max6675_spi
*/
#pragma once
#error not converted for STM32F4
/** setup communication to MAX6675 sensor */
void sensor_max6675_setup(void);

86
lib/sensor_mlx90614.c Normal file
View File

@ -0,0 +1,86 @@
/** library to communicate with MLX90614 infra-red thermometer
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: GPIO @ref sensor_mlx90614_gpio, I2C @ref i2c_master_i2c
* @note this library only uses the I²C interface, not the PWM output
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean type
#include <stdlib.h> // general utilities
#include <math.h> // NaN definition
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/i2c.h> // I²C library
/* own libraries */
#include "global.h" // global utilities
#include "sensor_mlx90614.h" // own definitions
#include "smbus_master.h" // SMBus utilities
/** @defgroup sensor_mlx90614_gpio GPIO pin used to control the MLX90614
* @{
*/
#define SENSOR_MLX90614_SCL PA8 /**< GPIO pin for SMBus SCL, use to send request condition to switch mode from PWM to SMBus (must be the same as used for SMBus communication) */
/** @} */
/** MLX90614 I²C slave address */
static uint8_t sensor_mlx90614_slave_addr = 0x5a;
bool sensor_mlx90614_setup(uint8_t slave_addr)
{
if (!smbus_master_check_signals()) { // check if there are pull-ups to operate I²C
return false;
}
// send request condition to switch to I²C mode
rcc_periph_clock_enable(GPIO_RCC(SENSOR_MLX90614_SCL)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(SENSOR_MLX90614_SCL), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_MLX90614_SCL)); // set pin as output
gpio_set_output_options(GPIO_PORT(SENSOR_MLX90614_SCL), GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN(SENSOR_MLX90614_SCL)); // set pin output as open-drain (should be pulled up by external resistors required for I²C communication
gpio_clear(GPIO_PORT(SENSOR_MLX90614_SCL), GPIO_PIN(SENSOR_MLX90614_SCL)); // set low to send request condition
sleep_us(1024 + 500); // SMBus Request is min. 1.024 ms
gpio_set(GPIO_PORT(SENSOR_MLX90614_SCL), GPIO_PIN(SENSOR_MLX90614_SCL)); // set back high to end request condition
if (slave_addr < 0x80) { // only the least 7-bit are valid (use default address else)
sensor_mlx90614_slave_addr = slave_addr; // save I²C slave address of MLX90614
}
smbus_master_setup(100, true); // setup SMBus with PEC
uint8_t response[2]; // response is always 2 bytes
// test if the device is present
smbus_master_command_read(sensor_mlx90614_slave_addr, 0x20 | 0x0e, response, LENGTH(response)); // I don't know whey, but the PEC for the first transfer is erroneous, maybe because it has some old bits in the calculation buffer
const enum smbus_master_rc rc = smbus_master_command_read(sensor_mlx90614_slave_addr, 0x20 | 0x0e, response, LENGTH(response)); // read its own slave address
if (SMBUS_MASTER_RC_NONE != rc) { // read failed
return false; // could not read from SMBus device
}
if (sensor_mlx90614_slave_addr != response[0]) { // the used slave address does not match the one in the ROM
return false; // this is probably not a MLX90614
}
return true;
}
float sensor_mlx90614_temperature_ambient(void)
{
uint8_t response[2]; // response is always 2 bytes
const enum smbus_master_rc rc = smbus_master_command_read(sensor_mlx90614_slave_addr, 0x00 | 0x06, response, LENGTH(response)); // read Ta from RAM
if (SMBUS_MASTER_RC_NONE != rc) { // read failed
return NAN;
}
const uint16_t temp = response[0] + (response[1] << 8); // low byte is transferred first
return temp * 0.02 - 273.15; // calculated temperature according to specification 7.7.1
}
float sensor_mlx90614_temperature_object(void)
{
uint8_t response[2]; // response is always 2 bytes
const enum smbus_master_rc rc = smbus_master_command_read(sensor_mlx90614_slave_addr, 0x00 | 0x07, response, LENGTH(response)); // read Tobj1 from RAM
if (SMBUS_MASTER_RC_NONE != rc) { // read failed
return NAN;
}
const uint16_t temp = response[0] + (response[1] << 8); // low byte is transferred first
return temp * 0.02 - 273.15; // calculated temperature according to specification 7.7.1
}

27
lib/sensor_mlx90614.h Normal file
View File

@ -0,0 +1,27 @@
/** library to communicate with MLX90614 infra-red thermometer
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: GPIO @ref sensor_mlx90614_gpio, I2C @ref i2c_master_i2c
* @note this library only uses the I²C interface, not the PWM output
*/
#pragma once
/** setup I²C bus to communicated with MLX90614 Infra-Red thermometer
* @param[in] slave_addr I²C slave address of MLX90614 device (least significant 7-bit, 0xff for default)
* @return if the display setup is successful, else the display is probably not on the I²C bus
* @warning only one display on the I²C bus is currently supported
* @note I²C frequency is 100 kHz
*/
bool sensor_mlx90614_setup(uint8_t slave_addr);
/** get ambient temperature of sensor
* @return ambient temperature in °C
* @note uses internal thermistor
*/
float sensor_mlx90614_temperature_ambient(void);
/** get measured object temperature
* @return object temperature in °C
* @note uses IR sensor
*/
float sensor_mlx90614_temperature_object(void);

View File

@ -6,6 +6,7 @@
* @note peripherals used: USART @ref sensor_pzem_usart, timer @ref sensor_pzem_timer
*/
#pragma once
#error not converted for STM32F4
/** a measurement response has been received */
extern volatile bool sensor_pzem_measurement_received;

View File

@ -6,6 +6,7 @@
* @note peripherals used: USART @ref sensor_sdm120_usart , GPIO @ref sensor_sdm120_gpio , timer @ref sensor_sdm120_timer
*/
#pragma once
#error not converted for STM32F4
/** a measurement response has been received */
extern volatile bool sensor_sdm120_measurement_received;

139
lib/sensor_sr04.c Normal file
View File

@ -0,0 +1,139 @@
/** library to determine range using HC-SR04 ultrasonic range sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: timer @ref sensor_sr04_timer, GPIO @ref sensor_sr04_gpio
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
/* own libraries */
#include "global.h" // common methods
#include "sensor_sr04.h" // own definitions
/** @defgroup sensor_sr04_timer timer used to measure echo response length
* @{
*/
#define SENSOR_SR04_TIMER 11 /**< timer ID peripheral peripheral */
#define SENSOR_SR04_CHANNEL 1 /**< timer channel used for echo input capture */
#define SENSOR_SR04_AF GPIO_AF3 /**< timer alternate function for the capture channel */
//PB9 TIM11_CH1
/** @} */
/** @defgroup sensor_sr04_gpio GPIO used to trigger measurement and get echo
* @{
*/
#define SENSOR_SR04_TRIGGER PB8 /**< GPIO used to trigger measurement (pulled up to 5V by HC-SR04 module) */
#define SENSOR_SR04_ECHO PB9 /**< GPIO used to get echo (active high), must be an timer input capture */
/** @} */
volatile uint16_t sensor_sr04_distance = 0;
/** if an echo has been received */
static volatile bool sensor_sr04_echo = false;
void sensor_sr04_setup(void)
{
// setup trigger GPIO
rcc_periph_clock_enable(GPIO_RCC(SENSOR_SR04_TRIGGER)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_TRIGGER)); // set pin as output
gpio_set_output_options(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN(SENSOR_SR04_TRIGGER)); // set pin output as open-drain
gpio_clear(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // idle low
// setup echo GPIO
rcc_periph_clock_enable(GPIO_RCC(SENSOR_SR04_ECHO)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_ECHO), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_ECHO)); // set pin to alternate function input capture
gpio_set_af(GPIO_PORT(SENSOR_SR04_ECHO), SENSOR_SR04_AF, GPIO_PIN(SENSOR_SR04_ECHO)); // set alternate function for input capture
// setup timer
rcc_periph_clock_enable(RCC_TIM(SENSOR_SR04_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_SR04_TIMER)); // reset timer peripheral to default
// the timers use clock from APB1 or APB2 (see memory map to figure out), derivate from AHB
// in case of STM32F401, AHB is max. 84 MHz, APB1 is max. AHB / 2 = 42 MHz but the clock is twice the speed thus 84 MHz, APB2 is max. AHB = 84 MHz
// for simplicity we use AHB for the calculation, but in fact it could be more complicated
timer_set_prescaler(TIM(SENSOR_SR04_TIMER), 36 - 1); // set prescaler so we can measure up to 28 ms (1.0 / (84E6 / 36) * 2**16 = 28.1 ms), this above the maximum distance 400 cm (400 cm * 58 us/cm ) = 23200 us, datasheet says the maximum is 25 ms)
timer_set_mode(TIM(SENSOR_SR04_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // count up
timer_enable_update_event(TIM(SENSOR_SR04_TIMER)); // use update event to catch the overflow
timer_update_on_overflow(TIM(SENSOR_SR04_TIMER)); // only set update event on overflow
timer_one_shot_mode(TIM(SENSOR_SR04_TIMER)); // stop running after overflow
timer_set_period(TIM(SENSOR_SR04_TIMER), UINT16_MAX); // use full range
// setup input capture
timer_ic_set_input(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_IN_TI(SENSOR_SR04_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_prescaler(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_ic_enable(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL)); // enable input capture
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear update flag
timer_enable_irq(TIM(SENSOR_SR04_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_SR04_TIMER), TIM_DIER_CCIE(SENSOR_SR04_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_SR04_TIMER)); // catch interrupt in service routine
}
void sensor_sr04_release(void)
{
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_TRIGGER)); // release pin
rcc_periph_reset_pulse(RST_TIM(SENSOR_SR04_TIMER)); // reset timer peripheral to default
rcc_periph_clock_disable(RCC_TIM(SENSOR_SR04_TIMER)); // enable clock for timer peripheral
}
void sensor_sr04_trigger(void)
{
// prepare timer
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable timer to prepare it
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear update flag
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
timer_set_counter(TIM(SENSOR_SR04_TIMER), 0); // reset counter
timer_generate_event(TIM(SENSOR_SR04_TIMER), TIM_EGR_UG); // clear shadow register
timer_ic_set_polarity(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_RISING); // capture on incoming echo (rising edge)
sensor_sr04_echo = false; // echo has not been received yet
// send pulse to trigger measurement
gpio_set(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // start pull
sleep_us(10 + 1); // pulse duration
gpio_clear(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // end pulse
// start timer to measure the echo (the rest is done in the ISR)
timer_enable_counter(TIM(SENSOR_SR04_TIMER)); // start timer
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_SR04_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF)) { // timeout reached
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear flag
if (!sensor_sr04_echo) { // no echo has been received
sensor_sr04_distance = 1; // provide measurement to user: object is probably too near
} else {
sensor_sr04_distance = UINT16_MAX; // provide measurement to user: object is probably too far
}
} else if (timer_get_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL))) { // edge detected on input capture
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
if (!sensor_sr04_echo) { // echo is incoming
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable while reconfiguring
timer_set_counter(TIM(SENSOR_SR04_TIMER), 0); // reset timer counter
timer_generate_event(TIM(SENSOR_SR04_TIMER), TIM_EGR_UG); // clear shadow register
timer_ic_set_polarity(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_FALLING); // capture on end of echo (falling edge)
timer_enable_counter(TIM(SENSOR_SR04_TIMER)); // re-enable
sensor_sr04_echo = true; // remember echo is incoming
} else { // end of echo
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable counter now that measurement is complete
const uint16_t count = TIM_CCR(SENSOR_SR04_TIMER,SENSOR_SR04_CHANNEL); // save captured bit timing (this clear also the flag)
sensor_sr04_distance =(count * (TIM_PSC(TIM(SENSOR_SR04_TIMER)) + 1) * 343 / 2) / (rcc_ahb_frequency / 1000); // calculate distance and provide result to user
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

26
lib/sensor_sr04.h Normal file
View File

@ -0,0 +1,26 @@
/** library to determine range using HC-SR04 ultrasonic range sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: timer @ref sensor_sr04_timer, GPIO @ref sensor_sr04_gpio
*/
#pragma once
/** distance in mm once an echo has been received
* @note distance set 1 when the object is too near (no echo received)
* @note distance set UINT16_MAX when the object is too far (echo received to late)
* @warning to be clear by user
*/
extern volatile uint16_t sensor_sr04_distance;
/** configure MCU peripherals to communicate with HC-SR04 sensor
*/
void sensor_sr04_setup(void);
/** configure MCU peripherals used to communicate with HC-SR04 sensor
*/
void sensor_sr04_release(void);
/** trigger measurement
* @note the resulting measurement will be set in sensor_sr04_distance
*/
void sensor_sr04_trigger(void);

502
lib/smbus_master.c Normal file
View File

@ -0,0 +1,502 @@
/** library to communicate using SMBus as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: I²C/SMBus @ref smbus_master_i2c
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/cm3/systick.h> // SysTick library
#include <libopencm3/cm3/assert.h> // assert utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/i2c.h> // SMBus library
/* own libraries */
#include "global.h" // global utilities
#include "smbus_master.h" // SMBus header and definitions
/** @defgroup smbus_master_i2c I²C/SMBus peripheral used for SMBus communication
* @{
*/
#define SMBUS_MASTER_I2C 3 /**< I²C peripheral ID */
#define SMBUS_MASTER_SCL PA8 /**< GPIO pin for SMBus SCL */
#define SMBUS_MASTER_SCL_AF GPIO_AF4 /**< GPIO pin alternate funtion for SMBus SCL */
#define SMBUS_MASTER_SDA PB4 /**< GPIO pin for SMBus SDA */
#define SMBUS_MASTER_SDA_AF GPIO_AF9 /**< GPIO pin alternate funtion for SMBus SDA */
/** @} */
/** if Packet Error Code is used */
static bool smbus_master_pec = false;
void smbus_master_setup(uint8_t frequency, bool pec)
{
// configure SMBus peripheral
rcc_periph_clock_enable(GPIO_RCC(SMBUS_MASTER_SCL)); // enable clock for SMBus I/O peripheral
gpio_set(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_PIN(SMBUS_MASTER_SCL)); // already put signal high to avoid small pulse
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SMBUS_MASTER_SCL)); // set SCL pin to alternate function
gpio_set_output_options(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(SMBUS_MASTER_SCL)); // set SCL pin output as open-drain
gpio_set_af(GPIO_PORT(SMBUS_MASTER_SCL), SMBUS_MASTER_SCL_AF, GPIO_PIN(SMBUS_MASTER_SCL)); // set alternate function to SMBus SCL pin
rcc_periph_clock_enable(GPIO_RCC(SMBUS_MASTER_SDA)); // enable clock for SMBus I/O peripheral
gpio_set(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_PIN(SMBUS_MASTER_SDA)); // already put signal high to avoid small pulse
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SMBUS_MASTER_SDA)); // set SDA pin to alternate function
gpio_set_output_options(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(SMBUS_MASTER_SDA)); // set SDA pin output as open-drain
gpio_set_af(GPIO_PORT(SMBUS_MASTER_SDA), SMBUS_MASTER_SDA_AF, GPIO_PIN(SMBUS_MASTER_SDA)); // set alternate function to SMBus SDA pin
rcc_periph_clock_enable(RCC_I2C(SMBUS_MASTER_I2C)); // enable clock for SMBus peripheral
i2c_reset(I2C(SMBUS_MASTER_I2C)); // reset peripheral domain
i2c_peripheral_disable(I2C(SMBUS_MASTER_I2C)); // SMBus needs to be disable to be configured
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_SWRST; // reset peripheral
I2C_CR1(I2C(SMBUS_MASTER_I2C)) &= ~I2C_CR1_SWRST; // clear peripheral reset
if (frequency < 10) { // enforce minimum SMBus frequency
frequency = 10;
} else if (frequency > 100) { // enforce maximum SMBus frequency
frequency = 100;
}
i2c_set_clock_frequency(I2C(SMBUS_MASTER_I2C), rcc_apb1_frequency / 1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
// use standard mode for frequencies below 100 kHz
i2c_set_standard_mode(I2C(SMBUS_MASTER_I2C)); // set standard mode (Sm)
i2c_set_ccr(I2C(SMBUS_MASTER_I2C), rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency of 100 kHz
i2c_set_trise(I2C(SMBUS_MASTER_I2C), (1000 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_SMBUS; // set I²C peripheral in SMBus mode (not host type, not using ARP)
smbus_master_pec = pec; // remember if PEC is used
if (smbus_master_pec) {
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_ENPEC; // enable PEC calculation
}
i2c_peripheral_enable(I2C(SMBUS_MASTER_I2C)); // enable SMBus after configuration completed
}
void smbus_master_release(void)
{
i2c_reset(I2C(SMBUS_MASTER_I2C)); // reset SMBus peripheral configuration
i2c_peripheral_disable(I2C(SMBUS_MASTER_I2C)); // disable SMBus peripheral
rcc_periph_clock_disable(RCC_I2C(SMBUS_MASTER_I2C)); // disable clock for SMBus peripheral
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(SMBUS_MASTER_SCL)); // set SCL pin back to input
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(SMBUS_MASTER_SDA)); // set SDA pin back to input
}
bool smbus_master_check_signals(void)
{
// enable GPIOs to read SDA and SCL
rcc_periph_clock_enable(GPIO_RCC(SMBUS_MASTER_SDA)); // enable clock for SMBus I/O peripheral
rcc_periph_clock_enable(GPIO_RCC(SMBUS_MASTER_SCL)); // enable clock for SMBus I/O peripheral
// pull SDA and SDC low to check if there are pull-up resistors
const uint32_t sda_moder = GPIO_MODER(GPIO_PORT(SMBUS_MASTER_SDA)); // backup port configuration
const uint32_t sda_pupdr = GPIO_PUPDR(GPIO_PORT(SMBUS_MASTER_SDA)); // backup port configuration
const uint32_t scl_moder = GPIO_MODER(GPIO_PORT(SMBUS_MASTER_SCL)); // backup port configuration
const uint32_t scl_pupdr = GPIO_PUPDR(GPIO_PORT(SMBUS_MASTER_SCL)); // backup port configuration
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(SMBUS_MASTER_SDA)); // set SDA to input and pull down (weak) to check if there is an external pull up (strong)
gpio_mode_setup(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(SMBUS_MASTER_SCL)); // set SCL to input and pull down (weak) to check if there is an external pull up (strong)
sleep_us(100); // let signal settle
const bool to_return = (gpio_get(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_PIN(SMBUS_MASTER_SCL)) && gpio_get(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_PIN(SMBUS_MASTER_SDA))); // check if the signals are still pulled high by external stronger pull-up resistors
GPIO_MODER(GPIO_PORT(SMBUS_MASTER_SDA)) = sda_moder; // restore port configuration
GPIO_PUPDR(GPIO_PORT(SMBUS_MASTER_SDA)) = sda_pupdr; // restore port configuration
GPIO_MODER(GPIO_PORT(SMBUS_MASTER_SCL)) = scl_moder; // restore port configuration
GPIO_PUPDR(GPIO_PORT(SMBUS_MASTER_SCL)) = scl_pupdr; // restore port configuration
return to_return;
}
void smbus_master_reset(void)
{
i2c_peripheral_disable(I2C(SMBUS_MASTER_I2C)); // disable SMBus peripheral
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_SWRST; // reset device
I2C_CR1(I2C(SMBUS_MASTER_I2C)) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(I2C(SMBUS_MASTER_I2C)); // re-enable device
}
enum smbus_master_rc smbus_master_start(void)
{
bool retry = true; // retry after reset if first try failed
enum smbus_master_rc to_return; // return code
uint16_t sr1; // read register once, since reading/writing other registers or other events clears some flags
try:
to_return = SMBUS_MASTER_RC_NONE; // return code
// send (re-)start condition
if (I2C_CR1(I2C(SMBUS_MASTER_I2C)) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
return SMBUS_MASTER_RC_START_STOP_IN_PROGESS;
}
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of SMBus peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
systick_set_frequency(500, rcc_ahb_frequency); // set timer to 2 ms (that should be long enough to send a start condition)
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
i2c_send_start(I2C(SMBUS_MASTER_I2C)); // send start condition to start transaction
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(I2C(SMBUS_MASTER_I2C)) & I2C_CR1_START) && !((sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C))) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until start condition has been accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C)); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = SMBUS_MASTER_RC_BUS_ERROR;
}
while (!((sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C))) & (I2C_SR1_SB | I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout && SMBUS_MASTER_RC_NONE == to_return) { // wait until start condition is transmitted
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C)); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
to_return = SMBUS_MASTER_RC_BUS_ERROR;
} else if (!(sr1 & I2C_SR1_SB)) { // the start bit has not been set although we the peripheral is not busy anymore
to_return = SMBUS_MASTER_RC_BUS_ERROR;
} else if (!(sr1 & I2C_SR2_MSL)) { // verify if in master mode
to_return = SMBUS_MASTER_RC_NOT_MASTER;
} else if (timeout) { // timeout has been reached, i.e. the peripheral hangs
to_return = SMBUS_MASTER_RC_NOT_MASTER;
}
if (SMBUS_MASTER_RC_NOT_MASTER == to_return && retry) { // error happened
retry = false; // don't retry a second time
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(I2C(SMBUS_MASTER_I2C)) &= ~I2C_CR1_SWRST; // release peripheral reset
goto try;
}
systick_counter_disable(); // we don't need to timer anymore
return to_return;
}
/** wait until stop is sent and bus is released
* @return SMBus return code
*/
static enum smbus_master_rc smbus_master_wait_stop(void)
{
enum smbus_master_rc to_return = SMBUS_MASTER_RC_NONE; // return code
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of SMBus peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
systick_set_frequency(500, rcc_ahb_frequency); // set timer to 2 ms (that should be long enough to send a stop condition)
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(I2C(SMBUS_MASTER_I2C)) & I2C_CR1_STOP) && !(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until stop condition is accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = SMBUS_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(I2C(SMBUS_MASTER_I2C)) & I2C_SR2_MSL) && !(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until bus released (non master mode)
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = SMBUS_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(I2C(SMBUS_MASTER_I2C)) & I2C_SR2_BUSY) && !(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR)) && !timeout) { // wait until peripheral is not busy anymore
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = SMBUS_MASTER_RC_BUS_ERROR;
}
while ((0 == gpio_get(GPIO_PORT(SMBUS_MASTER_SCL), GPIO_PIN(SMBUS_MASTER_SCL)) || 0 == gpio_get(GPIO_PORT(SMBUS_MASTER_SDA), GPIO_PIN(SMBUS_MASTER_SDA))) && !timeout) { // wait until lines are really high again
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (timeout) { // I2C_CR1_STOP could also be used to detect a timeout, but I'm not sure when
if (SMBUS_MASTER_RC_NONE == to_return) {
to_return = SMBUS_MASTER_RC_TIMEOUT; // indicate timeout only when no more specific error has occurred
}
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(I2C(SMBUS_MASTER_I2C)) &= ~I2C_CR1_SWRST; // release peripheral reset
}
systick_counter_disable(); // we don't need to timer anymore
return to_return;
}
enum smbus_master_rc smbus_master_stop(void)
{
// sanity check
if (!(I2C_SR2(I2C(SMBUS_MASTER_I2C)) & I2C_SR2_BUSY)) { // release if not busy
return SMBUS_MASTER_RC_NONE; // bus has probably already been released
}
if (I2C_CR1(I2C(SMBUS_MASTER_I2C)) & (I2C_CR1_START)) { // ensure start operation is not in progress
return SMBUS_MASTER_RC_START_STOP_IN_PROGESS; // the stop is sent after a
}
if (!((I2C_SR2(I2C(SMBUS_MASTER_I2C)) & I2C_SR2_TRA))) { // if we are in receiver mode
i2c_disable_ack(I2C(SMBUS_MASTER_I2C)); // disable ACK to be able to close the communication
}
if (!(I2C_CR1(I2C(SMBUS_MASTER_I2C)) & (I2C_CR1_STOP))) { // only send start if not already in progress
i2c_send_stop(I2C(SMBUS_MASTER_I2C)); // send stop to release bus
}
return smbus_master_wait_stop();
}
enum smbus_master_rc smbus_master_select_slave(uint8_t slave, bool write)
{
enum smbus_master_rc rc = SMBUS_MASTER_RC_NONE; // to store SMBus return codes
uint16_t sr1, sr2; // read register once, since reading/writing other registers or other events clears some flags
if (!((sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C))) & I2C_SR1_SB)) { // start condition has not been sent
rc = smbus_master_start(); // send start condition
if (SMBUS_MASTER_RC_NONE != rc) {
return rc;
}
}
if (!((sr2 = I2C_SR2(I2C(SMBUS_MASTER_I2C))) & I2C_SR2_MSL)) { // SMBus device is not in master mode
return SMBUS_MASTER_RC_NOT_MASTER;
}
// select slave
I2C_SR1(I2C(SMBUS_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
i2c_send_7bit_address(I2C(SMBUS_MASTER_I2C), slave, write ? I2C_WRITE : I2C_READ); // select slave, with read/write flag
while (!((sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C))) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return SMBUS_MASTER_RC_BUS_ERROR;
}
if (sr1 & I2C_SR1_AF) { // address has not been acknowledged
return SMBUS_MASTER_RC_NAK;
}
// do not check I2C_SR2_TRA to verify if we really are in transmit or receive mode since reading SR2 also clears ADDR and starting the read/write transaction
return SMBUS_MASTER_RC_NONE;
}
enum smbus_master_rc smbus_master_read(uint8_t* data, size_t data_size)
{
// sanity check
if (NULL == data || 0 == data_size) { // no data to read
return SMBUS_MASTER_RC_NONE;
}
if (smbus_master_pec && SIZE_MAX - 1 < data_size) { // too much data to send (we need one more byte to send the PEC)
return SMBUS_MASTER_RC_OTHER;
}
// SMBus start condition check
uint16_t sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C)); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return SMBUS_MASTER_RC_NOT_READY;
}
if (sr1 & I2C_SR1_AF) { // check if the previous transaction went well
return SMBUS_MASTER_RC_NOT_READY;
}
// prepare (N)ACK (EV6_3 in RM0008)
if (1 == data_size) {
i2c_disable_ack(I2C(SMBUS_MASTER_I2C)); // NACK after first byte
} else {
i2c_enable_ack(I2C(SMBUS_MASTER_I2C)); // NAK after next byte
}
uint16_t sr2 = I2C_SR2(I2C(SMBUS_MASTER_I2C)); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // SMBus device is not master
return SMBUS_MASTER_RC_NOT_MASTER;
}
if ((sr2 & I2C_SR2_TRA)) { // SMBus device not in receiver mode
return SMBUS_MASTER_RC_NOT_RECEIVE;
}
// read data
if (smbus_master_pec) { // we want to read the PEC
data_size++; // add one byte to read the PEC
I2C_SR1(I2C(SMBUS_MASTER_I2C)) &= ~I2C_SR1_PECERR; // clear flag
}
for (size_t i = 0; i < data_size; i++) { // read bytes
// set (N)ACK (EV6_3, EV6_1)
if (1 == data_size - i) { // prepare to sent NACK for last byte
i2c_send_stop(I2C(SMBUS_MASTER_I2C)); // already indicate we will send a stop (required to not send an ACK, and this must happen before the byte is transferred, see errata)
if (smbus_master_pec) {
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_PEC; // prepare to receive PEC
}
i2c_nack_current(I2C(SMBUS_MASTER_I2C)); // (N)ACK current byte
i2c_disable_ack(I2C(SMBUS_MASTER_I2C)); // NACK received byte to stop slave transmission
} else if (2 == data_size - i) { // prepare to sent NACK for second last byte
i2c_nack_next(I2C(SMBUS_MASTER_I2C)); // NACK next byte
i2c_disable_ack(I2C(SMBUS_MASTER_I2C)); // NACK received byte to stop slave transmission
} else {
i2c_enable_ack(I2C(SMBUS_MASTER_I2C)); // ACK received byte to continue slave transmission
}
while (!((sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C))) & (I2C_SR1_RxNE | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been received
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
return SMBUS_MASTER_RC_BUS_ERROR;
}
if (!smbus_master_pec || i < data_size - 1) { // don't save the PEC
data[i] = i2c_get_data(I2C(SMBUS_MASTER_I2C)); // read received byte
}
}
if (smbus_master_pec) { // check if the PEC is correct
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & I2C_SR1_PECERR) {
return SMBUS_MASTER_RC_PECERR;
}
}
return smbus_master_stop();
}
enum smbus_master_rc smbus_master_write(const uint8_t* data, size_t data_size)
{
// sanity check
if (NULL == data || 0 == data_size) { // no data to write
return SMBUS_MASTER_RC_NONE;
}
// SMBus start condition check
uint16_t sr1 = I2C_SR1(I2C(SMBUS_MASTER_I2C)); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return SMBUS_MASTER_RC_NOT_READY;
}
if (sr1 & I2C_SR1_AF) { // check if the previous transaction went well
return SMBUS_MASTER_RC_NOT_READY;
}
// master check
uint16_t sr2 = I2C_SR2(I2C(SMBUS_MASTER_I2C)); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // SMBus device is not master
return SMBUS_MASTER_RC_NOT_MASTER;
}
if (!(sr2 & I2C_SR2_TRA)) { // SMBus device not in transmitter mode
return SMBUS_MASTER_RC_NOT_TRANSMIT;
}
// write data
for (size_t i = 0; i < data_size; i++) { // write bytes
I2C_SR1(I2C(SMBUS_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
i2c_send_data(I2C(SMBUS_MASTER_I2C), data[i]); // send byte to be written in memory
while (!(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_TxE | I2C_SR1_AF)) && !(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been transmitted
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return SMBUS_MASTER_RC_BUS_ERROR;
}
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & I2C_SR1_AF) { // data has not been acknowledged
return SMBUS_MASTER_RC_NAK;
}
}
return SMBUS_MASTER_RC_NONE;
}
enum smbus_master_rc smbus_master_slave_read(uint8_t slave, uint8_t* data, size_t data_size)
{
enum smbus_master_rc rc = SMBUS_MASTER_RC_NONE; // to store SMBus return codes
rc = smbus_master_start(); // send (re-)start condition
if (SMBUS_MASTER_RC_NONE != rc) {
return rc;
}
rc = smbus_master_select_slave(slave, false); // select slave to read
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
if (NULL != data && data_size > 0) { // only read data if needed
rc = smbus_master_read(data, data_size); // read data (includes stop)
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
} else {
smbus_master_stop(); // sent stop condition
}
rc = SMBUS_MASTER_RC_NONE; // all went well
error:
if (SMBUS_MASTER_RC_NONE != rc) {
smbus_master_stop(); // sent stop condition
}
return rc;
}
enum smbus_master_rc smbus_master_slave_write(uint8_t slave, const uint8_t* data, size_t data_size)
{
enum smbus_master_rc rc = SMBUS_MASTER_RC_NONE; // to store SMBus return codes
rc = smbus_master_start(); // send (re-)start condition
if (SMBUS_MASTER_RC_NONE != rc) {
return rc;
}
// select slave to write
rc = smbus_master_select_slave(slave, true);
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
// write data only is some is available
if (NULL != data && data_size > 0) {
rc = smbus_master_write(data, data_size); // write data
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
}
// send optional PEC
if (smbus_master_pec) {
I2C_SR1(I2C(SMBUS_MASTER_I2C)) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_CR1(I2C(SMBUS_MASTER_I2C)) |= I2C_CR1_PEC; // start transmitting PEC
while ((I2C_CR1(I2C(SMBUS_MASTER_I2C)) & I2C_CR1_PEC) && // PEC is still being transmitted
!(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_AF)) && // no NAK received
!(I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO))); // no bus error received
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) { // bus error has been received
return SMBUS_MASTER_RC_BUS_ERROR;
}
if (I2C_SR1(I2C(SMBUS_MASTER_I2C)) & I2C_SR1_AF) { // no ACK received
return SMBUS_MASTER_RC_NAK;
}
}
rc = SMBUS_MASTER_RC_NONE; // all went well
error:
smbus_master_stop(); // sent stop condition
return rc;
}
enum smbus_master_rc smbus_master_command_read(uint8_t slave, uint8_t command, uint8_t* data, size_t data_size)
{
enum smbus_master_rc rc = SMBUS_MASTER_RC_NONE; // to store SMBus return codes
rc = smbus_master_start(); // send (re-)start condition
if (SMBUS_MASTER_RC_NONE != rc) {
return rc;
}
rc = smbus_master_select_slave(slave, true); // select slave to write
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
// write command
rc = smbus_master_write(&command, 1); // send memory address
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
// read data
if (NULL != data && data_size > 0) {
rc = smbus_master_start(); // send re-start condition
if (SMBUS_MASTER_RC_NONE != rc) {
return rc;
}
rc = smbus_master_select_slave(slave, false); // select slave to read
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
rc = smbus_master_read(data, data_size); // read memory (includes stop)
if (SMBUS_MASTER_RC_NONE != rc) {
goto error;
}
} else {
smbus_master_stop(); // sent stop condition
}
rc = SMBUS_MASTER_RC_NONE;
error:
if (SMBUS_MASTER_RC_NONE != rc) { // only send stop on error
smbus_master_stop(); // sent stop condition
}
return rc;
}
enum smbus_master_rc smbus_master_command_write(uint8_t slave, uint8_t command, const uint8_t* data, size_t data_size)
{
if (SIZE_MAX - 1 < data_size) { // prevent integer overflow
return SMBUS_MASTER_RC_OTHER;
}
if (data_size > 0 && NULL == data) {
return SMBUS_MASTER_RC_OTHER;
}
uint8_t buffer[1 + data_size]; // single buffer to write command and all data
buffer[0] = command; // save command in buffer
if (data) {
for (size_t i = 0; i < data_size; i++) {
buffer[1 + i] = data[i];
}
}
return smbus_master_slave_write(slave, buffer, 1 + data_size); // send command and data
}

112
lib/smbus_master.h Normal file
View File

@ -0,0 +1,112 @@
/** library to communicate using SMBus as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: I²C/SMBus @ref smbus_master_i2c
* @note SMBus functions not used: alert, ARP; SMBus functions used: PEC
*/
#pragma once
/** SMBus return codes */
enum smbus_master_rc {
SMBUS_MASTER_RC_NONE = 0, /**< no error */
SMBUS_MASTER_RC_START_STOP_IN_PROGESS, /**< a start or stop condition is already in progress */
SMBUS_MASTER_RC_NOT_MASTER, /**< not in master mode */
SMBUS_MASTER_RC_NOT_TRANSMIT, /**< not in transmit mode */
SMBUS_MASTER_RC_NOT_RECEIVE, /**< not in receive mode */
SMBUS_MASTER_RC_NOT_READY, /**< slave is not read (previous operations has been NACKed) */
SMBUS_MASTER_RC_NAK, /**< not acknowledge received */
SMBUS_MASTER_RC_PECERR, /**< device dais the PER is corrupted */
SMBUS_MASTER_RC_BUS_ERROR, /**< an error on the SMBus bus occurred */
SMBUS_MASTER_RC_TIMEOUT, /**< a timeout has occurred because an operation has not completed in the expected time */
SMBUS_MASTER_RC_OTHER, /** any other error (does not have to be SMBus related) */
};
/** setup SMBus peripheral
* @param[in] frequency frequency to use in kHz (10-100)
* @param[in] pec if Packet Error Code is used
*/
void smbus_master_setup(uint8_t frequency, bool pec);
/** release SMBus peripheral
*/
void smbus_master_release(void);
/** reset SMBus peripheral, fixing any locked state
* @warning the SMBus peripheral needs to be re-setup
* @note to be used after failed start or stop, and bus error
*/
void smbus_master_reset(void);
/** check if SDA and SCL signals are pulled up externally
* @return SDA and SCL signals are pulled up externally
*/
bool smbus_master_check_signals(void);
/** send start condition
* @return SMBus return code
*/
enum smbus_master_rc smbus_master_start(void);
/** select SMBus slave device
* @warning a start condition should be sent before this operation
* @param[in] slave SMBus 7-bit address of slave device to select
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
* @note 10-bit address are not specified by SMBus 3.1
* @return SMBus return code
*/
enum smbus_master_rc smbus_master_select_slave(uint8_t slave, bool write);
/** read data over SMBus
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return SMBus return code
* @warning the slave device must be selected before this operation
* @note a stop condition will be sent at the end (I²C peripheral does not permit multiple reads, and this is necessary for 1-byte transfer)
*/
enum smbus_master_rc smbus_master_read(uint8_t* data, size_t data_size);
/** write data over SMBus
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return SMBus return code
* @warning the slave device must be selected before this operation
* @note no stop condition or optional PEC are sent at the end, allowing multiple writes
*/
enum smbus_master_rc smbus_master_write(const uint8_t* data, size_t data_size);
/** sent stop condition
* @return SMBus return code
*/
enum smbus_master_rc smbus_master_stop(void);
/** read data from slave device
* @param[in] slave SMBus 7-bit address of slave device to select
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return SMBus return code
* @note start and stop conditions, and optional PEC are included
* @note 10-bit address are not specified by SMBus 3.1
*/
enum smbus_master_rc smbus_master_slave_read(uint8_t slave, uint8_t* data, size_t data_size);
/** write data to slave device
* @param[in] slave SMBus 7-bit address of slave device to select
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return SMBus return code
* @note start and stop conditions, and optional PEC are included
* @note 10-bit address are not specified by SMBus 3.1
*/
enum smbus_master_rc smbus_master_slave_write(uint8_t slave, const uint8_t* data, size_t data_size);
/** read data after sending read command from on an SMBus memory slave
* @param[in] slave SMBus 7-bit address of slave device to select
* @param[in] command command code
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return SMBus return code
* @note start and stop conditions, and optional PEC are included
* @note 10-bit address are not specified by SMBus 3.1
*/
enum smbus_master_rc smbus_master_command_read(uint8_t slave, uint8_t command, uint8_t* data, size_t data_size);
/** write data after sending write command on an SMBus memory slave
* @param[in] slave SMBus 7-bit address of slave device to select
* @param[in] command command code
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return SMBus return code
* @note start and stop conditions, and optional PEC are included
* @note 10-bit address are not specified by SMBus 3.1
*/
enum smbus_master_rc smbus_master_command_write(uint8_t slave, uint8_t command, const uint8_t* data, size_t data_size);

468
lib/swd.c Normal file
View File

@ -0,0 +1,468 @@
/** library for Serial Wire Debug (SWD) communication
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018-2021
* @note peripherals used: timer @ref swd_timer, GPIO @ref swd_gpio
* @implements "ARM Debug Interface Architecture Specification ADIv6.0" (ARM IHI 0074A)
* @note this library implements DP architecture version 3 (DPv3), but only DPv1 feature could be tested.
* @implements The physical layer (electrical characteristic and timing) is described in "ARM DSTREAM System and Interface Design Reference Guide" (ARM DUI0499K)
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean type
#include <stddef.h> // NULL definition
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer library
/* own libraries */
#include "global.h" // helper macros
#include "swd.h" // own definitions
/** @defgroup swd_gpio GPIO used for SWDIO and SWCLK
* @{
*/
#define SWD_SWCLK_PIN PB10 /**< default GPIO pin for clock signal */
#define SWD_SWDIO_PIN PB2 /**< default GPIO pin for data input/output signal */
static uint32_t swd_swclk_rcc = GPIO_RCC(SWD_SWCLK_PIN);
static uint32_t swd_swclk_port = GPIO_PORT(SWD_SWCLK_PIN);
static uint32_t swd_swclk_pin = GPIO_PIN(SWD_SWCLK_PIN);
static uint32_t swd_swdio_rcc = GPIO_RCC(SWD_SWDIO_PIN);
static uint32_t swd_swdio_port = GPIO_PORT(SWD_SWDIO_PIN);
static uint32_t swd_swdio_pin = GPIO_PIN(SWD_SWDIO_PIN);
/** @} */
/** @defgroup swd_timer timer used to generate a periodic clock
* @note the clock does not actually need to be periodic (it makes it just more readable)
* @{
*/
#define SWD_TIMER 2 /**< timer ID */
/** @} */
static volatile uint64_t swd_bits_out = 0; /** a buffer for the data bits to output/write (LSb first) */
static volatile uint64_t swd_bits_in = 0; /** a buffer for the data bits to input/read (LSB first) */
static volatile uint8_t swd_bits_count = 0; /** number of bits to read/write */
static volatile uint8_t swd_bits_i = 0; /** transaction bit index */
void swd_setup(uint32_t freq)
{
// setup GPIO
swd_set_pins(swd_swclk_port, swd_swclk_pin, swd_swdio_port, swd_swdio_pin);
// setup timer to generate periodic clock
rcc_periph_clock_enable(RCC_TIM(SWD_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SWD_TIMER)); // reset timer state
timer_disable_counter(TIM(SWD_TIMER)); // disable timer to configure it
timer_set_mode(TIM(SWD_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(SWD_TIMER), 1 - 1); // don't use prescaler, allowing period down to 72 MHz / 2^16 = 1099 Hz
uint32_t period = rcc_ahb_frequency / freq / 2; // get period based on frequency (/2 because on cycle has 2 edges)
if (period > 0xffff) { // maximum frequency reached
period = 0xffff;
} else if (period > 0) { // not minimum frequency reached
period -= 1; // correct timer overflow offset
}
timer_set_period(TIM(SWD_TIMER), period); // set period the generate clock frequency (x2 for the two edges)
timer_clear_flag(TIM(SWD_TIMER), TIM_SR_UIF); // clear update (overflow) flag
timer_update_on_overflow(TIM(SWD_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SWD_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow to generate the clock signal
nvic_enable_irq(NVIC_TIM_IRQ(SWD_TIMER)); // catch interrupt in service routine
// reset variables
swd_bits_count = 0;
}
/** release used pins */
void swd_release_pins(void)
{
// release GPIO
gpio_mode_setup(swd_swclk_port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, swd_swclk_pin); // put clock signal pin back to input floating
gpio_mode_setup(swd_swdio_port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, swd_swdio_pin); // put data signal pin back to input floating
}
void swd_release(void)
{
// release timer
timer_disable_counter(TIM(SWD_TIMER)); // disable timer
rcc_periph_reset_pulse(RST_TIM(SWD_TIMER)); // reset timer state
rcc_periph_clock_disable(RCC_TIM(SWD_TIMER)); // disable clock for timer peripheral
swd_release_pins(); // release GPIO
}
bool swd_set_pins(uint32_t swclk_port, uint32_t swclk_pin, uint32_t swdio_port, uint32_t swdio_pin)
{
// check if pin exists and is unique
if (__builtin_popcount(swclk_pin) != 1 || __builtin_popcount(swdio_pin) != 1) {
return false;
}
// check if pin really exists
if (swclk_pin > (1 << 15) || swdio_pin > (1 << 15)) {
return false;
}
// ensure pin is different
if (swclk_port == swdio_port && swclk_pin == swdio_pin) {
return false;
}
uint32_t swclk_rcc = 0;
switch (swclk_port) {
case GPIOA:
swclk_rcc = RCC_GPIOA;
break;
case GPIOB:
swclk_rcc = RCC_GPIOB;
break;
case GPIOC:
swclk_rcc = RCC_GPIOC;
break;
case GPIOD:
swclk_rcc = RCC_GPIOD;
break;
case GPIOE:
swclk_rcc = RCC_GPIOE;
break;
case GPIOF:
swclk_rcc = RCC_GPIOF;
break;
case GPIOG:
swclk_rcc = RCC_GPIOG;
break;
default: // unknown port
return false;
}
uint32_t swdio_rcc = 0;
switch (swdio_port) {
case GPIOA:
swdio_rcc = RCC_GPIOA;
break;
case GPIOB:
swdio_rcc = RCC_GPIOB;
break;
case GPIOC:
swdio_rcc = RCC_GPIOC;
break;
case GPIOD:
swdio_rcc = RCC_GPIOD;
break;
case GPIOE:
swdio_rcc = RCC_GPIOE;
break;
case GPIOF:
swdio_rcc = RCC_GPIOF;
break;
case GPIOG:
swdio_rcc = RCC_GPIOG;
break;
default: // unknown port
return false;
}
swd_release_pins(); // release already set pins (not a problem even if not already used
// remember pins
swd_swclk_rcc = swclk_rcc;
swd_swclk_port = swclk_port;
swd_swclk_pin = swclk_pin;
swd_swdio_rcc = swdio_rcc;
swd_swdio_port = swdio_port;
swd_swdio_pin = swdio_pin;
// setup GPIO for clock and data signals
rcc_periph_clock_enable(swd_swclk_rcc); // enable clock for GPIO peripheral for clock signal
gpio_set(swd_swclk_port, swd_swclk_pin); // inactive clock is high
gpio_mode_setup(swd_swclk_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swclk_pin); // the host controls the clock
gpio_set_output_options(swd_swclk_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, swd_swclk_pin); // set SWCLK pin output as push-pull
rcc_periph_clock_enable(swd_swdio_rcc); // enable clock for GPIO peripheral for data signal
gpio_set(swd_swdio_port, swd_swdio_pin); // inactive data is high (resetting the target when clock is active)
gpio_mode_setup(swd_swdio_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swdio_pin); // the data signal is half duplex, with the host controlling who is driving the data signal when
gpio_set_output_options(swd_swdio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, swd_swdio_pin); // set SWDIO pin output as push-pull
return true;
}
/** perform SWD transaction (one sequence of bits read or write)
* @param[in] output bits to write (LSb first)
* @param[in] bit_count number of bits to read/write
* @param[in] write true for write transaction, false for read transaction
* @return the bits read
*/
uint64_t swd_transaction(uint64_t output, uint8_t bit_count, bool write)
{
// sanity check
if (0 == bit_count) {
return ~0ULL;
}
// initialize read/write
if (write) {
gpio_mode_setup(swd_swdio_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swdio_pin); // we drive the data signal to output data
swd_bits_out = output; // set data to output
} else {
gpio_mode_setup(swd_swdio_port, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, swd_swdio_pin); // the target will drive the data signal, we just pull it up
swd_bits_out = ~0ULL; // set the value so we pull up
}
swd_bits_in = 0; // reset input buffer
swd_bits_count = bit_count; // set the number of bits to read/write
swd_bits_i = 0; // reset read index
// start transaction
timer_enable_counter(TIM(SWD_TIMER)); // start timer
while (swd_bits_i < swd_bits_count) { // wait until all bits are transmitted
__WFI(); // go to sleep
}
return swd_bits_out;
}
void swd_line_reset(void)
{
swd_transaction(~0ULL, 50 + 2, true); // sent high for at least 50 cycle to issue line reset and put target in reset state
}
void swd_jtag_to_swd(void)
{
swd_transaction(0xE79E, 16, true); // the sequence is a constant magic value
}
void swd_swd_to_jtag(void)
{
swd_transaction(0xE73C, 16, true); // the sequence is a constant magic value
}
void swd_idle_cycles(uint8_t nb)
{
swd_transaction(0, nb, true); // idle has data signal low
}
void swd_packet_request(bool apndp, uint8_t a, bool rnw)
{
uint8_t request = (1 << 0) | (0 << 6) | (1 << 7); // start, stop, and park bits are set
if (apndp) {
request |= (1 << 1); // set APnDP bit
}
if (rnw) {
request |= (1 << 2); // set RnW bit
}
request |= ((a & 0xc) << 1); // set A[3:2]
static const bool parity_lut[] = {false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false}; // true if the number of 1's is a 4-bit value is odd, false else
if (parity_lut[(request >> 1) & 0xf]) {
request |= (1 << 5); // set even parity bit
}
swd_transaction(request, 8, true); // write packet request
}
void swd_turnaround(uint8_t nb)
{
swd_transaction(~0ULL, nb, false); // switch to input to read and pull up
}
enum swd_ack_e swd_acknowledge_response(void)
{
return swd_transaction(~0ULL, 3, false) & 0x7; // read 3 bits
}
void swd_write(uint32_t wdata)
{
swd_transaction(wdata | ((0 == __builtin_parity(wdata)) ? (0ULL << 32) : (1ULL << 32)), 33, true); // set parity and send data
}
bool swd_read(uint32_t* rdata)
{
uint64_t data = swd_transaction(~0UL, 33, false); // read 33 bits
*rdata = data; // return the 32 bits of read data
return ((0==__builtin_parity(*rdata)) ? (0ULL << 32) : (1ULL << 32)) == (data & (1ULL << 32)); // check parity
}
void swd_jtag_to_ds(void)
{
swd_transaction(~0ULL, 5, true); // place JTAG TAP in TLR state
swd_transaction(0x33BBBBBA, 21, true); // send JTAG-to-DS select sequence
}
void swd_swd_to_ds(void)
{
swd_line_reset(); // place SWD TAP is reset state
swd_transaction(0xE3BC, 16, true); // send SWD-to-DS select sequence
}
void swd_selection_alert(enum swd_activation_code_e activation_code)
{
swd_transaction(~0ULL, 5, true); // ensure selection alert is detected
swd_transaction(0x6209F392, 32, true); // send selection alert sequence (part 1)
swd_transaction(0x86852D95, 32, true); // send selection alert sequence (part 2)
swd_transaction(0xE3DDAFE9, 32, true); // send selection alert sequence (part 3)
swd_transaction(0x19BC0EA2, 32, true); // send selection alert sequence (part 4)
swd_transaction(0, 4, true); // send 4 low cycles
// send activation code, if known
switch (activation_code) {
case SWD_ACTIVATION_CODE_JTAG:
swd_transaction(0, 24, true);
break;
case SWD_ACTIVATION_CODE_SWDP:
swd_transaction(0x58, 16, true);
break;
case SWD_ACTIVATION_CODE_JTAGDP:
swd_transaction(0x50, 16, true);
break;
default:
break; // unknown, but let the user issue it using swd_transaction
}
}
/** name of the manufacturer corresponding to its bank and id
* @implements JEDEC JEP106
* @note auto-generated and provided by the OpenOCD project ( https://sourceforge.net/p/openocd/code/ci/master/tree/src/helper/jep106.inc?format=raw )
*/
static const char * const swd_jep106[][126] = {
#include "jep106.inc"
};
const char *swd_jep106_manufacturer(uint8_t bank, uint8_t id)
{
// check id
if (id < 1 || id > 126) {
return "<invalid>";
}
// index is zero based
id--;
// check band and name
if (bank >= LENGTH(swd_jep106) || 0 == swd_jep106[bank][id]) {
return "<unknown>";
}
return swd_jep106[bank][id];
}
static const struct swd_partno_s {
uint16_t designer;
uint16_t partno;
uint16_t partno_mask;
const char* name;
} swd_partno[] = {
// based on https://developer.arm.com/docs/103489943/latest/what-is-the-id-code-of-a-cortex-m0-dap-or-cortex-m0-dap
{
.designer = 0x23B, // ARM
.partno = 0xBA00,
.partno_mask = 0xFF00,
.name = "CM3DAP", // used in Cortex-M3 and Cortex-M4
},
{
.designer = 0x23B, // ARM
.partno = 0xBB00,
.partno_mask = 0xFF00,
.name = "CM0DAP", // used in Cortex-M0
},
{
.designer = 0x23B, // ARM
.partno = 0xBC00,
.partno_mask = 0xFF00,
.name = "CM0PDAP", // used in Cortex-M0+
},
// based on RM0008 Reference manual, STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs
{
.designer = 0x020, // ST
.partno = 0x6412,
.partno_mask = 0xFFFF,
.name = "STM32F10xxx low-density",
},
{
.designer = 0x020, // ST
.partno = 0x6410,
.partno_mask = 0xFFFF,
.name = "STM32F10xxx medium-density",
},
{
.designer = 0x020, // ST
.partno = 0x6414,
.partno_mask = 0xFFFF,
.name = "STM32F10xxx high-density",
},
{
.designer = 0x020, // ST
.partno = 0x6430,
.partno_mask = 0xFFFF,
.name = "STM32F10xxx XL-density",
},
{
.designer = 0x020, // ST
.partno = 0x6418,
.partno_mask = 0xFFFF,
.name = "STM32F10xxx connectivity line",
},
// based on RM0368 Reference manual, STM32F401xB/C and STM32F401xD/E advanced Arm®-based 32-bit MCUs
{
.designer = 0x020, // ST
.partno = 0x6423,
.partno_mask = 0xFFFF,
.name = "STM32F401xB/C",
},
{
.designer = 0x020, // ST
.partno = 0x6433,
.partno_mask = 0xFFFF,
.name = "STM32F401xD/E",
},
};
const char* swd_dpidr_partno(uint16_t designer, uint16_t partno)
{
uint32_t i = 0; // swd_partno index
// find matching part number
for (i = 0; i < LENGTH(swd_partno); i++) {
if (designer == swd_partno[i].designer && ((partno & swd_partno[i].partno_mask) == swd_partno[i].partno)) {
break;
}
}
if (i < LENGTH(swd_partno)) {
return swd_partno[i].name;
} else {
return "<unknown>";
}
}
/** interrupt service routine called for timer
*
* this is just acting as a shift register
* the host will write data on the falling edge (-5 ns < Tos < 5 ns), and read data just before the rising edge (Tis > 4 ns)
* the target will read and write data signal on clock rising edge
* this phase shift is not very clear in the standard, but explains the line turn-round cycle when switching between writing and reading.
*
* only this ISR should change the data and clock signal output
* the timer stops on rising edge when the last bit is sent
* @implements ARM DUI0499K 2.1.2 Serial Wire Debug
*/
void TIM_ISR(SWD_TIMER)(void)
{
static bool edge_falling = true;
if (timer_get_flag(TIM(SWD_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SWD_TIMER), TIM_SR_UIF); // clear flag
if (swd_bits_i >= swd_bits_count) { // end of activity reached, and no data has been made available in time
timer_disable_counter(TIM(SWD_TIMER)); // disable timer
return; // nothing to do
}
if (edge_falling) { // falling edge: we output data
if (swd_bits_out & (1ULL << swd_bits_i)) { // output data, LSb first
gpio_set(swd_swdio_port, swd_swdio_pin); // output high
} else {
gpio_clear(swd_swdio_port, swd_swdio_pin); // output low
}
gpio_clear(swd_swclk_port, swd_swclk_pin); // output falling clock edge
} else { // rising edge: read data
if (gpio_get(swd_swdio_port, swd_swdio_pin)) { // read data
swd_bits_out |= (1ULL << swd_bits_i); // set bit
} else {
swd_bits_out &= ~(1ULL << swd_bits_i); // clear bit
}
swd_bits_i++;
gpio_set(swd_swclk_port, swd_swclk_pin); // output rising clock edge
}
edge_falling = !edge_falling; // remember opposite upcoming edge
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

209
lib/swd.h Normal file
View File

@ -0,0 +1,209 @@
/** library for Serial Wire Debug (SWD) communication
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018-2021