みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Raspberry Pi Pico Systick割り込みを使ってLチカをFlashに書く

はじめに

前回作ったプログラムをFlashから実行できるようにしてみる。

RESUSについて追記(2021/3/10)

Flash対応

Makefile

diff -Narubp blink_ram_int_systick/Makefile blink_flash_int_systick/Makefile
--- blink_ram_int_systick/Makefile    2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/Makefile  2021-02-28 07:45:26.010697915 +0900
@@ -7,20 +7,26 @@ ASMOPT = $(MCPU) -g
 COPT = $(MCPU) -ffreestanding -g -O0
 LOPT = -nostdlib -nostartfiles
 
-all: ram
+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
 
-ram: start.o main.o
-  $(CROSS_COMPILE)ld $(LOPT) start.o main.o -T memmap_ram.ld  -o led.elf 
+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

Flashに書き込みできるようにビルド済みのBoot Stage2のリンクとelf2uf2でuf2に変換できるようにする。

リンカスクリプト

リンカスクリプトmemmap_ram.ldではなくmemmap.ldにリネームして下記のように変更する。

diff -Narubp blink_ram_int_systick/memmap_ram.ld blink_flash_int_systick/memmap_ram.ld
--- blink_ram_int_systick/memmap_ram.ld   2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/memmap_ram.ld 2021-02-28 08:24:07.117410109 +0900
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: MIT */
 MEMORY
 {
+    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
       RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 256k
 }
 
@@ -8,11 +9,22 @@ ENTRY(reset)
 
 SECTIONS
 {
-    .text : {
-        *(.vectors)
-      *(.text*)
-    } > RAM
+    /* 
+       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
     .bss : {
         __bss_start__ = .;
    *(.bss*)

スタートアップ

ベクターテーブルはBoot Stage2で設定されるので、VTORの設定は削除。

diff -Narubp blink_ram_int_systick/start.S blink_flash_int_systick/start.S
--- blink_ram_int_systick/start.S 2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/start.S   2021-02-28 07:51:40.922563814 +0900
@@ -28,10 +28,6 @@ __vectors:
    .thumb_func
    .global reset
 reset:
-  ldr r1, =0xE000ED08 /* VTOR */
-  ldr r0, =__vectors
-  str r0, [r1]
-
    ldr r0, =0x20001000
    mov sp, r0
    bl init_bss

実行(失敗)

ビルドして出来上がったled.uf2をPicoに書き込む。

LEDがチカチカするが波形取ってみると下記のようになる。

f:id:mickey_happygolucky:20210228155147p:plain
Lチカ(失敗)

本来は100msになっているべきWidthが223.6msになっている。

XOSC

RP2040 Datasheetからこの辺りのことを調べてみる。

「2.4.5.1.1. SysTick timer」によると、watchdogブロックのクロックを使用する。

The SysTick timer uses a 1μs pulse as a clock enable. This is generated in the watchdog block as timer_tick.

SysTickはクロックソースをSYST_CSRのCLKSOURCEによって選択できるが、Lチカプログラムでは外部クロック(watchdogのクロック)を使用するようにしている。

「4.7.2 Tick generation」によると、1usのtickを得るために使用しているclk_tickclk_refから作られているらしく、正確なリファレンスクロックを得るには、Crystal Oscillator(XOSC)を動かす必要があるらしい。

The watchdog reference clock, clk_tick, is driven from clk_ref. Ideally clk_ref will be configured to use the Crystal Oscillator (Section 2.16) so that it provides an accurate reference clock.

「2.7. Boot Sequence」によるとclk_refは最初Ring Oscillator(ROSC)によって、低周波数(6.5MHz)で駆動しているとのこと。

The Ring Oscillator (Section 2.17) is started, providing a clock source to the clock generators. clk_sys and clk_ref are now running at a relatively low frequency (typically 6.5MHz).

Pico SDKを使用している場合は、xosc_init()によってこれらが正しく設定された状態でプログラムが実行される。

RAMバージョンで問題なく動作していたのは、Flashに書き込まれていたプログラムがpico-examplesのblinkerで、Pico SDKによってXOSCの初期化が行われていたからではないかと推測される。

レジスタ設定の確認

XOSCのレジスタ

XORCレジスタで設定する。

f:id:mickey_happygolucky:20210228155305p:plain

f:id:mickey_happygolucky:20210228155324p:plain

f:id:mickey_happygolucky:20210228155343p:plain

クロックソース

CLOCKSレジスタで設定する。

f:id:mickey_happygolucky:20210228155358p:plain

f:id:mickey_happygolucky:20210228155411p:plain

XOSC初期化処理の追加

XOSCを初期化しclk_sysのクロックソースをROSCからXOSCに変更する。

regs.h

regs.hを下記のように修正する。

diff --git a/blink_flash_int_systick/regs.h b/blink_flash_int_systick/regs.h
index 2d650ec..51792c4 100644
--- a/blink_flash_int_systick/regs.h
+++ b/blink_flash_int_systick/regs.h
@@ -34,5 +34,13 @@
 #define SYSTICK_RVR (0xE000E014)
 #define SYSTICK_CVR (0xE000E018)
 
+#define XOSC_BASE (0x40024000)
+#define XOSC_CTRL (XOSC_BASE+0x00)
+#define XOSC_STATUS (XOSC_BASE+0x04)
+#define XOSC_STARTUP (XOSC_BASE+0x0c)
+
+#define CLOCKS_BASE (0x40008000)
+#define CLOCKS_REF_CTRL (CLOCKS_BASE+0x30)
+#define CLOCKS_SYS_CTRL (CLOCKS_BASE+0x3c)
 
 #endif //REGS_H

XOSC関連とclk_ref、clc_sysのコントロールレジスタを追加。

main

main.cを下記のように修正する。

diff --git a/blink_flash_int_systick/main.c b/blink_flash_int_systick/main.c
index 19539f9..6553c63 100644
--- a/blink_flash_int_systick/main.c
+++ b/blink_flash_int_systick/main.c
@@ -24,6 +24,26 @@ void init_bss() {
         }
 }
 
+void init_xosc() {
+        write_reg(XOSC_CTRL, 0xaa0); /* 1_15MHZ */
+        write_reg(XOSC_STARTUP, 47); /* (((12 * MHZ) / 1000) + 128) / 256 = 47 */
+        write_reg_op(XOSC_CTRL, 0xfab << 12, OP_SET); /* ENABLE */
+
+        /* Wait for XOSSC to be stable */
+        while (!(read_reg(XOSC_STATUS) & 0x80000000)) ;
+}
+
+void init_clocks() {
+        init_xosc();
+
+        /* source of clk_ref to xosc */
+        write_reg_op(CLOCKS_REF_CTRL, 0x2, OP_SET);
+
+        /* reset the source of clk_sys to clk_ref which references xosc */
+        write_reg(CLOCKS_SYS_CTRL, 0x0);
+}
+
+
 void init_systick() {
         write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
         write_reg(SYSTICK_CVR, 0);

XOSCの初期化処理と、clk_refのクロックソースをXOSCに変更。 XOSCを参照するようにしたclk_refを、clk_sysのクロックソースとして再設定している。

RESUS

クロックの初期化処理を追加したのに、実際に波形を取ってみると思ったとおりに動かない(パルス幅が広くなる)ことがある。

これは初期化の最中にRESUSという機能が働いた結果おかしい動作になっているっぽいことがわかった。

RESUSはRP2040が持っているセーフティの機能で、clk_sysが止まってしまった場合、clk_refを使用して無理やり動かすというものらしい。よってclk_refまで止まるとRESUSでもどうにもできなくなるとのこと。

それはそれとして、クロックの初期化で一度clk_sysを止めるのでその際にRESUSが動いたままだと挙動がおかしくなることがあるらしい。そのためクロックの初期化をする前にRESUSを止めておく必要がある。

main.cのinit_clocks()を下記のように修正する。

void init_clocks() {
        /*  Disable resus that may be enabled from previous software */
        write_reg(CLOCKS_CLK_SYS_RESUS_CTRL, 0);

        init_xosc();

        /* source of clk_ref to xosc */
        write_reg_op(CLOCKS_REF_CTRL, 0x2, OP_SET);

        /* reset the source of clk_sys to clk_ref which references xosc */
        write_reg(CLOCKS_SYS_CTRL, 0x0);
}

CLOCKS_CLK_SYS_RESUS_CTRLの定義をrefs.hに追加する。

#define CLOCKS_CLK_SYS_RESUS_CTRL (CLOCKS_BASE+0x78)

スタートアップ

start.Sを下記のように修正する。

diff --git a/blink_flash_int_systick/start.S b/blink_flash_int_systick/start.S
index b39ba02..0eb8efc 100644
--- a/blink_flash_int_systick/start.S
+++ b/blink_flash_int_systick/start.S
@@ -31,6 +31,7 @@ reset:
        ldr r0, =0x20001000
        mov sp, r0
        bl init_bss
+       bl init_clocks
        bl main
        b hang

init_clocksを呼び出すように変更。

実行

ビルドして出来上がったled.uf2をPicoに書き込む。

LEDがチカチカするが波形取ってみると下記のようになる。

f:id:mickey_happygolucky:20210228155224p:plain
Lチカ

パルス幅が100msになっている。

まとめ

LチカをFlashに書くと、クロック周りの初期化をきちんとしないと正しいタイミングで点滅しない。

参考