3
\$\begingroup\$

I want to flash the UF2 bootloader for SAMD21 MCUs together with a compiled binary of a user program onto a SAMD21 MCU.

From what I understand how any bootloader works, the bootloader sits in a specific address space in the flash (in case of UF2 it seems to be 0x0-0x00002000) and then after that address space the bootloader will flash the user program and execute it.

Now it would be great if I can program my board with the bootloader and the default user programm all in once without needing to flash the bootloader with the programmer first and then connecting the board via USB to flash the user program.

Can I somehow concat the binaries so the programmer can flash everything at once in a single operation?

I planned on using https://github.com/adafruit/Adafruit_DAP with a Teesny for flashing. This works great using only the bootloader binary.

\$\endgroup\$

5 Answers 5

2
\$\begingroup\$

You've inaccurately specified the bootloader address, and that's important.

If the regions the bootloader and application programs would occupy are contiguous with at most a little padding to allow growth of future bootloader versions, then you can simply concatenate the binaries (with padding) and produce a single flat binary. If both are going into the same user flash bank, that would typically be the case. But if the regions are distcontinuous, then you'd need a format like an Intel Hex or s-record file or a .elf which can accommodate jumps to various addresses.

With a GCC style toolchain (as Atmel themselves now offer for their ARM parts) you can typically use the arm-none-eabi-objcopy (middle identification may vary) to manipulate and combine .elf, hex, and flat binary files. For simple cases you can also combine flat binaries with cat (if no padding is needed) or dd (if it is), and combine .hex files with cat or a scriptable text editor, possibly after stripping off any whole-file metadata.

For a typical program including the bootloader you are looking at, you'll find the actual memory assignments in the linker script (.ld) file, and you can find the usage of objcopy to produce a flat binary in the project Makefile.

Note that generally you want to build and link each of the bootloader and the application program as independent projects; you don't want the linker trying to share anything between them, except possibly for things you define at fixed dispatch addresses; otherwise it becomes near impossible to change one without requiring a rebuild of the other, which largely defeats the purpose of having a bootloader. When I do bootloaders for ARM-based systems, I typically just have the bootloader parse the vector table at the start of the application program, mimicking what the hardware itself does on startup; that also makes it possible to simply copy the application program's vector table to the hardware start address and have the chip start directly into the application without a bootloader, which can simplify debugging.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks this is helping a lot. So I looked up the linker script for UF2 and found this entry: MEMORY { rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x00002000 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 } Does that mean the bootloader start at 0x0 and the user program at 0x2? I'm guessing with RAM they just mean its writable instead of read only? \$\endgroup\$
    – timonsku
    Commented Apr 29, 2018 at 17:27
  • \$\begingroup\$ It means the bootloader uses (up to) 8K at address 0 which is also the start of user flash; you should then examine the documentation of the code or bootloader to figure out where the application program goes. The RAM isn't as significant since only used temporarily. While on your chip the user flash seems to actually begin at address 0, that is not true on all ARM parts, including some of the other Atmel ones - for example on the SAM4S it begins at 0x00400000 while on most ST parts it begins at 0x08000000 but also temporarily mapped to 0 on boot... \$\endgroup\$ Commented Apr 29, 2018 at 17:30
  • \$\begingroup\$ Alright ok now it makes more sense to me. So that RAM bit can probably just be ignored(?) and I now found that bit in the code #define APP_START_ADDRESS 0x00002000 which matches with that ld script statement and I take from that, that the application does starts directly after the bootloader. \$\endgroup\$
    – timonsku
    Commented Apr 29, 2018 at 18:09
  • 1
    \$\begingroup\$ Yes, basically. You just need to look at the size of your finished bootloader .bin - if its less than 8192 bytes exactly, you'd need to pad it before concatenating the application, if you want to end up with a single flat binary to flash. \$\endgroup\$ Commented Apr 29, 2018 at 18:47
1
\$\begingroup\$

1)

Depending on your toolchain, you may be able to take the binary of your application firmware that you get after compiling, and use the bootloaders linker file to link in the applications binary file to the correct address range.

2)

You can use an SRecord tool like http://srecord.sourceforge.net/. There are command line tools to manipulate srec files and convert to binary and back.

I suggest creating a batch file so you don't have to do this manually every time you compile and test.

3)

This is the most time consuming to implement. But if you do a lot of bootloaders and have different MCUs and platforms, this could automate a lot and save you time.

You can create your own PC application that takes binary files or srec files and a configuration file. Then it will combine the bootloader with the application and generate any other files you may want, like an encrypted file, inject an AES key into the image, push to testing, create log files, etc. But this may not be the way to go the first time around.

Notes:

Note that i'm assuming you have already edited the application to support a bootloader, such as editing the linker file to start at the correct address you need it to go once it's combined with the bootloader. Also that you already have the application re-locating the interrupt vector table to another location, and that the bootloader knows where to jump into the application and to verify the checksum of the application before booting it.

Once all that is done, then you can generate the binary file and combine it with the bootloader.

\$\endgroup\$
1
\$\begingroup\$

Take a look at SRecord - it was designed with this goal in mind. It handles not only binary files, but also many other formats, such as Intel HEX and Motorollla HEX.

\$\endgroup\$
0
\$\begingroup\$

I was having the same question and looking for the answer myself...

After having found hints in other locations, it is now clear to me that the UF2 format provides a much easier way to accomplish the objective!

Just concatenate the two files and the UF2 file-format and flasher (In ROM on my RP2040, I don't know for sure how it will work on yours) will do the rest for you!

On Linux I just:

  cat bootloader.uf2 myproject.uf2 > combined.uf2

I haven't used MS windows in decades, but from my DOS days I remember that

  copy bootloader.uf2 + myproject.uf2 combined.uf2 

should work. I expect that for compatiblity that still works. I expect that you can't do this from the GUI, but feel free to educate me (and more importantly others!).

The reason that this works is that an UF2 file is simply a "bunch of blocks" where each block says: "XXX bytes need to be programmed to address YYY". So just putting them after eachother will simply go from programming the bootloader to programming the main program, and it shouldn't even waste space with the area in between.

\$\endgroup\$
1
  • \$\begingroup\$ According to the spec (github.com/microsoft/uf2), the UF2 records contain a block number and block count, so the micro can know when the file has been fully received and start the user program. Two distinct files will have different counts. I tried this method on Windows, and the upload failed at about the point when the first 'part' had completed. I expect it's possible to join two files by tweaking their block count/number, but there's more to it than simple concatenation. \$\endgroup\$
    – user85471
    Commented Mar 7 at 0:03
0
\$\begingroup\$

Copy the first hex file, remove the end-of-file record, append the second hex file, load into your programmer.(If crafting a custom GUI to do that is too much work!). Easier with a custom GUI: Read the first hex file into a string array, seek to the end-of-file record, remove the record, append the second hex file. save and use.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.