Compare commits
172 Commits
Author | SHA1 | Date | |
---|---|---|---|
7854328a55 | |||
90ec47d49a | |||
776569d7c1 | |||
f7e0d514dc | |||
7f06533fe7 | |||
dd09112a22 | |||
dcd41db126 | |||
63f7f0ece5 | |||
938e2467a4 | |||
aa1d7b1dde | |||
bbc981fecb | |||
78bc3b97f3 | |||
3f18fef34c | |||
86fa08cb91 | |||
886965ee80 | |||
f335044c77 | |||
af7477d971 | |||
b7670d1e24 | |||
55ac60e080 | |||
8d6d7f315f | |||
1d118ced08 | |||
da57f5ee40 | |||
ac7b075b1c | |||
cfc6c509b7 | |||
ad1aa69180 | |||
ae394561e2 | |||
851c25ffe0 | |||
0e92b46c3e | |||
3f00d36f05 | |||
b0ab623968 | |||
571038023c | |||
b7da249c51 | |||
8bfccb86a0 | |||
9d415ed449 | |||
85fdb1bbac | |||
2dd6e09cd0 | |||
9af8516030 | |||
479a1316c3 | |||
3cd3a869f4 | |||
3c4cabb483 | |||
32aa557a00 | |||
af9d0d685a | |||
02f95f55ee | |||
24cec3850d | |||
462a65cb92 | |||
a8b7726c35 | |||
90dbc57166 | |||
62650bd1b2 | |||
4c1ee2bc26 | |||
0250d81862 | |||
beb6d33f85 | |||
5e688d467d | |||
b0351bcaaf | |||
7675f73d0b | |||
a1da462852 | |||
0e91b71aae | |||
759cfd577e | |||
c73600c91f | |||
34d44c2a88 | |||
884f007d19 | |||
71564dd4f1 | |||
4fba2fd7a4 | |||
d208297aa3 | |||
9d1a4006af | |||
6aa820690e | |||
ea30c7c879 | |||
a2f203a81f | |||
a74539ab4f | |||
9c9893e1da | |||
a716cb10cf | |||
0a18d73197 | |||
8c01cbd918 | |||
019b82d384 | |||
a89d8abb38 | |||
ced5582a1a | |||
4d53d868fe | |||
ce0848343d | |||
863fd744d7 | |||
d85d345ec4 | |||
35a614750d | |||
b3cf0d0302 | |||
3a6a64928d | |||
99d66f4e4e | |||
66521e1981 | |||
210fab8eae | |||
7318d70dcd | |||
0010c5e046 | |||
6b3b55839e | |||
6bed3ab0fb | |||
9a7c51f80e | |||
7a74f9709f | |||
fa29cfc29f | |||
2248ba1762 | |||
ac255816a1 | |||
95b63a06f5 | |||
7656c699bf | |||
c6a4f58b93 | |||
b82520fa9b | |||
25fcf8fe0b | |||
01eaa5cfab | |||
793611d629 | |||
ad52abc26b | |||
b0f5f127f6 | |||
a449b9b7ff | |||
4c6e9a4fda | |||
789b36fc21 | |||
c8861f40c4 | |||
77415cb41f | |||
11f5bc9771 | |||
8526dc084b | |||
fea286914b | |||
cfcc8a1bb6 | |||
510c82d00f | |||
26f6de3015 | |||
a9461b53f5 | |||
d7b6300a50 | |||
d0bd71b266 | |||
a46b6a1630 | |||
b100c4ae13 | |||
a0f9b4a530 | |||
e32e27100d | |||
5b0523f751 | |||
d6cac41b78 | |||
adc62ebb9a | |||
d9a15f2daa | |||
c4af940975 | |||
c58d27cf2e | |||
c3d7711258 | |||
78cb85421a | |||
ff5fbc847d | |||
51e0bfd188 | |||
c411d552a1 | |||
ceff33ea0e | |||
68955ddfec | |||
40ee01ce67 | |||
0b2bbf8c97 | |||
87af738378 | |||
e4ce622f15 | |||
dbd0ea4d27 | |||
a878a1ad9c | |||
aff4275478 | |||
609188d74e | |||
63a2e5e5ff | |||
ac1bea1d45 | |||
7b7f26ee47 | |||
3d00bdf3c0 | |||
319a02d2b4 | |||
cc8be1f278 | |||
e255573b1e | |||
0fe7e1fd39 | |||
2249f460e3 | |||
aae4009fbe | |||
a9284b7154 | |||
777fd7afb9 | |||
31079d95dd | |||
ced714129c | |||
4fcfd29d2b | |||
06de8d0be9 | |||
de36c7f3a2 | |||
8918b97618 | |||
0bb2be3727 | |||
a781fc5b3b | |||
00ef5d9344 | |||
9fbf5b4aad | |||
46083bdf5e | |||
8a165c4d71 | |||
9db9ea9dc1 | |||
4b514c6801 | |||
6a34352914 | |||
35c441355d | |||
9751880813 | |||
e58614002c |
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
225
README.md
@ -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
|
||||
-----
|
||||
|
96
Rakefile
96
Rakefile
@ -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
|
||||
|
1212
application.c
1212
application.c
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
44
bootloader.c
44
bootloader.c
@ -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
|
||||
}
|
||||
|
@ -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
110
global.c
@ -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
329
global.h
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
495
lib/i2c_master.c
495
lib/i2c_master.c
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
171
lib/interrupt.c
171
lib/interrupt.c
@ -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")));
|
||||
|
@ -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"
|
||||
|
@ -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
1416
lib/jep106.inc
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
213
lib/led_rgbpanel.c
Normal 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
23
lib/led_rgbpanel.h
Normal 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);
|
@ -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)
|
||||
|
@ -7,6 +7,7 @@
|
||||
* @warning all calls are blocking
|
||||
*/
|
||||
#pragma once
|
||||
#error not converted for STM32F4
|
||||
|
||||
/** display brightness levels
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
177
lib/sensor_dht11.c
Normal 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
29
lib/sensor_dht11.h
Normal 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);
|
@ -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
|
||||
}
|
||||
}
|
@ -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
181
lib/sensor_dht22.c
Normal 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
29
lib/sensor_dht22.h
Normal 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);
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
86
lib/sensor_mlx90614.c
Normal 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
27
lib/sensor_mlx90614.h
Normal 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);
|
@ -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;
|
||||
|
@ -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
139
lib/sensor_sr04.c
Normal 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
26
lib/sensor_sr04.h
Normal 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
502
lib/smbus_master.c
Normal 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
112
lib/smbus_master.h
Normal 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
468
lib/swd.c
Normal 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
|
||||
}
|
||||
}
|