超音波距離計測は、送受信モジュールから発射された音波が障害物に反射して戻ってくるまでの時間差(Tof: Time of Flight)を計測し、音速から物体までの距離を演算する手法です。STM32ファミリーでこの機能を構築する場合、GPIOによる信号制御と、ハードウェアタイマーを用いたマイクロ秒単位の高精度なパルス幅計測が核心となります。一般的なモジュール(例:HC-SR04)では、トリガー端子に規定時間以上のハイパルスを与え、エコー端子のハイ維持期間が往復時間に相当します。
周辺機能の初期化設定
計測に必要なピンと時計ソースを構成します。トリガー信号はプッシュプル出力、エコー信号は入力モードに設定します。計測精度を確保するため、エコー信号の立ち上がり/立ち下がりをハードウェアタイマーのInput Capture機能で捉える構成が推奨されます。システムクロックに応じてプリスケーラを調整し、タイマーカウントが1μsごとにインクリメントされるように設定します。
void Ultrasonic_Module_Config(void) {
GPIO_InitTypeDef gpio_init;
TIM_HandleTypeDef tim_us_handle;
/* トリガー端子: 出力設定 */
gpio_init.Pin = PIN_TRIGGER_SIG;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(PORT_CTRL_BUS, &gpio_init);
/* エコー端子: 入力設定 */
gpio_init.Pin = PIN_ECHO_SIG;
gpio_init.Mode = GPIO_MODE_INPUT;
gpio_init.Pull = GPIO_NOPULL;
HAL_GPIO_Init(PORT_SENSE_BUS, &gpio_init);
/* タイマー: 入力キャプチャモード設定 */
tim_us_handle.Instance = TIM_BASE_ADDR;
tim_us_handle.Init.Prescaler = 71; /* 72MHz SysClk -> 1MHz(1μs/tick) */
tim_us_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_IC_Init(&tim_us_handle);
TIM_IC_InitTypeDef ic_cfg;
ic_cfg.ICPolarity = TIM_ICPOLARITY_RISING;
ic_cfg.ICSelection = TIM_ICSELECTION_DIRECTTI;
ic_cfg.ICPrescaler = TIM_ICPSC_DIV1;
ic_cfg.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&tim_us_handle, &ic_cfg, TIM_CHANNEL_1);
}
信号発射とパルス幅取得ロジック
計測サイクルはトリガーパルスの生成から始まります。エコー端子がローからハイへ遷移した時点でタイマーカウンタをリセットし、ハイからローへ戻るまでの経過カウントを記録します。この値が音波の往復時間(マイクロ秒)に相当します。ハードウェアポーリング方式を採用し、通信タイムアウトを検出する安全装置を組み込むことで、センサー断線時や過距離時の無限ループを回避できます。
float Acquire_RawDistance(void) {
uint32_t tick_start = 0U;
uint32_t tick_end = 0U;
uint32_t pulse_width_us = 0U;
/* 1. トリガー信号生成 (15μs) */
HAL_GPIO_WritePin(PORT_CTRL_BUS, PIN_TRIGGER_SIG, GPIO_PIN_SET);
for (volatile uint32_t w = 0; w < 150; w++); /* 簡易ウェイト */
HAL_GPIO_WritePin(PORT_CTRL_BUS, PIN_TRIGGER_SIG, GPIO_PIN_RESET);
/* 2. タイマー起動と計測準備 */
__HAL_TIM_SET_COUNTER(&tim_us_handle, 0U);
HAL_TIM_IC_Start(&tim_us_handle, TIM_CHANNEL_1);
/* エコー立ち上がり待ち(タイムアウト監視付き) */
while (HAL_GPIO_ReadPin(PORT_SENSE_BUS, PIN_ECHO_SIG) == GPIO_PIN_RESET) {
if (__HAL_TIM_GET_COUNTER(&tim_us_handle) > 35000U) {
HAL_TIM_IC_Stop(&tim_us_handle, TIM_CHANNEL_1);
return -1.0f; /* タイムアウト */
}
}
tick_start = __HAL_TIM_GET_COUNTER(&tim_us_handle);
/* エコー立ち下がり待ち */
while (HAL_GPIO_ReadPin(PORT_SENSE_BUS, PIN_ECHO_SIG) == GPIO_PIN_SET) {
if (__HAL_TIM_GET_COUNTER(&tim_us_handle) - tick_start > 35000U) {
HAL_TIM_IC_Stop(&tim_us_handle, TIM_CHANNEL_1);
return -1.0f; /* 最大計測範囲超過 */
}
}
tick_end = __HAL_TIM_GET_COUNTER(&tim_us_handle);
HAL_TIM_IC_Stop(&tim_us_handle, TIM_CHANNEL_1);
/* 3. パルス幅演算 */
pulse_width_us = (tick_end > tick_start) ? (tick_end - tick_start) : 0U;
/* 4. 距離変換 (音速: 約343m/s @20℃, cm単位へ換算) */
/* 距離(cm) = 時間(μs) × 0.0343 ÷ 2 */
float raw_dist = (pulse_width_us * 0.0343f) / 2.0f;
return (raw_dist > 450.0f || raw_dist < 2.0f) ? -1.0f : raw_dist;
}
データ処理と出力インターフェース
取得した生距離データは、温度変動や反射面の材質によるノイズを含むため、そのままシステムに投入するのではなく、ソフトウェアフィルタリングを適用するのが実装的な定石です。移動平均フィルタやメディアンフィルタを使用し、例えば直近N回分の計測値をリングバッファに格納した後、最大値と最小値を排除した平均値を採用することで、突発的な外れ値による距離ジャンプを平滑化できます。
フィルタ処理済みのデータは、USARTやUSB CDCを通じて上位ホストへ送信したり、SPI/I2Cバス経由でグラフィックディスプレイへ描画したりします。通信プロトコルに合わせて浮動小数点数を固定小数点形式や整数値(mm単位など)に変換してペイロードに含めることで、帯域幅の使用効率を最適化できます。また、計測周期(通常50ms〜100ms間隔)をシステムタイマーで管理し、CPU負荷を分散させる構成も広く採用されています。