みつきんのメモ

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

PicoでベアメタルのLチカ(RAMバージョン) その1

はじめに

picoprobeでRAMに直接プログラムがロードできるようになったので、難しいboot2のことはおいておいて、 ベアメタルでLチカを試みる。

ホントは、コンパイル済みのboot2を組み込んでLチカしようと頑張ったが、現時点では挫折した。

心の師匠の成果物の丸パクリにならないように頑張ったが初期化コードの一部をまるっと拝借した。

あと、SysTickの設定なども手が回らなかったのでチカチカのタイミングは適当。

ディレクトリ構成

下記のようにフラットな構造とした。

├── Makefile
├── main.c
├── memmap_ram.ld
├── pico.gdbinit
├── regs.h
└── start.S

リンカスクリプト

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

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

ENTRY(reset)

SECTIONS
{
    .text : {
        *(.text*)
    } > RAM
}

単純にtextセクションをRAM上に配置する。

エントリポイントにresetを設定している。

スタートアップ

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

 .cpu cortex-m0plus
    .thumb

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

デバッガで実行すると、ENTRYから開始されるのでresetの先頭から実行される。

resetではスタックポインタを初期化してmain関数を呼び出している。

レジスタ定義

regs.hを下記の内容で作成する。

#ifndef REGS_H
#define REGS_H

#define OP_RW  (0x0000)
#define OP_XOR (0x1000)
#define OP_SET (0x2000)
#define OP_CLR (0x3000)

#define RESETS_BASE                 (0x4000C000)

#define RESETS_RESET             (RESETS_BASE+0x0)
#define RESETS_RESET_DONE        (RESETS_BASE+0x8)

#define SIO_BASE (0xD0000000)

#define SIO_GPIO_OUT_SET (SIO_BASE+0x14)
#define SIO_GPIO_OUT_CLR (SIO_BASE+0x18)
#define SIO_GPIO_OUT_XOR (SIO_BASE+0x1C)
#define SIO_GPIO_OE_SET (SIO_BASE+0x24)
#define SIO_GPIO_OE_CLR (SIO_BASE+0x28)

#define PADS_BANK0_BASE (0x4001C000)
#define PADS_BANK0_GPIO (0x68)

#define PADS_GPIO25 (PADS_BANK0_BASE + PADS_BANK0_GPIO)

#define IO_BANK0_BASE (0x40014000)
#define IO_BANK_GPIO25_CTRL (0xCC)

#define IO_GPIO25_CTRL (IO_BANK0_BASE + IO_BANK_GPIO25_CTRL)


#endif //REGS_H

Lチカに必要なレジスタの定義をregs.hに切り出している。

メインプログラム

main.cを下記の内容で作成する。

#include "regs.h"

#include <stdint.h>

uint32_t read_reg(uint32_t addr) {
        return (*((volatile uint32_t *)addr));
}

void write_reg(uint32_t addr, uint32_t value) {
        *((volatile uint32_t *)addr) = value;
}

void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
        write_reg(addr | op, value);
}


int main(void) {

        /////////////////////
        // This initialise code is ported from https://github.com/dwelch67/raspberrypi-pico/blob/master/blinker00/notmain.c
        // release reset on IO_BANK0
        write_reg_op(RESETS_RESET, 1<<5, OP_CLR); //IO_BANK0
        //wait for reset to be done
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<5))!=0) break;
        }
        write_reg_op(RESETS_RESET, (1<<8), OP_CLR); //PADS_BANK0
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<8))!=0) break;
        }
        /////////////////////

        // GPIO init
        write_reg(SIO_GPIO_OE_CLR, (1ul<<25));
        write_reg(SIO_GPIO_OUT_CLR, (1ul<<25));
        uint32_t ra = read_reg(PADS_GPIO25);
        write_reg_op(PADS_GPIO25, (ra^0x40)&0xC0, OP_XOR);

        write_reg(IO_GPIO25_CTRL, 0x5);
        uint32_t v = read_reg(IO_GPIO25_CTRL);

        // Blink
        write_reg(SIO_GPIO_OE_SET, (1ul<<25));
        while (1) {
                for (int i = 100000; i != 0; i--) ;
                write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
        }
        return 0;
}

基本的にはpico-examplesのblinkの処理を分解して必要な処理だけにしたが、 試行錯誤の結果、初期化コードだけはdwelch67先生の成果物を丸パクリ参考にした。

Makefile

Makefileを下記の内容で作成する。

CROSS_COMPILE ?= arm-none-eabi-

MCPU = -mcpu=cortex-m0plus

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

all: ram

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

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

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

ram: start.o main.o
    $(CROSS_COMPILE)ld $(LOPT) start.o main.o -T memmap_ram.ld  -o led.elf 

gdb初期化スクリプト

pico.gdbinitを下記の内容で作成する。

target remote :3333
mon reset init
load

ビルドと実行

ビルド

makeでビルドできる。

$ make

実行

まずopenocdを起動する。

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -s tcl

別のターミナルでgdb-multiarchを起動する。

$ gdb-multiarch -x pico.gdbinit led.elf
... (snip) ...
Type "apropos word" to search for commands related to "word"...
Reading symbols from led.elf...
0x20000116 in main () at main.c:83
83                  for (int i = 100000; i != 0; i--) ;
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
Loading section .text, size 0x158 lma 0x20000000
Start address 0x20000000, load size 344
Transfer rate: 2752 bits in <1 sec, 344 bytes/write.
(gdb) 

これでPicoにプログラムがロードされるので、下記で実行する。

(gdb) c
Continuing.

LEDがチカチカする。

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

まとめ

boot2を使わない、RAMで直接するLチカを試した。

SysTickやタイマーを使っていないためタイミングは適当。 Pico SDKで作る場合と違い、ボードの初期化処理は自分でしないといけない。

コアは1個しか動いていないはず。(何もしてないので。)