週末!プログラミング部

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

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

前回は、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、その延長で複数のGPIOを制御してLチカさせます。
内容は前回やったことに毛が生えたような感じですが、今後やりたいことがあり、そのデバッグ目的のためにやります。

User LEDについて

STM32F407VGに搭載されているUser LEDは、red/blue/green/orangeの4種類あります。
いずれもポートグループはDグループのようです。 f:id:NATSU_NO_OMOIDE:20210104131701p:plain

実装してみる

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

ポートグループのテーブルを作成

今回は、以下のような列挙型とポートグループのテーブルを作成しました。
テーブルにはポートグループのアドレスとポート番号を登録しておくようにします。
このテーブルに列挙型に定義したグループIDを使ってアクセスさせようと思います。
※このように設計しておくことが使いやすくなるかはさておき(・`ω´・)

typedef enum
{
    PIO_PORT_GROUP_D_12 = 0,
    PIO_PORT_GROUP_D_13,
    PIO_PORT_GROUP_D_14,
    PIO_PORT_GROUP_D_15,
    PIO_PORT_GROUP_NUM
} PIO_PORT_GROUP_ID;
typedef struct
{
    uint32_t port_reg_addr;
    uint8_t  port_idx;
} PIO_PORTSET;

static PIO_PORTSET s_pio_port_config_table[PIO_PORT_GROUP_NUM] =
{
    { REG_GPIO_D_ADDR, 12 },    /* DP12 */
    { REG_GPIO_D_ADDR, 13 },    /* DP13 */
    { REG_GPIO_D_ADDR, 14 },    /* DP14 */
    { REG_GPIO_D_ADDR, 15 }     /* DP15*/
};

ポートの初期化

つづいてポートの初期化です。

レジスタのおさらいですがモード選択レジスタは以下のようになっていました。
今回はDPの12, 13, 14, 15を使用したいので、24, 26, 28, 30bitに1を立てればよいようです。
f:id:NATSU_NO_OMOIDE:20210104140229p:plain

一方でスピード設定レジスタは、高速設定するために24~31bitに1を立てる必要があります。
f:id:NATSU_NO_OMOIDE:20210104140624p:plain

したがってポートグループのテーブルからポート番号を取り出して、
モード選択レジスタに立てるビットは、 「ポート番号 * 2」、
スピード設定レジスタに立てるビットは、「ポート番号 * 2」と「(ポート番号 * 2) + 1」
という感じで算出するようにしました。(このように設計しておくことがよいかはさておき。)

void PIO_init(void)
{
    ST_REG_GPIO* port_reg;
    uint8_t      port_idx;

    /* IOポート D にクロックを供給 */
    REG_RCC_AHB1ENR |= 0x8;

    for(int i = 0; i < PIO_PORT_GROUP_NUM; i++)
    {
        /* ポートグループのレジスタアドレスとポート番号を取得 */
        port_reg = (ST_REG_GPIO*)s_pio_port_config_table[i].port_reg_addr;
        port_idx = s_pio_port_config_table[i].port_idx;

        /* ポートモードを汎用出力モードに設定 */
        port_reg->MODER |= ( 1 << ( port_idx * 2 ) );

        /* ポート出力スピードを高速に設定 */
        port_reg->OSPEEDR |= ( ( 1 << ( port_idx * 2 ) )
                          |    ( 1 << ( ( port_idx * 2 ) + 1 ) ) );
    }
}

ポートの書き込み

書き込み関数は、引数にポートグループのIDを追加しました。
初期化関数と同様にポートグループのIDを使用してレジスタのアドレスとポーと番号を取得するように変更しました。

void PIO_write(PIO_PORT_GROUP_ID port_group_id, uint8_t lv)
{
    ST_REG_GPIO* port_reg;
    uint8_t      port_idx;

    /* ポートグループのレジスタアドレスとポート番号を取得 */
    port_reg = 
        ( ST_REG_GPIO* )s_pio_port_config_table[port_group_id].port_reg_addr;
    port_idx = s_pio_port_config_table[port_group_id].port_idx;

    /* Lだった場合 */
    if( lv == PIO_SIGNAL_LV_L )
    {
        port_reg->ODR &= ~( 1 << port_idx );
    }
    /* Hだった場合 */
    else
    {
        port_reg->ODR |= ( 1 << port_idx );
    }
}

ポートの読み込み

読み込み関数も書き込み関数とほぼ同じ変更です。

uint8_t PIO_read(PIO_PORT_GROUP_ID port_group_id)
{
    ST_REG_GPIO* port_reg;
    uint8_t      port_idx;
    uint8_t     lv;

    /* ポートグループのレジスタアドレスとポート番号を取得 */
    port_reg =
         (ST_REG_GPIO*)s_pio_port_config_table[port_group_id].port_reg_addr;
    port_idx = s_pio_port_config_table[port_group_id].port_idx;

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

    return (lv);
}

動かしてみる

ユースケースとしてmain関数を以下のようにしてみました。
このコードでは1[s]毎に点滅させるLEDを選択しています。

int main(void)
{
    uint8_t status  = PIO_PORT_GROUP_D_12;
    uint8_t gpio_lv = PIO_SIGNAL_LV_L;

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

    while(1)
    {
        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);

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

    return (0);
}


このように動けば成功です!
f:id:NATSU_NO_OMOIDE:20210110193737g:plain