みつきんのメモ

組み込みエンジニアです。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に書くと、クロック周りの初期化をきちんとしないと正しいタイミングで点滅しない。

参考

Raspberry Pi Pico Systick割り込みを使ってLチカをする

はじめに

SysTick割り込みを使用してLチカをする。

2021-02-26 Raspberry Pi Pico Systickを使ってLチカを正確にするのコードをベースに作業する。

SysTick割り込み

SysTick割り込みはm0+のコアで用意されている割り込みで、この割り込みが発生するとベクターテーブルに飛んでくる。

ここによるとベクターテーブルは下記のような構造になっている。

ベクターテーブル

ベクターテーブルは通常0x00000000に配置されCPUのリセットがかかったり割り込みが入ったりした場合に参照される様になっているが、VTORレジスタにアドレスを設定することで任意の場所に配置することができる。

今回はPicoのSRAM上にベクターテーブルを配置するようにする。

ベクターテーブルと割り込みハンドラの追加

ベクターテーブルとSysTickの割り込みハンドラとなるisr_systickを追加する。

スタートアップ

start.Sを下記のように変更する。

--- a/blink_ram_int_systick/start.S
+++ b/blink_ram_int_systick/start.S
@@ -2,10 +2,36 @@
    .cpu cortex-m0plus
    .thumb

+/* vector table */
+   .section .vectors, "ax"
+   .align 2
+   .global __vectors
+__vectors:
+.word 0x20001000
+.word reset
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word isr_systick
+
 /* reset handler */
    .thumb_func
    .global reset
 reset:
+   ldr r1, =0xE000ED08 /* VTOR */
+   ldr r0, =__vectors
+   str r0, [r1]
+
    ldr r0, =0x20001000
    mov sp, r0
    bl main

ベクターテーブルを__vectorsとして定義する。 Systick割り込みハンドラの位置にisr_systickを登録している。他の割り込みは使用しないのでisr_systickまでしか定義していない。

reset関数の先頭でVTORレジスタ__vectorsのアドレスを設定している。

リンカスクリプト

memmap_ram.ldを下記の様に修正する。

--- a/blink_ram_int_systick/memmap_ram.ld
+++ b/blink_ram_int_systick/memmap_ram.ld
@@ -9,6 +9,7 @@ ENTRY(reset)
 SECTIONS
 {
     .text : {
+        *(.vectors)
        *(.text*)
     } > RAM
 }

.vectorセクションがRAMに配置されるようにする。

main

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

--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -18,6 +18,11 @@ void init_systick() {
         write_reg(SYSTICK_CSR, 0x1);
 }

+static uint32_t tick_count = 0;
+void isr_systick() {
+        ++tick_count;
+}
+
 // us is up to max of 24bit (0xffffff)
 void delay_us(uint32_t us) {
         write_reg(SYSTICK_RVR, us);

Systickの割り込みハンドラとなるisr_systickを追加する。

割り込みが発生するたびにtick_countをインクリメントする。

ディレイ処理の改造

前回はマイクロ秒単位で作成したディレイ関数をミリ秒単位に変更する。

main.c

diff --git a/blink_ram_int_systick/main.c b/blink_ram_int_systick/main.c
index 830f1e4..77d1309 100644
--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -15,7 +15,9 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
 }

 void init_systick() {
-        write_reg(SYSTICK_CSR, 0x1);
+        write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
+        write_reg(SYSTICK_CVR, 0);
+        write_reg(SYSTICK_CSR, 0x3); /* TICKINT=1, ENABLE=1 */
 }

 static uint32_t tick_count = 0;
@@ -23,16 +25,10 @@ void isr_systick() {
         ++tick_count;
 }

-// us is up to max of 24bit (0xffffff)
-void delay_us(uint32_t us) {
-        write_reg(SYSTICK_RVR, us);
-        write_reg(SYSTICK_CVR, 0);
-
-        // wait for 0 to RVR
-        while (!read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
-
-        // wait for RVR counting down to 0.
-        while (read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+void delay_ms(uint32_t ms) {
+        uint32_t expire = tick_count + ms;
+        while (tick_count != expire)
+                __asm__ __volatile__("");
 }

 int main(void) {
@@ -66,7 +62,7 @@ int main(void) {
         // Blink
         write_reg(SIO_GPIO_OE_SET, (1ul<<25));
         while (1) {
-                delay_us(100*1000); //100ms
+                delay_ms(100); //100ms
                 write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
         }
         return 0;

RVRを1000-1に設定することで、割り込みの周期を1ミリ秒にしている。

BSS領域を使えるようにする

変数tick_countはスタティックな広域変数となっている。0で初期化しているため、コンパイラによって.bssセクションに配置される様になっている。

$ readelf -s led.elf | grep tick_count
25:    21: 20000224     4 NOTYPE  LOCAL  DEFAULT    2 tick_count
$ readelf -S led.elf | grep bss
7:  [ 2] .bss              NOBITS          20000224 000278 000004 00  WA  0   0  4

余談だが初期値を0以外にすると.dataセクションに配置される。

現在は.bssセクションはリンカスクリプトでもケアしていないので、下記のようにCのコード上0に初期化していても実際にはおかしな値が設定されている。

static uint32_t tick_count = 0;

これを正しく動作させるためには下記の様な修正をする必要がある。

  • リンカスクリプトを修正し.bssセクションが正しくRAM上に配置されるようにする。
  • mainが実装されるまでに0で初期化する。

リンカスクリプト

リンカスクリプトを下記のように修正する。

diff --git a/blink_ram_int_systick/memmap_ram.ld b/blink_ram_int_systick/memmap_ram.ld
index 97530d9..c16e1ea 100644
--- a/blink_ram_int_systick/memmap_ram.ld
+++ b/blink_ram_int_systick/memmap_ram.ld
@@ -12,4 +12,11 @@ SECTIONS
         *(.vectors)
        *(.text*)
     } > RAM
+
+    .bss : {
+        __bss_start__ = .;
+       *(.bss*)
+       . = ALIGN(4);
+       __bss_end__ = .;
+    } > RAM
 }

__bss_start____bss_end__はロケーションカウンタと呼ばれるもので、リンク後にその位置をプログラムから参照できるようにするもの。ここでは.bssセクションの先頭と末尾の位置を保存している。

main

一般的にはBSS領域の初期化などの処理はスタートアップのアセンブリで実装されることが多いが、ここではC言語からロケーションカウンタを参照し0で初期化する。

diff --git a/blink_ram_int_systick/main.c b/blink_ram_int_systick/main.c
index 77d1309..19539f9 100644
--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -14,6 +14,16 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
         write_reg(addr | op, value);
 }
 
+void init_bss() {
+        extern uint32_t __bss_start__;
+        extern uint32_t __bss_end__;
+        uint32_t *p = &__bss_start__;
+        uint32_t *end = &__bss_end__;
+        while (p != end) {
+                *p++ = 0;
+        }
+}
+
 void init_systick() {
         write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
         write_reg(SYSTICK_CVR, 0);

スタートアップ

スタートアップからinit_bssを呼び出す。

diff --git a/blink_ram_int_systick/start.S b/blink_ram_int_systick/start.S
index 793d991..8419f1b 100644
--- a/blink_ram_int_systick/start.S
+++ b/blink_ram_int_systick/start.S
@@ -34,6 +34,7 @@ reset:
 
        ldr r0, =0x20001000
        mov sp, r0
+       bl init_bss
        bl main
        b hang

波形を見る

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

こちらも100msのパルス幅になっている。

まとめ

Systickの割り込みを使用してLチカを行った。

ベクターテーブルの場所はVTORレジスタによって適切に設定する必要がある。 また今回は実装の都合上、tick_countをBSSに置いたためBSSの初期化なども行う必要があった。

今回の成果物はここに置いてある。

参考

*Cortex-M0+ Devices Generic User Guide

Raspberry Pi Pico Systickを使ってLチカを正確にする

はじめに

「PicoでベアメタルのLチカ(RAMバージョン) その2」として。

これまで作ってきたLチカは、適当な値をforでビジーループするだけだったので、タイミングはいい加減だった。

Cortex-M0+ではコアにSysTickを持っていて、これは難しい設定することもなく使用可能なので、これを使ってLチカのタイミングを正確にする。

SysTickはPico SDKの中では基本的には使用されていない。

SysTick

RP2040 Datasheetの「2.4.5.1.1. SysTick timer」によると、

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

watchdogのtimer_tickで生成される1usのパルスを使用するとのこと。

SYST_CSRCLKSOURCEの初期値が0になっており外部のクロックを使用するとのこと。これがtimer_tickのことかと思われる。

f:id:mickey_happygolucky:20210226084257p:plain

f:id:mickey_happygolucky:20210226084308p:plain

WATCHDOGのTICKレジスタRUNNINGENABLEが動いているので、timer_tickの1usのパルスは初期状態で動作している。

f:id:mickey_happygolucky:20210226084323p:plain

f:id:mickey_happygolucky:20210226084334p:plain

Lチカ(RAMバージョン)を改造

Lチカ(RAMバージョン)をSysTickを使用するように変更してみる。

regs.h

regs.hにSYSTICK関連のレジスタ定義を追加する。

diff --git a/blink_ram/regs.h b/blink_ram/regs.h
index dc0b11b..2d650ec 100644
--- a/blink_ram/regs.h
+++ b/blink_ram/regs.h
@@ -30,5 +30,9 @@
 
 #define IO_GPIO25_CTRL (IO_BANK0_BASE + IO_BANK_GPIO25_CTRL)
 
+#define SYSTICK_CSR (0xE000E010)
+#define SYSTICK_RVR (0xE000E014)
+#define SYSTICK_CVR (0xE000E018)
+
 
 #endif //REGS_H

main.c

main.cinit_systick()delay_us()を追加する。

初期状態ではSysTickでは動作していないので、init_systick()で動かすようにする。その際CLKSOURCEは0に設定し、timer_tickを使用するようにする。

delay_us()では、RVRに任意のディレイ時間をus単位で設定する。次にCVRに0を設定しRVRにリセットされるまで待つ。 次に、CVRが0になるまで待つことで設定した時間の分だけビジーループする。

diff --git a/blink_ram/main.c b/blink_ram/main.c
index 47f4935..f4b4135 100644
--- a/blink_ram/main.c
+++ b/blink_ram/main.c
@@ -14,6 +14,22 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
         write_reg(addr | op, value);
 }
 
+void init_systick() {
+        write_reg(SYSTICK_CSR, 0x1);
+}
+
+// us is up to max of 24bit (0xffffff)
+void delay_us(uint32_t us) {
+        write_reg(SYSTICK_RVR, us);
+        write_reg(SYSTICK_CVR, 0);
+
+        // wait for 0 to RVR
+        while (!read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+
+        // wait for RVR counting down to 0.
+        while (read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+}
+
 int main(void) {
 
         /////////////////////
@@ -39,10 +55,13 @@ int main(void) {
         write_reg(IO_GPIO25_CTRL, 0x5);
         uint32_t v = read_reg(IO_GPIO25_CTRL);
 
+        // SysTick init
+        init_systick();
+
         // Blink
         write_reg(SIO_GPIO_OE_SET, (1ul<<25));
         while (1) {
-                for (int i = 100000; i != 0; i--) ;
+                delay_us(100*1000); //100ms
                 write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
         }
         return 0;

波形を見る

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

ちゃんと100ms幅になっている。

まとめ

今回のような単純な使い方では、SysTickは24ビットしかないのであまり長い時間待たせられない。

例えば割り込みを使用してTickカウンタを作って、その差分で待つようにすれば24ビットの壁は超えられると思うが、ちゃんとしたタイマー処理を使用したい場合は、やはりタイマーを使用するほうが良いと思われる。

Raspberry Pi Pico J-LlinkでOpenOCD

はじめに

J-Link EDUが手元にあるので、OpenOCDでPicoのデバッグができないか試してみた。

接続

下記のように接続する。

Pico ARM-JTAG-SWD
SWDIO SWDIO(7)
SWDCLK SWDCLK(9)
GND GND(4,6,8,10,12,14,16,18,20)
VSYS VCC(1)

PlatformIOのJ-LINK

設定ファイル

${PICO_SDK_PATH}/../openocd/tcl/pico-jlink.cfgを下記の内容で作成する。

source [find interface/jlink.cfg]
transport select swd

source [find target/rp2040.cfg]
adapter speed 4000

アダプタをSWDモードに設定し、アダプタのスピードを設定する。

スピードを設定する際にadapter_khzを使うとadapter speedを使えと怒られる。

OpenOCDの実行(失敗)

下記のコマンドでOpenOCDを実行する。

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f pico-jlink.cfg -s tcl

下記のようなエラーになる。

Open On-Chip Debugger 0.10.0+dev-geb22ace-dirty (2021-02-24-18:16)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : RP2040 Flash Bank Command
DEPRECATED! use 'adapter speed' not 'adapter_khz'
adapter speed: 4000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Oct 22 2019 16:28:15
Info : Hardware version: 10.10
Info : VTarget = 4.808 V
Info : clock speed 4000 kHz
Error: Sequence 4 not supported.
Info : DAP init failed


Error: Sequence 3 not supported.
Error: Sequence 4 not supported.

エラーの箇所

src/jtag/drivers/jlink.cの下記の関数でエラーになっている。

static int jlink_swd_switch_seq(enum swd_special_seq seq)
{
    const uint8_t *s;
    unsigned int s_len;

    switch (seq) {
        case LINE_RESET:
            LOG_DEBUG("SWD line reset");
            s = swd_seq_line_reset;
            s_len = swd_seq_line_reset_len;
            break;
        case JTAG_TO_SWD:
            LOG_DEBUG("JTAG-to-SWD");
            s = swd_seq_jtag_to_swd;
            s_len = swd_seq_jtag_to_swd_len;
            break;
        case SWD_TO_JTAG:
            LOG_DEBUG("SWD-to-JTAG");
            s = swd_seq_swd_to_jtag;
            s_len = swd_seq_swd_to_jtag_len;
            break;
        default:
            LOG_ERROR("Sequence %d not supported.", seq);
            return ERROR_FAIL;
    }

    jlink_queue_data_out(s, s_len);

    return ERROR_OK;
}

このseqが3、4の場合にエラーになっている。

enum swd_special_seq {
    LINE_RESET,
    JTAG_TO_SWD,
    SWD_TO_JTAG,
    SWD_TO_DORMANT,
    DORMANT_TO_SWD,
}

SWD_TO_DORMANTDORMANT_TO_SWDに未対応ということらしい。

調べてみると、既にPR(Rp2040 jlink #19)は出されている。

patchは2つあり、下記の修正はドンピシャの内容となっている。

From ef234fa045dfae30e4557db0867d4720f46bbbd3 Mon Sep 17 00:00:00 2001
From: Liam Fraser <liam@raspberrypi.com>
Date: Sun, 24 Jan 2021 08:59:02 +0000
Subject: [PATCH] Add DORMANT_TO_SWD and SWD_TO_DORMANT sequences to jlink
 driver

---
 src/jtag/drivers/jlink.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/jtag/drivers/jlink.c b/src/jtag/drivers/jlink.c
index ae8ce49c6..5b2c1ea89 100644
--- a/src/jtag/drivers/jlink.c
+++ b/src/jtag/drivers/jlink.c
@@ -2149,6 +2149,16 @@ static int jlink_swd_switch_seq(enum swd_special_seq seq)
            s = swd_seq_swd_to_jtag;
            s_len = swd_seq_swd_to_jtag_len;
            break;
+       case DORMANT_TO_SWD:
+           LOG_DEBUG("DORMANT-to-SWD");
+           s = swd_seq_dormant_to_swd;
+           s_len = swd_seq_dormant_to_swd_len;
+           break;
+       case SWD_TO_DORMANT:
+           LOG_DEBUG("SWD-to-DORMANT");
+           s = swd_seq_swd_to_dormant;
+           s_len = swd_seq_swd_to_dormant_len;
+           break;
        default:
            LOG_ERROR("Sequence %d not supported.", seq);
            return ERROR_FAIL;

もう一つの方はack待ちが不要なコマンドに対応するものらしい。

From 7e5ea1861a118120a78dac1fd812f1bdcaedc0cc Mon Sep 17 00:00:00 2001
From: graham sanderson <graham.sanderson@gmail.com>
Date: Mon, 25 Jan 2021 12:09:03 -0600
Subject: [PATCH] Add handling of no-ack commands to jlink driver

Change-Id: I4f7b0dad37bc68cde168962d86e53d7f5ea1cad7
---
 src/jtag/drivers/jlink.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/jtag/drivers/jlink.c b/src/jtag/drivers/jlink.c
index 5b2c1ea89..e2537b78f 100644
--- a/src/jtag/drivers/jlink.c
+++ b/src/jtag/drivers/jlink.c
@@ -2002,6 +2002,8 @@ struct pending_scan_result {
    void *buffer;
    /** Offset in the destination buffer */
    unsigned buffer_offset;
+   /** true if the command has nmo acknowledgement */
+   bool no_ack;
 };
 
 #define MAX_PENDING_SCAN_RESULTS 256
@@ -2195,7 +2197,9 @@ static int jlink_swd_run_queue(void)
    }
 
    for (i = 0; i < pending_scan_results_length; i++) {
-      int ack = buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3);
+       int ack = pending_scan_results_buffer[i].no_ack ?
+               SWD_ACK_OK :
+               buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3);
 
        if (ack != SWD_ACK_OK) {
            LOG_DEBUG("SWD ack not OK: %d %s", ack,
@@ -2259,6 +2263,9 @@ static void jlink_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data, uint3
 
        jlink_queue_data_out(data_parity_trn, 32 + 1);
    }
+   pending_scan_results_buffer[pending_scan_results_length].no_ack =
+           (0 == ((cmd ^ swd_cmd(false, false, DP_TARGETSEL)) &
+                         (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32)));
 
    pending_scan_results_length++;

Checkに引っかかっておりマージされないらしい。

パッチの適用

ローカルのソースコードにこれらの変更を適用してみる。手元ではとりあえず手で修正した。

$ make -j $(nproc)

ビルドが通った。

OpenOCDの実行(成功)

再度実行してみる

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f pico-jlink.cfg -s tcl
Open On-Chip Debugger 0.10.0+dev-geb22ace-dirty (2021-02-24-18:16)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : RP2040 Flash Bank Command
DEPRECATED! use 'adapter speed' not 'adapter_khz'
adapter speed: 4000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Oct 22 2019 16:28:15
Info : Hardware version: 10.10
Info : VTarget = 4.825 V
Info : clock speed 4000 kHz
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x00000001
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x10000001
Info : rp2040.core0: hardware has 4 breakpoints, 2 watchpoints
Info : rp2040.core1: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for rp2040.core0 on 3333
Info : Listening on port 3333 for gdb connections

問題なく起動した。

blinkを実行

pico-examplesのblinkを実行してみる。

OpenOCDとは別のターミナルで下記を実行する。

$ cd ${PICO_EXAMPLES_PATH}/build/blink
$ gdb-multiarch blink.elf
(gdb) target remote :3333
(gdb) mon reset init
(gdb) load
(gdb) c

LEDがチカチカした。

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

まとめ

J-Link対応のためのPRは既に出されているが 2021/2/24時点ではマージされていない。

修正内容自体は正しそうなので、自分でこれらの修正を取り込むか、PR用のブランチを使用すればJ-Link自体は使用できそう。

CIがしくじっているだけのような気もするので、もうすぐ取り込まれるかも。

Raspberry Pi Pico ARM-USB-TINY-H + ARM-JTAG-SWDでデバッグ

はじめに

ツイッターでRaspberryPi4が無いケースでOpenOCDでデバッグするにはどういう手段があるか。という話題が出た。

ARM-USB-TINY-H + ARM-JTAG-SWD

ARMのMCUデバッグで安価で使い勝手が良いものとしてはARM-USB-TINY-Hが挙げられる。

しかしこのデバイスはSWDには対応していない。JTAG-SWDは乗っかる信号線はTMSとTCLKだが、JTAGしか理解できないデバッガはTMSにSWDIOが送られてきても理解できない。

ARM-JTAG-SWDはSWDIOで扱う信号をJTAGのTMS、TDI、TDOに適宜振り分けるアダプタとなっている(はず)。

f:id:mickey_happygolucky:20210224070715p:plain
回路図

接続

下記のように接続する。

Pico ARM-JTAG-SWD
SWDIO SWDIO(7)
SWDCLK SWDCLK(9)
GND GND(4,6,8,10,12,14,16,18,20)
VSYS VCC(1)

PlatformIOのOlimex ARM-USB-TINY-H

デバッグ

OpenOCD

Pico SDKのOpenSDKを使用する。

${PICO_SDK_PATH}/../openocd/tcl/pico-jtag-swd.cfgを下記の内容で作成する。

source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg]
source [find interface/ftdi/olimex-arm-jtag-swd.cfg]
source [find target/rp2040.cfg]
adapter speed 1000

クロック設定を忘れるとエラーになる。1000khzに得に根拠は無いので適宜最適値を探してほしい。

また、クロックを設定する際にadapter_khzを使うとadapter speedを使えと怒られる。

下記のコマンドで起動する。

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f pico-jtag-swd.cfg -s tcl

blinkを実行

pico-examplesのblinkを実行してみる。

OpenOCDとは別のターミナルで下記を実行する。

$ cd ${PICO_EXAMPLES_PATH}/build/blink
$ gdb-multiarch blink.elf
(gdb) target remote :3333
(gdb) mon reset init
(gdb) load
(gdb) c

LEDがチカチカした。

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

まとめ

ARM-USB-TINY-HはARM-JTAG-SWDアダプタを使えばPicoのデバッグができる。

JLinkはまだ対応中のようだ

参考

Pico ベアメタルをClang(LLVM)でビルド

はじめに

ここまで作ってきたベアメタルのLチカはGCCでビルドしてきたが、それをclangでビルドしてみる。

clang-10のインストール

Ubuntu 20.04環境でClangをインストールする。バージョンは10。

$ sudo apt install -y clang-10 lld-10 make

Makefile

ベースはLチカのFLASH + コンパイル済みBoot Stage2バージョン

まずはMakefileを下記のように変更する。

LLVM_OPT = --target=armv6-m-unknown-none-eabi -mcpu=cortex-m0plus 

ASMOPT = $(LLVM_OPT) -c -g
COPT = $(LLVM_OPT) -c -mthumb -ffreestanding -g -O0
LOPT = $(LLVM_OPT) -nostdlib

all: led.uf2

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

start.o: start.S
    clang-10 $(ASMOPT) start.S -o start.o

boot2.o: boot2/bs2_default_padded_checksummed.S
    clang-10 $(ASMOPT) boot2/bs2_default_padded_checksummed.S -o boot2.o

main.o: main.c
    clang-10 $(COPT) -fpic -mthumb -c main.c -o main.o

led.elf: start.o boot2.o main.o
    clang-10 -fuse-ld=lld $(LOPT) start.o boot2.o main.o -T memmap.ld  -o led.elf 

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

GCC環境とのコマンドは下記のように異なっている。

機能 GCC Clang
アセンブラ as clang
コンパイラ gcc clang
リンカ ld clang

clangは全ての機能においてclangから呼び出されるようになっている

ポイントはアセンブルコンパイルの時にリンクまでしたくない場合は-cをつけること。

clangではクロスコンパイルのためにそれぞれのバイナリがあるのではなくLLVMのバックエンドを切り替えるだけなので、すべてオプションで指定する。

基本的にはRaspberry Pi Pico向けの場合下記のようになる。

LLVM_OPT = --target=armv6-m-unknown-none-eabi -mcpu=cortex-m0plus 

それぞれの機能で渡したいオプションが違うので下記のようにする。

ASMOPT = $(LLVM_OPT) -c -g
COPT = $(LLVM_OPT) -c -mthumb -ffreestanding -g -O0
LOPT = $(LLVM_OPT) -nostdlib

リンカスクリプト

そのままでは下記のようなエラーになる。

ld.lld: error: no memory region specified for section '.ARM.exidx'
clang: error: ld.lld command failed with exit code 1 (use -v to see invocation)
make: *** [Makefile:26: led.elf] エラー 1

これは.ARM.exidxセクションが無いというエラー。

前にリンカスクリプトを眺めた時はnewlibを使用する時に必要になるセクションらしい。とのことだったが、LLVMでバイナリを吐き出す場合にも必要になるようだ。

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

    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > FLASH
}

これでエラーは回避できるようになった。

$ make
clang-10 -fuse-ld=lld --target=armv6-m-unknown-none-eabi -mcpu=cortex-m0plus  -nostdlib start.o boot2.o main.o -T memmap.ld  -o led.elf 
elf2uf2 led.elf led.uf2

まとめ

アセンブラコンパイラ、リンカの呼び出しが全てclangだとは思わなかった。

.ARM.exidxセクションは必要になる。

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はない。

参考