ESP32-FreeRTOSにて、スイッチのイベントを検出する


はじめに

趣味で電子工作をやっており、FreeRTOSの勉強中です。
今回は、ESP32デバイスを使って、スイッチ押下の検出を行います。

開発環境

以下が確認した環境となります。

  • Windows10 64bit
  • Arduino 1.8.10
    • ボード : ESP32 Dev Module
    • ライブラリ : FreeRTOS 10.2.0-3
  • VSCode
    • 拡張機能 - PlatformIO

PlatformIOを使用しています。
※Arduino IDEでも動くことは確認しましたが、Arduino IDEで動かす場合は、main.cppを.inoファイルに置き換える必要があります。

機材

  • ESP32-WROOM-32 開発ボード x1
  • ブレッドボード x1
  • タクトスイッチ x3
  • ジャンパーケーブル

接続図

作業方針

本システムを作る上で、以下のことを考えました。

  • プルアップ抵抗はESP32内蔵を使う
  • スイッチイベントの検出は、割り込みおよびソフトタイマーを使用する

プルアップ抵抗はESP32内蔵を使う

電子回路において、不定な状態(浮いている)を作らないために、プルアップ(またはプルダウン)抵抗が必要となります。
ESP32には、PIN設定にて、内部のプルアップ抵抗を有効にすることができます。
今回は、それを使用します。

ピン設定は、pinMode()関数にて行います。

pinMode(pin, mode)
  • pin
    • 接続図に習い、18(, 19, 23)を指定
  • mode
    • INPUT_PULLUPを指定(入力PINとして使用、内部プルアップ有効)
    • ※INPUTを指定した場合、外側にプルアップ用の抵抗が必要

参考:Arduino Reference - pinMode()

スイッチイベントは割り込みとソフトタイマーを使用する(チャタリングの対応)

スイッチ(今回はタクトスイッチ)を使うと、チャタリングという現象が起きます。
ハードウェアのアプローチもありますが、今回は簡易的にソフトウェアでの処理を考えます。

↑のタイミング図の"スイッチ波形" - "(不定な期間)"がチャタリングとなります。
チャタリングの期間に、スイッチ状態を取得すると
論理を間違えたり、複数回取得するなど間違って処理する可能性があります。

チャタリングは、ハード構成などに影響し、何msecと定義することはできませんが、
経験上は10msec程度とっておけば大丈夫かなというところです。

ポイントは以下です。

  • スイッチの変化点で割り込みをかける
  • チャダリング中は、割り込みを禁止する
  • (安定期間)で状態を取得する

スイッチの変化点で割り込みをかける

割り込み登録(アタッチ)は、attachInterrupt()関数にて行います。

attachInterrupt(interrupt, function, mode)
  • interrupt
    • PIN - 18(, 19, 23)を指定
    • ※digitalPinToInterrupt()で指定することが推奨
  • function
    • 割り込みが発生時にコールされる関数を指定
  • mode
    • FALLINGを指定
    • スイッチを押していない状態時は、HIGHになっているため、HIGHからLOWへの変化点を指定

参考:Arduino Reference - attachInterrupt()

チャダリング中は、割り込みを禁止する

割り込み解除(デタッチ)は、detachInterrupt()関数にて行います。

detachInterrupt(interrupt)
  • interrupt
    • PIN - 18(, 19, 23)を指定
    • ※digitalPinToInterrupt()で指定することが推奨

参考:Arduino Reference - detachinterrupt()

(安定期間)でスイッチ状態を取得する

(安定期間)でスイッチを状態を取得するために、FreeRTOS機能のソフトウェアタイマー機能を使用する。

ソフトウェアタイマー機能の利用は、以下の手順で行う

  • ソフトウェアタイマーハンドラを作成する
  • ソフトウェアタイマーをスタートさせる

ソフトウェアタイマーハンドラの作成は、xTimerCreate()関数にて行います。

 TimerHandle_t xTimerCreate
                 const char * const pcTimerName,
                   const TickType_t xTimerPeriod,
                   const UBaseType_t uxAutoReload,
                   void * const pvTimerID,
                   TimerCallbackFunction_t pxCallbackFunction;
  • const char * const pcTimerName
    • タイマーの名前を指定する(任意の名前で可能)
    • デバッグ時使用したりします
  • const TickType_t xTimerPeriod
    • 30msecを指定(タイマーがコールされるタイミングを指定)
  • const UBaseType_t uxAutoReload
    • pdFALSEを指定(ワンショット動作)
  • void * const pvTimerID
    • (void*)0を指定
  • TimerCallbackFunction_t pxCallbackFunction
    • タイマー時刻指定時のコールバック関数を指定
    • 本コールバック関数でスイッチ状態の取得および割り込みのアタッチを行う

※余談ですが、uxAutoReloadをpdTRUEを指定すると、リピート動作となります。
 リピート処理は、OS内で自分自身のタスクに対し、キューで通知します。
 ソフトウェアタイマー内でキューが枯渇すると、デッドロックの状態に陥る構造となっているので注意が必要です。

ソフトウェアタイマー開始は、xTimerStartFromISR()関数 / xTimerStart()関数にて行います。
本システムにて、ソフトウェアタイマーの開始は割り込み処理内で行うため、
xTimerStartFromISR()を使用します。

 BaseType_t xTimerStartFromISR
               (
                  TimerHandle_t xTimer,
                  BaseType_t *pxHigherPriorityTaskWoken
               );
  • TimerHandle_t xTimer
    • ソフトウェアタイマー生成時のハンドルを指定します
  • BaseType_t *pxHigherPriorityTaskWoken
    • 今回は他のタスクに邪魔されないため、無視します

参考:FreeRTOS API Reference - xTimerCreate()
参考:FreeRTOS API Reference - xTimerStartFromISR()

以下がシーケンス図になります。

シーケンス図

コード

コードは、下記に配置しています。

esp32Samples - SampleSw

実行結果

スイッチ3つを順番に押下際のUSBシリアルのログです。

vSendSw : xCode:1, xType:2, count:1
vSendSw : xCode:2, xType:2, count:2
vSendSw : xCode:4, xType:2, count:3

さいごに

FreeRTOSの機能について、触れる部分が少なかったかなと思います。