Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみる
前回までは、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみたいと思います。
SysTickとは
ARMマイコンが周辺機器とは別に持っている24ビットのシステム・タイマです。
特徴としては以下のとおりです。
- LOADレジスタ値(再ロード値)をVALレジスタ(カウンタ値)に読み込む。
- VALレジスタ値をカウント・ダウンする。
- VALレジスタが0になるとSysTick割り込みを発生させる。
- VALレジスタが0になると1. を実施する。
用途としてはOSのスケジューリングを行うためのシステム時間の計測などに使用されます。
レジスタの調査
今回はCMSISライブラリを使用しないのでSysTickのレジスタを調べる必要があります。
レジスタは、STM32F407VGのプログラミング・マニュアルに記載されています。
STK_CTRLレジスタ
SysTickを制御したり、ステータスを確認したりするレジスタです。
これによると
ビット 0 (カウンタ・イネーブル)を 1 にするとカウンタが開始し、0 にするとカウントが停止するようです。
ビット 1 (SysTick 例外要求の有効化)を 1 にするとカウント・ダウンが0になったとき、SysTick割り込みを発生するようになります。
ビット 2 (クロック・ソース)はクロック・ソースの選択です。1を選択しておくことでプロセッサ・クロックを利用するみたいです。
ビット 16 (COUNTFLAG)はSTK_VALレジスタが0になったときに 1 になり、STK_VALレジスタが更新されたときに 0 になるようです。
つまりカウント・ダウンが完了したときに 0、再ロード値が読み込まれたとき 1 になります。
STK_LOADレジスタ
カウント・ダウンの開始位置を示すレジスタです。
0~23の24ビットでカウント・ダウンさせたい時間を指定するようです。
STK_VALレジスタ
現在のカウント値を示すレジスタです。 STK_LOADレジスタと同様に24ビットでカウンタ値を持ちます。 このレジスタが初期化されるとSTK_CTRLレジスタのCOUNTFLAGもクリアされるようです。
実装してみる
全体を説明すると長くなるので、ここでは部分的に紹介します。
Tagは「20210111」です。
割り込みの設定
SysTick割り込みをハンドルするためにベクタ・テーブルを用意します。
ベクタ・テーブルは以下のような感じにする必要があります。
SysTickは例外番号15のようです。
ベクタ・テーブルは.isr_vectorセクションに配置するようにしています。
ちなみに.text .data .sdata .bss以外の固有セクションに配置するときは、.section .isr_vector,"a",%progbitsのような感じで指定してやる必要があるみたいです。
"a"は配置する、%progbitsはデータという意味らしいです。
.syntax unified .thumb .section .isr_vector,"a",%progbits .align 2 .globl __Vectors __Vectors: .long __main_stack_start /* Main stack pointer (MSP) */ .long Reset_Handler /* Reset Handler */ .long NMI_Handler /* NMI Handler */ .long HardFault_Handler /* Hard Fault Handler */ .long MemManage_Handler /* MPU Fault Handler */ .long BusFault_Handler /* Bus Fault Handler */ .long UsageFault_Handler /* Usage Fault Handler */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long SVC_Handler /* SVCall Handler */ .long DebugMon_Handler /* Debug Monitor Handler */ .long 0 /* Reserved */ .long PendSV_Handler /* PendSV Handler */ .long SysTick_Handler /* SysTick Handler */
リンカ・スクリプトは以下のような感じです。
ベクタ・テーブルはROMの先頭に配置するようにしました。
MEMORY { /* 0x20000000 から 128KB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x20000 /* 0x08000000 から 1024KB FLASH */ ROM (rx) : ORIGIN = 0x08000000, LENGTH = 0x100000 } SECTIONS { /* The startup code into ROM memory */ /* ベクターテーブル配置用のセクション */ .isr_vector : { . = ALIGN(4); /* ロケーションカウンタから4byteアライン */ KEEP(*(.isr_vector)) /* vectorテーブル */ . = ALIGN(4); /* ロケーションカウンタから4byteアライン */ } >ROM
SysTickドライバ
ドライバは初期化、開始、停止という感じで機能分けするようにしてみました。
#include "iodefine.h" #include "SysTick.h" uint8_t tick_1ms_flag; void SysTick_Handler(void); /* OSタイマドライバ初期化 */ void SysTick_init(void) { REG_STK.VAL = 0; /* カウンタの現在値をクリア */ REG_STK.LOAD = 16000; /* カウントダウン開始位置を設定(1ms = 16MHz ) */ REG_STK.CTRL = 0x6; /* 制御レジスタを設定 */ /* - 2bit : クロックソースに プロセッサ・クロック(AHB)を設定 */ /* - 1bit : SysTick 例外要求を有効 */ /* - 0bit : カウンタ停止 */ tick_1ms_flag = 0; } /* OSタイマ開始 */ void SysTick_start(void) { REG_STK.CTRL |= 0x1; /* カウンタ開始 */ } /* OSタイマ停止 */ void SysTick_stop(void) { REG_STK.CTRL &= ~(0x1); /* カウンタ停止 */ } /* 割り込みハンドラ */ void SysTick_Handler(void) { if ( ( REG_STK.CTRL & 0x00010000UL ) != 0UL ) { tick_1ms_flag = 1; } }
動かしてみる
ユースケースとして以下のようなプログラムを用意しました。
ビジーループ内では、SysTick割り込み(1[ms])があったときに立つtick_1ms_flagを監視させました。
その後、tick_1ms_flagが立った回数をカウントして1[s]を計測するようにしています。
1[s]経過していたら、これまでと同様にLチカさせています。
#include "PIOdriver.h" #include "SysTick.h" #include "type.h" extern uint8_t tick_1ms_flag; int main(void) { uint8_t status = PIO_PORT_GROUP_D_12; uint8_t gpio_lv = PIO_SIGNAL_LV_L; static uint32_t tick_1sec_flag = 0; /* ポートドライバを初期化 */ PIO_init(); /* OSタイマドライバを初期 */ SysTick_init(); /* OSタイマ開始 */ SysTick_start(); while(1) { /* 1msTick割り込みがあった場合 */ if(tick_1ms_flag == 1) { tick_1ms_flag = 0; tick_1sec_flag++; } /* 1sec経過していなかった場合 */ if(tick_1sec_flag != 1000) { continue; } tick_1sec_flag = 0; switch(status) { case PIO_PORT_GROUP_D_12: status = PIO_PORT_GROUP_D_13; break; case PIO_PORT_GROUP_D_13: status = PIO_PORT_GROUP_D_14; break; case PIO_PORT_GROUP_D_14: status = PIO_PORT_GROUP_D_15; break; case PIO_PORT_GROUP_D_15: status = PIO_PORT_GROUP_D_12; break; defualt: break; } /* ポート読み込み */ gpio_lv = PIO_read(status); /* 読み込んだポートの値を反転させて書き込み */ PIO_write(status, ~gpio_lv); } return (0); }
こんな感じで動けば成功です!