Compiling and flashing a bootloader on SAMD21 MCU using ST-Link V2

Some time ago I decided to build a Voron V2 3D printer. Mainly because my Tronxy X5S is not really reliable. I got Prusa MK3S clone, which is excellent, but it is hard to enclose to print ABS with it. The Voron is a beast of a machine with 4 independent Z steppers, for a total of 7 steppers on a machine with single extruder. This was a challenge as my PrntrBoard V2 could handle max of 6 stepper motors.

In the mean time I discovered that the Klipper firmware can run two boards, for example one with the 4 Z steppers and another with the A, B (Voron V2 is a CoreXY machine) and E steppers. That was cool, but a bit of a waste of electronic components. Then some pointed me to the HUVUD tool board a very nice small board designed to fit on the back of a NEMA 17 stepper motor. This board can control the extruder stepper, the hotend and the extruder fans.

There were two things I was not very fond of: first the board was using CAN interface and that would need extra wiring and USB->CAN adapter. The board was using STM32 MCU which is now quite rare and expensive due to the global chip shortage.

I set and re-designed the HUVUD board, got rig of the CAN bus and replaced it with USB control. Then I replaced the MCU with SAMD21E16 chip. Initially I tried E15, but the flash was not big enough.

Klipper seems to have support for this chip, there is Arduino Core and some cheap prototyping boards (Seeeduino Cortex M0+ – for example).

Finally to the topic at hand: how does one flash a bootloader onto SAMD21 MCU – which is quite handy for updating the firmware over USB. A quick google search would reveal several lengthy articles. The most useful one I found was from Adafruit. Alas this usually involves using a J-Link adapter and there are quite expensive. You can get clones from china, but they don’t work.

So here is my attempt to distill the information on the topic. First the bootloader code – the best place I found for this code is the Arduino Core for SAMD21 on github. In that repository there is a folder called bootloaders and inside there is a folder called “zero” – that contains the Arduino bootloader code.

There are excellent instructions in the README how to modify the code for your board – mainly define what pins your board uses for LED(s),  most of these are optional like the TX/RX LEDs. By default this code is for SAMD21x18 series MCU, but it works just fine on the versions with smaller flash. You can modify the bootloader_samd21x18.ld file and change the flash size to match your chip. Compiling the code is easy just use make with a parameter for your board. There is a shell script (build_all_bootloaders.sh) that compiles the code for a variety of boards – you can see how the parameters are defined there.

When you are done compiling the bootloader, by default you will have a .hex and a .bin file in the current folder. There is also a .elf file in the build/ folder, but we are not going to need that.

Connecting the debug adapter to the board: You need to connect the following signals from the ST-Link to your board (SWDIO, SWCLK, RST, GND). You can also power your board from the ST-LINK V2 adapter 3.3V supply, but that provides only minuscule amount of power.

You would need openocd to program the bootloader. I got the default version that comes with Ubuntu 20.04 (0.10.0), allegedly even version 0.9 would work, but YMMV.

Before we proceed you need to add 64 bytes of values 0xFF to your binary file with the bootloader code. Allegedly this is to work-around a bug in the openocd code. Without this padding you would get  errors in the verification step on the last page of the bootloader code. The value 0xff is the default value of the flash when the chip is erased.

To generate a file with 64 values of 0xff, I used the following command, courtesy of slashdot:

dd if=/dev/zero ibs=1 count=64 | tr "\000" "\377" >paddedFile.bin

Then just concatenated the two files together with

cat samd21_sam_ba_huvud_07s.bin paddedFile.bin>fw.bin

Here ‘samd21_sam_ba_huvud_07s.bin’ is the name of my compiled bootloader and paddedFile.bin is the file from the previous step.

Start openocd with the following magic command:

openocd -f interface/stlink-v2.cfg -c 'set CHIPNAME at91samd21e15; set ENDIAN little; set CPUTAPID 0x0bc11477; source [find target/at91samdXX.cfg]'

You may have to modify the MCU model (I used Samd21e15 chip, put your exact chip version there). You would see some console output similar to this:

Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
adapter speed: 400 kHz
Info : Unable to match requested speed 400 kHz, using 240 kHz
Info : Unable to match requested speed 400 kHz, using 240 kHz
Info : clock speed 240 kHz
Info : STLINK v2 JTAG v31 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.250331
Info : at91samd21e15.cpu: hardware has 4 breakpoints, 2 watchpoints

The program would appear to hang there, but no to worry, open another terminal window and type

telnet localhost 4444

This would open a telnet session to the openocd server. By default the openocd server listens for telnet connection on port 4444 and GDB connection on port 3333.

In the telnet terminal you would see something like:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
>

This is the openocd command prompt and we are ready to send commands.

Send the following two commands first. Note that you would not get any response after the init command.

init
reset init

You would get a reponse after the ‘reset init’ command, some variation of the following:

target halted due to debug-request, current mode: Thread 
xPSR: 0x61000000 pc: 0x00000848 msp: 0x20000ffc

The pc, msp and xPSR register may hold different values for you.

You can erase the whole chip with the following command:

at91samd chip-erase
Note that this would erase all code stored on the chip, so proceed with caution.
Now we are ready to write the bootloader code. Send the following command:
load_image fw.bin 0x00000000 bin
Here fw.bin the the file we produced by concatenating the compiled bootloader code and the 64 byte padding file. The 0x00000000 is the start address in the flash where the code would be programmed and ‘bin’ is the file format.
Note the path name is relative to the folder where the openocd server was started, not the folder where the telned session is started. If you get an error “couldn’t open <path to>/fw.bin” it is most likely the openocd program can not open the path from its current location.
If all is well you would get something like the following response:
> load_image fw.bin 0x00000000 bin 
4924 bytes written at address 0x00000000
downloaded 4924 bytes in 0.291628s (16.489 KiB/s)

Now it is important to verify that the code was written to the flash correctly:

verify_image fw.bin 0x00000000 bin

Here again fw.bin the path to the firmware file and 0x00000000 is the start address in the flash where the code was written.

If all is well you would get something like the following response:

> verify_image fw.bin 0x00000000 bin
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20000ffc
verified 4924 bytes in 0.459733s (10.460 KiB/s)

The time, number of bytes and speed will vary from system to system. If you get flash errors, for example:

target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0xfffffffc
Error: checksum mismatch - attempting binary compare
diff 0 address 0x000012c0. Was 0xff instead of 0x24
diff 1 address 0x000012c1. Was 0xff instead of 0x06
diff 2 address 0x000012c2. Was 0xff instead of 0x00
diff 3 address 0x000012c3. Was 0xff instead of 0x01

Verify that you erased the chip before attempting to program the code and you followed the padding instructions earlier. The example above was due to me not following the padding and I was always getting these errors in the last 58 bytes of the code.

To close the telnet session you can type ‘exit’, note that this would not exit the openocd server we started in the other window. You can connect to the server again with the telnet command.