'bare metal' multi/cross platform? Help with platformio.ini

Hi,

I’d like to have my simple scheduler running on multiple platforms: avr, esp32, i.mx rt, rpi, linux x86_64 (as a process), but I can’t find a way to do it.

If I create a project using multiple ‘board’ options I end up with a platformio.ini file having arduino as a framework, except for rpi (it gets WiringPi) and x86_64 (no framework).
I tried to change platformio.ini file by hand, multiple combos, but none got all the platforms to build my simple main.cpp, just an empty int main(int argc, char **argv).

Any tip?

AVR, ESP32, I.MX RT are fine, for RPi there is no cross-compiler available (issue) so you’d have to compile on a real Pi to get an executable. For Linux x86_64, you’d also have to be on either Linux or Mac.

What is the content of the platformio.ini? The right way should be to create a file with multiple environments, each with their own platform and board.

If you leave out the framework = .. line, you get a baremetal project (no external files but the ones in src/ and lib/ are compiled).

Note that compiling really baremetal for ESP32 is tricky: You have to do a lot of initialization stuff yourself, feed the watchdogs and start both CPU cores, use a special linker script and write it to a special address in flash (where normally the bootloader would be, as the first application that starts up in flash). I’ve played around with that a bit on GitHub - maxgerhardt/minimal-esp32-sdk: Implements a no-dependencies (e.g. to ESP-IDF or Arduino-ESP32) SDK for the ESP32. and I was able to do some simple printf()s with functions located in the mask-ROM of the ESP32, based on looking what ESP-IDF does in their bootloader.

An example bare-metal project is e.g. platform-atmelavr/examples/native-blink at develop · platformio/platform-atmelavr · GitHub. You can also check out each platform documentation you want to work with and check out the examples there.

Example platformio.ini

[platformio]
; list of environments to be built by 
; default when just running the "Default build"
default_envs = uno, esp32, imx_rt, native

[env:uno]
platform = atmelavr
board = uno

[env:esp32]
platform = espressif32
board = esp32dev
; use explicit linker script to link application
board_build.ldscript = bootloader.ld

[env:imx_rt]
platform = nxpimxrt
board = mimxrt1064_evk
; inject macro to identify platform
build_flags = -D__IMXRT__
; could also choose a Teensy 4.x here..

[env:linux_x84_x64]
platform = linux_x86_64

[env:raspbi3]
platform = linux_arm
board = raspberrypi_3b

; special environment:
; use native system compiler for this OS
; may overlap with linux or raspbi.
[env:native]
platform = native

example src\main.cpp

#include <stdlib.h>

/* include platform specific code */
#ifdef __AVR__
#include <avr/io.h>
#include <util/delay.h>
#endif

void blinky(){
#ifdef __AVR__
	/* toggle PORTB5 */
	DDRB = 1 << 5;
	while(1) {
		_delay_ms(500);
		PORTB ^= 1 << 5;
	}
#elif defined(__XTENSA__)
#warning "ESP32 Xtensa code goes here.."
#elif defined(__IMXRT__)
#warning "I.MX RT ARM code goes here.."
#elif defined(__x86_64__)
#warning "x86_64 code goes here.."
#elif defined(__linux__) && defined (__arm__)
#warning "ARM Linux (raspbi) code goes here.."
#endif
}

void entry_point() {
	blinky();
}

/* entry point is called differently for ESP32 in bare-metal.. */
#if defined(__XTENSA__)
extern "C" void __attribute__((noreturn)) call_start_cpu0(void) {
	entry_point();
	while(1) {}
}
#else
int main() {
	entry_point();
	return 0;
}
#endif

with bootloader.ld from repo.

If I compile that on Windows as a host operating systems (so, I can’t compile the Linux and Raspbi executables), I get

> pio run 
[..]
Environment    Status    Duration
-------------  --------  ------------
uno            SUCCESS   00:00:01.318
esp32          SUCCESS   00:00:01.338
imx_rt         SUCCESS   00:00:01.252
native         SUCCESS   00:00:00.917

Thanks for the complete solution.

About RPi I was hoping to go bare metal using both the arm and vc4 toolchains, not just a linux_arm binary. I have a crosscompiler for that but I need real time performance so getting rid of linux is mandatory. There’s a reverse engineering effort able to initialize both the arm core and the dual core VPU, then it is possible to use the 12 vector QPUs but I haven’t been able to build the gcc toolchain (without digging a bit more).

About ESP32, I’ve been playing with that a couple of years ago. I’ve built Micropython as a static library, then use the first core to run FreeRTOS and init Micropython on the second core. But I was upset because of the sdk being a bit more than a sdk, your minimal-sdk looks promising instead. Are you going to complete that?

About imx_rt, if I switch to teensy41 I get

.platformio/packages/toolchain-gccarmnoneeabi@1.50401.190816/bin/../lib/gcc/arm-none-eabi/5.4.1/../../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-exit.o): In function `exit':
exit.c:(.text.exit+0x16): undefined reference to `_exit'
collect2: error: ld returned 1 exit status
*** [.pio/build/teensy41/firmware.elf] Error 1

But I think it is easy to fix.

Hah, really interesting – if you get some minimal example working using some toolchain, that’d be cool, and can maybe be imported into PlatformIO.

Even Micropython is built on ESP-IDF, which I also find huge. Hence why I played around with a true baremetal application.

Hm not really. Or, maybe? But technically all the low-level hardware definitions are there (imported from ESP-IDF) so one can build on that – or just use ESP-IDF. There may be other leightweight ESP32 SDKs that I’m not aware of though. If there are better ones, I’d recommend them though. That project was 1 day of work.

Can be fixed by using the libnosys linker flags.

[env:imx_rt]
platform = teensy
board = teensy41
; inject macro to identify platform
build_flags = -D__IMXRT__
extra_scripts = add_nanolib.py

With the add_nanolib.py file in the root of the directory

Import("env")
#env.Append(LINKFLAGS=["--specs=nano.specs"])
env.Append(LINKFLAGS=["--specs=nosys.specs", "--specs=nano.specs"])

resolves the linker flag by importing the missing functions from the nosys or nano libraries.

Well, the vc4 toolchain I linked is gcc7 if I remember well. It doesn’t build using the supplied bash script, but I didn’t troubleshoot and I might have been missing some prereqs on my machine. It would be nice to have it included in mainline gcc.

The whole reverse engineering effort is stale since 4-5 years; the last developer (christinaa, author of rpi-open-firmware linked above) stop working on it because she didn’t like the un-initialized (and undocumented) security features, preferring what she defined something like “really open source hardware”. As others have noted in the thread discussing this, the crypto keys should be in users hands, not in the supplier hands… so… weird…
But most of the hw init is there; if I remember well the only missing part is power management. In the case gcc includes the vc4 target, the bootloader work may be resumed…

I find very strange in 10 years there haven’t been enough interest in bare metal rpi. RPi Zero is the only 5$ mcu having 1+2 cores (+ 12 vector cores and and a working assembler), 256MB of ram and a full set of GPIOs.
It doesn’t have many peripherials but thanks to its speed can bit bang anything missing, eventually. On paper looks more useful as bare metal than tiny linux machine…

Thanks for the information keep suggesting such post. I am looking for the same content you shared.

MyGroundBiz Login

Errata corrige: for RPi one guy is working on rpi-open-firmware and made a lot of progress on the topic. His repo.