RP2040 パーツ解説 製作物

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

投稿日:2025-11-09 更新日:

前回の記事で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色8x8マトリクスLEDを制御するために、16個のLEDドライバTM1640(秋月電子113225)を搭載しています。このICは本来16桁までのカソードコモン7セグLEDを制御することができますが、配線を考えると2色8x8マトリクス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コードのバージョン(大きさ)。今回は29x29のバージョン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ディスプレイのデータ管理

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

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

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

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

文字描画

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

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

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

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

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

アナログ時計描画

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

背景はこのような32x32のデータです。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


関連記事

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

以前にマイクロQRコードで時刻を表示する時計を作ったのですが(記事書いてなかった)、スマホ標準のQRコード読み取り機能ではマイクロQRコードに対応していなくて、展示会でお客さんに読み取って貰えないとい …

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

前回APDS-9960のジェスチャーセンサーを使用しました。今回はジェスチャーに加えて近接センサーも使用してみたいと思います。 前回の記事はこちらhttps://rikeden.net/?p=120さ …

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

前々回のジェスチャーセンサー、前回の近接センサーに引き続き、APDS-9960の最後の機能である照度・RGBセンサーを使ってみました。 最初に言っておきますが、今回は動作確認程度のかなり適当な内容にな …

【PIC】圧電素子を使って2000円で電子マリンバを作ってみた

前回の記事で圧電素子の使い方について紹介したので、今回はその応用編として電子マリンバを作ってみました。 以前ネットで電子マリンバなるものを見つけたとき、めちゃくちゃ欲しいと思ったんですが、ウン十万とい …

PICで128x64グラフィックLCDを使ってみる

今回はAliExpressで買った128×64のグラフィックLCDを使ってみました。使用したPICはPIC16F18857です。 400円ぐらいで安かったのはいいですが、データシートや型番が不明なので …