STM32CubeMXの全体像
STM32CubeはSTMicroelectronicsが提供する統合ソフトウェアエコシステムであり、STM32マイコンおよびマイクロプロセッサ向けの開発を支援します。開発者はIARやKeilといった既存のIDEと組み合わせて利用するだけでなく、STM32CubeMX、STM32CubeProgrammer、STM32CubeMonitorなどのツールを自由に取り込むことができます。
代表的なSTM32開発手法を次に示します。
- レジスタ直接操作:51系マイコンと同じ感覚で行う古典的手法。コードはコンパクトで高速ですが、膨大なレジスタ情報を覚える必要があり、移植性は低いです。
- 標準ペリフェラルライブラリ(SPL):STがかつて提供していた関数ベースのライブラリ。現在はHALライブラリに置き換わり、更新は停止しています。
- HALライブラリ + STM32CubeMX:STが推奨する現行の開発フロー。GUIでマイコンのピン配置やペリフェラルを設定し、初期化コードを自動生成します。最新のプロジェクトではこの方式が標準です。
STM32Cubeエコシステムの主要コンポーネント
- STM32CubeMX:グラフィカルなコンフィギュレーションツール。Cortex‑Mコア向けにはCコードを、Cortex‑Aコア向けにはDevice Treeを生成します。
- STM32CubeIDE:Eclipseベースの統合開発環境。CubeMX機能を内蔵し、コンパイル・デバッグ・書き込みまで一貫して行えます。
- STM32CubeProgrammer:書き込み専用ツール。JTAG、SWD、UART、USB DFUなど様々なインタフェースでフラッシュメモリの読み書きやベリファイが可能です。
- STM32CubeMonitor:実行時の電力プロファイルや通信データを可視化するモニタリングツール群。
- STM32Cube MCU Package:各STM32シリーズ向けのHALドライバ、ミドルウェア、サンプルコードを含むソフトウェアパッケージ。
- STM32Cube Expansion:STまたはサードパーティが提供する拡張パッケージ。特定のアプリケーション(例:BLEスタック、TouchGFX)を追加します。
以下、具体的な実験を通じて基本的なペリフェラルの設定方法とコードを解説します。(ベースはSTM32F103RCT6、必要に応じてF407やL451も使用)
1. 最初のプロジェクト(GPIO出力)
解説の共通準備として、次の手順でプロジェクトを作成します。
- チップ選択(例:STM32F103RCT6)
- クロックソース設定(HSE:8MHz水晶、LSE:32.768kHz)
- クロックツリー構成(PLL、SYSCLK、AHB、APB1/APB2)
- GPIOピン設定
- Cortex‑M内核設定(デバッグIF:SW、NVIC優先度グループ)
- 初期化コード生成
- ユーザコード追加
ペリフェラル毎に上記のベースプロジェクトを複製・拡張します。
コード例(LED0を500ms間隔で点滅)
// main.c ユーザコード領域内
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
HAL_Delay(500);
}
補足:CubeMXが生成するコードは /* USER CODE BEGIN ... */ と /* USER CODE END ... */ の間にユーザが追加した部分のみ保持されます。それ以外の場所に書いたコードは、再生成時に上書き削除されるため注意が必要です。
2. ボタン入力とGPIO割り込み(External Interrupt)
ハードウェア構成
- KEY0 – PE4:プルアップ入力、下降エッジで割り込み、LED0をトグル
- KEY1 – PE3:プルアップ入力、下降エッジで割り込み、LED1をトグル
- KEY_UP – PA0:プルダウン入力、立ち上がりエッジで割り込み、ブザー制御
CubeMX設定のポイント
- 該当ピンのGPIOモードを
GPIO_EXTIxに設定 - NVICで該当ラインの割り込みを有効化(プリエンプト優先度を例えば1に設定)
- コード生成後、
HAL_GPIO_EXTI_Callback()をユーザコード領域に記述
割り込みコールバック(書き換え例)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_Delay(10); // 簡易デバウンス(割り込み内でのDelayは推奨しないが、説明用)
if (GPIO_Pin == WK_UP_Pin) {
if (HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == GPIO_PIN_SET) {
HAL_GPIO_TogglePin(BEEP_GPIO_Port, BEEP_Pin);
}
}
else if (GPIO_Pin == KEY0_Pin) {
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET) {
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
}
}
else if (GPIO_Pin == KEY1_Pin) {
if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
}
3. UART通信(ポーリング・割り込み・printfリダイレクト)
STM32のUSART/UARTは非同期全二重シリアル通信を提供します。ボーレートは共通パラメータで、データビット・パリティ・ストップビットも設定可能。
ポーリング送受信
#define TX_BUF_SIZE 5
uint8_t sendbuf[TX_BUF_SIZE] = {0x41,0x42,0x43,0x44,0x45}; // "ABCDE"
HAL_UART_Transmit(&huart1, sendbuf, TX_BUF_SIZE, HAL_MAX_DELAY);
// 受信
uint8_t recvbuf[10];
HAL_UART_Receive(&huart1, recvbuf, 6, 1000); // 1秒タイムアウト
HAL_UART_Transmit(&huart1, recvbuf, 6, HAL_MAX_DELAY);
printf関数のリダイレクト
usart.c に次の関数を追加します。
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
プロジェクト設定で「Use MicroLIB」を有効にし、文字コードをGBK(日本語環境)に設定すると日本語が正しく表示されます。
割り込み受信
uint8_t rxBuf[6];
HAL_UART_Receive_IT(&huart1, rxBuf, 6);
// コールバック
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
HAL_UART_Transmit(&huart1, rxBuf, 6, HAL_MAX_DELAY);
HAL_UART_Receive_IT(&huart1, rxBuf, 6); // 次回受信の再設定
}
}
4. ウォッチドッグ(IWDG / WWDG)
システムの暴走検出とリセットに使われます。
独立ウォッチドッグ(IWDG)
LSI(40kHz)をクロック源とし、プリスケーラとリロード値でタイムアウトを設定。1秒周期に設定する例:
// CubeMX: IWDGを有効、プリスケーラ=16分周(IWDG_Prescaler_16)、リロード=2500
HAL_IWDG_Refresh(&hiwdg); // 1秒以内に呼ばないとリセット
// リセット要因の判定
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) {
printf("IWDG Reset!\r\n");
__HAL_RCC_CLEAR_RESET_FLAGS();
}
ウィンドウウォッチドッグ(WWDG)
APB1クロック(36MHz)で動作。上位ウィンドウ(早期にリセットがかかる)と下位ウィンドウ(0x3F)の間でのみ餌やりが許可されます。
// CubeMX: WWDGプリスケーラ=8、ウィンドウ値=0x5F、カウンタ初期=0x7F
// 超えないようにループ内で餌やる
HAL_WWDG_Refresh(&hwwdg);
// または割り込みで早期警戒時に餌やり
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
HAL_WWDG_Refresh(hwwdg);
printf("Fed in early wakeup\r\n");
}
5. タイマの基本(基本タイマ TIM6 の更新割り込み)
基本タイマはアップカウントのみ可能。PSC=7199, ARR=4999 で500ms周期の割り込みを実現(72MHzクロック時)。
HAL_TIM_Base_Start_IT(&htim6);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6) {
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
6. PWM出力(汎用タイマ TIM3_CH2)
PB5から1kHz、デューティ可変のPWMを出力します。システムクロック72MHz、PSC=71, ARR=999 → 出力周波数 1kHz。
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
while (1) {
// デューティを250→499→0…と変化
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, current_duty);
HAL_Delay(10);
}
7. 入力キャプチャ(高レベルパルス幅測定)
PA0に接続されたKEY_UPの押下時間を測定。TIM5_CH1、プリスケーラ71で1us分解能。
// キャプチャコールバックで立ち上がり/立ち下がりエッジを管理
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5) {
static uint8_t captureDone = 0;
static uint16_t capturedVal = 0;
if (!captureDone) {
// 立ち上がりエッジ:カウンタリセット、次は立ち下がり
__HAL_TIM_SET_COUNTER(htim, 0);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
captureDone = 1;
} else {
// 立ち下がりエッジ:値を読み出し、次の測定に備える
capturedVal = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
captureDone = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
// パルス幅 = capturedVal [us](オーバーフロー処理は省略)
}
}
}
8. ADC(単一チャネル変換)
ADC1、チャネル1(PA1)を使用。VREF=3.3V、12ビット分解能0.8mV。サンプリングタイム239.5サイクルで高精度。
// adc.c にユーティリティ関数を追加
uint32_t ADC_GetAverage(uint32_t ch, uint8_t times)
{
uint32_t sum = 0;
for (uint8_t i = 0; i < times; i++) {
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ch;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
sum += HAL_ADC_GetValue(&hadc1);
HAL_Delay(5);
}
return sum / times;
}
9. CAN通信(bxCAN)
STM32F103やL451の内蔵CANコントローラ。Max 1Mbps。
CubeMX設定
- CAN1のTX/RXピンを選択
- クロックツリーでAPB1を80MHzなどに設定
- Bit Timingパラメータ:Prescaler=10, BS1=13, BS2=2 → 500kbps, サンプルポイント87.5%
- Operating Mode = Normal
- NVICでRX0割り込みを有効化
送信例(標準フレーム)
static void CAN_SendStd(uint16_t id, uint8_t *data, uint8_t len)
{
CAN_TxHeaderTypeDef txHeader = {0};
uint32_t mailbox;
txHeader.StdId = id;
txHeader.IDE = CAN_ID_STD;
txHeader.RTR = CAN_RTR_DATA;
txHeader.DLC = len;
HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &mailbox);
}
受信コールバック(FIFO0)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);
// rxData を処理
}
10. CRC(ハードウェアCRC)
CubeMXでCRCを有効にし、データ整合性チェックに利用。
extern CRC_HandleTypeDef hcrc;
uint8_t testData[] = {0x01, 0x02, 0x03, 0x04};
uint32_t crcResult = HAL_CRC_Calculate(&hcrc, (uint32_t*)testData, sizeof(testData));
CubeMXの「Input Data Format」がBYTESになっていることを確認。