Arduino Due (or other SAM3/4) upload fails with ATMEL ICE JTAG on VSCode with PlatformIO

A Guided Tour through a Build Execution

Finally I want to make a guided tour of how exactly the compiler options are collected for a simple PlatformIO Arduino project.

We start a standard project created by pio init -b due for the Arduino Due board, that gives the platformio.ini of

[env:due]
platform = atmelsam
board = due
framework = arduino

and an empty src\main.cpp sketch with

#include <Arduino.h>
void setup() {} 
void loop() {}

Per above we already know that that will then be using platform-atmelsam together with the Due’s due.json board definition file. Note some important options in there like "core": "arduino", "mcu": "at91sam3x8e", "cpu": "cortex-m3", "extra_flags": "-D__SAM3X8E__ -DARDUINO_SAM_DUE", "variant": "arduino_due_x", (upload) "protocol": "sam-ba". They will be referred to later.

Upon building of the project (pio run), the platform’s platform.py will be invoked. Most notabaly we setup the needed framework package

Which, with the info given above, will be framework-arduino-sam.

Next, the builder/main.py of the platform is invoked.

Note that the toolchain package (toolchain-gccarmnoneeabi) is not modified, so the standard version will apply.

We first grab create / grab some variables from SCons and PlatformIO

We then go on to tell the SCons construction environment what our compiler and other tools are. Refer to the above chapter about SCons and its documentation to understand that better.

We teach SCons how to convert from a .elf to a .bin file. This will be important later as the upload programm will need a .bin file, not an .elf. We do so using $OBJCOPY, which, through SCons’s variable substition, will go to arm-none-eabi-objcopy.

We tell SCons that we want the firmware (as ELF) and additionally the .bin from it (remember upload_protocol = "sam-ba")

We also inject variables in the SCons environment needed for uploading the project, triggering this code

with a small addition.

That completes main.py. Then, since we specified framework = arduino, and the platform.json has mapped

we will now be executing arduino.py.

This wrapper script will check what sub-SCons-script to execute

leading us into arduino-sam.py.

One of the first things that script will do is execute arduino-common.py.

In that file then, we start doing actual compiler flags setup with flags that are apparently common to all the Arduino cores that AtmelSAM will be compiling.

(click above to see the full code). This includes CCFLAGS for C and C++ options, CXXFLAGS, CPPDEFINES and `CFLAGS´.

It continues with some library path and linker settings. These will be used in the final link command. We can e.g. see that it links agsinst libm (math) by default. It also sets up the libraries/ folder, in which an Arduino core typically has the list of all its internal libraries, to LIBSOURCE_DIRS.

Due to the attributes set in the board definition file, we will also be triggering this code

which sets up our linker script as flash.ld from the due’s variant folder.

and again since the board definition file has the needed flags for USB, we will be hitting this code

That finishes execution of arduino-common.py, so we are back in arduino-sam.py.

In there, we add more CPPPATH (include folders!), CPPDEFINES, LINKFLAGS and LIBS, just like the original Arduino core would do.

We finish up by adding a reference to the source code of the Arduino core variant (arduino_due_x) source files, typically variant.cpp and friends, using env.BuildLibrary() and modifications to env.

And finally, the Arduino core files (source) are added to the build system.

Now the SCons environment is set up. Additionally, per links above, PlatformIO now processes special platformio.ini like build_flags for additional compiler flags, discovers used libraries and adds those to the build system etc. In this projects we are having none of those. PlatformIO will however add the extra_flags attribute of the board JSON file as flags, as listed above.

With all of this, SCons / PlatformIO can finally start building the firmware. In a verbose compilation (pio run -v), we see all the flags we have collected.

arm-none-eabi-g++ -o .pio\build\due\src\main.cpp.o -c -fno-rtti -fno-exceptions -std=gnu++11 -fno-threadsafe-statics -Os -ffunction-sections -fdata-sections -Wall -mcpu=cortex-m3 -mthumb -nostdlib --param max-inline-insns-single=500 -DPLATFORMIO=50200 -D__SAM3X8E__ -DARDUINO_SAM_DUE -DARDUINO=10805 -DF_CPU=84000000L -DUSBCON -DUSB_VID=0x2341 -DUSB_PID=0x003E “-DUSB_PRODUCT="Arduino Due"” -DUSB_MANUFACTURER="Arduino" -DARDUINO_ARCH_SAM -Iinclude -Isrc -IC:\Users\Max\.platformio\packages\framework-arduino-sam\cores\arduino -IC:\Users\Max\.platformio\packages\framework-arduino-sam\system\libsam -IC:\Users\Max\.platformio\packages\framework-arduino-sam\system\CMSIS\CMSIS\Include -IC:\Users\Max\.platformio\packages\framework-arduino-sam\system\CMSIS\Device\ATMEL -IC:\Users\Max\.platformio\packages\framework-arduino-sam\variants\arduino_due_x src\main.cpp

Is e.g. the invocation for building the main.cpp, all others are equivalent to that. You can go ahead and double check that every single compilation option listed here really comes from the pieces of code discussed above.

The final linker command would be

arm-none-eabi-g++ -o .pio\build\due\firmware.elf -T flash.ld -Os -mcpu=cortex-m3 -mthumb -Wl,–gc-sections -Wl,–check-sections -Wl,–unresolved-symbols=report-all -Wl,–warn-common -Wl,–warn-section-align -Wl,–entry=Reset_Handler -u _sbrk -u link -u _close -u _fstat -u _isatty -u _lseek -u _read -u _write -u _exit -u kill -u _getpid .pio\build\due\src\main.cpp.o -L.pio\build\due -LC:\Users\Max.platformio\packages\framework-arduino-sam\variants\arduino_due_x\linker_scripts\gcc -LC:\Users\Max.platformio\packages\framework-arduino-sam\variants\arduino_due_x -Wl,–start-group .pio\build\due\libFrameworkArduinoVariant.a .pio\build\due\libFrameworkArduino.a -lm -lsam_sam3x8e_gcc_rel -lgcc -Wl,–end-group

Which again matches what we’ve seen above, e.g. regarding the LIBS and link flags, etc.

As for uploading, when we do a pio run -t upload -v (verbose upload), we see

Auto-detected: COM13
Forcing reset using 1200bps open/close on port COM13
bossac --info --debug --port “COM13” --write --verify --reset --erase -U false --boot .pio\build\due\firmware.bin

As the upload command. The first bit is about the PlatformIO core using COM port autodetection (and reset into bootloader mode), the second part is, as defined by the main.py code, the invocation for bossac as configured for that board.

Do I need to understand all this to develop with PlatformIO?

No. I’d say in 99% of cases, users do need to know how PlatformIO works internally, beyond how it is described in the documentation. Having to touch a platform manifest, platform script, a builder script or board definition file is rare. Most things can be solved with simple platformio.ini instructions without ever touching those things above. For more advanced use cases, scripting is also supported.

  • Don’t like a particular compiler flag that the builder script set for you? build_unflags it.
  • Want a particular compiler flag (e.g., a define, include directory, optimization flag, …) that the builder script didn’t set for you? Add it via build_flags.
  • Don’t like a specific attribute that was set it the board definition file? Change it dynamically using board_<section>.<attribute> = <value> (example).
  • Need to exclude certain files from the build that were auto-included? srcFilter them.
  • Need to add a flag to the upload execution? upload_flags.
  • Need to modify the SCons environment (and thus have access to all the compiler settings via a script) for some really specially stuff? Use extra_scripts and Advanced Scripting.
  • You want to do something special in the project’s build process, like executing some action before or after a file is compiled? Use above Advanced Scripting with Pre- and Post actions.
  • Need some custom upload method that PlatformIO does not implement by default? Use the upload_command in the platformio.ini or Advanced Scripting.
  • Need to invoke some custom debug server with flags that PlatformIO doesn’t implement? Use debug_server and friends.
  • Don’t like the package version or source that the build script gives you? Use platform_packages or specify a different version of platform.
  • Your library needs to be built in a special way (e.g., compiler flags, linking precompiled file, custom logic at each compilation,…)? Use a library.json, which also has the possibility to use advanced scripting.
  • etc., etc…

Documentation and further reading

Refer to the official documentation

And these topics

And of course the source code for the linked entities, such as the PlatformIO core, platforms, builder scripts, etc, for a more info on these specific topics.

2 Likes