twiiterもチェック!! 15億画素カメラ自作企画進行中!

【prk_firmware】外部require機能を活用してOLEDを制御しよう【自作キーボード】

パソコン
この記事は約18分で読めます。
mrubyファミリー Advent Calendar 2023 の 10日目の記事になります.
どらびです.
先日mrubyファミリー Advent Calendar 2023の5日目の記事として投稿しましたこちらの記事
ですが,こちらはあくまでキーボード全体についての内容ということで,今回はもう少しprk_firmwareの中身やコード詳細に触れながら,どんなコードを書いて,それがどのように機能しているかを説明していこうと思います.

この記事を書くきっかけ

今回この記事のメインになるrequire機能に関して,このFWの作者であるはすみきんさんに以前より実装予定である旨を聞いていて,23/11/23に満を持して登場した機能を使って約1週間の突貫工事でOLED機能を実装したのでみんなに見てほしいというのが第一,

もうすこし高尚な理由としてははすみきんさんが昨年のアドベントカレンダーに書いている

トラックボールやOLEDを動かすためには、I2CやSPIというマイコンペリフェラルを使用する必要があります。 PRKには1年以上前にI2C実装を含むOLED機能のPRが来ていたりします。 でもマージしていません。 理由は、低レベルAPIをちゃんと設計することなしにこれらの機能を外形上動くようにつくってしまうと、そのあとが面倒になると思っているからです。 繰り返しますが、実現したいのはマイコンプログラミングのフレームワークであって、自キのためだけのOLEDではありません。
に深く共感したからです.
作者がちゃんと宣言通りきっちりと応用可能な形で,しかも他機種にも容易に展開可能な手法を実装してくれたのですから,それを使ってできる限りの範囲でみんなが使いやすいモジュールを書いて残すことで,prk_firmwareの利用人数も増えていくでしょうし,輪が広がれば広がるほど機能が充実して私もハッピーという算段です.
(これから紹介するOLEDのスクリプトもまだまだ未完成なのでみんなに書いてもらって自分が楽したいのは秘密.)

prk_firmwareとは?

この記事を読んでいる人は恐らく大半が自作キーボード経験者,あるいは自作キーボードに興味がある方だと思っているので蛇足かもしれませんが,せっかくのアドベントカレンダー記事ですので,もっと広い範囲の方が来るかもしれない想定でできるだけこの記事単独で話の流れがわかるように書いていこうと思います.

「そんなこと知っとるわい!」という方はこの章は飛ばしてください

というわけでprk_firmwareとは何ぞやという話ですが,

「picorubyを用いた自作キーボードFWのフレームワーク」とgitのreadmeに記載があります.

GitHub - picoruby/prk_firmware: A keyboard firmware platform in PicoRuby
A keyboard firmware platform in PicoRuby. Contribute to picoruby/prk_firmware development by creating an account on GitHub.

自作キーボードを作成されたことがある方はご存じかと思いますが,自作キーボードはマイコンを搭載して,そこにマトリクス上に配置したスイッチからの入力を入れてあげることで作成します.

こうすると何がよいかというと,「このスイッチが押されたははAを入力したことにする!」という部分をマイコンが仲介してくれるので,キーの割り当て(アサイン)をSWの改変のみで自由に変更することができます.

逆に言えばマイコンにこのアサインを割り当ててあげたりするためのFWをマイコンに焼いてあげないと,ただの文鎮と化してしまうわけです.

現状有名なキーボード用のFWとしては
(qmkとprk以外触ったことがないので正直自分もこの辺詳しくないです.間違ってたら教えてください.)

  • QMK Firmware
  • ZMK Firmware

があると思います.

この他,これらのFWの使い勝手を向上するためのアプリケーションとしてRemapやVIAやVial(名前がややこしい)があると思っています.(いずれも使ったことない)

 

prk_firmwareの特徴

まずはざっくり列挙します.

  • 言語がpicorubyである
  • RP2040(要はraspberrypi pico)をターゲットデバイスとしている
  • FWをインストールするとストレージとして認識されるようになる
  • ストレージにキーマップが入っており,動的に編集できる

順に見ていきましょう.

言語がpicorubyである

当然と言えば当然です.というのもこのFWの作者はpicorubyの生みの親であるはすみきんさんです.

picorubyって何?って話になるかもしれませんが,あんまり詳しく書くとこの記事の文量がとんでもないことになるので,ここではざっくりと「rubyという開発言語をマイコンでも使用できるように軽量に実装したもの」くらいの説明にとどめておきます.

 

RP2040(要はraspberrypi pico)をターゲットデバイスとしている

最近?というにはちょっと前すぎるような気もしますが流行りのraspi picoを使ってキーボードを作れるということで,敷居が若干低くなっている効果はあると思います.ただし個人的に参入障壁が一番低くなっている部分は次とその次の要素によるものだと思っています.

 

FWをインストールするとストレージとして認識されるようになる

これはpicorubyの特徴というよりはraspiもそうなっているので,という話ではありますが,とりあえず購入した状態でUSBに繋いでPCに接続するとストレージとして認識されて,そこにFWを入れると書き込まれるというのが非常に扱いやすいです.

参入障壁がかなり低いと思われるarduinoでもアプリを入れて,そこからCOMポートで接続してプログラムを書き込むというのが必要で,そのような経験が全くない人からすると結構大変なことだと思います.

その点prk_firmwareもraspi picoも既にコンパイルされたFWを焼くだけなら,あるいはキーマップを入れるだけなら全く何も必要としないところは初心者にオススメする十分な要素たりうると思っています.

 

ストレージにキーマップが入っており,動的に編集できる

これも上記とほぼ同じことですが,QMKなどでは動的にキーマップを変更するのが難しいというのが古参的にはありました.今はVialだのRemapだのがありますのでずいぶん楽になっているのかもしれませんが個人的にはprkの方が扱いやすくない?と思っています.

こればっかりは個人の感想ですのでこれ以上追求するのはやめておきます.

 

追加された`require`機能について

ようやっと本題にたどり着きそうですが,既に3000近く文字を書いていることに自分自身が一番驚いています.

さておき,今回の記事で最もキーになる追加機能,require機能について説明していきます.

とは言っても詳しい内容はprk_firmwareのwikiに記載がありますのでそちらをご覧いただくのがよいと思います.

require
A keyboard firmware platform in PicoRuby. Contribute to picoruby/prk_firmware development by creating an account on GitHub.

キーボードの自作の記事でも書きましたが,要は

「自作のライブラリを読み込んで使えるようになる機能」です.python使ってる人専用の表現ですが,自作ライブラリのimportができるようになりました.

というお話です.

まさしくはすみきんさんが昨年おっしゃられていた部分だと思っています.

FWというのは基本的にはいろんなプラットフォームの上で動作するはずで,ここではそれが個々のキーボードにあたるわけです.

共通した部分はFW上に盛り込まれてもいい,むしろ盛り込まれているべきですが,例えばキーボードの場合,トラックボール,トラックパッド,トラックポイント,ジョイスティックなどのポインティングデバイスに始まり,ソレノイド,スピーカーなどの音で伝えるデバイス,振動子,そして今回の話題であるOLEDなど,実に多様なデバイスが載るかもしれないし,載らないかもしれません.

ただでさえリソースが厳しいマイコンの上に,これらの使うかもわからない機能の情報が載っていても大概のものは無駄になると言ってよいでしょう.

それであれば,どのデバイスでも使う低レイヤな機能だけをFWに実装し,各々あとから追加できる方が自由度が高く,無駄のない作り方にできます.

また,このようにしておくとスクリプトの見通しがよくなるので,結果的にバグなどの早期発見につながり,ユーザーみんなハッピーになれるわけです.

余談①

生態系に例えると,上流をきれいにしておけば,下流には多様な生物が棲めるようになるでしょうし,同時に何か毒物等が混入したときに,どこからそれが流れてきたのか追いやすいのです.それと同じです.

余談②

PCのOSやスマホも,後から好みのアプリを入れて自分好みの仕様にすると思います.この世に数えきれないくらいあるアプリが全部あらかじめスマホにインストールされていたら発狂ものです.同じです.

 

OLED機能の実装

え,4000文字近く書いてるんですが…(ドン引き)

ようやく本題の本題です.

ここではOLEDを制御するためのrequire機能を用いた実装について説明します.

 

requireするための準備(前提条件)

まず,https://github.com/picoruby/prk_firmware/archive/refs/tags/0.9.23.zip からFWをダウンロードしてきてpicoに書き込むと,(書き込み方はwikiを参照してください.)

ディレクトリ構成としてはこんな感じになっていると思います.

/
├README.txt
└lib/
で,ここにスクリプトを入れていきます.
※ちょっと話が逸れて申し訳ないですが,今回使用するOLEDはSSD1306というコントローラICで制御するので,以降これ用のスクリプト名はssd1306.rbとします.
メインとなるkeymap.rbはルートディレクトリ,これからrequireするためのクラスの記述をしたスクリプトをlib配下に置きます.
/
├README.txt
├keymap.rb
└lib
└ssd1306.rb
この状況にしてあげることで,初めてkeymap.rbからlib配下内のスクリプトをrequireすることができます.逆に言うとkeymap.rbからはこのディレクトリ以外は見えないことになってますのでご注意ください.

ssd1306.rbの中身

ここからは具体的にスクリプトの中身を見ていきましょう.
※未完成ですが一応スクリプトはgitにアップしていますので,参考にしたい方はご覧ください.
GitHub - stDRBrd28/TPS36
Contribute to stDRBrd28/TPS36 development by creating an account on GitHub.
 
# ssd1306.rb
require "i2c"

class SSD1306
  def initialize(unit_name:, freq:, sda:, scl:)
    @i2c = I2C.new(unit: unit_name, frequency: freq, sda_pin: sda, scl_pin: scl)

    # initialize
    @i2c.write(0x3C, [0b10000000, 0x00])
    @i2c.write(0x3C, [0b00000000, 0xAE])
    @i2c.write(0x3C, [0b00000000, 0xA8, 0x3F])
    @i2c.write(0x3C, [0b10000000, 0x40])
    @i2c.write(0x3C, [0b10000000, 0xA1])
    @i2c.write(0x3C, [0b10000000, 0xC8])
    @i2c.write(0x3C, [0b00000000, 0xDA, 0x12])
    @i2c.write(0x3C, [0b00000000, 0x81, 0xFF])
    @i2c.write(0x3C, [0b10000000, 0xA4])
    @i2c.write(0x3C, [0b00000000, 0xA6])
    @i2c.write(0x3C, [0b00000000, 0xD5, 0x80])
    @i2c.write(0x3C, [0b00000000, 0x20, 0x10])
    @i2c.write(0x3C, [0b00000000, 0x21, 0x00, 0x7F])
    @i2c.write(0x3C, [0b00000000, 0x22, 0x00, 0x07])
    @i2c.write(0x3C, [0b00000000, 0x8D, 0x14])
    @i2c.write(0x3C, [0b10000000, 0xAF])
  end

  def all_clear()
    i=0
    while i<8 do
      @i2c.write(0x3C, [0b10000000,
      0xB0 | i])

      j=0
      while j<128 do
        @i2c.write(0x3C, [0x00, 0x21, 0x00 | j, 0x00 | j+1])
        @i2c.write(0x3C, [0b01000000, 0x55])
        j=j+1
      end
      i=i+1
    end
  end

  def all_white()
    i=0
    while i<8 do
      @i2c.write(0x3C, [0b10000000,
      0xB0 | i])

      j=0
      while j<128 do
        @i2c.write(0x3C, [0x00, 0x21, 0x00 | j, 0x00 | j+1])
        @i2c.write(0x3C, [0b01000000, 0xFF])
        j=j+1
      end
      i=i+1
    end
  end

  def draw_all(pic:)
    k=0
    while k<128*8 do
      i = k / 128
      j = k-i*128

      @i2c.write(0x3C, [0b10000000,
      0xB0 | i])

      @i2c.write(0x3C, [0x00, 0x21, 0x00 | j, 0x00 | j+1])
      @i2c.write(0x3C, [0b01000000, pic[k].bytes[0]])
      k=k+1
    end
  end
end

requireという機能自体はrubyという言語に含まれている内容で,picorubyにも元々FW内に組み込まれている関数を使用する場合には利用できていたものですので,仕組みや使い方の詳細に関してはそちらを参考にして頂けますと幸いです.

rubyはオブジェクト指向言語なのでrequire自体はある意味必須機能とも言えます.

というわけでこのスクリプト内ではこのOLEDを制御するための一連の定数や関数を集めたクラスを定義して,それをkeymap.rbで呼び出すというのが筋の良い形になると思います.

実際に上記のスクリプトでもSSD1306というクラスを定義しています.

このスクリプトはまだ試作段階なので,非常にシンプルな関数しか用意しておらず,またその関数ももう少し引数を増やして設定できる項目を増やしてあげるべきです.具体例は後程記載します.

とは言ってももうすでに6400字に到達しているので,ここから当記事内でSSD1306の個別な仕様を細かく説明しだすととんでもないことになります.なのでその辺に関しては私が参考に読んでいたブログとデータシートのリンクでお茶を濁すことにします.

有機EL ( OLED ) SSD1306 を再検証してみました ( I2C 通信用 )
I2C 通信の OLED ( 有機EL ) SSD1306 を以前よりも詳しく再検証してみました。ESP32 と ESP8266 両方で動作させています。Segment / Common 方式のため、普通のグラフィックディスプレイとは異なり、グラフィカルな表示は実に難しいデバイスです・・・

https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

 

initialize関数の具体的な中身

さて,さすがに一つくらい具体的な話はしておきたいので,一点.initialize関数の詳細を見てみましょう.

 
  def initialize(unit_name:, freq:, sda:, scl:)
    @i2c = I2C.new(unit: unit_name, frequency: freq, sda_pin: sda, scl_pin: scl)

    # initialize
    @i2c.write(0x3C, [0b10000000, 0x00])
    @i2c.write(0x3C, [0b00000000, 0xAE])
    @i2c.write(0x3C, [0b00000000, 0xA8, 0x3F])
    @i2c.write(0x3C, [0b10000000, 0x40])
    @i2c.write(0x3C, [0b10000000, 0xA1])
    @i2c.write(0x3C, [0b10000000, 0xC8])
    @i2c.write(0x3C, [0b00000000, 0xDA, 0x12])
    @i2c.write(0x3C, [0b00000000, 0x81, 0xFF])
    @i2c.write(0x3C, [0b10000000, 0xA4])
    @i2c.write(0x3C, [0b00000000, 0xA6])
    @i2c.write(0x3C, [0b00000000, 0xD5, 0x80])
    @i2c.write(0x3C, [0b00000000, 0x20, 0x10])
    @i2c.write(0x3C, [0b00000000, 0x21, 0x00, 0x7F])
    @i2c.write(0x3C, [0b00000000, 0x22, 0x00, 0x07])
    @i2c.write(0x3C, [0b00000000, 0x8D, 0x14])
    @i2c.write(0x3C, [0b10000000, 0xAF])
  end

ここでは,

  1. i2cクラスのインスタンス作成
  2. SSD1306の初期化を行うためのi2c通信

これしかやっていません.

1.は1行目のみで完結しています.これはi2cの低レイヤ部分をクラスとしてまとめてくれているおかげで,実際スクリプト全体の最初の方にrequire i2c の記述があると思います.これは私が実装して外部からrequireしているわけではないので,今回のver以前でも使えていた機能です.

というわけで本題は2の処理です.initialize関数関数の大半の中身はこれで,@i2cから始まる行がそれぞれデータを送信しています.

再掲になりますが,詳細全部に関しては

有機EL ( OLED ) SSD1306 を再検証してみました ( I2C 通信用 )
I2C 通信の OLED ( 有機EL ) SSD1306 を以前よりも詳しく再検証してみました。ESP32 と ESP8266 両方で動作させています。Segment / Common 方式のため、普通のグラフィックディスプレイとは異なり、グラフィカルな表示は実に難しいデバイスです・・・

をご覧頂ければと思います.

先ほど機能が足りていないと話しましたが,具体例としては例えば9行目の

 
    @i2c.write(0x3C, [0b10000000, 0xA1])

は,水平方向の向きを決めることができます.OLEDは設置方向がデバイスによって様々ですので,ここは機種の状況によって動的に変更できるのが望ましいでしょう.

今は反転させている設定になります.反転させたくない場合は0xA1を0xA0にすればよいです.こういわれると実装できそうですよね?

(※わかってるならやれよという話ですが,現状これを記述する意味は私にとってはないのです.ちゃんと他の部分も含めて徐々に整備するつもりですのでご容赦を.)

勿論縦方向の反転も設定があります.その辺は必要に応じてデータシートを読み込みながら,必要な情報をSSD1306に送れるようにスクリプトを作成していくのです.

 

その他の関数について

もう分量がとんでもないことになっている(8500文字)のと,OLEDの処理の話なのでちょっと話がrequireから逸れてしまう都合もあり最低限にとどめます.

all_clear関数はすべてのドットを黒に(光ってない状態)に塗り替えるということをしています.

all_white関数はその逆ですね.

draw_all関数は引数に取ったstringを1バイトずつ拾ってきて16進数の数字に直して送信しています.

このやり方できれいに狙った画像が出るように,string配列を作ってくれるスクリプトをpythonで別途書いてます.その辺はgitのreadmeで説明する予定ですのでここでは割愛します.

 

まとめ

とんでもなく長くなってしまいましたが,自作のスクリプトをrequireできることのメリットとその実例について,OLEDを例に挙げて説明してみました.

prk_firmwareを採用する理由として,RP2040が使えるからという理由で使っている方もいらっしゃるかもしれませんが,個人的にはオブジェクト指向言語で記述でき,しかもコンパイル不要(厳密には起動時に動的にコンパイルして動かしているのでユーザーが意識する必要がない)という点が最も大きなメリットだと思っています.

今回の外部requireはその点において最も大きなブレイクスルーと言っても過言ではないと思っていて,これを機にどんなマイナーなHWを採用しても,FW本体のコードを必要以上に荒らすことなく実装でき,またその成果物を必要な人に還元する土壌ができたと思っています.

これを機に変態キーボード自作への第一歩を踏み出してみてはいかがでしょうか?

 

何度でも再掲しますが,現在個人的には

コレ↓積むとか

SpaceMouse® Module | MEGATRON
Intuitive control of complex 3D movement with one hand only - an industrial module for easy integration ★ compact ✓ reliable ✓ ► top advice | top quality

CO2センサ積むかとか

非常に期待していますので,この機能を活用して是非新たな扉を一緒に開いていきましょう!

ではでは.

コメント

タイトルとURLをコピーしました