Skip to content

Commit e4825a9

Browse files
authored
Add vfd_bench and other improvements (#8)
1 parent 47ddc19 commit e4825a9

File tree

167 files changed

+15105
-1199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+15105
-1199
lines changed

.clang-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ AlignAfterOpenBracket: Align
66
AlignConsecutiveMacros: false
77
AlignConsecutiveAssignments: false
88
AlignConsecutiveDeclarations: false
9-
AlignEscapedNewlines: Right
9+
AlignEscapedNewlines: Left
1010
AlignOperands: true
1111
AlignTrailingComments: true
1212
AllowAllArgumentsOnNextLine: true

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ jobs:
88
- uses: actions/checkout@v2
99
- name: CI action step for stm32
1010
id: stm32ci
11-
uses: milesfrain/stm32-action@F4_V1.25.2
11+
uses: milesfrain/stm32-action@F4_V1.25.2-local-share
1212

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
.metadata
77
RemoteSystemsTempFiles
88
FreeRTOS_TAD_logs
9+
*.bin

.vscode/c_cpp_properties.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "Linux",
5+
"includePath": [
6+
"${workspaceFolder}/**",
7+
"/usr/local/share/stm_repo/STM32Cube_FW_F4_V1.25.2/Middlewares/Third_Party/FreeRTOS/Source/include",
8+
"/usr/local/share/stm_repo/STM32Cube_FW_F4_V1.25.2/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS",
9+
"/usr/local/share/stm_repo/STM32Cube_FW_F4_V1.25.2/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
10+
"/usr/local/share/stm_repo/STM32Cube_FW_F4_V1.25.2/Drivers/STM32F4xx_HAL_Driver/Inc"
11+
],
12+
"defines": [],
13+
"compilerPath": "/usr/bin/gcc",
14+
"cStandard": "gnu17",
15+
"cppStandard": "gnu++14",
16+
"intelliSenseMode": "gcc-x64"
17+
}
18+
],
19+
"version": 4
20+
}

.vscode/launch.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "g++ - Build and debug commander",
9+
"type": "cppdbg",
10+
"request": "launch",
11+
"program": "${workspaceFolder}/host_apps/commander/commander",
12+
"args": [],
13+
"stopAtEntry": false,
14+
"cwd": "${workspaceFolder}",
15+
"environment": [],
16+
"externalConsole": false,
17+
"MIMode": "gdb",
18+
"setupCommands": [
19+
{
20+
"description": "Enable pretty-printing for gdb",
21+
"text": "-enable-pretty-printing",
22+
"ignoreFailures": true
23+
}
24+
],
25+
"preLaunchTask": "C/C++: g++ build commander",
26+
"miDebuggerPath": "/usr/bin/gdb"
27+
},
28+
{
29+
"name": "g++ - Build and debug throughput",
30+
"type": "cppdbg",
31+
"request": "launch",
32+
"program": "${workspaceFolder}/host_apps/throughput/throughput",
33+
"args": [],
34+
"stopAtEntry": false,
35+
"cwd": "${workspaceFolder}",
36+
"environment": [],
37+
"externalConsole": false,
38+
"MIMode": "gdb",
39+
"setupCommands": [
40+
{
41+
"description": "Enable pretty-printing for gdb",
42+
"text": "-enable-pretty-printing",
43+
"ignoreFailures": true
44+
}
45+
],
46+
"preLaunchTask": "C/C++: g++ build throughput",
47+
"miDebuggerPath": "/usr/bin/gdb"
48+
}
49+
]
50+
}

.vscode/tasks.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"tasks": [
3+
{
4+
"type": "cppbuild",
5+
"label": "C/C++: g++ build commander",
6+
"command": "make",
7+
"args": [],
8+
"options": {
9+
"cwd": "${workspaceFolder}/host_apps/commander"
10+
},
11+
"problemMatcher": [
12+
"$gcc"
13+
],
14+
"group": "build",
15+
"detail": "Task generated by Debugger."
16+
},
17+
{
18+
"type": "cppbuild",
19+
"label": "C/C++: g++ build throughput",
20+
"command": "make",
21+
"args": [],
22+
"options": {
23+
"cwd": "${workspaceFolder}/host_apps/throughput"
24+
},
25+
"problemMatcher": [
26+
"$gcc"
27+
],
28+
"group": "build",
29+
"detail": "Task generated by Debugger."
30+
},
31+
],
32+
"version": "2.0.0"
33+
}

README.md

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
## Overview
22

3-
This repo demonstrates how to incorporate some best practices into STM32CubeIDE-based projects. It preserves ST's .ioc workflow with autogeneration and easy pin reconfiguration in a git-friendly way, and also showcases:
3+
This repo demonstrates a strategy for working with STM32CubeIDE-based projects. It preserves ST's .ioc workflow with autogeneration and easy pin reconfiguration in a git-friendly way, and also showcases:
44
- C++ used appropriately in a constrained embedded environment (no dynamic allocations - see [no_new.cpp](common/src/no_new.cpp)).
55
- Convenience wrappers for static allocation of FreeRTOS components (see [static_rtos.h](common/inc/static_rtos.h))
66
- Code deduplication:
77
- Linking to versioned vendor firmware
88
- Common code shared across projects
9-
- Unit testing with [CppUTest](https://cpputest.github.io/) (not used extensively yet, [example](common/tests/src/test_basic.cpp))
9+
- Unit testing with [CppUTest](https://cpputest.github.io/) ([example](common/tests/src/test_software_crc.cpp))
1010
- Code coverage with lcov
1111
- Autoformatting with [clang-format](https://clang.llvm.org/docs/ClangFormat.html)
1212
- ITM debug logging
1313
- FreeRTOS task profiling
14-
- High-performance UART and USB communication interface abstractions (see the [loopback](loopback) project)
14+
- High-performance UART and USB peripheral abstractions (see the [loopback](loopback) project)
1515

1616
CI checks on each pull request ensure that all projects compile, pass unit tests, and are formatted correctly. Here are example PRs demonstrating:
1717
- [Broken build](https://github.com/milesfrain/stm32template/pull/2)
1818
- [Failing unit tests](https://github.com/milesfrain/stm32template/pull/3)
1919
- [Incorrect formatting](https://github.com/milesfrain/stm32template/pull/4)
2020
- [Passing all checks](https://github.com/milesfrain/stm32template/pull/5)
2121

22+
## Projects
23+
24+
- [`common`](common) - Common code shared among all projects.
25+
- [`loopback`](loopback) - Demonstrates sending binary packets across USB and UART peripherals.
26+
- [`vfd_bench`](vfd_bench) - A more real-world project demonstrating control of VFDs (variable-frequency drives) via modbus commands.
27+
- [`host_apps`](host_apps) - Applications which run on the host PC for communicating with the target microcontroller.
28+
2229
## Linux setup instructions
2330

2431
These instructions were verified on a fresh install of Ubuntu 20.04.
@@ -34,7 +41,7 @@ sh st-stm32cubeide_1.4.0_7511_20200720_0928_amd64.sh
3441
```
3542
Note that `ctrl-C` will let you jump to the bottom of the licenses.
3643

37-
When updating the IDE, it will continue to use the original install path. So rather than requiring constant path editing or full IDE reinstalls for CI script compatibility, we're using a versionless symlinked path that's also compatible with the docker image directory structure. Set up that symlink by running (substitue `1.5.1` for your particular IDE version):
44+
When updating the IDE, it will continue to use the original install path. So rather than requiring constant path editing or full IDE reinstalls for CI script compatibility, we're using a versionless symlinked path that's also compatible with the docker image directory structure. Set up that symlink by running (substitute `1.5.1` for your particular IDE version):
3845
```
3946
sudo mkdir /opt/st
4047
sudo ln -frs ~/st/stm32cubeide_1.5.1 /opt/st/stm32cubeide
@@ -86,9 +93,10 @@ sudo apt install libncurses5
8693
### Setting-up the real workspace
8794

8895
#### Firmware linking
96+
8997
We need to setup a symlink to the STM FW repo to ensure we all share the same absolute path to shared project assets.
9098
```
91-
sudo ln -s ~/STM32Cube/Repository /usr/share/stm_repo
99+
sudo ln -s ~/STM32Cube/Repository /usr/local/share/stm_repo
92100
```
93101
Switch your workspace to the checked-out repo.
94102
```
@@ -98,9 +106,16 @@ Use the symlinked firmware location in this workspace.
98106
```
99107
Window > Preferences > STM32Cube > Firmware Updater > Firmware Repository
100108
```
101-
Set to `/usr/share/stm_repo`.
109+
Set to `/usr/local/share/stm_repo`.
110+
111+
#### Patching FreeRTOS
112+
113+
There was a bug in FreeRTOS message buffers that has recently been [fixed](https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/264); however, this fix has not yet been incorporated into [ST's F4 firmware package](https://github.com/STMicroelectronics/STM32CubeF4). As of writing, ST's F4 firmware V1.26.1 uses FreeRTOS V10.3.1. The FreeRTOS bug affects at least V10.4.1 and below.
114+
115+
In the meantime, the simplest solution is to overwrite `Middlewares/Third_Party/FreeRTOS/Source` with a clone of this [patched repo](https://github.com/milesfrain/stm32FreeRTOS).
102116

103117
#### Project import
118+
104119
Add each folder to this workspace.
105120
```
106121
File > Open Projects from Files System
@@ -111,22 +126,6 @@ loopback
111126
```
112127
Now try building and flashing each project. Sometimes you'll need to expand the project folder in order for the build hammer to work.
113128

114-
### Debugging info
115-
116-
When you first debug, you'll see a popup. This default configuration works fine, but if you want to see ITM traces you'll need to enable SWV:
117-
`Debugger > Serial Wire Viewer`, check `Enable`, set `Core Clock` to `96.0`. This needs to match `SYSCLK` in the .ioc clock configuration.
118-
119-
If you skip this step for the first pop-up, you can find the setting later under `Run > Debug Configurations`.
120-
121-
To view ITM traces while debugging, open `Window > Show View > SWV > SWV ITM Data Console`. Click on the wrench icon in this new terminal and check `ITM Stimulus Ports` `2` and `0`. Logs are written to port `0`, but we're using port `2` as a workaround for this issue:
122-
https://community.st.com/s/question/0D53W00000Hx6dxSAB/bug-itm-active-port-ter-defaults-to-port-0-enabled-when-tracing-is-disabled
123-
124-
Then click the red circle to "Start Trace". Note that this can only be toggled when execution is paused.
125-
126-
For FreeRTOS task monitoring, install the extension described here:
127-
https://blog.the78mole.de/freertos-debugging-on-stm32-cpu-usage/
128-
129-
130129
## New STM32CubeIDE project setup
131130

132131
Here are some notes on the steps to create a new C++ project. You can either start a fresh project, or import an existing .ioc. This guide shows the "from .ioc" method.
@@ -157,7 +156,7 @@ In the `Project Explorer`, right click on the active project and select `Convert
157156
Copy the `custom` directory over from the `loopback` example. Note that you'll likely need to right-click on your project in the `Project Explorer` and select `Refresh` for this new folder to appear.
158157

159158
#### Link to common code
160-
In the `Project Explorer`, right click on the active project, `New > Folder > Advaced > Link to ...`, paste:
159+
In the `Project Explorer`, right click on the active project, `New > Folder > Advanced > Link to ...`, paste:
161160
```
162161
WORKSPACE_LOC/common
163162
```

ci.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@ build_ret=$?
1717
./unit.sh
1818
unit_ret=$?
1919

20+
# build host apps
21+
pushd host_apps;
22+
make
23+
host_apps_ret=$?
24+
popd
25+
2026
# disable echo for summaries
2127
set +x
2228

2329
# print summaries
2430
echo format return $format_ret
2531
echo build return $build_ret
2632
echo unit test return $unit_ret
33+
echo host apps return $host_apps_ret
2734

2835
status=$((format_ret || build_ret || unit_ret))
2936
echo exit status $status

common/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
## Overview
2+
3+
This directory contains common code shared across applications.
4+
5+
## Interfaces
6+
7+
Common [interfaces](inc/interfaces.h) (specifically `read()` and `write()`) enable composability among hardware abstractions and some FreeRTOS wrappers.
8+
9+
See the [loopback](../loopback) and [vfd_bench](../vfd_bench) projects for more usage examples of these interfaces.
10+
11+
## Static FreeRTOS Wrappers
12+
13+
The STM32 applications in this repo use FreeRTOS. Most FreeRTOS components allow either dynamic or static allocation, with static allocation offering some additional runtime predictability. Unfortunately, static allocation can be more tedious and error-prone with the existing C-API. C++ wrappers simplify the creation of static components, and offer some additional conveniences.
14+
15+
See [static_rtos.h](inc/static_rtos.h) for what wrappers are currently available. More wrappers may be added as required.
16+
17+
## Hardware Abstractions
18+
19+
High-performance hardware abstractions are available for UART and USB peripherals. These abstractions are DMA-based and built on top of FreeRTOS.
20+
21+
Common interfaces make it easy to substitute peripherals. For example, it is trivial to swap a UART communications link for USB.
22+
23+
See the [loopback](../loopback) project readme for more detailed examples of these abstractions in-use.
24+
25+
## Binary Packets
26+
27+
Data is sent between devices (e.g. microcontrollers and host PC) and between tasks running on each device as binary c-struct packets.
28+
29+
<img src="../docs/images/binary-packet.png" width="600">
30+
31+
Binary packets all share the following fields:
32+
- `magicStart` - A hardcoded value which improves efficiency of resynchronizing after data corruption.
33+
- `CRC` - The CRC value of all following bytes in the packet.
34+
- `length` - The number of bytes in the packet. This excludes the size of the first two `magicStart` and `CRC` fields. Those two fields are considered part of the "Packet Wrapper" and are only applied to data "on the wire". These fields are not passed around internally.
35+
- `sequenceNum` - An incrementing number assigned to each packet, which is used to check if any packets were dropped.
36+
- `origin` - Describes where the packet was created.
37+
- `id` - Describes which packet to use from the following `body` union field.
38+
- `body` - A union of all packet types. For efficiency, only the bytes used by the particular sub-type are transmitted, rather than always sending bytes for the full union.
39+
40+
The packet definitions ([`packets.h`](inc/packets.h)) and parsing, printing, and packing code ([`packet_utils.cpp`](src/packet_utils.cpp)) are shared across all applications in this repo. This avoids packet definition mismatches between applications running on the host PC and target devices.
41+
42+
To add new a binary packet, edit the following:
43+
- [`PacketID`](inc/packets.h)
44+
- [`Packet`](inc/packets.h)
45+
- [`packetBodySizeFromID`](inc/packets.h)
46+
- [`packetIdToString`](inc/packets.h)
47+
- [`snprintPacket`](src/packet_utils.cpp)
48+
49+
50+
## ITM Logging
51+
52+
Tasks may send printf-style log messages over the ITM interface via the `ItmLogger`. Since this is commonly used by all tasks, it is included as part of [TaskUtilities](#Task-Utilities) for convenience.
53+
54+
<img src="../docs/images/itm-logging.png" width="400">
55+
56+
To see ITM traces in the IDE you'll need to enable SWV: `Run > Debug Configurations > Debugger > Serial Wire Viewer`, check `Enable`, set `Core Clock` to `96.0`. This needs to match `SYSCLK` in the .ioc clock configuration.
57+
58+
To view ITM traces while debugging, open `Window > Show View > SWV > SWV ITM Data Console`. Click on the wrench icon in this new terminal and check `ITM Stimulus Ports` `31` and `0`. Logs are written to port `0`, but we're using port `31` as a workaround for [this issue](https://community.st.com/s/question/0D53W00000Hx6dxSAB/bug-itm-active-port-ter-defaults-to-port-0-enabled-when-tracing-is-disabled).
59+
60+
Then click the red circle to "Start Trace". Note that this can only be toggled when execution is paused. When tracing is disabled, then the expensive printf formatting operation is skipped.
61+
62+
<img src="../docs/images/itm-ide.png" width="600">
63+
64+
Counters tracking incoming and outgoing byte and packet totals are also logged over ITM. These can be a helpful sanity check for troubleshooting suspected data loss. See the [`ItmPort` enum](inc/itm_logging.h) for port mapping. Unfortunately, the IDE interface is [not as user-friendly as it could be](https://community.st.com/s/question/0D53W00000Y4DuCSAV/log-numeric-data-in-swv-itm-data-console).
65+
66+
An area of future work is to completely eliminate formatting operations from the target microcontroller. One possibility is to send an ID representing the particular format string, along with the raw arguments to the host for formatting.
67+
68+
Note that [binary packets](#binary-packets) are another high-performance logging option that is currently available, but they're a bit more tedious to introduce than printf log statements for debugging purposes.
69+
70+
## Watchdog
71+
72+
A watchdog task monitors other tasks for unexpected stalls. A maximum of 24 tasks may be monitored, due to the use of FreeRTOS event group for efficiency.
73+
74+
To use this feature, pass the watchdog object to each task you wish to monitor, then call `registerTask()` in each task's entry function, followed by `kick()` for each loop iteration.
75+
76+
If any task is not kicked for 2000 ticks (2 seconds with current project config), then a stall is detected and a watchdog timeout is reported. This timeout report is sent over ITM logging and/or Binary Packet logging, depending on which output options are provided to the watchdog. This watchdog task may be extended to allow a hardware watchdog to reset the device; however, reporting without resetting seems like the most practical way to handle stalls during product prototyping.
77+
78+
See [TaskUtilities](#Task-Utilities) for a more convenient way to use the watchdog.
79+
80+
<img src="../docs/images/watchdog.png" width="300">
81+
82+
## Error capture
83+
84+
There are a limited number of hardware breakpoints available on Cortex-M devices. The STM32F4 has 6. In order to conserve the number of breakpoints that are necessary to detect a variety of errors, common error-detection functions in [`catch_errors.cpp`](src/catch_errors.cpp) are aggregated. For example, setting a single breakpoint at `catchDefault()` will break at all critical errors, nonCritical warnings, and non-ideal timeouts.
85+
86+
<img src="../docs/images/catch.png" width="400">
87+
88+
## Task Utilities
89+
90+
<img src="../docs/images/task-utilities.png" width="500">
91+
92+
Almost all tasks make use of ITM Logging and Watchdog. Task Utilities wraps this common functionality and adds some additional conveniences:
93+
94+
### ITM Logging
95+
96+
Manages a local `LogMsg` struct per task, which is necessary for high-performance logging.
97+
98+
### Watchdog
99+
100+
Manages a per-task ID necessary for using the watchdog functions. It also provides wrappers for `read()` and `write()` interfaces to allow infinite blocking without triggering watchdog timeouts, while still allowing inspecting stack frames of blocked tasks during debugging. Setting a breakpoint at `allTimeouts()` steps through all blocking tasks.
101+
102+
### Initialization
103+
104+
Rather than passing both a `Watchdog` and `ItmLogger` instance to each task, pass a single `TaskUtilitiesArg` to the task's constructor.

common/inc/basic.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@
77

88
// Just putting this here, so we don't need to include all of <algorithm>
99
template<class T>
10-
const T& min(const T& a, const T& b)
10+
inline constexpr const T& min(const T& a, const T& b)
1111
{
1212
return a < b ? a : b;
1313
}
1414

15+
template<class T>
16+
inline constexpr const T& max(const T& a, const T& b)
17+
{
18+
return a > b ? a : b;
19+
}
20+
21+
// Rounds up, rather than performing truncating integer division.
22+
// Assumes positive integers.
23+
// roundUpDiv(12, 7) == 2
24+
template<class T>
25+
inline constexpr T roundUpDiv(const T& n, const T& d)
26+
{
27+
return (n + d - 1) / d;
28+
}
29+
1530
// Helper function for concatenating prefix to names
1631
// Note that the existing strncat is not exactly what we need here.
1732
inline char* concat(char* dst, const char* s1, const char* s2, size_t len)
@@ -21,3 +36,8 @@ inline char* concat(char* dst, const char* s1, const char* s2, size_t len)
2136
strncpy(dst + lenS1, s2, len - lenS1);
2237
return dst;
2338
}
39+
40+
// Could combine this macro with enum definition,
41+
// but might lose some IDE autocompletion
42+
#define ENUM_STRING(enumType, enumName) \
43+
case enumType::enumName: return #enumName;

0 commit comments

Comments
 (0)