RP2040 パーツ解説 製作物

【Pico W】QR Clock ver.2を作ってみた【ソフト編】

投稿日:

前回の記事で QR Clock ver.2 のハードウェアを紹介しました。今回はソフトウェアについて紹介していきます。

マイコンには Raspberry Pi Pico W を使用しています。ピン数が多いことや CPU のスペックがそれなりにあること、 Wi-Fi が使えることなどが選定理由です。

もともと MicroPython でプログラムを書こうと思っていましたが、 QR コード生成処理が重たく MicroPython では間に合わなかったのでC言語を用いて開発することにしました。

なお、プログラムは GitHub 上で公開しているのでこちらも参考にしてください。

C言語での開発環境

C 言語、また今回は使用していませんが C++ 言語での開発には Raspberry Pi 公式が提供している SDK (Software Development Kit) を使用することができます。

ドキュメント等はこちらのページに集まっています。

SDK のインストールは VSCode の「 Raspberry Pi Pico 」というプラグインを利用するのが簡単で、詳細は忘れましたが最初にプロジェクトを作成した際に自動で SDK がインストールされた覚えがあります。

Raspberry Pi PicoのVS Code拡張機能の画面表示。緑のボード上にRaspberry Piのロゴとインストールボタン、プロジェクトメニューのアイコンが見える。

注意点として、 SDK が C ドライブのユーザホームディレクトリ直下にインストールされたのですが、その場合プロジェクトを D ドライブに作るとうまくいきませんでした。

SDK のサイズが結構大きくて再インストールするのは面倒だったため、プロジェクトを C ドライブ配下に作成することで解決しました。 D ドライブを使いたい場合などはインストール場所に注意しましょう。

プロジェクトの作成は拡張機能のメニューの「 New C/C++ Project 」を押すことで開始します。

VSCodeのメニューにある「New C/C++ Project」オプションを示すスクリーンショット。左側にプロジェクトメニューが表示されている。

プロジェクトの設定画面が出るので、プロジェクト名、ボードの種類、作成場所を指定します。

大事な設定項目としては、 USB を標準入出力として使えるようにする「 Console over USB 」にチェックをいれるのと、無線通信のオプションとして「 Polled lwIP 」を指定しました。

もう一つの無線オプションである Background lwIP は試していないので知りません。

名前、ボード タイプ、SDK バージョン、コンソール サポートのオプションを含む、Raspberry Pi Pico W SDK の QRCloc​​k プロジェクトの基本設定構成画面。

これでプロジェクトが作成されました。ビルドや書き込みについては記事の後半で説明します。

リアルタイムクロックIC DS1302

RTC (リアルタイムクロック)に DS1302 (秋月電子 113634)を用いています。データシートを基にプログラムを作成しました。

DS1302 との通信は I2C や SPI ではない独自形式のシリアル通信となっています。通信に必要な線は 3 本で、以下のような形式で読み込み、書き込みを行います。

DS1302の読み込みおよび書き込みのタイミングチャート

注目すべき点として以下の点が挙げられます。

  • 1 byte目がアドレス、2 byte目がデータ
  • 下位ビットから順に送信される
  • クロックの立上りで値の取り込みが行われる

アドレスの R/C のビットについては、 1 にすることで 32 byte の RAM が使えますが、今回は特に必要がないので使いません。

この単独の読み書きをするプログラムを次のように作成しました。

通信に使用する GPIO ピンをまとめて構造体で管理することにしています。

時刻情報は DS1302 のレジスタに BCD (Binary Coded Decimal) 形式で格納されています。これは 10 進数の各桁を 2 進数の 4 桁ずつに対応させる表現です。

ds1302のレジスタマップ

7 セグ時計など 1 桁ずつそのまま扱う場合には便利ですが、今回はもっと汎用的な形式で扱いたいので、pico/stdlib.hに含まれるdatetime_t型で読み書きできる関数を用意しました。

RTC の動作設定 (ClockHalt) やトリクルチャージの設定は初回起動時に一度行えばいいのですが、簡単のため時刻設定時に毎回設定するようにしています。

LEDドライバ TM1640

16 個の 2 色 8×8 マトリクス LED を制御するために、 16 個の LED ドライバ TM1640 (秋月電子 113225)を搭載しています。この IC は本来 16 桁までのカソードコモン 7 セグ LED を制御することができますが、配線を考えると 2 色 8×8 マトリクス LED でも使用することができました。

データシートによれば、 TM1640 との通信は 2 本の線を用いる独自形式のシリアル通信です。

TM1640 LED ドライバのコマンド データ転送プロセスと SRAM データ アドレス形式の書き込み。

これをプログラムにするのですが、今回は 16 個の TM1640 をまとめて操作する前提のプログラムとなっています。クロック線は全体で共通で、信号線は 16 本個別に制御します。

まずは 16 チャネルまとめて通信の開始、終了をする処理と、同一のデータを全体に送信する関数を紹介します。

これを使って、コマンドを全体に送信することができます。コマンドはデータコマンド、アドレスコマンド、表示制御コマンドの 3 種類があるのでそれぞれ簡単に説明します。

まずデータコマンドについて、これはデータ送信時にアドレスが自動でインクリメントされるかどうかを設定できます。

今回の用途では、データを更新するときは特定のアドレスだけでなくて全体を書き換えたいので、 Address auto +1 のモードで使用します。

tm1640のデータコマンドのコマンド表

次にアドレスコマンドについて、これはデータを書き換える際に対象のアドレスを指定するコマンドです。 Address auto +1 モードの場合、連続するデータの書き込み開始アドレスを指定することになります。

今回の用途では、必ず全体を書き換えるので、 0 番地以外を指定することはありません。 0x0F の次は多分 0x00 に戻ると思うので、毎回 16 byte ずつデータを送信していればアドレスコマンドを打たなくても大丈夫かもしれませんが、念のため書き込みの度にアドレスを 0x00 に指定するようにしています。

tm1640のアドレスコマンドのコマンド表

最後に表示制御コマンドですが、これは LED の明るさと表示の ON/OFF を設定することができます。

これは基本的には初期化処理の中で一度だけ実行すればよいものになります。

TM1640の表示制御コマンドのコマンド表

データコマンドと表示制御コマンドを打つ関数を紹介します。

さて、肝心のデータを個別に送信する処理についてですが、まずはデータの表現方法を考える必要がありました。

QR コードや文字の描画を行うことを考えると、複数の LED マトリクスを並べたディスプレイ全体の各ピクセルごとの色の配列があると便利そうです。ただ TM1640 の制御という抽象度を考えると、もっと低レベルで各 IC が担当する 2x8x8 = 128 bit を 64 bit 整数 2 つで表すことにしました。

データを送信する関数は次のようになっています。 4 重ループの順番が少々複雑ですが、仕方ないかなと思いました。

ロータリーエンコーダ

今回作成した QR Clock ver.2 は表示モード設定などの操作にロータリーエンコーダを採用しました。

ロータリーエンコーダについては過去に使い方を解説したことがありますが(過去記事)、今回ははじめ MicroPython で開発しようとしていたこともあり、こちらのライブラリを参考にさせていただきました。

この Python のコードをもとに、プッシュスイッチの機能とソフトウェアによるチャタリング除去機能を実装しました。ロータリーエンコーダ部分についてはチャタリング除去なしでもほとんど問題なく動いていたのですが、プッシュスイッチ部分では必須でした。

デバイスを管理する構造体には、ピン番号の他プッシュや回転の検出を表すフラグ変数と、内部の計算で使うための状態を記憶する変数が含まれています。

rotary_main_loop()を定期的に呼び出すことで GPIO の読み取りとイベント検出を行います。

メイン関数では次のような使い方をしています。

QRコード計算

この作品の一番大事なところとも言える QR コードを生成する処理です。前作のマイクロ QR コード時計では QR コード生成処理を手書きしたのですが、今回は開発期間短縮のために既存のライブラリを用いることにしました。

Python用ライブラリ

冒頭にも書いた通り、まずは MicroPython で QR コードを生成しようと考えていました。しかし、 MicroPython 用の QR コードライブラリは見当たらず、通常の Python 用のライブラリとしてこちらを試してみました。

PyPI のページの「ファイルをダウンロード」から .tar.gz ファイルをダウンロードして解凍すると、 Python のソースファイルを入手できました。

通常の Python 環境で動作確認を取ったのち、 MicroPython 環境で動かしてみると、「標準ライブラリのbisectモジュールが存在しない」といったエラーが出ました。

MicroPython は Python の縮小版なのでライブラリが足りないことがあります。今回のケースでは、bisect.pyというファイルを作成して、bisect_left()関数を標準ライブラリと同じ引数の仕様で定義すれば解決しました。

そのほかの修正点として、不要な画像化のプログラムを削除したり、再帰で計算していた多項式の剰余をループで計算するように変更したり、正規表現のreモジュールの一部使えない関数を回避したりといった変更を加えると MicroPython でも動くようになりました。

しかし、 QR コード 1 つを計算するのに 1000 ms 以上の時間がかかったため、毎秒表示を更新するという要件を満たすことができず、 MicroPython は諦めることにしました。

C言語用ライブラリ

C 言語用の QR コード生成ライブラリとして、こちらの GitHub で公開されている libqrencode を使用しました。

リポジトリのトップディレクトリにある.c/.hファイルをダウンロードして、手元のプロジェクトに追加します。この中でメインファイルからインクルードする必要があるのはqrencode.hだけです。

datetime_t型の日付、時刻情報から文字列を組み立てて、それを QR コードにエンコードするプログラムを以下に示します。

まずはsprintf()を使って QR コードに埋め込む文字列を作成します。日本語を使うのでファイルの文字コードには気を付けましょう。

次に libqrencode のQRcode_encodeString()を呼び出します。各引数の意味は次のとおりです。

  • 第1引数:埋め込む文字列のポインタ。
  • 第2引数:QRコードのバージョン(大きさ)。今回は29×29のバージョン3。
  • 第3引数:誤り訂正レベル。
  • 第4引数:エンコードのモード。数字モードや英数字モードなどもあるが、今回は文字コードをそのまま使う8ビットバイトモード。
  • 第5引数:case sensitive. 大文字小文字関係ないなら0。8ビットバイトモードだとおそらく関係ないが1にしておいた。

QR コードが計算できたら、それを LED の状態を管理する 2 次元配列display_matrixに格納しています。qrcode->dataunsigned charのデータですが、各ビットに意味が割り当てられています。最下位ビットが白黒を表しているので、 1 と論理積を取って最下位ビットを取り出しています。

QR コードを LED に表示させるとこんな感じです。

QRコードを表示している様子

Wi-FiでNTPサーバーから時刻取得

Raspberry Pi Pico W の魅力の一つは Wi-Fi が使える点です。時計に必要な時刻合わせの機能を NTP を使ってできるようにしました。

プログラムについては、公式のサンプルプログラムの中に NTP を使うものがあったので、それをベースに改変しています。

今回は Wi-Fi は NTP でしか使わないので、ntp_client.cに Wi-Fi 関連のコードもまとめています。作成したntp_get_time()の処理は大雑把にいうと以下の流れになっています。

  1. ハードコードしておいたSSID、パスワードでWi-Fiに接続。
  2. DNSでNTPサーバー(pool.ntp.org)のIPアドレスを取得。
  3. NTPリクエストを送信。
  4. 帰ってきたデータから時刻(JST)を計算してdatetime_tに変換。

それぞれの処理について少し詳しく見ていきます。

初期化・Wi-Fi接続

まずは↑のステップの前段階として無線機能の初期化処理です。この関数はメイン関数の最初の諸々の初期化処理の中で呼び出しています。

それぞれの詳しい内容は全く把握していませんが、cyw43_arch_init()cyw43_arch_enable_sta_mode()の 2 つの関数を呼べばよいようです。

次に Wi-Fi のアクセスポイントに接続する処理です。この処理は実際に NTP を利用するときにはじめて実行するようにしています。

cyw43_arch_wifi_connect_timeout_ms()に SSID 、パスワード、認証方式、タイムアウト時間を渡します。

通信管理構造体

ここから DNS サーバーおよび NTP サーバーと通信していくのですが、通信に関する状態をNTP_Tという一つの構造体にまとめて管理しています。

通信は UDP で行います。udp_new_ip_type()を使って通信を管理する PCB (Protocol Control Block) というものを作るようです。

DNSで名前解決

DNS はドメイン名から IP アドレスに変換するための仕組みです。 NTP サーバーの IP アドレスを取得するために DNS サーバーに問い合わせます。

dns_gethostbyname()に NTP サーバーのドメイン名と取得した IP アドレスの格納場所のアドレス、処理が完了したときに実行されるコールバック関数のアドレス、コールバック関数への引数を渡して実行します。

ネットワーク関係の処理はcyw43_arch_poll()を何度も呼ぶことで進みます。ただし、 CPU をある程度解放する必要があるようで、 while ループの中でsleep_ms()を呼ばないと処理が進まないという罠がありました。

NTPリクエスト

次に NTP サーバーに実際にリクエストを送信します。

ntp_request()がリクエストを送信する関数です。udp_sendto()を利用して DNS で取得した NTP サーバーの IP アドレスに対してデータを送信しています。

リクエストの後、 NTP サーバーから時刻データを含む返事が返ってくるはずですが、それを処理するのがntp_recv()関数です。この関数では返事が正しいことをチェックしたうえで NTP 時間から UNIX 時間へと時刻を変換しています。この関数は udp_recv()によってコールバック関数に登録されています。

時刻変換・後処理

UNIX 時間を JST に変換した上でdatetime_tのデータに変換します。

最後に udp_remove() でメモリの解放を行って終了します。

またここまでの各段階で処理が失敗したときにgoto FAIL;としていましたが、ntp_get_time()の最後にメモリの解放処理を置いてそこに飛ぶようにしています。

タイマー割り込み

時刻の更新処理を 1 秒ごとに行うため、 Raspberry Pi Pico W 本体のタイマー割り込みを使用しています。

レジスタをいじったりする必要はなく、一定の周期でコールバック関数を呼ぶように設定する API のadd_repeating_timer_ms()を使用します。

プログラム中では以下のようにフラグをセットするコールバック関数を 1000 ms ごとに呼ぶように設定しています。

第 1 引数は周期を指定しますが、負の場合はコールバック関数の呼び出し間隔として設定し、正の場合はコールバック関数の処理が終わってから次に呼び出されるまでの時間として設定することを意味しています。

第 4 引数のtimerはコールバック関数に渡される構造体ですが、特に使用はしていません。

LEDディスプレイのデータ管理

ここからはハードウェアの使い方ではなくソフトウェア的な話題になります。

8×8 の赤緑 2 色 LED マトリクスモジュールが 16 個並んで、 32×32 の LED ディスプレイを構成しているのですが、文字や図形を描画したいという視点ではモジュール単位の区切りなどは考えたくありません。

そこで、以下に示すように enum として Color_t を定義し、 Color_t[32][32]としてディスプレイのデータを管理することにしました。

しかし LED ドライバの TM1640 に渡すデータは前述したとおり、 64 bit x 2 色の uint64_t[16][2]としているので、これらの間の変換処理を用意しています。

文字描画

メニュー機能およびデジタル時計モードで英数字や記号を描画します。使用する文字種は限られているので、必要な文字だけフォントデータを手作業で作成することにしました。

フォントは隣の文字との余白も含めて 8×6 px のサイズとし、 1 列を 1 byte として各文字 6 byte のデータとしています。使う文字だけ用意しているので、文字とバイト列の組として記録してあります。

LED ディスプレイを 4 行 5 列の文字表示器として扱って、文字列を描画するdisplay_print_string_to_matrix()関数を用意しています。

デジタル時計モードで時刻を表示するとこんな感じです。

QRClock2で時計モードで時刻を表示している様子の写真

アナログ時計描画

アナログ時計モードは事前に準備しておいた背景(文字盤)データと、直線描画処理の組み合わせで実装しました。

背景はこのような 32×32 のデータです。 Python の PIL を利用して適当なプログラムを書いて作成しました。uint32_t[32]としてプログラムに埋め込んであります。

直線の描画プログラムは ChatGPT に生成させました。draw_hand()が時計の針を描画するための関数で、長さと角度を入力します。座標は時計の中心を原点とする座標を考えて、draw_pixel()で範囲外の対策とともにディスプレイの中心に原点が来るように調整しています。

アナログ時計モードで時刻を表示するとこんな感じです。

QRClock2でアナログ時計モードで時刻を表示している様子の写真

メインループ

メインループでやるべき仕事は以下の通りです。

  • ロータリーエンコーダの入力処理
  • ロータリーエンコーダにクリックや回転のイベントがあった場合、現在の状態に応じた処理
  • 1秒ごとの時刻更新フラグが立っている場合、時刻を更新しモードに応じた表示処理

現在の状態による場合分けが多いので少し面倒ではありますが、一つずつ処理を書いていくだけです。

ビルド・書き込み

最後にプログラムのビルドと書き込みの方法を紹介します。

まずビルドについてですが、プロジェクトを作成した際に生成された CMakeLists.txt を編集します。

ソースファイルが複数に分割されている場合、 add_executable という項目で各 .c ファイルを対象に追加する必要がありました。

また Wi-Fi の SSID やパスワードをプログラム中で#defineする代わりに secrets.cmake というファイルに書いておき、 CMakeList.txt で include して target_compile_definitions という項目でコンパイル時に定義を追加できます。

それ以外の設定項目についてはよく把握していない部分も多いので説明しません。真似したい方はファイルを読んでみてください。

ビルドや書き込みは VS Code の画面右下にある Compile, Run などのボタンを押すか、コマンドパレットから Raspberry Pi Pico: Compile Pico Project などを選択することで実行できます。

書き込みの際は、本体の BOOTSEL ボタンを押しながら PC に接続する必要があることに注意しましょう。

おわりに

QRClock2 のプログラムは様々なプログラムを組み合わせていて、それぞれを雑多に紹介しました。一部分だけでも参考になれば嬉しいです。

冒頭にも書きましたが、プログラムは GitHub で公開しているので参考にどうぞ。

またニコニコにこの作品の動画を投稿しているので、未視聴の方は見ていただけると嬉しいです。

-RP2040, パーツ解説, 製作物

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

オペアンプを使った積分型ADCを作ってみた

トランジスタ技術 2022 年  …

【Pico W】QR Clock ver.2を作ってみた【ハード編】

以前にマイクロ QR コード&# …

ロータリーエンコーダの使い方

今回は PIC で ロータリーエ …

バブルソートを行う電子回路を作ってみた

今回は CPU を用いずにハー& …

APDS-9960のRGBセンサーを使ってみる

前々回のジェスチャーセン&#12 …