Note: The firmware will be uploaded to the repository soon.
This page provides instructions to compile, flash, and calibrate the motor drivers.
The source code can be found in the actuator/firmware
folder and is based on
the Pigweed framework.
The motor driver is based on the STM32H755II, which is a dual-core (Arm M4 @ 240MHz and M7 @ 480MHz) microcontroller. We are using the M4 core for the main motor control loop. The M7 core is available for experimental use. Both cores can communicate with each other using shared memory. The motor driver uses EtherCAT communication to exchange telemetry and to receive motor current commands.
Note: The firmware works transparently with either the Holberton or Gebru + Cortes motor driver variants (see hardware overview). No compilation flags required.
Note: We are using Bazel as the build system. The
instructions below were tested with bazel 7.1.2
on Debian and Ubuntu.
To build the firmware binaries and helper scripts, run:
# From the top-level directory:
bazel build --cxxopt='-std=c++17' //actuator/firmware/...
To run the firmware tests on the host, run:
# From the top-level directory:
bazel test --cxxopt='-std=c++17' //actuator/firmware/...
There are two ways to flash the motor drivers: using a J-Link connected to the Mataric debugger board or via EtherCAT (bootloader). If you are flashing the board for the first time, the J-Link option should be used.
When bringing up a brand new motor driver, the EtherCAT (TMC8462-BA) chip's
EEPROM has to be flashed to enable EtherCAT communication. Assuming that the
EtherLab library has been installed, flashing the EEPROM
can be done using the ethercat
command:
cd actuator/ethecat_eeprom
ethercat sii_write -p 0 barkour_robot_sii.hex
After rescanning the bus:
ethercat rescan
ethercat slaves
The device should show up:
0 1:0 INIT E Cortes
Note: The board will be in the error (E) state if no firmware is present on the microcontroller.
Note: The same SII binary can be used for either the Holberton or Cortes
variants. They will both show up as Cortes
on the EtherCAT bus.
Important: The provided file uses Google's EtherCAT vendor ID. Update this to your own vendor ID provided by the EtherCAT Technology Group.
Connect the Mataric board to the Cortes or Holberton board as shown below:
Run the helper.par
script to flash the board:
./helper.par --flash --tool=jlink
To flash a single device, invoke helper.par script providing paths to each binary and specifying EtherCAT device alias of the device to be updated.
./helper.par --flash --tool ecat --m4 <path to m4.bin> --m7 <path to m7.bin> --device_alias <device_alias>
An example of updating a single device with expected output:
user@ethercat-host:~/binaries_foe$ ./helper.par --flash --tool ecat --m4 new_fw/m4.bin --m7 new_fw/m7.bin --device_alias 3
INFO:absl:Flashing firmware over ethercat
INFO:absl:M4 binary path: new_fw/m4.bin
INFO:absl:M7 binary path: new_fw/m7.bin
INFO:absl:device_alias: 3
INFO:absl:Flashing M7.
INFO:absl:Finished flashing M7.
INFO:absl:Flashing M4.
INFO:absl:Finished flashing M4.
INFO:absl:Power-cycle the device to boot new firmware.
Once binaries are successfully transferred to the device over EtherCAT, the STM32H755 will boot new firmware after being restarted. The most reliable way to achieve that is by power cycling the device.
Another option is to send a restarting command over EtherCAT directly using the Etherlab command line:
ethercat download -a <device_alias> -t uint32 0x3030 00 0x01234567
To flash all EtherCAT devices, helper.par
can be invoked with --all
flag as
below,
./helper.par --flash --tool ecat --m4 <path to m4.bin> --m7 <path to m7.bin> --all
An example of updating all devices with expected output:
user@ethercat-host:~/binaries_foe$ ./helper.par --flash --tool ecat --m4 new_fw/m4.bin --m7 new_fw/m7.bin --all
INFO:absl:Flashing firmware over ethercat
INFO:absl:M4 binary path: new_fw/m4.bin
INFO:absl:M7 binary path: new_fw/m7.bin
INFO:absl:Devices (by alias) to reflash: ['1', '2', '3', '7', '8', '9', '10', '11', '12', '4', '5', '6']
INFO:absl:Flashing device 6.
INFO:absl:Flashing M7.
INFO:absl:Finished flashing M7.
INFO:absl:Flashing M4.
INFO:absl:Finished flashing M4.
INFO:absl:Flashing device 5.
INFO:absl:Flashing M7.
INFO:absl:Finished flashing M7.
INFO:absl:Flashing M4.
INFO:absl:Finished flashing M4.
...
INFO:absl:Flashing device 1.
INFO:absl:Flashing M7.
INFO:absl:Finished flashing M7.
INFO:absl:Flashing M4.
INFO:absl:Finished flashing M4.
INFO:absl:Power-cycle the network to boot new firmware.
After updating firmware on all devices it is highly recommended to power cycle the network so that microcontrollers boot with the new firmware. That is because restarting each device separately, as described in previous section, can disrupt EtherCAT network and some devices might lose connection. If you are using the Holberton motor driver, it is possible to power cycle the devices via EtherCAT (see here).
File over EtherCAT is a method of exchanging large volumes of data between via an EtherCAT bus. It is a mailbox protocol and as such available in all states except INIT. However, in this implementation FoE is artificially limited to BOOT state only, since it can cause heavy bus load and impede EtherCAT performance.
FoE is used here to flash new firmware onto the devices. Currently the firmware
is split into two binaries - m4.bin
and m7.bin
.
On the device side, FoE is handled by SOES, with the help from some callbacks. On the host side, FoE is initiated by Etherlab commands. The following approximate sequence of events is triggered:
- User sets device into BOOT (
ethercat state BOOT
). - User initiates FoE data transfer (
ethercat foe_write -a <device_alias> m4.bin
). - Interrupt is generated on M4 to inform that an EtherCAT exchange took place.
- EtherCAT task wakes up and reads AL event register 0x220. Flag SM0/1 event is active.
ESC_foeprocess()
is called, which in turn callsfoe_writefile_cfg_t.write_function()
, which is set toflash_firmware()
.flash_firmware()
processes the incoming data chunk by chunk. This method erases flash sectors and writes the new data to the flash memory.
Activation of the firmware is handled separately. A custom uint32_t
object
dictionary at index 0x3030:00
is used to trigger a bank swap and a device
reboot when 0xDEADC0DE
is written to it. This activates the new firmware. Use
ethercat download -a <device_alias> -t uint32 0x3030 00 0xDEADC0DE
to do this
manually. Do note that ethercat rescan
might be needed after a firmware update
to ensure Etherlab is aware of the SDO entry.
The STM32H755 has two identical memory banks of 1 megabyte each (flash bank 1
starting at address 0x08000000
and flash bank 2 starting at address
0x08100000
). The banks each consist of 8 128 kB sectors.
The flash banks can be swapped, which is transparent to the microprocessor
cores. This means that after a bank swap, bank 1 is now bank 2 and vice versa
and all addresses that used to point to bank 1 now point to bank 2 and vice
versa. We use this mechanism to enable firmware updates. The current firmware is
always running from flash bank 1 and the update tool (flash_firmware()
) always
writes to flash bank 2. A bank swap causes the new firmware to become active.
The firmware is organized as follows:
0x08000000 - 0x0807FFFF: M7 code (max 512 kilobyte).
0x08080000 - 0x080FFFFF: M4 code (max 512 kilobyte).
The firmware for each core is compiled separately, resulting in two ELFs and two
BINs. See the helper
script for details. This is nothing more than a
byte-by-byte copy of the program data.
As the M7 core always boots first, we place its firmware at the start of the
bank (at the default address for the STM32). When the M7 boots up
(main_m7.cc
), it checks that the boot addresses for the M7 and M4 are correct.
If not, it updates the relevant registers of the STM32 and reboots the device.
When the M4 boots up, it updates the address of the interrupt vector table to
point to the start of its flash memory (the default is located at the start of
the memory bank, so we have to make sure the M4 does not use the M7's interrupt
vector table).
The 50/50 split between the M7 and M4 is just an arbitrary choice and might not be ideal. To change the boot addresses, update the following files and make sure that the M4's code starts at the beginning of a flash sector (128 kB):
flash_firmware()
inactuator/firmware/barkour/m4/ecat_device.cc
.- The linker files in
actuator/firmware/targets/m4
andactuator/firmware/targets/m7
. actuator/firmware/barkour/m7/main_m7.cc
to set the correct boot addresses.
Note: Flash bank 2 can be used for data storage during normal operation. It will only be overwritten when new firmware is flashed.
To run the calibration routines, the Mataric programmer board must be attached to the motor driver. There are two calibration routines, which are independent of one another, and must be run separately.
Successful completion of either calibration routine involves a restart of the EtherCAT stack, to force a write of the calibrated values to EEPROM / SII. Depending on the state of the EtherCAT host and device stacks at the time of calibration, this restart can sometimes cause the EtherCAT stack to enter an error state. Power cycling the motor after calibration will usually resolve any such issues.
The electrical angle offset is the difference between the encoder zero position, and the nearest position where the electrical angle of the motor is zero. This is critical for operation, as the motor cannot be commutated without this information. It is calculated by applying a fixed set of voltage commands to the motor to hold its electrical angle to 180 deg, then to 90 deg, and reading the encoder angle. The final 0 position offset is then calculated and stored.
A validation check is performed to ensure that the motor turned ~90 electrical degrees during the process, if the measured turn is with 10 Deg the calibration is considered good, if the rotation was out by more than 10 Deg an error is reported and the calibration is aborted.
The motor should have no significant load attached to it, and should be placed in a position where it is able to rotate freely for at least ~15 (mechanical) degrees in each direction. Ideally it should also be placed such that the axis of rotation is parallel to gravity.
The routine is triggered by by holding the nButton
on the Mataric board while
the power is turned on or the nRST
button is pushed to reset the STM. This
will run the electrical angle calibration, store the calibration results to
EEPROM (as part of the EtherCAT SII), and restart the EtherCAT stack.
During the calibration process the LEDs on the Mataric board will indicate the status. At the start of calibration the orange Blinky2 LED will be lit. If the calibration is successful the Blinky2 LED will go out and the board can be operated normally. If there is an error during calibration the Red Blinky1 LED will light and the board will enter a hung error state.
Additional debug info is also reported on the pw console output during the calibration.
The electrical angle offset can be read via SDO, with index 0x3000
, subindex
0x1
, as an int32
, with units of encoder counts. The SDO at index 0x3000
,
subindex 0x2
, type uint8
should read 1
if the calibration has been
completed successfully, and any other value otherwise.
The joint angle offset is the difference between a negative-side joint endstop and a joint zero position.
To run this routine, the robot joint should be held against its negative
endstop, with a Mataric board attached. Then the following sequence of button
presses is needed on the Mataric board: - The reset button pressed and held
(i.e. not let go yet!) - The Blinky1
button pressed and held. - The reset
button released. - (A few seconds later) the Blinky1
button released.
This results in the Blinky1
button being held as the firmware starts up, which
will trigger a series of encoder reads. The median of these will be used as the
zero offset, and stored to EEPROM (as part of the EtherCAT SII). The EtherCAT
stack will then be restarted.
If the calibration routine has been successful, the position of the motor (as reported over DS402) will be relative to this recorded position.
The joint angle offset can be read via SDO, with index 0x3001
, subindex 0x1
,
as an int32
, with units of encoder counts. The SDO at index 0x3001
, subindex
0x2
, type uint8
should read 1
if the calibration has been completed
successfully, and any other value otherwise.
Error codes corresponding to certain error conditions are be written to the
CANopen "manufacturer status register", index 0x1002
, subindex 0x0
. This can
be mapped to a PDO to allow for monitoring errors in the cyclic control loop on
the host side.
This contains a uint32
value, where the bits are set according to the
following table. The bit indices are given in LSB order.
Bit Index | Error Condition | Result |
---|---|---|
0 | Electrical angle calibration | Controller loop stops |
: : routine failed : : | ||
1 | Joint angle calibration | Controller loop stops |
: : routine failed : : | ||
2 | Calibrated electrical angle | Controller loop stops |
: : offset not found : : | ||
3 | Calibrated joint angle offset | Joint angle offset of 0 used. |
: : not found : : | ||
4 | Invalid motor parameters set | Controller loop stops |
5 | Invalid current limit set | Current limit set to infinity |
6 | Failed to configure one or | Controller loop stops |
: : more controller components : : | ||
7 | Failed to get sensor readings | Controller loop stops, if this |
: : / derived quantities : is the first read, else CiA 402 : | ||
: : : fault : | ||
8 | STO enabled | CiA 402 fault or failure to |
: : : initialize : | ||
9 | Unexpected gate driver state | CiA 402 fault |
: : change while voltage is : : | ||
: : applied to motor : : | ||
10 | EtherCAT not in "operational" | CiA 402 fault |
: : state while CiA 402 state : : | ||
: : machine initialized : : | ||
11 | Bus voltage too low | CiA 402 fault |
12 | Overheat detected | CiA 402 fault |
13 | STO line 1 down while voltage | CiA 402 fault |
: : is applied to motor : : | ||
14 | STO line 2 down while voltage | CiA 402 fault |
: : is applied to motor : : |
The error bits which result in a CiA 402 fault will be reset upon a CiA 402 fault recovery state transition.
Manufacturer status register can be read over EtherCAT using following command
ethercat upload -a <alias> -t uint32 0x1002 0x0
The primary mechanism for inter-core communications is a block of shared memory
which both cores can access. We use the SRAM4 block in Domain 3 for this
purpose, which has address range 0x38000000
- 0x3800FFFF
.
This block is recommended by the reference manual to be used for this purpose. It can also be seen from the linker files that the M4 core uses SRAM1, SRAM2 and SRAM3 for its working memory, whereas the M7 core uses the AXI SRAM. This means that SRAM4 is not used by either core for working memory, and so is safe to use for inter-core communications without the risk of e.g. corrupting the stack.
The shared variables, addresses and expected use patterns are listed below. In
code, they are described in the
actuator/firmware/barkour/common/shared_memory.h
header.
Address | Address Alias in Code | Type | Expected Use Pattern |
---|---|---|---|
0x38000000 |
kUidWord0Address |
uint32_t |
Written to by the M7 core on startup, before the M4 core starts. Read-only thereafter. Contains a copy of the STM variable unique identifier word 0, UID[31\:0] , which can only be read by the M7 core. |
0x38000004 |
kUidWord1Address |
uint32_t |
Written to by the M7 core on startup, before the M4 core starts. Read-only thereafter. Contains a copy of the STM variable unique identifier word 1, UID[63\:32] , which can only be read by the M7 core. |
0x38000008 |
kUidWord2Address |
uint32_t |
Written to by the M7 core on startup, before the M4 core starts. Read-only thereafter. Contains a copy of the STM variable unique identifier word 2, UID[95\:64] , which can only be read by the M7 core. |
This section describes the implementation details of the EtherCAT stack.
Terminology:
- ESC: EtherCAT Slave Controller (TMC8462)
- SII: Slave Information Interface
- ETG: EtherCAT Technology Group
- ESCReg: EtherCAT Slave Controller register (see TMC8462 data sheet)
SOES is an open source EtherCAT device stack implementation. The kernel state machine and general device functionality is contained in the following files, together with their corresponding headers:
esc.c
esc.h
esc_coe.c
esc_coe.h
esc_foe.c
esc_foe.h
ecat_slv.c
ecat_slv.h
Using SOES requires the following steps:
- Implement an interface to the hardware
- Specify general configuration options for SOES
- Merge everything into your application
The communication between the ESC and SOES is implemented in these functions:
void ESC_init(const esc_cfg_t *config)
: Initialize hardwarevoid ESC_read(uint16_t addr, void *buf, uint16_t len)
: Read contents of a registervoid ESC_write(uint16_t addr, void *buf, uint16_t len)
: Write data to a register
The ESC is connected via an SPI bus clocked at 30 MHz.
The device application is configured in the following header files:
actuator/firmware/barkour/m4/ecat/ecat_options.h
: General options configuring the deviceactuator/firmware/barkour/m4/ecat/utypes.h
: struct definition for all data to be exchanged between the SOES kernel and application.
The EtherCAT object dictionary is defined in
actuator/firmware/barkour/m4/ecat/objectlist.c
.
SOES uses a callback mechanism as well as mandatory functions where the user can
implement custom behaviour. These functions are defined in
actuator/firmware/barkour/m4/ecat_device.cc
. Take a look, it is extensively
documented in source code.
Mandatory are: * cb_set_outputs()
: Called just after a RxPDO (SM2 event) is
received (host to device) in order to set outputs. Ideally most of the work is
done * here because it is synchronous to RxPDO reception. * cb_get_inputs()
:
Called just before data is transferred into the output buffer (SM3).
In this application the following optional callbacks are implemented: *
set_defaults()
: Called in transition INIT->PREOP * pre_state_change()
:
Called just before a state change. Can be used to perform exit instructions or
to reject the change * post_state_change()
: Called just after state has
changed, but just before it is acknowledged in the ALstatus register (0x0130) *
safeoutput()
: Called upon exit of OP state, Error State, etc; used by the
application to ensure outputs are switch to safe state.
These optional callbacks are configured in struct EthercatDevice
that is
passed to SOES during stack initialization.
The EtherCAT stack runs in a separate thread on the M4. There is a single entry
point EthercatDevice::run()
, implemented in
actuator/firmware/barkour/m4/ecat_device.cc
, which runs forever. First the
stack is configured with ecat_slv_init()
after which it goes into an infinite
loop, waiting on a semaphore, calling ecat_slv_poll()
to get EtherCAT status
information and DIG_process()
to process PDOs.
The loop is interrupt driven. Whenever an EtherCAT packet is received, it
generates an interrupt that releases a semaphore from
EthercatDevice::EofInterrupt()
. This implementation is thus called
SM-Synchronous.
In this section various typical EtherCAT scenarios are described to show how the implementation reacts.
Service Data Objects (SDO) is mailbox communication, similar to remote procedure calls. Mailbox communication is available in all states except INIT.
Every EtherCAT object defined in the object list can be accessed (read or written) via SDO.
SDO is handled completely by SOES, with help from some callbacks. The following approximate sequence of events is triggered:
- Server initiates a request (
ethercat upload -p0 0x1000 0
) - Interrupt is generated on M4 to inform that an EtherCAT exchange took place
- ECat task wakes up and reads AL event register 0x220. Flag SM0/1 event is active
ESC_coeprocess()
is called, which in turn callspre_object_download_hook()
andpost_object_download_hook()
where a non-zero return value calls SDO abort.- SOES prepares an answer and sends it back to the host.
Process Data Objects (PDO) are typically process variables that are exchanged between host and device on a continuous basis, usually with every EtherCAT cycle.
As a PDO is an EtherCAT object, it is also a SDO but not the other way round. Whether an EtherCAT object is a PDO or not is defined in the object dictionary.
During the setup phase in PREOP, the host configures, or maps, PDOs into a SyncManager using SDO transactions. In fact SyncManager configuration simply sets up objects 0x1C12 (SM2, Outputs, RxPDO) and 0x1C13 (SM3, Inputs, TxPDO) in a special way.
PDOs are exchanged during SAFEOP and OP only
PDO is handled completely by SOES, with help from some callbacks. The following rough sequence of events is triggered:
- Server initiates a PDO exchange 1. Interrupt is generated on M4 to inform that an EtherCAT exchange took place
- ECat task wakes up and reads AL event register 0x220. Flag SM2/3 event is active
- User calls
DIG_process()
1. If SM2 event flag is set (outputs, RxPDO), the values are read intostruct _Object
struct by SOES. Thereaftercb_set_outputs()
(a user function) is called to inform of the arrival of new data 1. User callbackapplication_hook()
is called - User callback
cb_get_inputs()
is called and SOES copies data out ofstruct _Object
into the EtherCAT output area to get ready for a TxPDO frame.