Cortex-M4でCMSISライブラリを使わずにGPIO制御してみる(拡張)
前回は、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、その延長で複数のGPIOを制御してLチカさせます。
内容は前回やったことに毛が生えたような感じですが、今後やりたいことがあり、そのデバッグ目的のためにやります。
User LEDについて
STM32F407VGに搭載されているUser LEDは、red/blue/green/orangeの4種類あります。
いずれもポートグループはDグループのようです。
実装してみる
全体を説明すると長くなるので、ここでは部分的に紹介します。
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を立てればよいようです。
一方でスピード設定レジスタは、高速設定するために24~31bitに1を立てる必要があります。
したがってポートグループのテーブルからポート番号を取り出して、
モード選択レジスタに立てるビットは、 「ポート番号 * 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); }
このように動けば成功です!