週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみる

前回までは、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみたいと思います。

SysTickとは

ARMマイコンが周辺機器とは別に持っている24ビットのシステム・タイマです。
特徴としては以下のとおりです。

  1. LOADレジスタ値(再ロード値)をVALレジスタ(カウンタ値)に読み込む。
  2. VALレジスタ値をカウント・ダウンする。
  3. VALレジスタが0になるとSysTick割り込みを発生させる。
  4. 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 になります。
f:id:NATSU_NO_OMOIDE:20210111162846p:plain

STK_LOADレジスタ

カウント・ダウンの開始位置を示すレジスタです。
0~23の24ビットでカウント・ダウンさせたい時間を指定するようです。
f:id:NATSU_NO_OMOIDE:20210111163639p:plain

STK_VALレジスタ

現在のカウント値を示すレジスタです。 STK_LOADレジスタと同様に24ビットでカウンタ値を持ちます。 このレジスタが初期化されるとSTK_CTRLレジスタのCOUNTFLAGもクリアされるようです。 f:id:NATSU_NO_OMOIDE:20210111163857p:plain

実装してみる

全体を説明すると長くなるので、ここでは部分的に紹介します。
Tagは「20210111」です。

割り込みの設定

SysTick割り込みをハンドルするためにベクタ・テーブルを用意します。
ベクタ・テーブルは以下のような感じにする必要があります。
SysTickは例外番号15のようです。
f:id:NATSU_NO_OMOIDE:20210111170721p:plain

ベクタ・テーブルは.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);
}

こんな感じで動けば成功です! f:id:NATSU_NO_OMOIDE:20210110193737g:plain