STM32 HALで赤外線リモコン信号をキャプチャする実装

1. NECフォーマットの概要

NECプロトコルは 38 kHz キャリアを用いた赤外線通信規格である。受信側で観測すると、以下の時間幅でビットが表現される。

  • 論理 1:560 µs の Low 後 1.68 ms の High(合計 2.24 ms)
  • 論理 0:560 µs の Low 後 560 µs の High(合計 1.12 ms)

1フレームは次で構成される。

  1. リーダーコード:9 ms Low → 4.5 ms High
  2. アドレス(8 bit、LSB ファースト)
  3. アドレス反転(8 bit)
  4. コマンド(8 bit)
  5. コマンド反転(8 bit)

ボタンを押し続けると「リピートコード」が送信される:9 ms Low → 2.25 ms High → 560 µs Low → 約 96 ms High。

2. ハードウェア構成

赤外線受信モジュール(VS1838B 等)の出力を TIMx CH1 に接続し、入力キャプチャで立ち上がり/立ち下がりエッジを取得する。

3. タイマー設定

CubeMX で TIM1 CH1 を Input Capture に設定し、両エッジ検出にする。プリスケーラは 1 µs カウントになるよう調整(例:APB2=72 MHz → 72-1)。

4. 変数とバッファ

volatile uint8_t  edge;          // 0:立ち上がり 1:立ち下がり
volatile uint32_t t_rise;        // 立ち上がり時刻
volatile uint32_t t_fall;        // 立ち下がり時刻
volatile uint32_t width_us;      // パルス幅[µs]
uint32_t          ir_buf[34];    // 最大34個格納
uint8_t           ir_idx = 0;
uint8_t           frame_ok = 0;

5. 入力キャプチャ割り込み

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Channel != HAL_TIM_ACTIVE_CHANNEL_1) return;

    if (edge == 0) {                       // 立ち上がり
        t_rise = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
                                     TIM_INPUTCHANNELPOLARITY_FALLING);
        edge = 1;
    } else {                               // 立ち下がり
        t_fall = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
                                     TIM_INPUTCHANNELPOLARITY_RISING);
        edge = 0;

        width_us = (t_fall >= t_rise) ? (t_fall - t_rise)
                                        : (0xFFFF - t_rise + t_fall);

        if (width_us > 4000 && width_us < 5000) {   // リーダー検出
            ir_idx = 0;
            ir_buf[ir_idx++] = width_us;
        } else if (ir_idx > 0 && ir_idx < 34) {
            ir_buf[ir_idx++] = width_us;
            if (ir_idx == 34) frame_ok = 1;
        }
    }
}

6. メインループでのデコード

uint8_t decode_nec(uint32_t *buf)
{
    uint8_t cmd = 0;
    for (int i = 0; i < 8; i++) {
        uint32_t high = buf[17 + i];        // コマンドビット開始位置
        if (high > 1400 && high < 1800)
            cmd |= (1 << i);
        else if (high < 800)                // 論理0
            cmd |= (0 << i);
        else
            return 0xFF;                    // エラー
    }
    return cmd;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_TIM1_Init();

    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);

    while (1) {
        if (frame_ok) {
            uint8_t key = decode_nec(ir_buf);
            switch (key) {
                case 0x18: move_forward();  break;
                case 0x4A: move_backward(); break;
                case 0x10: turn_left();     break;
                case 0x5A: turn_right();    break;
            }
            frame_ok = 0;
        }
    }
}

7. リピートコードの扱い

リピートコードは High 部が 2.25 ms なので、width_us が 2000–2500 µs の場合は前回のコマンドを再実行するだけでよい。

タグ: STM32 HAL NEC IR timer

6月27日 22:05 投稿