はじめに
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
波形を見る
こちらも100msのパルス幅になっている。
まとめ
Systickの割り込みを使用してLチカを行った。
ベクターテーブルの場所はVTORレジスタによって適切に設定する必要がある。 また今回は実装の都合上、tick_countをBSSに置いたためBSSの初期化なども行う必要があった。
今回の成果物はここに置いてある。