Using both cores of RP2040

I am trying to use both cores of a RPi Pico RP2040. Unfortunately the example multicore program in Pico_Examples does not blink. It gets stuck when starting a procedure in the second core. When I stop the program in GDB, it stops in RP.Multicore.Launch_Core1.

Starting program: /home/re/Devel/rp2040/pico_examples_2.2.0_ba306c6f/multicore/obj/main 
^C
Program received signal SIGINT, Interrupt.
0x10003d3e in rp.multicore.launch_core1 ()
(gdb)

I am now on the way to use a tasking runtime. That was discussed before here. Unfortunately I cannot find an example program (simple or not). Do I have to declare a task for core0? If not, how would I communicate between the two tasks? Can I still use the internal FIFO queue or is that excluded in the tasking runtime?

Which run-time are you using?

The original problem (program stuck in launch_core1) arises in the unmodified pico_examples crate. It uses light-cortex-m0p for all examples, including the multicore. I tried with gcc-14 and gcc-15.

The questions in the last paragraph were a preparation for switching to light-tasking-rpi-pico (or light-tasking-rpi-pico-smp). Perhaps that works better, but I am missing some examples. So I haven’t tried yet.

I don’t have an example to hand right now for using the tasking runtime on the RP2040, but to answer your questions about tasking:

Do I have to declare a task for core0?

It’s not mandatory as the environment task (which calls your program’s main) runs on core0. You can also create multiple tasks on core0 if you wish; the runtime will use priority-based scheduling to manage task execution on the same core.

how would I communicate between the two tasks?

You can use standard Ada tasking mechanisms for inter-task communication, i.e. protected objects.

Can I still use the internal FIFO queue or is that excluded in the tasking runtime?

The SIO FIFO is reserved for use by the runtime only (the runtime uses this to “poke” the other core to wake it up when it might need to (re)schedule a task).

Note that if you want to use both cores with the tasking runtime then you’ll need to use the light-tasking-rpi-pico-smp runtime (the other runtime only runs on core0). To declare a task that runs on the second core you can declare the task at package level like this:

task My_Task with CPU => 2;

I’m asking because the answer will be very different between a “tasking“ run-time like (light-tasking- or embedded-) , and the light-cortex-m0p used in the example.

I am more familiar with the light-cortex-m0p method since I’m using it in my PGB-1 project.

It would be interesting to have the traceback from GDB on this error.

For the tasking run-times, you have to use the Ada multicore tasking features `pragma CPU` to specify on which core a task will run, and that’s it.

Here is the backtrace.

Starting program: /home/re/Devel/rp2040/pico_examples_2.2.0_ba306c6f/multicore/obj/main 
^C
Program received signal SIGINT, Interrupt.
0x10003d3e in rp.multicore.launch_core1 ()
(gdb) bt
#0  0x10003d3e in rp.multicore.launch_core1 ()
#1  0x10003d92 in rp.multicore.launch_core1 ()
#2  0x100004ae in led_control.start ()
#3  0x1000027c in main ()
(gdb) 

I hit Ctrl-C after a few seconds and the startup is still in rp.multicore.launch_core1. (see here, lines 53 .. 62). That is a loop that exits only if it receives a positive echo across the FIFO link between the two cores. For some reason that condition is never true.

You have to enable debugging on the rp2040_hal create otherwise the traceback is not that useful.

drop this in the alire.toml

[build-profiles]
rp2040_hal = "development"
1 Like

Now with debug symbols

Starting program: /home/re/Devel/rp2040/pico_examples_2.2.0_ba306c6f/multicore/obj/main 
^C
Program received signal SIGINT, Interrupt.
rp.multicore.fifo.pop_blocking ()
    at /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/f238c7f671e6fe4b10850b8ecde2981188bb13bd16c1eb000e440a7ec5b651db/src/drivers/rp-multicore-fifo.adb:52
52            end loop;
(gdb) bt
#0  rp.multicore.fifo.pop_blocking ()
    at /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/f238c7f671e6fe4b10850b8ecde2981188bb13bd16c1eb000e440a7ec5b651db/src/drivers/rp-multicore-fifo.adb:52
#1  rp.multicore.launch_core1 (trap_vector=1048577, stack_pointer=<optimized out>, entry_point=<optimized out>)
    at /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/f238c7f671e6fe4b10850b8ecde2981188bb13bd16c1eb000e440a7ec5b651db/src/drivers/rp-multicore.adb:55
#2  0x1000438e in rp.multicore.launch_core1 (trap_vector=<optimized out>, stack_pointer=<optimized out>, 
    entry_point=<optimized out>)
    at /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/f238c7f671e6fe4b10850b8ecde2981188bb13bd16c1eb000e440a7ec5b651db/src/drivers/rp-multicore.adb:85
#3  0x100004b2 in led_control.start ()
#4  0x1000027c in main ()
(gdb) l
47
48         function Pop_Blocking return HAL.UInt32 is
49         begin
50            while not RX_Ready loop
51               Cortex_M.Hints.Wait_For_Event;
52            end loop;
53            return SIO_Periph.FIFO_RD;
54         end Pop_Blocking;
55
56         --------------

Using a simple example of two tasks for the two cores and light-tasking-smp runtime I get a linker error

Link
   [link]         blink_light_tasking.adb
/home/re/.local/share/alire/toolchains/gnat_arm_elf_14.2.1_524d4d41/bin/../lib/gcc/arm-eabi/14.2.0/../../../../arm-eabi/bin/ld: /home/re/.local/share/alire/toolchains/gnat_arm_elf_14.2.1_524d4d41/arm-eabi/lib/gnat/light-tasking-rpi-pico-smp/adalib/libgnarl.a(s-bbcppr.o): in function `__gnat_irq_trap':
s-bbcppr.adb:(.text.__gnat_irq_trap+0x0): multiple definition of `__gnat_irq_trap'; /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/fa1cda59bab93f54d62523320660916369107304d32734ffc25b44071cc4e85d/lib/libRp2040_Hal.a(rp_interrupts.o):rp_interrupts.adb:(.text.__gnat_irq_trap+0x0): first defined here

Source code is here.

Ah, I forgot that when you use the tasking runtimes with rp2040_hal you’ll need to configure rp2040_hal so that it doesn’t add its own interrupt handlers and startup code (since those are handled by the tasking runtime).

Try adding this to your alire.toml:

[configuration.values]
rp2040_hal.Interrupts = "bb-runtimes"
rp2040_hal.Use_Startup = false

It looks ok. Maybe the problem is that `Cortex_M.Hints.Wait_For_Event` is triggering a SIGINT in gdb…

Can you try with this gdb command before:handle SIGINT nostop

No. It has nothing to do with SIGINT. That is me who hits Ctrl-C (SIGINT) to stop the program after waiting for several seconds (or minutes). And it always shows the same location. The program is stuck in the loop of Pop_Blocking, it seems to me that RX_Ready never becomes True.
Does the RP2040 require some initial configuration to activate the second core?

OK, something strange. It works and the program runs fine if I remove the debugger and cycle power. It does NOT blink if the debugger is attached or if power was not cycled (i.e. debugger removed but power left on)

Now that my problem with the second core and light-cortex-m0p RTS seems to be related to some interference between debugger, target and power and not the coding, I’d like to make it also work with the light-tasking-rpi-pico-smp RTS.

Configuring the rp2040_hal for bb_runtime interrupts and no startup code I can cleanly compile and link. At the start of the program I get an unhandled exception in the elaboration code even before the main program starts

Starting program: /home/re/Devel/rp2040/rp2040_snippets/blink_light_tasking/bin/blink_light_tasking 

Breakpoint 1, 0x10008f7c in __gnat_last_chance_handler ()
(gdb) bt
#0  0x10008f7c in __gnat_last_chance_handler ()
#1  0x10007d62 in system.interrupts.install_restricted_handlers ()
#2  0x10004c6e in rp__timer__interrupts__alarm_handlerTVIP ()
#3  0x10004c92 in <rp__timer__interrupts___elabb> ()
    at /home/re/.local/share/alire/builds/rp2040_hal_2.5.0_4f701bfa/36e5e93a515bf95a15ed695e8fb3aefe1168e1b6ba7e26e09c1cd2568a994d5f/src/interrupts/bb_runtimes/rp-timer-interrupts.adb:15
#4  0x1000028c in adainit () at b__blink_light_tasking.adb:96
#5  0x10000338 in main () at b__blink_light_tasking.adb:117
#6  0x10009400 in _start_rom ()

Do I miss some other configuration? I cannot find some doc about light-tasking on the rp2040.

It looks like both rp2040_hal and the tasking runtime are both trying to use the same alarm interrupt (ALARM2). The runtime reserves this peripheral and interrupt to implement the semantics of Ada’s delay and delay until statement. The last chance handler is being reached because RP.Timer.Interrupts also tries to register an interrupt handler for ALARM2, which is rejected by the runtime because that is reserved.

As a workaround I recommend avoiding the use of RP.Timer.Interrupts (don’t with it at all in your code) and use Ada’s delay and delay until statements instead when using the tasking runtime.

The lack of documentation/examples about the tasking runtimes is certainly something that could be improved.

OK, something strange. It works and the program runs fine if I remove the debugger and cycle power. It does NOT blink if the debugger is attached or if power was not cycled (i.e. debugger removed but power left on)

I notice that rp2040_hal doesn’t provide a way to reset core1 before launching it. I wonder if that could be contributing to the problem.

In the tasking runtimes, I made it so that it hard-resets core1 before launching it to ensure it is in a known state (i.e. the core1 bootrom is waiting for the handshake protocol).

You could try copying this code to reset core1 before launching it and see if that helps.

1 Like

Thanks very much. Adding your reset routine actually solved the problem. Now the code starts on both cores, even without detaching the debugger and without power cycling. I only had to replace the 0 and 1 in your code by False and True. I created a corresponding PR for rp2040_hal.

1 Like

Also the problem of the double initialisation could be solved by removing all with statements to rp2040_hal (except RP.GPIO).
When running the program it proceeds only slightly further. The program now hangs in Tasks'Elab_Spec in adainit. tasks.ads is actually quite short:

package Tasks is

   task Command with CPU => 1;

   task Blink with CPU => 2;

   protected Shared is
      procedure On;
      procedure Off;
      function Is_On return Boolean;
   private
      Status : Boolean := False;
   end Shared;

end Tasks;

The only critical part seem the aspects with CPU => x which creates an exception when initialising the secondary stack.

Breakpoint 1, 0x10002db0 in __gnat_last_chance_handler ()
(gdb) bt
#0  0x10002db0 in __gnat_last_chance_handler ()
#1  0x10003178 in system.secondary_stack.ss_init ()
#2  0x100024ca in system.tasking.restricted.stages.create_restricted_task ()
#3  0x100026c4 in system.tasking.restricted.stages.create_restricted_task ()
#4  0x100002ce in tasks__commandTKVIP ()
#5  0x100004a0 in <tasks___elabs> ()
    at /home/re/Devel/rp2040/rp2040_snippets/blink_light_tasking/src/tasks.ads:3
#6  0x10000226 in adainit () at b__blink_light_tasking.adb:33
#7  0x1000025c in main () at b__blink_light_tasking.adb:47
#8  0x10003218 in _start_rom ()

Do you have a known working program that uses both cores?

That’s surprising that it’s raising an exception during secondary stack initialization. It certainly shouldn’t be caused by the code you show there. Unfortunately I don’t have a RP2040 that I can use to investigate with myself (I need to order a new one), and it has been a while since I’ve used one so I don’t have a multicore example around at the moment.

I suspect that, for some reason, the compiler isn’t statically allocating a secondary stack buffer and the default secondary stack pool is empty. It might be possible to work around this problem by configuring the secondary stack explicitly via a pragma/aspect or by setting binder switches in your GPR file. One of the following might work:

  • try setting the Secondary_Stack_Size explicitly. For example, task Command with CPU => 1, Secondary_Stack_Size => 512;
  • try configuring the default secondary stack size via the binder switch -D:
package Binder is
   for Switches ("Ada") use ("-D512");
end Binder;
  • try allocating a couple extra secondary stacks in the pool via the binder switch -Q (might also need to be paired with -D above):
package Binder is
   for Switches ("Ada") use ("-Q2", "-D512");
end Binder;

Let me know if any of those work for you.