ANN: Configurable bareboard runtimes v15.2.0

I have released version 15.2.0 of my configurable bareboard runtime crates.

light, light-tasking, and embedded runtime profiles are available for the following targets:

  • Raspberry Pi RP2040
  • Raspberry Pi RP2350 (Cortex-M33 cores)
  • Nordic Semi nRF52 Series
  • STMicroelectronics STM32F0xx Series
  • STMicroelectronics STM32G0xx Series
  • STMicroelectronics STM32G4xx Series

You can find pre-generated runtimes as crates in the Alire Community Index (v15.2.0 should show up from tomorrow), or attached as assets in the release announcement on GitHub.

The crates are named after the runtime profile and target, e.g. light_tasking_rp2040 is the light-tasking runtime for the RP2040.

Previously, these runtimes were managed in separate repositories on GitHub, but having them spread out like this was starting to make maintenance more burdensome. So I’ve now moved these runtimes to a new repository: community-bb-runtimes. If you have any problems, questions, or requests for these runtimes, that’s the place to open an issue.

Changes in v15.2.0:

  • Added the light runtime profile for the RP2040 and RP2350
  • RP2350: Fixed FPU not being initialized at startup
  • RP2350: Fixed incorrect interrupt IDs for CPU 2 interrupts
  • RP2350: Fixed compilation error with single-core runtime configurations
  • RP2350: Fixed potential race condition when interrupts are nested
  • RP2040: Fixed crash during context switch for tasks on the second CPU
  • Improved GNATprove compatibility when analyzing projects that use the runtimes.

In this version I’ve also put a lot of attention to improving the quality of the releases. I now have automated on-target testing set up with various test cases to provide some sanity checks that target-specific things like interrupts and multicore tasking are working correctly on each supported target. This should help prevent future regressions and provide better quality assurance.

10 Likes

Hello @damaki,

the Pi RP2040 and Pi RP2350 are dual cores MCU. Does it mean you provide a symmetric runtime?
What I mean if it’s possible to write code like that:

task A witch CPU => 1;
task B with CPU => 2;

If so, how do you manage the priority inheritance protocol when a protected object is used by two tasks on different CPUs?

Nice. But will it work with pico_bsp or will they fight over the startup code? Over on Telegram they said there is a possibility.

That’s correct, you can write code like that to pin tasks to either of the two cores!

The priority ceiling protocol works as normal. When a task (on either core) makes a call to a protected object, it inherits the ceiling priority of the protected object, so you cannot then make another call to a protected object with a lower ceiling priority.

If two tasks on different CPUs try to call the same protected object at the same time, then one CPU will “win” and will get the lock the protected object, and the other CPU will wait (spin) on the lock until the first CPU returns from the protected call and releases the lock, at which point the next CPU will acquire the lock.

3 Likes

Yes, it works with rp2040_hal and pico_bsp!

You will, however, need to configure rp2040_hal to tell it not to use its own startup code or interrupt handlers by adding this to your project’s alire.toml:

[configuration.values]
rp2040_hal.Use_Startup = false
rp2040_hal.Interrupts = "bb_runtimes"
2 Likes

Cool. I could have the internal and external LED blink at the same time at different intervals. (I don’t do anything serious with my pi). I’ll have a look at it tomorrow.

2 Likes

By the way, I have a demo project that shows using both cores and rp2040_hal/pico_bsp here: GitHub - damaki/pico_smp_demo: Ada multicore demo for the Raspberry Pi Pico (RP2040) · GitHub

2 Likes

I tried that. however:

multiple definition of `_6o8_o0gkdun5pa8+ta0fx_70r)fo2:m7 multiple definition of `8__ef0u7_n6gcf_nbla4to2_o2kr1uo5pm2_'2;f
 5uC0n:c3\_U1l5so0eofrks7\6uKeRp9M'aA;11 0\CcA:1\bUps8p4eD5ra8sta\a8K\\RlLMiAob1c\\allAi\pbapRlDipar2te0\a4c\0aL_ocHcahalle.\\aab(luriiplr-edr\soc\mal.ciohg)he:t\_

Of course, as always the actual error message is multiple pages as usual with any linker error. What could that be? I can give you the full error message if that helps. Source code is on SourceForge.

It looks like you’re using version 2.6.0 of rp2040_hal, which has a known issue that there are conflicting aeabi symbols for floating point operations (both rp2040_hal and the runtime are trying to provide them). This is fixed in version 2.7.0 of rp2040_hal, so I recommend upgrading to that version.

Version 2.7.0 is also now compatible with the GNAT FSF 15 toolchain, so you can also update that to use this latest 15.2.0 release of the runtimes (though version 14 will still work if you really want to stick with that).

Basically, try update your alire.toml dependencies to this:

[[depends-on]]
rp2040_hal                      = "^2.7.0"
pico_bsp                        = "^2.2.0"
light_tasking_rp2040            = "^15.2.0"

I also recommend updating your Alire index to make sure you can see the latest versions of everything by running:

alr index --update-all

I’ve tried this locally and I was able to reproduce your issue and fix it by doing the above.

Needed a bit more fine tuning but this compiled:

[[depends-on]]
rp2040_hal                      = "^2.7"
pico_bsp                        = "^2.2"
light_tasking_rp2040            = "^15.2"

[configuration.values]
rp2040_hal.Use_Startup          = false
rp2040_hal.Interrupts           = "bb_runtimes"
light_tasking_rp2040.Max_CPUs   = 2
light_tasking_rp2040.Board      = "rpi_pico"
d)

Strangely “.0” didn’t work.

1 Like

Thanks for the answer. A similar question: how do you manage the clock for scheduling on both CPUs? Do you have only one timer as a common base for both of them?

1 Like

I use the TIMER peripheral as a common time base between the two CPUs, and I reserve one of the four 32-bit ALARM channels for the runtime to schedule an interrupt (the exact ALARM channel used is configurable via a crate configuration variable, with ALARM3 as the default). So this gives both CPUs a common time base.

The runtime’s alarm interrupt handler always runs on the first CPU. This handler checks both CPUs for any expired alarms. If an alarm has expired on the second CPU, then the first CPU “pokes” the second CPU to generate an interrupt, then the poke handler on that CPU then handles the expired alarm and performs any required rescheduling and context switches.

1 Like

Why not using the CPU SysTick?

1 Like

There’s two reasons for not using the SysTick:

  1. Each CPU has its own SysTick, and a CPU cannot read the other CPU’s SysTick, so this would not provide a common time base.
  2. The TIMER allows for more accurate task delays and lower power.

The SysTick is a simple down counter, so it would generate an interrupt every 1 ms (or however long the period is configured for). Not only would this waste power and CPU time by waking up the CPU frequently just to check for expired alarms, it would also mean that task delays are only as accurate as the tick period.

By contrast, the TIMER peripheral has 1 µs resolution and its 32-bit ALARMs allows the runtime to schedule an interrupt anywhere in the next 2^32 microsecond period (about 1 hour 11 minutes). This avoids unnecessary wakeups (unless the next task wakeup time is longer than 1 hour 11 minutes in the future) allowing the CPU to sleep for longer, and makes task delays more accurate since the interrupt is generated exactly when the next alarm expires.

6 Likes

Thanks for sharing your knowledge

1 Like

Hi,

one question. I am trying the runtimes for my nRF52 board (SuperMicro, aka, Nice!Nano), and I am not getting any functional results. I have tried a blinky too in TinyGo https://tinygo.org/ and it works. I believe that since my board has a UF2 bootloader UF2 Bootloader Details | Adafruit Feather M0 Express | Adafruit Learning System, there may be an issue between the bootloader code and Ada’s code. Does that make any sense? I assumed that the final binary would not care much about where it is booted from, but I may be wrong. The UF2 bootloader is the same that is used in the RPi Picos, and they seem to work, so that surprises me even more :confused:

My code is:

with Interfaces.NRF52.GPIO;
use  Interfaces.NRF52.GPIO;

procedure Test2 is
   LED_Pin : constant := 15;
   procedure Set_LED_Output
     with Inline
   is
   begin
      P0_Periph.DIR.Arr(LED_Pin) := Output;
   end Set_LED_Output;

   -- procedure Wait (Ticks : Integer)
   --   with Inline
   -- is
   --    Counter : Integer := 0;
   -- begin
   --    while Counter /= Ticks loop
   --       Counter := Counter + 1;
   --    end loop;
   -- end Wait;
   
   type Mode is (Off, On);
   procedure Set_LED (Dir : Mode)
     with Inline
   is
   begin
      P0_Periph.OUT_k.Arr(LED_Pin) := (if Dir = Off then Low else High);
   end Set_LED;

   -- Wait_Time : constant := 1_000_000;
   Interval : constant Standard.Duration := 0.5;
begin
   Set_LED_Output;
   loop
      Set_LED(Off);
      delay Interval;
      -- Wait (Wait_Time);
      Set_LED(On);
      delay Interval;
      -- Wait (Wait_Time);
   end loop;
end Test2;

and the uploading method is the following (I have tried both, including the commented out method)

[[actions]]
type = "post-build"
# command = ["uf2conv.py", "bin/test2.hex", "-f", "0xADA52840", "-b", "0x26000", "-o", "bin/flash.uf2"]
command = ["adafruit-nrfutil", "dfu", "genpkg", "--dev-type", "0x0052", "--application", "bin/test2.hex", "bin/dfu-package.zip"]

[[actions]]
type = "post-build"
command = ["adafruit-nrfutil", "dfu", "serial", "--package", "bin/dfu-package.zip", "-p", "/dev/ttyACM0", "-b", "115200"]

Both methods are documented to work. I have also tried manually copying the .uf2 to the device.

Does anybody have any guesses as to what be wrong with the setup/code/upload/RTS?

Best regards,
Fer

The executables built with these runtimes are not position-independent executables (PIE), so they need to be loaded and executed at a specific address. With the default linker script used by the runtime, the program expects to run at the start of flash (address 0x0) which would overlap with the bootloader in this case, so won’t work.

So since the UF2 bootloader runs from the start of flash, so you’ll need to use a custom linker script to link your program to a different address. Based on the link you provided, I think this should be after the first 16 KiB, at address 0x4000.

To use a custom linker script, you can set your linker switches to point to your custom linker script in your GPR file, for example:

package Linker is
   for Switches ("Ada") use ("-L", "path/to/linker/script/dir/", "-T", "common-ROM.ld");
end Linker;

For your custom script, you could just copy the runtime’s default linker scripts common-ROM.ld and memory-map.ld, located in the ld/ directory in the runtime crate, then change the flash line in memory-map.ld to this:

  flash   (rx) : ORIGIN = 0x00004000, LENGTH = 496K

Then, when you build your application, you will need to pass -XLOADER=USER to GPRbuild to prevent the default linker script from being used. For example:

alr build -- -XLOADER=USER

Or alternatively, you can set it in your alire.toml file:

[gpr-set-externals]
LOADER = "USER"

Hopefully this fixes your issue.

Thank you for such a complete response!! I am documenting below my journey in case anybody finds the same issues and maybe finds a solution, but I do not expect any more “tech support” from this thread. I am happy with all the help I already received :slight_smile:

Sadly, no matter what I tried, nothing is working. I do not believe it is an issue with the Ada code, it is most likely the bootloader… The issue seems to be that the boot address is… not really documented ;-; There is no RTFM when there is no M… First issue is that converting .hex to .uf2 automatically puts your application in 0x0. The tool (see uf2/utils/uf2conv.py at 90e9741f217f5a40c98ba74d663e408041037578 · microsoft/uf2 · GitHub) seems to be doing some boot detection thing, but that is not working. So the first thing I had to do is to change the object file to a .bin.

Then, the uf2_boot for the nRF52 (GitHub - adafruit/Adafruit_nRF52_Bootloader: USB-enabled bootloaders for the nRF52 BLE SoC chips · GitHub) seems to be booting to address 0x1000 in flash. This is the address in the code (of course I had to do speleology to get that information, great). This is additionally confirmed in the following forums https://devzone.nordicsemi.com/f/nordic-q-a/89216/adafruit-bootloader-nrf-connect-zephyr-application, https://devzone.nordicsemi.com/f/nordic-q-a/97314/uf2-image-and-bootloader and Can't upload UF2 file to nrf52840 - #5 by PJ_Glasso - XIAO - Seeed Studio Forum. HOWEVER, in their readme they indicate that v6 of the bootloader (the one I am using) needs the boot flag set to 0x26000, which I also tried to no avail. I basically tried 0x0, 0x1000, 0x2000, 0x4000, 0x26000, 0x27000 (both in the linker and in the uf2 converter with the same values) and all of them failed.

I also reflashed the boot system and I saw that it was 0x39000 in size. From what I have read, the boot system seems to be broken into the initial bits for the boot, which startup the system at 0x0 and some others that live way up in the memory (something like 0xE000000). Just in case, I tried to put my application on 0x39000 and 0x40000 and it does nothing. The chip just reboots itself and nothing happens, the LEDs do not flash in the “error mode”, the board literally just reboots and that is it…

From what I have seen, the bootloader also protects quite a bit of memory regions, so that it probably does not get overwritten or modified, so maybe that is why I am getting the “error LED” with the low addresses but not the higher up ones…

Anyhow, I think I would need to take out the bootloader and use a proper programmer to work with this… Oh well… What worries me now the most is the RPi Pico 2 that I have also comes with the UF2 bootloader D: but some people seemed to have worked with it without much issue, so lets hope for the best!

Best regards and thank you for your answer!
Fer

I agree it’s probably not the Ada code here. The application is either being linked to the wrong address, or the bootloader is doing some other kind of checking or validation that we need to adhere to for this board.

The memory map with the UF2 bootloader is more involved than I thought. I found this page which shows the memory map for the Adafruit nRF52840 Feather board and it looks like the nRF52 SoftDevice (Nordic’s proprietary BLE stack) is located at the start of flash, and the application is located after it (at address 0x26000 for SoftDevice S140 v6). Maybe the SoftDevice got overwritten or corrupted when you were trying different addresses?

I’m not 100% sure of the boot order from poking around the sources, but it could be that the SoftDevice boots first which then boots the application. It could also be that the SoftDevice expects a specific application format, like maybe some kind of image descriptor. I’m not familiar enough with the SoftDevice to know for sure :confused:

If your TinyGo blinky still works, maybe you could find which linker script that is using to see which addresses they use?

BTW I’ve used the UF2 bootloader on my RP2350 with an Ada application and it worked OK. I used picotool to convert the ELF file built by GNAT to UF2 format, then put the RP2350 into bootloader mode so that it appears as a USB drive and dragged and dropped the UF2 file to it. This issue you’re facing seems to be to be purely related to this particular nRF52 board and bootloader.

Looks like the SoftDevice also reserves a block at the start of RAM, so you might also need to update your linker script so that your application’s RAM doesn’t overlap with the SoftDevice there too.

1 Like