HALでゴリゴリSTマイコン

HALでゴリゴリSTマイコン

皆さんこんにちは。KINKI KNIGHTSのラーメンおじさんです。 この記事はKINKI KNIGHTSアドベントカレンダーの16日目です。 他の記事もよろしくお願いします!

皆さんはマイコンの開発環境に何を使っていますか? サ終が宣言されたmbedでしょうか?それとも機動性に欠けるSTM32 CubeIDEでしょうか?(過激派)

KINKI KNIGHTのほとんどの基板はSTM32F303で設計されています。 これらの制御をHALを使って実装しています。
STマイコンでHALといえばSTM32 Cube IDEですが、GUIは使いたくない過激派なのと、 コードの構造に制約ができるのが嫌だったため、生でHALをゴリゴリと書いています。

この記事では、STM32CubeIDEを使用せずにゴリゴリとHALを書いてマイコンを動かす方法を解説します。

環境構築

Cube IDEに縛られる必要性はないので、好きなエディターを使用します。 KINKI KNIGHTSではVS CodeとPlatformIOを使用しています。

PlatformIOをインストールして、「Create New Project」 image.png (39.4 kB)

Boardに使用したいマイコンを選択し、FrameworkにSTM32CubeIDEを指定します。 物によってはマイコン単体のBoardが用意されていないので、下記のように同じマイコンのNucleoを選択するのも一手。 image.png (19.4 kB)

プロジェクトが生成されました。 image.png (9.6 kB)

main.cppを作成します。 画面下部のチェックマークをクリックするとビルドが走ります。 image.png (71.9 kB)

環境構築完了です!簡単ですね!!

Lチカまでの用意

Lチカするまでに最低限次の要素を設定する必要があります。

  • ライブラリのインクルード
  • HALの初期化
  • クロックの設定
  • 割り込みハンドラの定義
  • 機能ごとのクロックの有効化

ライブラリのインクルード

HALを読み込みます。

#include <stm32f3xx_hal.h>

HALの初期化

HAL_Init();

Clockの設定

外部発信子の設定や分周の設定です。 深入りすると面倒なので割愛します。迷ったら初回だけなのでIDEで生成したものを引っ張ってくればいいかも。

RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}

RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                          |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK){Error_Handler();}

RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK){Error_Handler();

割り込みハンドラの定義

最低限、Error_HandlerとSystick_Handlerを定義してやりましょう。 マイコン側から割り込み呼び出しされる関数をC++で定義する場合、C言語からも呼び出せるようにしてやる必要があるので注意しましょう。

extern "C" void Error_Handler(void){}
extern "C" void SysTick_Handler(void)
{
    HAL_IncTick();
    HAL_SYSTICK_IRQHandler();
}

機能ごとのクロックの有効化

マイコンの機能ごとにクロックを有効化していきます。 必要なものだけを有効化することで消費電力が抑えられます。 UARTとかのペリフェラル使うときに見落として禿げがち。

// この辺はマスト
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
// Lチカするなら該当のGPIOも有効化しましょう
__HAL_RCC_GPIOA_CLK_ENABLE();

Lチカしよう!!

お待たせしました!!Lチカしていきましょう。

必要なもの

  • GPIOの設定
  • ON/OFFするプログラム

GPIOの設定

GPIOを設定するには、GPIO_InitTypeDef型の構造体に必要な情報を設定してHAL_GPIO_Init関数に放り込んでやるだけです。

GPIO_PIN_3GPIOB等はHAL側で既に定義されています。

GPIO_InitTypeDef GPIO_InitStruct = {0};
// 出力するピンを指定。GPIO PA3のようにグループとピン番号でピンが特定される(左記例はグループA)
GPIO_InitStruct.Pin = GPIO_PIN_3;
// 出力モード
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
// プルアップ・プルダウン設定。
GPIO_InitStruct.Pull = GPIO_NOPULL;
// Lチカくらいなら低速でOK
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
// 設定したいGPIOのグループと設定の構造体を放り込む
HAL_GPIO_Init(GPIOA &GPIO_InitStruct);

ON/OFFする!!

HAL_GPIO_WritePin関数でGPIO出力をON・OFFできます。
また、HAL_Delay関数でミリ秒単位で待てます。

while(True){
    // OFF
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
    HAL_Delay(500);
    // ON
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
    HAL_Delay(500);
}

いい感じにまとめる

ここまでのコードをベタ張りするだけじゃぁ芸がないので、関数でいい感じにまとめましょう。
せっかくC++なんていう高級言語を使っているんですから、オブジェクト指向な構成にしてみてもいいかもですね。

#include <stm32f3xx_hal.h>
// 割り込み定義
extern "C" void Error_Handler(void){}
extern "C" void SysTick_Handler(void)
{
    HAL_IncTick();
    HAL_SYSTICK_IRQHandler();
}

// システムクロック設定
void SystemClockConfig()
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    Error_Handler();

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
    Error_Handler();
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    Error_Handler();
}

// クロック有効化
void clockEnable(){
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

void initGpio(){
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

int main(){
    HAL_Init();
    SystemClockConfig();
    clockEnable();
    initGpio();

    while (1)
    {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
        HAL_Delay(500);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
        HAL_Delay(500);
    }
    
}

最後に

いかがだったでしょうか?無事にLチカができました。 CubeIDEを使わずにHALの設定をゴリゴリ書くことで、ちょっとパラメータを見直したいときに コードを再生成する必要なく、関数の引数を一つ変えてやるだけで済む素敵な環境が手に入ります。

今回のLチカコードも、ゴリゴリ書けば70行程度ですが、CubeIDEで生成するとMspInitが~とかやって分量が増えます。 自分の管理できる範囲でコントロールするのが一番です。

皆さんも素敵なHALライフを送りましょう!!

最後までお読みいただきありがとうございました。
明日は、ぱぁさんの技育典開発録になります!お楽しみに!!

お知らせ

KINKI KNIGHTSでクラウドファンディングを開始しました。

皆様の応援をよろしくお願いします!


All News →