週末!プログラミング部

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

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

前回は、eclipse Embedded CDTでSTM32F407VG(Cortex-M4)のベアメタルをデバッグしました。
今回は、CMSISライブラリを使わずにGPIOを制御してUser LED(green)をLチカさせてみたいと思います。

User LED(green)の接続先

まずはUser LED(green)の接続先を探します。
接続先は、STM32F4DISCOVERYのユーザマニュアルに記載されています。

これによるとUser LED(green)は、PD12(ポートグループDの12番)に接続されているようです。 f:id:NATSU_NO_OMOIDE:20210104131701p:plain

レジスタの調査

今回はCMSISライブラリを使用しないのでPD12を制御するためのレジスタを調べる必要があります。
レジスタは、STM32F407VGのテクニカルリファレンスマニュアルに記載されています。

RCC_AHB1ENRレジスタ

AMRは省電力のために必要なペリフェラル毎にクロックを供給するかどうかを設定できます。
リセット時は大抵の場合、クロックは供給されておらず自分で設定する必要があります。

f:id:NATSU_NO_OMOIDE:20210104133710p:plain Cortex-M4にはRCC(たぶんReset Clock Control?)というリセットやクロック制御をするための機能があります。
この機能が持つレジスタRCC_AHB1ENRというレジスタあり、さらに3bit目にGPIODEN(PDクロックイネーブル)があります。 このbitを1にすれば、PDにクロックが供給されるようです。

GPIOx_MODER

GPIOの制御をするための基本的なレジスタ
GPIOx_MODERレジスタは、GPIOポートのモードを設定できます。
今回はPD12を制御したいので、このレジスタの24bit目を1に、25bit目を0に(汎用出力モード)にすればよさそうです。 f:id:NATSU_NO_OMOIDE:20210104140229p:plain

GPIOx_OSPEEDR

あと設定したほうがよさそうなのはGPIOx_OSPEEDRレジスタくらいでしょうか?
このレジスタはIOの出力スピードを設定できます。
これによると24bit目を1に、25bit目を1に(高速)にすればよさそうです。 f:id:NATSU_NO_OMOIDE:20210104140624p:plain

実装してみる

全体を説明すると長くなるので、ここでは部分的に紹介します。
ソースコード全体はgithubeclipse Embedded CDTのworkspaceとして読み込めるようにして置いてみました。
自分のお勉強目的でコメントが汚いですがお許しください(>_<)

まずはレジスタの定義です。
GPIO関連のレジスタは構造体を作成してメモリマットIOアドレスにマッピングしてあります。

typedef struct /* GPIOレジスタ構造 */
{
    uint32_t MODER;
    uint32_t OTYPER;
    uint32_t OSPEEDR;
    uint32_t PUPDR;
    uint32_t IDR;
    uint32_t ODR;
    uint32_t BSRR;
    uint32_t LCKR;
    uint32_t AFRL;
    uint32_t AFRH;
} ST_REG_GPIO;

#define REG_RCC_AHB1ENR    ( *( ( uint32_t* )0x40023830 ) )
#define REG_GPIO_D     ( *( ( ST_REG_GPIO* )0x40020C00 ) )


続いてPIO(Port IO)ドライバです。
本当は、read/write関数はポートグループとポート番号を指摘できるようにしてもう少し汎用的に使用できるようにしたかったのですが今回は手抜きです笑

/* PIOドライバ初期化処理 */
void PIO_init(void)
{
    /* IOポート D にクロックを供給 */
    REG_RCC_AHB1ENR |= 0x8;

    /* PD12のポートモードを汎用出力モードに設定 */
    REG_GPIO_D.MODER |= ( 1 << 24 );

    /* PD12のポート出力スピードを高速に設定 */
    REG_GPIO_D.OSPEEDR |= ( ( 1 << 24 ) | ( 1 << 25 ) );
}

/* PIOドライバ書き込み機能 */
void PIO_write(uint8_t lv)
{
    /* Lだった場合 */
    if( lv == PIO_SIGNAL_LV_L )
    {
        REG_GPIO_D.ODR &= ~( 1 << 12 );
    }
    /* Hだった場合 */
    else
    {
        REG_GPIO_D.ODR |= ( 1 << 12 );
    }
}

/* PIOドライバ読み込み機能 */
uint8_t PIO_read(void)
{
    uint8_t lv;

    /* Lだった場合 */
    if( ( ( REG_GPIO_D.ODR >> 12 ) & 0x1 ) == PIO_SIGNAL_LV_L )
    {
        lv = PIO_SIGNAL_LV_L;
    }
    /* Hだった場合 */
    else
    {
        lv = PIO_SIGNAL_LV_H;
    }

    return (lv);
}


最後にエントリポイントです。
LチカはPIO_read()の値を反転させて作っています。
waitでforで強引に実装しました。

int main(void)
{
    /* ポートドライバを初期化 */
    PIO_init();

    while(1)
    {
        /* 読み込んだポートの値を反転させて書き込み */
        PIO_write( ~PIO_read() );

        /* wait 1sのつもり*/
        for(int i = 0; i < 16000000; i++)
        {
            __asm("nop");
        }
    }

    return (0);
}

動かしてみる

前回と特に変わりないですがUserLED(green)がチカチカしていれば成功です。 f:id:NATSU_NO_OMOIDE:20210104142735g:plain
こんな簡単なことでもCMSISライブラリを使うとあっと言う間にできてしまうのに久しぶりにレジスタを調べたりしていると思いのほか時間がかかりました(;´Д`)