Skip to content

Commit d1760e8

Browse files
author
Jamie Smith
authored
Add support for DFU upload methods (#443)
* Implement dfu-util support * Fix terminal progress bar * DFU support: add STM32CUBE DFU * Quick doc fixes
1 parent 7d6a1fe commit d1760e8

File tree

6 files changed

+121
-6
lines changed

6 files changed

+121
-6
lines changed

connectivity/drivers/emac/CompositeEMAC.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Composite EMAC
22

3-
The original Mbed OS EMAC API was added in Mbed OS 5.9. That API grouped all EMAC functionality into a single abstract class that targets/vendors had to implement, the `EMAC` class. Since then, dozens of Mbed targets have received EMAC drivers, and the strengths and weaknesses of this API have become clear. The general structure is good, and the idea of abstracting the memory manager and the network stack from the EMAC driver works well.
3+
The original Mbed OS Ethernet driver (aka EMAC driver) API was added in Mbed OS 5.9. That API grouped all Ethernet MAC functionality into a single abstract class that targets/vendors had to implement, the `EMAC` class. Since then, dozens of Mbed targets have received EMAC drivers, and the strengths and weaknesses of this API have become clear. The general structure is good, and the idea of abstracting the memory manager and the network stack from the EMAC driver works well.
44

55
However, the [EMAC interface](https://github.com/mbed-ce/mbed-os/blob/0553c111850997d847dc6a3189ac0b7048304e57/connectivity/netsocket/include/netsocket/EMAC.h#L33) is difficult to implement, especially for people not intimately familiar with Mbed and its IP stacks. It requires EMAC implementations to implement the specifics of memory management, MAC address tracking, and DMA ring usage themselves, even though quite a bit of this logic is common to all MAC drivers. This has led to duplicated code, and quite often to half-assed code as well as chip vendors have struggled to conform to the (in some ways not very well defined) EMAC API.
66

@@ -20,11 +20,13 @@ Before we can get into the details of how CompositeEMAC works, we need to go ove
2020

2121
To run an ethernet connection, two chips need to work together*: the microcontroller and an external Ethernet PHY (PHYsical layer transceiver) chip. The microcontroller sends and receives logic level Ethernet packets, while the PHY transforms those into Ethernet signals, which are decidedly *not* logic level (and actually have a lot in common with radio signals). The Ethernet signals, called MDI (Media Dependent Interface) pairs, are sent through an isolation transformer, which removes common mode interference and provides electrical isolation (e.g. so that the two ends of the connection can have different ground voltage levels). Then, they go into the ethernet jack and across the ethernet cable to the link partner!
2222

23+
*though some MCUs, e.g. TI's TM4C129 line, do have the Ethernet PHY integrated into the MCU.
24+
2325
The PHY and the MCU are connected via a standard called [Reduced Media Independent Interface](https://en.wikipedia.org/wiki/Media-independent_interface#RMII) (RMII), which transfers the Ethernet packets as serialized bytes. This is an 8-wire bus with a 50MHz clock, four receive lines, and three transmit lines. The clock is traditionally either supplied by the PHY or by a dedicated clock generator chip, though some MCUs support supplying this clock as well. In addition to RMII, there's also a two-wire command and control bus called [Management Data IO](https://en.wikipedia.org/wiki/Management_Data_Input/Output) (MDIO) (though it can also be referred to Station Management Interface (SMI) or even "MiiM" for some reason). MDIO is used for talking directly to the PHY, not for sending Ethernet packets. MDIO is an open-drain bus similar to I2C, but with 16-bit words instead of bytes and a specific frame format (referred to as "Clause 22"). Unlike RMII, MDIO is a multi-drop bus, so you can actually connect up to 15 PHYs or other devices to one set of MDIO lines as long as they have different addresses!
2426

2527
Inside the microcontroller, the bridge between the CPU and Ethernet is a peripheral called the Ethernet MAC. MAC stands for "Media Access Control" and refers to the second layer of the Ethernet protocol stack, the logic which encodes Ethernet packets and decides when to send them across the wire. The MAC has a number of moving parts inside, which are shown in the diagram above. The simplest is the block of configuration registers, which is accessible at a specific memory address and sets up operation of the MAC (e.g. what MAC addresses the hardware should accept and which checksums should be inserted/checked by the MAC). There is also an MDIO master interface, which controls the MDIO lines to talk to the PHY. And then, we have the DMA.
2628

27-
Every Ethernet MAC I've seen also has DMA functionality. This means that the Ethernet peripheral can transmit and receive packets without direct CPU intervention. This is very important because it means your device can hit high network speeds without needing to have your CPU blocked for lots of time waiting on Ethernet packets to move through the hardware! For transmit, there will be a Tx DMA module which fetches data from the main RAM, and then enqueues the packet bytes plus control information into a FIFO (which is usually at least a couple thousand bytes long). Then, another block in the MAC, sometimes called the MTL (MAC Translation Layer) takes these bytes, applies any needed Ethernet framing, and shifts them out of the RMII Tx port.
29+
Every Ethernet MAC I've seen also has DMA functionality. This means that the Ethernet peripheral can transmit and receive packets without direct CPU intervention. This is very important because it means your device can hit high network speeds without needing to have your CPU blocked for lots of time waiting on Ethernet packets to move through the hardware! For transmit, there will be a Tx DMA module which fetches data from the main RAM, and then enqueues the packet bytes plus control information into a FIFO (which is usually at least a couple thousand bytes long). Then, another block in the MAC operating on its own timing, sometimes called the MTL (MAC Translation Layer) takes these bytes, applies any needed Ethernet framing, and shifts them out of the RMII Tx port.
2830

2931
For reception, the process works the same but in reverse: the decoder and shifter block takes in packets and enqueues their bytes into the Rx FIFO. Then, the Rx DMA dequeues the packets and stores them into RAM at the right location.
3032

@@ -139,7 +141,7 @@ The Tx interrupt* will detect that the descriptor pointed to by `txReclaimIndex`
139141
*Actually, interrupts cannot deallocate memory in Mbed, so the Tx interrupt handler actually signals the MAC thread to wake up and process the completed descriptor.
140142

141143
##### Final State
142-
Eventually, after a couple hundred microseconds, the second packet will be transmitted, and its descriptors will also be reclaimed. The Tx descriptor ring is now empty, except that the descriptor pointers are now both pointing to Desc 3.
144+
Eventually, after a couple hundred microseconds, the second packet will be transmitted, and its descriptors will also be reclaimed. The Tx descriptor ring is now empty, and matches the initial state except that the descriptor pointers are now both pointing to Desc 3.
143145

144146
![DMA ring after packet 1 deallocated](doc/tx-ring-step-5.svg)
145147

@@ -196,7 +198,7 @@ Anyway, when the packet is received, the MAC will write it into the next three a
196198

197199
The Rx ISR checks the DMA ring to make sure we have at least one complete packet. Then, it signals the MAC thread to dequeue the packet and pass it off to the IP stack.
198200

199-
When the packet is dequeued, the buffers will be removed from the descriptors and, for now, they stay in the "owned by application" region of the descriptor ring (between `rxNextIndex` and `rxBuildIndex`).
201+
When the packet is dequeued, the buffers will be removed from the descriptors and, for now, the descriptors stay in the "owned by application" region of the descriptor ring (between `rxNextIndex` and `rxBuildIndex`).
200202

201203
![Rx ring after dequeue](doc/rx-ring-after-dequeue.svg)
202204

tools/cmake/UploadMethodManager.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ endif()
9898
# Load the upload method.
9999
# Upload methods are expected to refer to the following variables:
100100
# - MBED_UPLOAD_SERIAL_NUMBER - USB serial number of the mbed board or of the programmer
101-
# - MBED_UPLOAD_BASE_ADDR - Base address of the flash where the bin file will be updated
101+
# - MBED_UPLOAD_BASE_ADDR - Base address of the flash where the bin file will be written to
102102
#
103103
# Upload methods are expected to set the following variables:
104104
# - UPLOAD_${UPLOAD_METHOD}_FOUND - True iff the dependencies for this upload method were found
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright (c) 2025 Jamie Smith
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# ----------------------------------------------
5+
# CMake finder for the dfu_util tool
6+
#
7+
#
8+
# This module defines:
9+
# dfu_util_PATH - full path to dfu-util executable
10+
# dfu_util_FOUND - whether or not the ArduinoBossac executable was found
11+
12+
set(dfu_util_PATHS "")
13+
14+
# try to figure out where dfu_util may be installed.
15+
# We will look for it both from the Arduino IDE, and from a manual install.
16+
if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
17+
18+
# on Windows, assume that the user extracted the binaries to Program Files
19+
# if the host is 64 bit, there will be a Program Files (x86) folder, this covers both
20+
file(GLOB dfu_util_PATHS "C:/Program Files*/dfu-util*/")
21+
22+
# On my computer the path is C:\Users\jamie\AppData\Local\Arduino15\packages\arduino\tools\dfu-util\0.10.0-arduino1\dfu-util.exe
23+
file(GLOB dfu_util_arduino_PATHS "$ENV{LocalAppData}/Arduino*/packages/arduino/tools/dfu-util/0*")
24+
list(APPEND dfu_util_PATHS ${dfu_util_arduino_PATHS})
25+
else()
26+
27+
# Linux / Mac
28+
# a possible path would be $HOME/.arduino15/packages/arduino/tools/dfu-util/0.10.0-arduino1/dfu-util
29+
file(GLOB dfu_util_PATHS "$ENV{HOME}/.arduino*/packages/arduino/tools/dfu-util/0*")
30+
31+
endif()
32+
33+
find_program(dfu_util_PATH NAMES dfu-util HINTS ${dfu_util_PATHS} DOC "Path to the dfu-util executable")
34+
35+
if(EXISTS "${dfu_util_PATH}")
36+
# Detect version
37+
execute_process(COMMAND ${dfu_util_PATH} --version
38+
OUTPUT_VARIABLE dfu_util_VERSION_OUTPUT)
39+
40+
# The output looks like "dfu-util 0.11\n", so use a regex to grab the version
41+
string(REGEX REPLACE "dfu-util ([^\n]+).+" "\\1" dfu_util_VERSION ${dfu_util_VERSION_OUTPUT})
42+
endif()
43+
44+
find_package_handle_standard_args(dfu_util REQUIRED_VARS dfu_util_PATH VERSION_VAR dfu_util_VERSION)
45+
46+
47+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2025 Jamie Smith
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
### dfu_util upload method
5+
### This method can be used for chips with DFU bootloaders that talk to the dfu-util program.
6+
### The chip must be rebooted into bootloader mode for the upload method to work.
7+
# This method creates the following parameters:
8+
# DFU_UTIL_TARGET_VID_PID - VID:PID pair of the target device being programmed.
9+
# DFU_UTIL_TARGET_INTERFACE - Interface number of the interface on the target that should be programmed.
10+
11+
set(UPLOAD_SUPPORTS_DEBUG FALSE)
12+
13+
### Check if upload method can be enabled on this machine
14+
find_package(dfu_util)
15+
set(UPLOAD_DFU_UTIL_FOUND ${dfu_util_FOUND})
16+
17+
### Function to generate upload target
18+
function(gen_upload_target TARGET_NAME BINARY_FILE)
19+
20+
set(DFU_UTIL_SERIAL_ARGS "")
21+
if(NOT "${MBED_UPLOAD_SERIAL_NUMBER}" STREQUAL "")
22+
list(APPEND DFU_UTIL_SERIAL_ARGS --serial ${MBED_UPLOAD_SERIAL_NUMBER})
23+
endif()
24+
25+
add_custom_target(flash-${TARGET_NAME}
26+
COMMAND ${dfu_util_PATH}
27+
--device ${DFU_UTIL_TARGET_VID_PID}
28+
--download ${BINARY_FILE}
29+
--alt ${DFU_UTIL_TARGET_INTERFACE}
30+
${DFU_UTIL_SERIAL_ARGS}
31+
--dfuse-address ${MBED_UPLOAD_BASE_ADDR}:leave
32+
VERBATIM
33+
USES_TERMINAL)
34+
35+
endfunction(gen_upload_target)

tools/cmake/upload_methods/UploadMethodSTM32CUBE.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ function(gen_upload_target TARGET_NAME BINARY_FILE)
3434
${STM32CUBE_CONNECT_COMMAND}
3535
${STM32CUBE_UPLOAD_PROBE_ARGS} # probe arg must be immediately after -c command as it gets appended to -c
3636
-w ${BINARY_FILE} ${MBED_UPLOAD_BASE_ADDR}
37-
-rst)
37+
-rst
38+
VERBATIM
39+
USES_TERMINAL)
3840

3941
endfunction(gen_upload_target)
4042

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) 2025 Jamie Smith
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
### STM32Cube DFU Upload Method
5+
# This method needs the following parameters:
6+
# STM32CUBE_DFU_CONNECT_COMMAND - "Connect" (-c) command to pass to the programmer
7+
8+
### Check if upload method can be enabled on this machine
9+
find_package(STLINKTools COMPONENTS STM32CubeProg)
10+
set(UPLOAD_STM32CUBE_DFU_FOUND ${STLINKTools_FOUND})
11+
12+
set(UPLOAD_SUPPORTS_DEBUG FALSE)
13+
14+
### Function to generate upload target
15+
16+
function(gen_upload_target TARGET_NAME BINARY_FILE)
17+
18+
set(STM32CUBE_UPLOAD_PROBE_ARGS sn=${MBED_UPLOAD_SERIAL_NUMBER})
19+
20+
add_custom_target(flash-${TARGET_NAME}
21+
COMMENT "Flashing ${TARGET_NAME} with STM32CubeProg..."
22+
COMMAND ${STM32CubeProg_COMMAND}
23+
-c ${STM32CUBE_DFU_CONNECT_COMMAND}
24+
${STM32CUBE_UPLOAD_PROBE_ARGS} # probe arg must be immediately after -c command as it gets appended to -c
25+
-w ${BINARY_FILE} ${MBED_UPLOAD_BASE_ADDR}
26+
VERBATIM
27+
USES_TERMINAL)
28+
29+
endfunction(gen_upload_target)

0 commit comments

Comments
 (0)