みつきんのメモ

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

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ビットの壁は超えられると思うが、ちゃんとしたタイマー処理を使用したい場合は、やはりタイマーを使用するほうが良いと思われる。