Multi-Architecture libraries. Determining target platform or board type

Multi-Architecture libraries. Determining target platform or board type.

I'm debugging a multi-platfrom library which supports multiple architectures and appears also to have worked at one time with an older version of an STM32 platform. I'm trying to get it to work with an STM32F103C8 "bluepill" and am using the Official STMicroelectronics Arduino Core version 2.2.0 (the latest)

The library I am debugging selects code depending on the platform using preprocessor macro definitions such as

#if defined(__AVR_ATmega32U4__)
. . .
#endif

#if defined(__SAM3X8E__)
. . .
#endif

#if defined(__STM32F1__)
. . .
#endif

It is the last one which now appears to fail. In the meantime, by looking at another multi-platform library, I have discovered that the following will work (with STMicroelectronics Arduino Core version 2.2.0):

#if defined(STM32F1)
. . .
#endif

Here is the sample code I used added to the blink sketch:

/*
  Blink
*/


#if defined(STM32F1)
#warning IS "STM32F1"
#else
#warning IS NOT "STM32F1"
#endif

#if defined(__STM32F1__)
#warning IS "__STM32F1__"
#else
#warning IS NOT "__STM32F1__"
#endif 

#if defined(ARDUINO_ARCH_STM32F1)
#warning IS "ARDUINO_ARCH_STM32F1" 
#else
#warning IS NOT "ARDUINO_ARCH_STM32F1"
#endif 



// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

and here the sumarised results:

C:\Users\6v6gt\Documents\Arduino\_STM32\Blink1\Blink1.ino:8:2: warning: #warning IS "STM32F1" [-Wcpp]
    8 | #warning IS "STM32F1"
      |  ^~~~~~~
C:\Users\6v6gt\Documents\Arduino\_STM32\Blink1\Blink1.ino:16:2: warning: #warning IS NOT "__STM32F1__" [-Wcpp]
   16 | #warning IS NOT "__STM32F1__"
      |  ^~~~~~~
C:\Users\6v6gt\Documents\Arduino\_STM32\Blink1\Blink1.ino:22:2: warning: #warning IS NOT "ARDUINO_ARCH_STM32F1" [-Wcpp]
   22 | #warning IS NOT "ARDUINO_ARCH_STM32F1"
      |  ^~~~~~~
Compiling libraries...
Compiling library "SrcWrapper"

It is not obvious from the platform's boards.txt file or the platform.txt file which is the correct macro definition to use. Neither can I find a clear formula in this link Platform specification - Arduino CLI

So how can the correct platform identifier macro be chosen/ determined ?

Here are the boards.txt and platform.txt files, zipped because .txt is a forbidden extension:

boards.zip (35.8 KB)

platform.zip (3.4 KB)

There are #defines for both the processor and the board. You need to figure out which one to use to enable each conditional block. You can most easily see the necessary #define by compiling an empty sketch, with the target board of interest selected, then look at the compiler output. For each compilation unit, the compiler command line will contain the processor and board #defines.

1 Like

OK. Thanks for that. I tried with a practically empty sketch (the blink sketch).
However, in the build, I can't find anything like a dump of the preprocessor environment, at least not with the standard compilation options.
I have, of course, the sketch before the preprocessor and the sketch after the preprocessor but nothing in between. Anyway, it would be good to know in advance how these macro definitions are created (if there is any standard logic, that is) instead of by experiment.

There is nothing in the Arduino build system that defines such a macro or enforces its definition by the boards platform author. That is the reason you don't find it in the Arduino Platform Specification.

There is a convention for boards platforms to define a ARDUINO_ARCH_<architecture> macro though, and this is mentioned in the library specification:

https://arduino.github.io/arduino-cli/latest/library-specification/#working-with-multiple-architectures

I would always give preference to a macro defined at a lower level if available (e.g., the AVR and __AVR macros for the AVR architecture), with the idea that they are more portable and less prone to breakage.

Unfortunately, I have found that it is sometimes difficult to identify those macros. The ones defined by the boards platform recipes are much easier to find.

1 Like

The Arduino boards platform configuration files use a templating system where keys (known as "properties") may be referenced in another property's definition by using the {key name} syntax.

A series of compilation command "pattern" properties are defined in the platform.txt file. Those produce the commands run by the Arduino build system. I'll use the recipe.cpp.o.pattern property that generates the compilation command for C++ files (including the .ino sketch files) as an example:

recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} {build.info.flags} {compiler.cpp.extra_flags} {build.extra_flags} {compiler.arm.cmsis.c.flags} {includes} "{source_file}" -o "{object_file}"

The important part of this line for our purposes is the reference to the build.info.flags property:

{build.info.flags}

This is an arbitrary property the STM32 boards platform authors have defined in platform.txt as:

build.info.flags=-D{build.series} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DBOARD_NAME="{build.board}" -DVARIANT_H="{build.variant_h}"

Here you can see the template for the flag that defines the global ARDUINO_ARCH_XXX macro:

-DARDUINO_ARCH_{build.arch}

The build.arch property is defined automatically by the Arduino build system, so we will find documentation for it in the platform specification:

https://arduino.github.io/arduino-cli/latest/platform-specification/#build-process

  • {build.arch}: The MCU architecture (avr, sam, etc...)

So how does the build system determine this value? It is explained here:

https://arduino.github.io/arduino-cli/latest/platform-specification/#hardware-folders-structure

The new hardware folders have a hierarchical structure organized in two levels:

  • the first level is the vendor/maintainer
  • the second level is the supported architecture

A vendor/maintainer can have multiple supported architectures. For example, below we have three hardware vendors called "arduino", "yyyyy" and "xxxxx":

hardware/arduino/avr/...     - Arduino - AVR Boards
hardware/arduino/sam/...     - Arduino - SAM (32bit ARM) Boards
hardware/yyyyy/avr/...       - Yyy - AVR
hardware/xxxxx/avr/...       - Xxx - AVR

So it is simply based on the path the platform is installed under. When the boards platform is installed via the Arduino Boards Manager, that is determined by the value of the packages[*].platforms[*].architecture key in the platform's package index:

https://arduino.github.io/arduino-cli/latest/package_index_json-specification/#platforms-definitions

When the platform was installed manually, it might be whatever folder name the user happened to use. This is more of a problem with the platforms that are located in the root of the Git repository, as is the case with the official Arduino boards platforms, the STM32duino platform, the Espressif platforms and others. Some 3rd party platforms have intentionally structured their repositories so that they already contain an architecture folder of the correct name. Examples include all the "MCUdude" platforms and the "SpenceKonde" (AKA "DrAzzy") platforms.

1 Like

@in0
Those are extremely comprehensive answers as indeed is the documentation you have referred to. Thank you very much for that.
Following that I did see that the following standard type definition is supported

#if defined(ARDUINO_ARCH_STM32)
. . .
#endif 

But that is rather rough because the term STM32 spans multiple ARM versions.

I had wondered if a definition should be directly derived from a property in the boards.txt entry the user selected, say generic platform/architecture macro name, which the platform owner would maintain but I now see the issue appears to be much wider than simply providing a mechanism to selectively include/exclude code sections in a sketch, which was my starting point here.

It also occurred to me later that I could have spared some debugging effort by using Eclipse/Sloeber (instead of the plain Arduino IDE) because that seems to be able to resolve at least some of the preprocessor macro definitions in real time and can highlight sections of code which are affected by such definitions. Of course, that does not help finding new definitions.

There is a macro for identifying the board selection as well. It comes from this part of the build.info.flags property:

-DARDUINO_{build.board}

The build.board property is defined in each board definition in the platform's boards.txt configuration file. The properties of a given board definition are only used when that specific board is selected from the Arduino IDE Tools > Board menu. I'll use the "Nucleo-144" board as an example. The human friendly board name is defined by this property:

Nucleo_144.name=Nucleo-144

but the identifier used by machines is the board ID which is the first level key in the property: Nucleo_144 in this case. When this board is selected, the sub-properties with this first level key name will be defined.

So it would follow that this line:

Nucleo_144.build.board=Nucleo_144

causes the build.board property to be set to the value Nucleo_144.

However, things are a bit more complicated with this particular board definition. It uses the "custom board options" feature of the Arduino platform framework to add an additional "Board part number" menu under the Tools menu in the Arduino IDE when the board is selected from the Tools > Board menu. Properties definitions are made according to which selection the user has made in that custom menu. These properties will override any properties of the same name that were defined at the higher level of the boards definition. So if you have Tools > Board part number > Nucleo F207ZG selected, this definition will override the previous one:

Nucleo_144.menu.pnum.NUCLEO_F207ZG.build.board=NUCLEO_F207ZG

So the compiler flag is expanded to -DARDUINO_NUCLEO_F207ZG and you can identify the board selection via this ARDUINO_NUCLEO_F207ZG macro.

Since the Arduino build system does automatically generate a build.board property if the platform author has not set one, this property is documented in the platform specification:

https://arduino.github.io/arduino-cli/latest/platform-specification/#boardstxt

However, note that the choice of whether to define a board identification macro is still at the whim of each platform author. I think most of them follow the convention, but the build system does not enforce it.

1 Like

There are three places that that these sort of symbols are likely to be defined.

  1. The compiler may internally define symbols. avr-gcc does this based on the -mmcu command-line option (-mmcu=atmega328p results in AVR_ATmega328P being defined.) As far as I know, the ARM compiler doesn't do this, since it's a "generic" compiler that doesn't want to have to know about all of the many ARM vendor's chip names. It DOES have some symbols set based on options like -mcpu=cortex-m3 and -mthumb and similar.
  2. A combination of "standard" include files combined with a option that selects particular include directories. For instance, you can tell the users to "always" #include <sam.h> and then ave the compiler use .../SAMD21G18A/include as the include path, and then SAMD21G18A/include/sam.h can define all sorts of chip-specific stuff. I think that having many different files all called "sam.h" is generally a bad idea, and this isn't done very often.
  3. Finally, you can specify "top-level" symbols on the compiler command line, and further include files can branch off from there. I believe that this is what most of the ARM based Arduino boards end up doing.

You can look at the commands used to invoke the compile by turning on "vebose output" for compilation in the Arduino preferences panel, and then looking at the output produced from your "verify" or "upload" operation. This is pretty verbose - I usually copy just the "compiling sketch" line to an editor and change all the spaces to newlines so I can look at the individual compiler options without going cross-eyed.

For an STM1xx "Blue Pill" board with the current ST-maintained core, it looks like there are four relevant symbols defined:

   -DSTM32F1xx
   -DARDUINO_BLUEPILL_F103C6
   -DARDUINO_ARCH_STM32
   -DSTM32F103x6

Which one you want to use for a multi-core library is ... complicated ... since there are hundreds of different ST chips/boards supported, and I don't think the peripherals are all 100% compatible across all of the chips/architectures.

Presumably this incompatibility with old sketches arose because the newer, ST-support STM32 core defines different symbols that the older, Roger Clark STM32 core.

2 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.