Why does my Pico 2 keep booting the wrong core when I flash it?

Nov 08, 2026 Last reply: 2 days ago 4 Replies

First reply

MakerCopper_1990

The core choice isn't made by what kind of binary you wrote. On the RP2350 the boot ROM always starts out running on the Arm core, reads your flash image, and only hands control to the RISC-V core if the image carries a small block saying 'this one is RISC-V'. So a RISC-V…

Read the full reply

I've been messing around with the RP2350 and trying to learn the RISC-V side of it, mostly writing little test programs and stepping through them with a debug probe over SWD. The weird thing is that sometimes when I load a program, the chip comes up running on the Arm core instead, and I have to go in and poke it before it behaves. My setup is a spare Raspberry Pi board I had lying around, nothing fancy, and I'm writing plain assembly by hand because I want to see what every single instruction does. No build system, no toolchain, just me and a text editor. Is there something on the chip that decides which core to boot from flash, or is it based on what kind of binary I wrote? If I write a RISC-V binary and flash it, will it always come up on the RISC-V core, or do I need to set something in the image itself? I tried building the same blink program for both cores and one of them seems to swap over at boot while the other one sits there doing nothing until I reset it. Honestly I might just be confusing myself with how the boot process works. Any pointers on what actually picks the core at power on would be a big help.


The core choice isn't made by what kind of binary you wrote. On the RP2350 the boot ROM always starts out running on the Arm core, reads your flash image, and only hands control to the RISC-V core if the image carries a small block saying 'this one is RISC-V'. So a RISC-V binary doesn't come up on the RISC-V core by magic, it comes up there because the boot code read that flag out of the image and did the swap itself.

That explains exactly what you're seeing. The blink build that swaps over at boot has a proper image definition block with the architecture field set. The one that sits there doing nothing til you poke it is almost certainly missing that block, or has it set for the wrong core, so the boot code either runs the bytes as Arm nonsense or just falls through and waits for something to happen.

Since you're writing raw assembly with no toolchain, nobody is adding that block for you. You'll have to append it yourself at the end of the binary before flashing. It's a fixed little table and one of the fields is the architecture selector. Set it for RISC-V and your program shoud come up on the RISC-V side every time. The datasheet has the layout and there are example hex dumps floating around. It's tedious to type by hand but you only do it once, and honestly it fits the whole 'see what every byte does' project you've got going.

One more wrinkle worth knowing: when you load into RAM over SWD instead of flashing, the boot ROM isn't in the picture at all. Then it's purely whatever your debugger does, meaning which core's registers it pokes and which one it tells to start running. If you're halting and resuming core 0 from your probe, you're executing Arm code regardless of what you assembled. So for your RAM tests, check which core the probe is actually attaching to, because that's the real decision point there, not anything in your image.

This thing is much trickier than I thougth. I thought if I just wrote the instructions for the RISC-V core, it woud just run there, but having to manually add some specific setup piece to the end of the file sounds like a huge pain to do by hand. Since I am writing everything in plain text without any fancy tools helping me, I just want to make sure I get this flag block right. If I am just typing out the raw hex bytes for this flag, what does it look like in relation to the actual program code itself?

I am finding this whole thing super complicated since I am building the assembler from scratch too. It seems like every little piece of the image, even the setup flag at the end, has to be perfectly placed in the right memory section or the whole thing just fails to boot correctly. My main confusion is how to tell the assembler where exactly to put this specific architecture selector code since I don't have any standard linking system helping me.

I've been trying to manually map out the internal memory layout based on the documentation, and getting the offsets right is proving really difficult. Is there a simple reference table or something that lists the exact starting addresses for diferent binary components, or am I expected to just guess based on how the flash works?

The assembler part is where most people trip up because you're manually stitching the parts together. You don't just append the flag to the end of your code; you need to place that block specifically into the image header region. If you're building it from scratch, you're basically writing a linker script in assembly, which is why it feels so complicated. On my last project using the Pico, I learned that the RP2350 expects the whole image to be a fixed size before it executes. I found that keeping the entire configuration header under 32 bytes is essential for reliable flash loading, which saves you from having to check the datasheet every time you tweak a setting. Once you figure out the offset, the rest is just memory mapping.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required