みつきんのメモ

組み込みエンジニアです。Interface誌で「My オリジナルLinuxの作り方」連載中

PicoでベアメタルのLチカ(FLASH + コンパイル済みBoot Stage2バージョン) その1

はじめに

Raspberry Pi Pico SDKでのFlashからのブートについて調べたでは、Flashからブートするための仕組みを調査した。

Boot Stage2に相当する部分を自作するのは結構面倒だということがわかったので、コンパイル済み(CRC計算済み)のBoot Stage2である、 bs2_default_padded_checksummed.Sをリンクしてプログラムをつくってみる。

そのためにはBoot Stage2が何をやっているかを調べる必要があったが、既に調べている人がいたので参考にする。(ありがたや!)

また、FLASHやメモリ上にどのように配置しなければならないかを調べるためにPico SDKのリンカスクリプトを眺めた

下準備

elf2uf2

PicoのFLASHに書き込む場合、ビルドしたelf形式のファイルをuf2形式に変換する必要がある。ここではPico SDKに含まれるelf2uf2というツールを使用する。

そのためには、あらかじめPATHの通った場所にファイルをコピーしておく。

$ cp ${PICO_EXAMPLES_PATH}/build/elf2uf2/elf2uf2 ~/bin

~/binにパスを通すためには下記のように.bashrcなどに設定しておく。

$ echo 'export PATH=${HOME}/bin:${PATH}' >> ~/.bashrc
$ source ~/.bashrc

Boot Stage2

PicoのデフォルトのBoot Stage2である、bs2_default_padded_checksummed.SをPico SDKからコピーする。

$ mkdir boot2
$ cp ${PICO_EXAMPLES_PATH}/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default_padded_checksummed.S ./boot2

ディレクトリ構成

Boot Stage2をboot2ディレクトリに格納してそれ以外はフラットな構造。自分で作るファイルはRAM版とほぼ同じ。

├── Makefile
├── boot2
│   └── bs2_default_padded_checksummed.S
├── main.c
├── memmap.ld
├── pico.gdbinit
├── regs.h
└── start.S

違っているのは下記の3つのファイルなので、これ以外のものはRAM版)の記事を参照してほしい。

リンカスクリプト

memmap.ldを下記の内容で作成する。

MEMORY
{
    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
    RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 256k
}

ENTRY(reset)

SECTIONS
{
    /* 
       boot2 section is for embed the precompiled boot2.
       this code from memmap_default.ld in pico-sdk
    */
    .boot2 : {
        __boot2_start__ = .;
        KEEP(*(.boot2))
        __boot2_end__ = .;
    } > FLASH

    ASSERT(__boot2_end__ - __boot2_start__ == 256,
        "ERROR: Pico second stage bootloader must be 256 bytes in size")
    .text : {
        KEEP (*(.vectors))
        KEEP(*(.text*))
    } > FLASH
}

スタックポインタもRAM上においてしまうのでSCRATCH_XやSCRATCH_Yは定義しない。

FLASHの先頭256バイトの位置にBoot Stage2を配置する。

Boot Stage2によって、その直後の0x10000100の位置にあるはずのベクターテーブルのリセットベクタを読みに来るので、.vectors.boot2の直後に配置されるようにする。 プログラム本体である.textベクターテーブルの後ろに配置されるようにしておく。

スタートアップ

start.Sを下記の内容で作成する。

 .cpu cortex-m0plus
    .thumb

/* vector table */
    .section .vectors, "ax"
    .align 2
    .global __vectors
__vectors:
.word 0x20001000
.word reset

/* reset handler */
    .thumb_func
    .global reset
reset:
    ldr r0, =0x20001000
    mov sp, r0
    bl main
    b hang
    
.thumb_func
hang:    b .

ベクターテーブルは先頭の位置とresetベクタの位置が合っていればとりあえずプログラムは起動できる。 ただし割り込みなどを使用する場合は、きちんと定義する必要がある。

リセットベクタとしてreset関数を登録しておく。

resetはスタックポインタを設定してmainにジャンプするだけ。

Makefile

Makefileを下記の名前で作成する。

CROSS_COMPILE ?= arm-none-eabi-

MCPU = -mcpu=cortex-m0plus

ASMOPT = $(MCPU) -g
COPT = $(MCPU) -ffreestanding -g -O0
LOPT = -nostdlib -nostartfiles

all: led.uf2

clean:
    rm -f *.o
    rm -f *.elf
    rm -f *.list
    rm -f *.uf2
    rm -f *~

start.o: start.S
    $(CROSS_COMPILE)as $(ASMOPT) start.S -o start.o

boot2.o: boot2/bs2_default_padded_checksummed.S
    $(CROSS_COMPILE)as $(ASMOPT) boot2/bs2_default_padded_checksummed.S -o boot2.o

main.o: main.c
    $(CROSS_COMPILE)gcc $(COPT) -fpic -mthumb -c main.c -o main.o

led.elf: start.o boot2.o main.o
    $(CROSS_COMPILE)ld $(LOPT) start.o boot2.o main.o -T memmap.ld  -o led.elf 

led.uf2: led.elf
    elf2uf2 led.elf led.uf2

makeコマンドでuf2形式に変換できるようにした。

書き込み

ボード側のBOOTSELボタンを押しながら、USBケーブルでPCと接続すると自動的に/media/${USER}/RPI-RP2にマウントされる。

その状態でled.uf2をボードに書き込む。

$ cp led.uf2 /media/${USER}/RPI-RP2

書き込み終了時点からLEDがチカチカした!

f:id:mickey_happygolucky:20210222090906g:plain
Lチカ

まとめ

軽い気持ちでBoot Stage2を組み込んだらベアメタル簡単につくれるのでは?と作業を始めたが、結構大変だった。

リンカスクリプトについても、最低限起動に必要なルールを踏襲するだけで割と自由に作れることがわかった。

おそらくその2はない。

参考