[自作キーボード]QMKファームウェアでシリアルLED(WS2812B)に対応する ~明るさセンサ(TEMT6000)と連携して自動調光~

目次

Abstract

  • 前回記事の続き
  • 明るさセンサ(TEMT6000)でWS2812Bの輝度を調整してみた

Introduction

前回まででシリアルLED(WS2812B)の使い方がなんとなく分かりました。ただ、LEDで光らせるのは綺麗ですが昼間とかであまり見えないのに無意味に光らせるも無駄ですし、手動でON/OFF切り替えるのも面倒くさいです。ということで、明るさセンサ(環境光センサ)を使用してLEDの自動調光をしてみたいと思います。

Preparation

QMKでの明るさセンサの利用

QMKのドキュメントページを見ると明るさセンサに関する直接的な機能は有りませんでしたが、ADC Driverを使用することは可能なようです。また、明るさセンサの方を調べると信号出力がアナログ電圧なセンサ(フォトトランジスタ型)も有りましたので、ADC Driver + 明るさセンサの組み合わせで使用したいと思います。


使用する明るさセンサの選定

センサの信号出力がアナログ電圧で簡単に使えるチップととしてTEMT6000を選定。

Maker Parts Number 価格 製造元ページ データシート
VISHAY TEMT6000 100~1000円 Link Link

※現在はVISHAYのページには「TEMT6000X1」しかないみたいです。
 SparkFunの販売サイトには無印の「TEMT6000」のデータシートがあったので、一応そちらのリンクも

自分は以下のAliexpressのショップで1個80円くらいの奴を買いました。
All-goods-are-freeshipping Store, temt6000


TEMT6000の動作確認

TEMT6000の出力(明るさ)信号の電圧の最大値は電源電圧によって決まります。出力信号(S端子)はRP2040のADC用のGPIOで受ける想定ですので、TEMT6000の電源電圧には3.3Vを用います。 一応QMKで使用する前に単純にPicoから3.3V, GNDを接続し、部屋の電気やデスクライトを使って出力信号(S端子)の出力電圧を確認してみました。期待通り明るくするほど出力電圧が電源電圧に近づいてくことが分かります。

↓接続図

↓TEMT6000の信号出力をオシロで確認してみた動画



Method

QMKの ADC Driver をページを参考にすればTEMT6000の出力を読み取れます。 実際にやることは以下の3つだけです

  1. ADCドライバを有効化する
  2. ADC用のヘッダをインクルードする
  3. ADC用の関数を使ってADC用GPIOの電圧値を読む
  4. ログ機能を有効化して、電圧値をログ出しする
  5. 電圧値に応じてLEDの輝度を変更する

1. ADCドライバを有効化する

rules.mkに以下の文を追加するだけです。

rules.mk

ANALOG_DRIVER_REQUIRED = yes

ANALOG_DRIVER_REQUIREDがyesだと qmk_firmware/builddefs/common_features.mk L959辺りの処理でADCドライバ用のソースコードanalog.cが組み込まれるようになるみたいです。RP2040だとChibiOS用の実際には以下のファイルがビルド対象になるようです。
qmk_firmware/platforms/chibios/drivers/analog.c


2. ADC用のヘッダファイルをインクルードする

ADCを使用したいソースコードファイルでADC用のヘッダファイルanalog.hをインクルードします。 今回はkeymap.c内にADCの処理書いてくのでkeymap.cでインクルードします。

keymap.c

#include "analog.h"

RP2040用で実際に読み込まれるヘッダファイルは1.で記載と同様に以下のようです。
qmk_firmware/platforms/chibios/drivers/analog.h


3. ADC用の関数を使って特定のGPIOの電圧値を読む

↑のヘッダファイル見ればわかりますが、使える関数は4つのみです。 引数に見慣れないpin_t型とadc_mux型がありますが、pin_t型は単純にGPIOを指定するための型でRP2040なら"GPx"(xは数字)で指定するやつです。adc_mux型はADCのチャンネルとGPIOのピンをセットにした構造体です。

function discription
int16_t analogReadPin(pin_t pin) 引数pinで指定したGPIOから電圧値を読み取ります。この関数の場合はADCのチャンネルを指定せず一番小さいチャンネルが指定される。RP2040ならADC0, ADC1, ADC2の3つのチャンネルがありますが、必ずADC0が指定されます。なので引数も自動的にGP26を指定する必要がありますね。
int16_t analogReadPinAdc(pin_t pin, uint8_t adc); 引数で指定したpinとADCの組み合わせで電圧値を読み取ります。
adc_mux pinToMux(pin_t pin); 引数pinを、そのpinとそのpinに対応するADCチャンネルが入った構造体にして返します。
int16_t adc_read(adc_mux mux); pinToMux()で取得した構造体を引数に渡して電圧値を読み取ります。

4つありますが、手っ取り早いのはanalogReadPinAdc()ですかね。RP2040の場合はGPIOとADCのチャンネルの組み合わせは以下に決まってるのでそれ指定するだけです。

GPIO 対応するADCチャンネル
GP26 0(ADC0)
GP27 1(ADC1)
GP28 2(ADC2)

以下に電圧値を読むサンプルを記載します。とりあえずqmkのメインループの最後に呼ばれるhousekeeping_task_user()内に処理を書いてます。また、毎ループログ出しするとログで溢れかえっちゃうので10000回に1回だけログ出ししてます。

keymap.c

void housekeeping_task_user(void)
{
    static unsigned int count = 0;
    if( (count % 10000) == 0)
    {
        int16_t val = analogReadPinAdc(GP26, 0);
        // int16_t val = adc_read(pinToMux(GP26)); こちらの書き方でも読み取れます
        xprintf("val=%d\n", val);
    }
    count++;
};

4. ログ機能を有効化して、電圧値をログ出しする

QMKでログを確認する方法は以下のページで紹介されています。

QMK Debuging FAQ

基本的にはログ機能を有効化して、printf()書いてUSB HIDの通信にログ吐いて、USB HID通信をキャプチャできるツールでログを確認という流れですね。

4.1. info.jsonでfeatures - consolesをtrueに変更して有効化する

info.json

    "features": {
        ~省略~
        "console": true,
        ~省略~
    },

4.2. xprintf()※等のログ出力関数でログを出す

ログ出力関数は /quantum/logging/print.h で定義されてます。色々printf系の関数ありますが、ほとんどxprintf()をマクロでラップしてるだけみたいですね。

4.3. USB HID通信のキャプチャツールを使ってログを確認する

以下3パターンあるのでお好きな方法で。自分はコマンドラインでログ取りたかったのでhid_lisetenを使いました。qmk consolesはqmkがWSL環境だと、USBデバイスと繋ぐの大変ですし、qmk toolboxはrp2040環境だとログ以外の機能使えないのでわざわざ使わなくても良いかなと。

RP2040をUSBでPCに繋いだ状態で光センサの出力電圧をログ出しした様子(3節で作成したkeymap.cでのログ出しです)

$ hid_listen
Waiting for device:
Listening:
val=12  // 部屋が暗い状態
val=14
val=10
val=11
val=14
val=13
val=13
val=13
val=11
val=21  // 部屋の電気ON
val=43
val=90
val=126 // 部屋が明るい状態
val=133
val=123
val=132
val=127
val=126
val=78
val=313 // デスクライトON
val=290
val=331
val=324
val=292
val=308
val=328
val=312
val=294
val=320

5. WS2812Bを調光する処理を作成する

明るさセンサの信号出力がADCで取れましたので、あとは↑のログの値を元に適当に閾値を決めて制御処理を書くだけです。今回は以下のような簡単な仕様で処理を書いてみました。

  • 部屋が明るいときはWS2812Bの輝度を0まで下げる
  • 部屋が暗いときはWS2812Bの輝度を100まで上げる
  • 明るい暗いの閾値は↑のログの内容から明るい部屋の値と暗い部屋の値の半分くらいの値で50とする
  • 輝度を変更するときはちらつかないように段々と(フェードさせて)変更する
    • rgblight_decrease_val(), rgblight_increase_val()という関数を使うと1ステップずつ輝度を変更できます

作成したソースコードは以下

keymap.c

void housekeeping_task_user(void)
{
    static unsigned int count = 0;
    if( (count % 10000) == 0)
    {
        int16_t val = analogReadPinAdc(GP26, 0);
        xprintf("val=%d\n", val);
        if (50 < val)
        {
            if(rgblight_get_val() != 0)
            {
                rgblight_decrease_val();
            }
        }
        else
        {
            if(rgblight_get_val() <= 100)
            {
                rgblight_increase_val();
            }
        }
    }
    count++;
};

Result

期待通りに部屋を明るくした時に段々とLEDが消え、部屋を暗くしたときにLEDが光りはじめる動作となりました。

Conclusion

今回は簡単な制御で輝度調整してみましたが、移動平均取るとか輝度制御用のテーブルを使うとか細かく制御してあげればいい感じに自動調光できそうですね。ただ、明るさセンサの配置場所は検討が必要そうです。キーボードの配置的に自分の影がセンサにかかりやすいので、場所によってセンサの値に影響が出てしまいます。(自分の影の影響受けない場所だとキーボードの奥側側面とかがいいのかな)。。

次回はキーボードに簡単な音声ガイダンスを再生できる機能を持たせてみたいと思います。