August 29, 2023

Raspberry Pi Pico - the SDK - the blink example

I cloned the pico-sdk and the pico-examples repository. Then I decided to start with an investigation of the "blink" example.

How do we build it? Some surprises arise immediately.

The Readme in the examples and for the SDK are all but useless.
Some searches lead me to do what I would never have expected, namely this:

git clone the above ... then
cd pico-examples
export PICO_SDK_PATH=../../pico-sdk/
mkdir build
cd build
cmake ..
After you do that, from that "global" build directory, you can do:
make blink
Who would have guessed?
This is nothing like I would have expected.
You do not cd into each example directory and make a build directory there for the specific example.

Here is the build output from "make blink"

So, we have a million subdirectories in the "build" directory now, but where the heck is blink.elf? It is in the directory "blink/blink.elf". There is a Makefile in there also, a 2352 line monster generated by cmake. We even have a blink.uf2 file! The tragic thing is that the build log says nothing about how this was generated.

After doing all this, I make a trivial edit to blink.c then repeat the build to get:

[tom@trona build]$ make blink
[  0%] Performing build step for 'ELF2UF2Build'
[100%] Built target elf2uf2
[  0%] No install step for 'ELF2UF2Build'
[  0%] Completed 'ELF2UF2Build'
[  0%] Built target ELF2UF2Build
[  0%] Built target bs2_default
[  0%] Built target bs2_default_padded_checksummed_asm
[  0%] Building C object blink/CMakeFiles/blink.dir/blink.c.obj
[  0%] Linking CXX executable blink.elf
[100%] Built target blink
These steps would be worth studying in detail.

The Makefiles are gigantic monstrosities. They yield (I suppose) functionality, but inadvertantly produce obfuscation.

A brief study of the Makefile reveals a useful trick. Set VERBOSE to anything and you will get much more output, as follows:

touch ../blink/blink.c
make VERBOSE=xxx blink
This yields the output shown here.: Ignoring the gigantic link line, and replacing some long directory paths, the build ends as follows:
cd /u1/Projects/rp2040/pico-examples/build/blink
/usr/bin/arm-none-eabi-objcopy -Oihex blink.elf blink.hex
/usr/bin/arm-none-eabi-objcopy -Obinary blink.elf blink.bin
/usr/bin/arm-none-eabi-objdump -h blink.elf > blink.dis
/usr/bin/arm-none-eabi-objdump -d blink.elf >> blink.dis
../elf2uf2/elf2uf2 blink.elf blink.uf2
So, "objcopy" is used in a variety of ways to extract and present information. Finally a tool "elf2uf2" is used to generate the uf2 file.

I boil down the all important link line as follows:

cd /u1/Projects/rp2040/pico-examples/build/blink
/usr/bin/cmake -E cmake_link_script CMakeFiles/blink.dir/link.txt --verbose=xxx

/usr/bin/arm-none-eabi-g++ -mcpu=cortex-m0plus -mthumb -O3 -DNDEBUG -Wl,--build-id=none --specs=nosys.specs
-Wl,--wrap=sprintf -Wl,--wrap=snprintf -Wl,--wrap=vsnprintf
-Wl,--wrap=__clzsi2 -Wl,--wrap=__clzdi2 -Wl,--wrap=__ctzsi2 -Wl,--wrap=__ctzdi2
-Wl,--wrap=__popcountsi2 -Wl,--wrap=__popcountdi2
-Wl,--wrap=__clz -Wl,--wrap=__clzl -Wl,--wrap=__clzll
-Wl,--wrap=__aeabi_idiv -Wl,--wrap=__aeabi_idivmod -Wl,--wrap=__aeabi_ldivmod -Wl,--wrap=__aeabi_uidiv -Wl,--wrap=__aeabi_uidivmod
-Wl,--wrap=__aeabi_uldivmod -Wl,--wrap=__aeabi_dadd -Wl,--wrap=__aeabi_ddiv -Wl,--wrap=__aeabi_dmul
-Wl,--wrap=__aeabi_drsub -Wl,--wrap=__aeabi_dsub
-Wl,--wrap=__aeabi_cdcmpeq -Wl,--wrap=__aeabi_cdrcmple -Wl,--wrap=__aeabi_cdcmple -Wl,--wrap=__aeabi_dcmpeq
-Wl,--wrap=__aeabi_dcmplt -Wl,--wrap=__aeabi_dcmple -Wl,--wrap=__aeabi_dcmpge -Wl,--wrap=__aeabi_dcmpgt -Wl,--wrap=__aeabi_dcmpun
-Wl,--wrap=__aeabi_i2d -Wl,--wrap=__aeabi_l2d -Wl,--wrap=__aeabi_ui2d -Wl,--wrap=__aeabi_ul2d
-Wl,--wrap=__aeabi_d2iz -Wl,--wrap=__aeabi_d2lz -Wl,--wrap=__aeabi_d2uiz -Wl,--wrap=__aeabi_d2ulz -Wl,--wrap=__aeabi_d2f
-Wl,--wrap=sqrt -Wl,--wrap=cos -Wl,--wrap=sin -Wl,--wrap=tan -Wl,--wrap=atan2 -Wl,--wrap=exp -Wl,--wrap=log -Wl,--wrap=ldexp
-Wl,--wrap=copysign -Wl,--wrap=trunc -Wl,--wrap=floor -Wl,--wrap=ceil -Wl,--wrap=round -Wl,--wrap=sincos
-Wl,--wrap=asin -Wl,--wrap=acos -Wl,--wrap=atan -Wl,--wrap=sinh -Wl,--wrap=cosh -Wl,--wrap=tanh -Wl,--wrap=asinh -Wl,--wrap=acosh -Wl,--wrap=atanh
-Wl,--wrap=exp2 -Wl,--wrap=log2 -Wl,--wrap=exp10 -Wl,--wrap=log10 -Wl,--wrap=pow -Wl,--wrap=powint -Wl,--wrap=hypot
-Wl,--wrap=cbrt -Wl,--wrap=fmod -Wl,--wrap=drem -Wl,--wrap=remainder -Wl,--wrap=remquo -Wl,--wrap=expm1 -Wl,--wrap=log1p
-Wl,--wrap=fma -Wl,--wrap=__aeabi_lmul -Wl,--wrap=__aeabi_fadd -Wl,--wrap=__aeabi_fdiv -Wl,--wrap=__aeabi_fmul -Wl,--wrap=__aeabi_frsub
-Wl,--wrap=__aeabi_fsub -Wl,--wrap=__aeabi_cfcmpeq -Wl,--wrap=__aeabi_cfrcmple -Wl,--wrap=__aeabi_cfcmple -Wl,--wrap=__aeabi_fcmpeq
-Wl,--wrap=__aeabi_fcmplt -Wl,--wrap=__aeabi_fcmple -Wl,--wrap=__aeabi_fcmpge -Wl,--wrap=__aeabi_fcmpgt -Wl,--wrap=__aeabi_fcmpun
-Wl,--wrap=__aeabi_i2f -Wl,--wrap=__aeabi_l2f -Wl,--wrap=__aeabi_ui2f -Wl,--wrap=__aeabi_ul2f -Wl,--wrap=__aeabi_f2iz -Wl,--wrap=__aeabi_f2lz
-Wl,--wrap=__aeabi_f2uiz -Wl,--wrap=__aeabi_f2ulz -Wl,--wrap=__aeabi_f2d -Wl,--wrap=sqrtf -Wl,--wrap=cosf -Wl,--wrap=sinf -Wl,--wrap=tanf
-Wl,--wrap=atan2f -Wl,--wrap=expf -Wl,--wrap=logf -Wl,--wrap=ldexpf -Wl,--wrap=copysignf -Wl,--wrap=truncf -Wl,--wrap=floorf -Wl,--wrap=ceilf
-Wl,--wrap=roundf -Wl,--wrap=sincosf -Wl,--wrap=asinf -Wl,--wrap=acosf -Wl,--wrap=atanf -Wl,--wrap=sinhf -Wl,--wrap=coshf -Wl,--wrap=tanhf
-Wl,--wrap=asinhf -Wl,--wrap=acoshf -Wl,--wrap=atanhf -Wl,--wrap=exp2f -Wl,--wrap=log2f -Wl,--wrap=exp10f -Wl,--wrap=log10f -Wl,--wrap=powf
-Wl,--wrap=powintf -Wl,--wrap=hypotf -Wl,--wrap=cbrtf -Wl,--wrap=fmodf -Wl,--wrap=dremf -Wl,--wrap=remainderf -Wl,--wrap=remquof
-Wl,--wrap=expm1f -Wl,--wrap=log1pf -Wl,--wrap=fmaf -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free
-Wl,--wrap=memcpy -Wl,--wrap=memset -Wl,--wrap=__aeabi_memcpy -Wl,--wrap=__aeabi_memset -Wl,--wrap=__aeabi_memcpy4
-Wl,--wrap=__aeabi_memset4 -Wl,--wrap=__aeabi_memcpy8 -Wl,--wrap=__aeabi_memset8 -Wl,-Map=blink.elf.map
-Wl,--script=/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_standard_link/memmap_default.ld
-Wl,-z,max-page-size=4096 -Wl,--gc-sections -Wl,--no-warn-rwx-segments
-Wl,--wrap=printf -Wl,--wrap=vprintf -Wl,--wrap=puts -Wl,--wrap=putchar -Wl,--wrap=getchar
CMakeFiles/blink.dir/blink.c.obj
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_stdlib/stdlib.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_gpio/gpio.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_platform/platform.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_claim/claim.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_sync/sync.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_irq/irq.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_sync/sem.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_sync/lock_core.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_sync/mutex.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_sync/critical_section.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_time/time.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_time/timeout_helper.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_timer/timer.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_util/datetime.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_util/pheap.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/common/pico_util/queue.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_uart/uart.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_clocks/clocks.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_pll/pll.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_vreg/vreg.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_xosc/xosc.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_divider/divider.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_runtime/runtime.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_printf/printf.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_bootrom/bootrom.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_divider/divider.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_double/double_aeabi.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_double/double_init_rom.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_double/double_math.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_double/double_v1_rom_shim.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_float/float_aeabi.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_float/float_init_rom.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_float/float_math.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_float/float_v1_rom_shim.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_malloc/pico_malloc.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_standard_link/crt0.S.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_standard_link/new_delete.cpp.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_standard_link/binary_info.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_stdio/stdio.c.obj"
"CMakeFiles/blink.dir/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.obj"
-o blink.elf
../pico-sdk/src/rp2_common/boot_stage2/bs2_default_padded_checksummed.S
Notice buried in the midst of all this:
-Wl,--script=/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_standard_link/memmap_default.ld
-Wl,-z,max-page-size=4096 -Wl,--gc-sections -Wl,--no-warn-rwx-segments
The linker script is also big (251 lines), and interesting.

I am somewhat distressed to see that g++ is used to do the linking (I have every hope of avoiding C++ in my dealings with the rp2040).

The stage 2 bootloader is what first got me interested in all of this. Note that the file is not located in the sdk distribution itself, the full path is:

/u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default_padded_checksummed.S
Amusingly this is a block of binary information, regenerated as assembly code. To find out how this is generated, we will have to redo the original build step with VERBOSE set and dig out the relevant lines.

Start over

Nuke the build directory and do it all again:
cd pico-examples
export PICO_SDK_PATH=../../pico-sdk/
rm -rf build
mkdir build
cd build
cmake ..
make VERBOSE=xxx blink
This goes quicker than you might think.

There are two topics of interest in this for me. First is "elf2uf2". Here I have tried to condense and simplify the "log" of what was done and I won't claim that I might no have made mistakes.

cd /u1/Projects/rp2040/pico-examples/build/elf2uf2
/usr/bin/cmake -E touch /u1/Projects/rp2040/pico-examples/build/blink/elf2uf2/src/ELF2UF2Build-stamp/ELF2UF2Build-configure
make
/usr/bin/cmake -S/u1/Projects/rp2040/pico-sdk/tools/elf2uf2 -B/u1/Projects/rp2040/pico-examples/build/elf2uf2 --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /u1/Projects/rp2040/pico-examples/build/elf2uf2/CMakeFiles /u1/Projects/rp2040/pico-examples/build/elf2uf2//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make  -f CMakeFiles/elf2uf2.dir/build.make CMakeFiles/elf2uf2.dir/depend
/usr/bin/cmake -E cmake_depends "Unix Makefiles" /u1/Projects/rp2040/pico-sdk/tools/elf2uf2 /u1/Projects/rp2040/pico-sdk/tools/elf2uf2 /u1/Projects/rp2040/pico-examples/build/elf2uf2 /u1/Projects/rp2040/pico-examples/build/elf2uf2 /u1/Projects/rp2040/pico-examples/build/elf2uf2/CMakeFiles/elf2uf2.dir/DependInfo.cmake "--color="
make  -f CMakeFiles/elf2uf2.dir/build.make CMakeFiles/elf2uf2.dir/build
/usr/bin/c++  -I/u1/Projects/rp2040/pico-sdk/src/common/boot_uf2/include -std=gnu++14 -MD -MT CMakeFiles/elf2uf2.dir/main.cpp.o -MF CMakeFiles/elf2uf2.dir/main.cpp.o.d -o CMakeFiles/elf2uf2.dir/main.cpp.o -c /u1/Projects/rp2040/pico-sdk/tools/elf2uf2/main.cpp
/usr/bin/cmake -E cmake_link_script CMakeFiles/elf2uf2.dir/link.txt --verbose=xxx
/usr/bin/c++ CMakeFiles/elf2uf2.dir/main.cpp.o -o elf2uf2

See if it runs

I plug in my pico, hold down the bootsel and the reset button I added, and it mounts as /run/media/tom/RPI-RP2/ on my linux system. Then I do:
cp blink.uf2 /run/media/tom/RPI-RP2/
And the LED gets to blinking! And when I hit reset, it runs the blink demo every time, so this has successfully made its way to flash. A complete success.

In conclusion

Note the odd layout used with the examples; this is how you play the game:

The example source is in: pico-examples/build/blink

The example is built by: cd pico-examples/build ; make blink

The build results end up in: pico-examples/build/blink

Second stage boot

This is a topic of particular interest to me and it deserves a page of its own.


Have any comments? Questions? Drop me a line!

Tom's electronics pages / tom@mmto.org