みつきんのメモ

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

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