もともと初代 Raspberry Pi 用に買ったものだけれど、初代 Raspberry Pi をいよいよ捨てたので、ついでにいろいろ調べながら再度使ってみる。

LCD-show-180817.tar.gz の中の ./LCD32-show がやっていることは以下の通り。

しかし fbcp のインストールは失敗してた。まぁとりあえず使える。

なお、どういったコントローラが使われているかスペックに書いてないが、以下の通り。

  • タッチコントローラーは SPI ads7846
  • 液晶は SPI ili9340 240x320

fbcp は何をしている?

frame buffer をコピーするアプリケーションなのは名前から明らかだけれど、何のために?と思った。結論をいうと GPU でレンダリングした結果を SPI 接続の液晶へ表示させるためのハックのようだ。GPU を使わないなら入れなくても動かすことはできる。

dtb dts

dtb ファイル (コンパイル済みのデバイスツリーブロブ) が含まれているけど、ソースファイルがない。以下のようにするとソースにディスアセンブルできる。

dtc -I dtb -O dts -o waveshare32b-overlay.dts waveshare32b-overlay.dtb

以下のような感じのデバイスツリーとなっていた。

/dts-v1/;

/ {
        compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709";

        fragment@0 {
                target = <0xdeadbeef>;

                __overlay__ {
                        status = "okay";

                        spidev@0 {
                                status = "disabled";
                        };

                        spidev@1 {
                                status = "disabled";
                        };
                };
        };

        fragment@1 {
                target = <0xdeadbeef>;

                __overlay__ {

                        waveshare32b_pins {
                                brcm,pins = <0x11 0x1b 0x16>;
                                brcm,function = <0x0 0x0 0x0>;
                                linux,phandle = <0x1>;
                                phandle = <0x1>;
                        };
                };
        };

        fragment@2 {
                target = <0xdeadbeef>;

                __overlay__ {
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;

                        waveshare32b@0 {
                                compatible = "ilitek,ili9340";
                                reg = <0x0>;
                                pinctrl-names = "default";
                                pinctrl-0 = <0x1>;
                                spi-max-frequency = <0xf42400>;
                                rotate = <0x5a>;
                                fps = <0x19>;
                                bgr;
                                buswidth = <0x8>;
                                reset-gpios = <0xdeadbeef 0x1b 0x0>;
                                dc-gpios = <0xdeadbeef 0x16 0x0>;
                                debug = <0x0>;
                                linux,phandle = <0x2>;
                                phandle = <0x2>;
                        };

                        waveshare35a-ts@1 {
                                compatible = "ti,ads7846";
                                reg = <0x1>;
                        };

                        waveshare32b_ts@1 {
                                compatible = "ti,ads7846";
                                reg = <0x1>;
                                spi-max-frequency = <0x1e8480>;
                                interrupts = <0x11 0x2>;
                                interrupt-parent = <0xdeadbeef>;
                                pendown-gpio = <0xdeadbeef 0x11 0x0>;
                                ti,x-plate-ohms = [00 3c];
                                ti,pressure-max = [00 ff];
                        };
                };
        };

        __overrides__ {
                speed = <0x2 0x7370692d 0x6d61782d 0x66726571 0x75656e63 0x793a3000>;
                rotate = [00 00 00 02 72 6f 74 61 74 65 3a 30 00];
                fps = [00 00 00 02 66 70 73 3a 30 00];
                debug = <0x2 0x64656275 0x673a3000>;
        };

        __symbols__ {
                waveshare32b_pins = "/fragment@1/__overlay__/waveshare32b_pins";
                waveshare32b = "/fragment@2/__overlay__/waveshare32b@0";
        };
        __fixups__ {
                spi0 = "/fragment@0:target:0", "/fragment@2:target:0";
                gpio = "/fragment@1:target:0", "/fragment@2/__overlay__/waveshare32b@0:reset-gpios:0", "/fragment@2/__overlay__/waveshare32b@0:dc-gpios:0", "/fragment@2/__overlay__/waveshare32b_ts@1:interrupt-parent:0", "/fragment@2/__overlay__/waveshare32b_ts@1:pendown-gpio:0";
        };

        __local_fixups__ {
                fixup = "/fragment@2/__overlay__/waveshare32b@0:pinctrl-0:0", "/__overrides__:speed:0", "/__overrides__:rotate:0", "/__overrides__:fps:0", "/__overrides__:debug:0";
        };
};

Device Tree って何か?

簡単にいうとそのハードウェアのドライバの用の設定ファイル。ほぼ同じ制御方法をするハードウェアであっても、細かい制御アドレスの違い・使用バス・接続されているGPIOピンの違いが存在する。これらをいちいちカーネルにハードコードしてコンパイルするのは面倒なので、設定ファイルとして分離してある。ARM Linux だと標準的に使われている。

DTS ファイルの書きかた https://elinux.org/Device_Tree_Usage

  • compatible = "ilitek,ili9340" の部分でドライバを指定している "vendor,product" 形式。

正直読むのに苦労する。完全に理解してない。

タッチコントローラのキャリブレーション

$ DISPLAY=:0 xinput_calibrator
...
Section "InputClass"
        Identifier      "calibration"
        MatchProduct    "ADS7846 Touchscreen"
        Option  "Calibration"   "195 3780 3881 252"
        Option  "SwapAxes"      "1"
EndSection
$ sudo vi /usr/share/X11/xorg.conf.d/99-calibration.conf 
# copy calibration from above
  1. トップ
  2. tech
  3. Raspberry Pi 2 B+ と 3.2inch LCD

Chromium (Chrome の OSS版) を Raspberry Pi で Kiosk モード (全画面単一アプリケーションモード) で起動して、UI を作れると便利そうなのでやってみた。たとえば動画のように、3.2inch LCD 上のUIを Web 技術で完結させることができる。

Chromium を kiosk で自動起動する

raspi-config で起動モードを Desktop (autologin) に設定する。

sudo raspi-config

マウスカーソルを自動で消すやつをいれる。

$ sudo apt-get install unclutter

不正終了で残るファイルを reboot 時に消しておく。

$ crontab -e
@reboot rm -rf ~/.config/chromium/Singleton*

Raspberry Pi は Desktop の場合 LXDE が使われる。これの起動時に必要なものを自動起動するように設定する。

chromium-browser の引数は重要。とりあえずこのあたりを指定しておくと良い。

$ vi ~/.config/lxsession/LXDE-pi/autostart
#@lxpanel --profile LXDE-pi
#@pcmanfm --desktop --profile LXDE-pi
#@xscreensaver -no-splash
#@point-rpi

@xset s off
@xset -dpms
@xset s noblank
@unclutter
@chromium-browser --remote-debugging-port=9222 --no-default-browser-check --noerrdialogs --kiosk --incognito https://example.com

Chrome を remote-debugging

    • remote-debugging-port をつけているので remote debugging が有効なのだが、おそらくループバックインターフェイスしか受け付けてないのでLAN内の他のコンピュータからは直接アクセスできなかった。このため他のコンピュータから Raspberry Pi へログインするときにポートフォワードする。
ssh -L 9223:127.0.0.1:9222 pi@192.168.0.23 

これで、ログインしたコンピュータで http://localhost:9223/ するとデバッグコンソールを出せる (タブ一覧が出るので選択すれば良い)。JS が実行できるので、raspi 上で操作しなくとも特定 URL に移動したり、リロードしたりできる。もちろんエレメントインスペクタも使うことができる。

  1. トップ
  2. tech
  3. Raspberry Pi で Chromium を起動してリモート操作

珍しく直撃コースだったので、気圧変化がなかなか面白かった。上のグラフは自宅でとっている気圧計 (BME280) のデータを prometheus 経由で grafana に表示したもの。

最低気圧 973hPa となっていて、当時の台風のほぼ中心気圧まで下がった。

オレンジの線は8時間の標準偏差で、気圧変動の激しさの目安に出しているつもり。普通は2もいかない。

P&G のプロモーションの「企業にあわせて自分を偽ったことがある」かってやつ面白いというか思うところがあった。

面接とかってなんとか良く見せなければならないという固定観念があって、なんでそう思っていたかはわからないけど、とにかくそういう気持ちがあって気が重かった。自分の場合は恵まれていて、最初ITでバイトしようと思ったとき、紹介してくれた人に面接で何言えばいいかわからないと話したら「金が欲しいんだったら金が欲しいと言えばいい」と答えていて、なるほどと思ってその通り面接で言ったのだった (それで採用された)。

これはそれまで複雑に考えすぎてたので、急にすっきりしたという感じがしたので印象に残っている。とにかく正直に話して、マッチングしないならそれでおしまいという単純な話に気付いたのがそれだったからだ。

ツールの使いかたで時間をとられるのは非常にもったいないので、おおむね最近のコードは Chrome をメインに「モダンブラウザで書ける新しい ECMAScript の新記法・いざとなればポリフィル可能なAPIはすべて使っても良い」が「それ以上のことはなし」というルール。専業でずっと同じプロダクトだけ触ってメンテできるならいいけど、いろんなことをやって半年後に戻ってきたら動かない・コンパイルできない・修正できない、というのは全くありえない話。

触る人間 (ユーザ) が決まっているプロジェクト、例えば管理ツールならこのぐらいでちょうど良いと思う。

依存するものが増えることと、それで得られるメリットは常に天秤にのっていると考えないといけない。当たり前だ。余計なことはしないのが一番良い。バカにされるとしても、複雑なものより簡単なもののほうが常に優れている。自分の理解力を誇示するために成果物を複雑にしようとするな。

Aliexpress で 3W 365nm のパワーUV LEDを買ってみた。ちなみに 3W と書いてあっても 3W じゃないことが多いので注意が必要。絶対最大定格 700mA Forward Voltage が 3.0〜4.0V。なので2.1W〜2.8W程度。放射束はスペック上 900mW と書いてある。

これを定電流降圧回路で600mAで光らせた。放熱器必須。アルミ基板なので熱を吸われてしまい半田がつけにくい。

蛍光させてみる

パスポート

桜のマークを視認する場合 365nm ぐらいで発光するとよくわかる。可視光が少ない紫外線でないと蛍光がわかりにくい。パスポートの場合、顔写真ページには顔写真そのものが蛍光印刷されていておもしろい。

千円札

印章はとてもわかりやすく蛍光する。裏側の印章は蛍光しない。「千円」の漢数字のところは非常にわかりにくいが蛍光している。またお札全体に帯がついていてこれも薄く蛍光している。蛍光インクは透明だがすこしテカテカしているので、実は蛍光しなくても光を反射させると普通にわかってしまう。

UVレジンの硬化時間

一瞬(0.5秒ぐらい)で流動性は完全になくなるが表面がべとつく。約3分程度で表面が完全硬化する感じ (ベトツキがなくなる)。

パワーLEDだと1灯でも照らす範囲が広いので、適当なリフレクタ ( 3Dプリントしたものにアルミ箔を貼ったもの) をつけるとなかなかフラットに照射できる。

UV計


マザーツール ポケットサイズ紫外線強度計 SP-82UV - マザーツール

マザーツール

4.0 / 5.0

UV計にも感度スペクトルがあるので、相対的な強さしかわからないが、OptSupply OSV1YL5111A (放射束が2〜4mW(@20mA)) と比べるとほぼ同じ距離で10倍程度違う。

なお太陽光の快晴時地表 UV 強度は 60W/m2 = 6,000μW/cm2 らしい。パワー UV LED 1灯でも今回測定した距離では2倍ほどの紫外線照度がある。

少し離して2灯でこんな感じ。これ以上近付けると測定器の計測限界を超えるみたい。

ちなみにこのUV計は国内ではマザーツールが扱っているがLutron のSP-82UVみたい。Lutron の校正証書みたいのがついてくる。

ref.

  1. トップ
  2. tech
  3. パワー UV LED を試す

nanopi-neo2_friendlycore-xenial_4.14.52_20180628.img.zip の場合

  • /etc/rc.local から lcd2usb_print、QtE-Demo/run.sh をコメントアウト
    • どっちもいらない
  • timedatectl set-timezone Asia/Tokyo
  • sudo apt-get -y install chrony

udev gpio rule

Nano Pi Neo 2 の提供 OS イメージだと /sys/gpio が root 保有になっており、gpio の操作に特権が必要になっている。これを Raspbian と同様に gpio group をつくり、グループに対してパーミッションを与えるようにする。

グループを追加する。

sudo groupadd gpio
sudo usermod -aG gpio pi

udev のルールを追加し、gpio に変更があった場合、所有権と権限を設定しなおすようにする。

$ sudo vim /etc/udev/rules.d/80-gpio.rules
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chown -R root:gpio /sys/devices/platform/soc/*pinctrl/gpio*'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chmod -R ug+rw /sys/devices/platform/soc/*pinctrl/gpio*'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chown -R root:gpio /sys/class/gpio'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chmod -R ug+rw /sys/class/gpio'"
sudo udevadm control --reload-rules

test をやると rule の実行状態がわかる。結果が成功したかどうかとかもこれでわかる。

sudo udevadm test --action=change /sys/devices/platform/soc/1c20800.pinctrl/gpiochip1/gpio/gpio0

trigger でもルール適用できるがあまり詳細なことはわからない。

sudo udevadm trigger --verbose --subsystem-match=gpio

NanoHat OLED (ソフトウェア側)

レポジトリは https://github.com/friendlyarm/NanoHatOLED OLED 自体は I2C 経由、スイッチは GPIO に接続されている。当然だが NanoHatOLED に付属の python スクリプトを用いなくとも OLED やボタンを自力でハンドリングすることは可能 (後述)

/etc/rc.local から /usr/local/bin/oled-start が起動される。

/usr/local/bin/oled-start は /root/NanoHatOLED に cd して ./NanoHatOLED を起動してる。

./NanoHatOLED はバイナリだが、やってることはスイッチの GPIO を監視して、子プロセスに対しそれぞれ gpio0=SIGUSR1 gpio2=SIGUSR2 gpio3=SIGALRM を発生させているだけ。正直なんで C でやってるのかよくわからない。

./NanoHatOLED は /root/NanoHatOLED/BakeBit/Software/Python && python bakebit_nanohat_oled.py を起動している。

NanoHat OLED (ハードウェア側)

I2C 接続。アドレスは 0x3c (0b0111100)。128x64 OLED コントローラは SSD1306 (互換)

datasheet http://wiki.friendlyarm.com/wiki/images/a/af/096-30-SPEC_QG-2864KLBEG01_VER_C.pdf https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

I2C のコネクタは 2.0mm ピッチ。Grove システムのやつと一緒っぽい。http://wiki.seeedstudio.com/Grove_System/ http://akizukidenshi.com/catalog/g/gC-12634/ PHコネクター? かと思ったが違う。PH コネクタはピッチは同じだが挿すことができない。

シリアルは 2.54mm ピッチ。ちゃんと調べてない。

NanoHat OLED を node.js で直接さわる

そこそこいい感じに書けた。

GPIO のボタンは Node.js の fs モジュールだけで GPIO の割込みを扱うには? | tech - 氾濫原 に書いた通りの方法で扱っている。EventEmitter の API にしてある。

OLED に対応する canvas (node-canvas) オブジェクトを保持し、更新があったかどうかを確認しつつ一定周期で再描画するような設計にした。これにより普通にブラウザでJSを書くのと同じようなモデルでコードを書くことができる。

  1. トップ
  2. tech
  3. NanoPi NEO2 でやること、NanoHat OLED のメモ書き

Neewer 調光可能な二色660 LEDビデオライト 耐久性のあるメタルフレーム、 Uブラケットと遮光板付き 3200-5600K、CRI96+ スタジオ撮影、YouTube、商品撮影、ビデオ撮影に適用 - NEEWER

NEEWER

4.0 / 5.0

NEEWER LED-NL660 というものをタイムセールで買ってみたので ColorMunki Photo + Argyll CMS で測定してみた。

 

WHITE と YELLOW というつまみがあり、2種類のLEDの光量が変更できるようになっている。色温度の調整はこれを微妙に動かして行う。

WHITE 全部

 Result is XYZ: 5747.510694 6065.655076 5528.248292, D50 Lab: 439.775063 -11.375823 -26.558254
 Ambient = 6065.7 Lux, CCT = 5545K (Duv 0.0049)
 Suggested EV @ ISO100 for 6065.7 Lux incident light = 11.2
 Closest Planckian temperature = 5376K (DE2K 6.5)
 Closest Daylight temperature  = 5492K (DE2K 2.3)
 Color Rendering Index (Ra) = 97.5 [ R9 = 85.5 ]
  R1  = 97.0  R2  = 98.3  R3  = 99.7  R4  = 98.1  R5  = 97.6  R6  = 96.7  R7  = 97.9
  R8  = 94.6  R9  = 85.5  R10 = 97.6  R11 = 97.7  R12 = 83.7  R13 = 97.5  R14 = 98.4
 Television Lighting Consistency Index 2012 (Qa) = 98.6

スペック上は5600Kまでと書いてあるが 5500K の LED っぽい。

YELLOW 全部

 Result is XYZ: 5563.515641 5243.322549 2120.666499, D50 Lab: 418.170369 60.679109 158.296845
 Ambient = 5243.3 Lux, CCT = 3126K (Duv 0.0016)
 Suggested EV @ ISO100 for 5243.3 Lux incident light = 11.0
 Closest Planckian temperature = 3079K (DE2K 3.0)
 Closest Daylight temperature  = 3095K (DE2K 0.9)
 Color Rendering Index (Ra) = 98.1 [ R9 = 88.9 ]
  R1  = 99.0  R2  = 99.3  R3  = 99.0  R4  = 98.8  R5  = 98.5  R6  = 98.1  R7  = 97.3
  R8  = 95.0  R9  = 88.9  R10 = 98.3  R11 = 98.5  R12 = 85.5  R13 = 99.2  R14 = 98.6
 Television Lighting Consistency Index 2012 (Qa) = 98.1

3100Kぐらいが下限

WHITE+YELLOW 全部

 Result is XYZ: 11395.644074 11395.813163 7722.945028, D50 Lab: 546.392814 29.623320 61.495553
 Ambient = 11395.8 Lux, CCT = 4173K (Duv 0.0006)
 Suggested EV @ ISO100 for 11395.8 Lux incident light = 12.2
 Closest Planckian temperature = 4158K (DE2K 0.9)
 Closest Daylight temperature  = 4232K (DE2K -3.6)
 Color Rendering Index (Ra) = 98.8 [ R9 = 96.1 ]
  R1  = 99.3  R2  = 99.6  R3  = 99.6  R4  = 99.6  R5  = 98.9  R6  = 97.3  R7  = 98.4
  R8  = 97.8  R9  = 96.1  R10 = 99.1  R11 = 97.1  R12 = 83.4  R13 = 99.2  R14 = 98.5
 Television Lighting Consistency Index 2012 (Qa) = 98.3

最も明るいが4200Kぐらいの中途半端な色温度になる。

5000K

5000K 程度にあわせたとき。(WHITE 全部+YELLOWが左水平とちょっとぐらい)

 Result is XYZ: 6934.349461 7181.415867 5994.476376, D50 Lab: 466.163889 1.000754 -3.284202
 Ambient = 7181.4 Lux, CCT = 5029K (Duv 0.0029)
 Suggested EV @ ISO100 for 7181.4 Lux incident light = 11.5
 Closest Planckian temperature = 4941K (DE2K 4.1)
 Closest Daylight temperature  = 5042K (DE2K -0.6)
 Color Rendering Index (Ra) = 98.2 [ R9 = 90.1 ]
  R1  = 98.4  R2  = 99.3  R3  = 99.5  R4  = 98.5  R5  = 99.0  R6  = 97.0  R7  = 97.9
  R8  = 96.0  R9  = 90.1  R10 = 99.3  R11 = 96.9  R12 = 85.9  R13 = 99.0  R14 = 98.7
 Television Lighting Consistency Index 2012 (Qa) = 98.6

Ra は全ての指定色でかなり高い値が出てる。R12(青)もそこそこ高い値が出ていて良い。

このときのスペクトル

以下のようにすれば動くことが確認できた。

select や poll を明示的に呼び出すことができないが、edge を設定しさえすれば fs.watch でイベントを受けとれるようだ。

また、seek() もなぜか存在してないが read() の引数に position というのがあり、これが seek() 相当になるようだ。

const fs_ = require('fs');
const fs = fs_.promises;
fs.watch = fs_.watch;

async function watchInterrupt(pin, func) {
	try {
		await fs.writeFile(`/sys/class/gpio/export`, `${pin}`);
	} catch (e) {
		if (e.code === 'EBUSY') {
			// ignore
		} else {
			throw e;
		}
	}
	await fs.writeFile(`/sys/class/gpio/gpio${pin}/direction`, 'in');
	await fs.writeFile(`/sys/class/gpio/gpio${pin}/edge`, 'both'); // falling raising both
	const fh = await fs.open(`/sys/class/gpio/gpio${pin}/value`, 'r');
	const buf = new Uint8Array(1);
	fh.read(buf, 0, 1, 0);
	const watcher = fs.watch(`/sys/class/gpio/gpio${pin}/value`, {}, (eventType, filename) => {
		if (eventType === "change") {
			fh.read(buf, 0, 1, 0);
			const val = buf[0]-48;
			func(val, pin);
		} else {
			// XXX
			console.log(`unchecked event "${eventType}" occured with "${filename}"`);
		}
	});
	return {
		close: async () => {
			watcher.close();
			fh.close();
			await fs.writeFile(`/sys/class/gpio/gpio${pin}/edge`, 'none');
		}
	};
}

(async () => {
        watchInterrupt(0, (val, pin) => {
                console.log(`gpio${pin} changed -> ${val}`);
        });
        watchInterrupt(2, (val, pin) => {
                console.log(`gpio${pin} changed -> ${val}`);
        });
        watchInterrupt(3, (val, pin) => {
                console.log(`gpio${pin} changed -> ${val}`);
        });
})();
  1. トップ
  2. tech
  3. Node.js の fs モジュールだけで GPIO の割込みを扱うには?

画面外になる部分が partial update でもチラチラ更新されてしまうので、これを隠すためにフレームをつくってつけた。

https://www.thingiverse.com/thing:3102072

  1. トップ
  2. tech
  3. WaveShare 4.2inch E-paper 用のフレーム

やってること

  • prometheus の API を叩く
  • 表示する

だけ。 表示部分はせっかく python の骨格があるので基本的にはそのまま python で書いてる。けど Pillow (python の画像処理ライブラリ) はそれほど高機能というわけではなくて、文字表示時に細かいオプションが一切指定できなかったりするので Ruby と Cairo とか慣れた他の方法でやったほうがよかったかも。

フォントは Noto Sans CJK の Black。Pillow が OTF をちゃんと読めてよかった。

表示はフルリフレッシュを避けるように数値部分だけを部分書き換えしている。これにより表示更新が早くなるのと、フルリフレッシュ時みたいな全体が反転するチラツキがなくなるので常時していても邪魔くさく感じにくいはず。

部分書き換え (partial update)

↑ 試している様子 (値はランダム)。起動時はフルリフレッシュしてその後は常に部分書き換え

Ben Krasnow: Fast partial refresh on 4.2" E-paper display from Waveshare / Good Display の記事を参考に LUT をコピペしたらできたが LUT が何をしてるのかさっぱりわからない。

E-paper display の覚え書き (LUT ってなんだ) | tech - 氾濫原 LUT について追記

コード

https://github.com/cho45/electronic_badge_2018/blob/partialupdate/sketch/sketch.py

lib/epd4in2.py とかにもかなり変更を入れてる。LUT まわりが↑のエントリの epd4in2.cpp からのコピペなのでライセンス的に微妙。

  1. トップ
  2. tech
  3. builderscon の電子名札に家庭のメトリクス表示

Waveshare 4.2 Black/White EPD

全体的な動作の説明がスペックに書いてないので正確にわかることがあんまりない。

What is LUT?

Look-up Table。EPD 書き換えのときに、各フェーズでどのようにしてピクセルに電圧をかけるかを決めると思われる。

W2W / B2W / W2B / B2B LUT

6バイトでワンセット、7回繰替えされる。42バイト(コマンドバイトを除く)。各状態遷移のときにパネルにどういう電圧のかけかたをするかを規定する。

以下のようなフルリフレッシュ用 LUT だと

    lut_wb = [
        0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
        0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
        0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
        0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]

以下のような構造になる。

LUT_WB
lv: VDL GND GND GND
fr:  23   0   0   0 repeat: 2
lv: VDL VDH GND GND
fr:  23  23   0   0 repeat: 2
lv: VDL GND GND GND
fr:  10   1   0   0 repeat: 1
lv: VDH VDH GND GND
fr:  14  14   0   0 repeat: 2
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0

たぶん level select で指定した電圧を frame 数ぶんかける、そしてそれを repeat 数回行う、ということだと思う。違うかもしれない。VDH をかけると黒に、VDL にすると白になる。

VCOM LUT

VCOM とは? 名前からするに各ピクセルの共通側電圧の設定。他の LUT とあわせて共通側電圧をどうするかを設定すると思われる。基本的に他の LUT の frame、repeat 数とあわせて全状態で GND にしとけばいいっぽい。

6バイトでワンセット、7回繰替えされる。最後に2バイトつくので44バイト(コマンドバイトを除く)

VCOM
lv: GND GND GND GND
fr:  23   0   0   0 repeat: 2
lv: GND GND GND GND
fr:  23  23   0   0 repeat: 2
lv: GND GND GND GND
fr:  10   1   0   0 repeat: 1
lv: GND GND GND GND
fr:  14  14   0   0 repeat: 2
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0
lv: GND GND GND GND
fr:   0   0   0   0 repeat: 0

DTM1 DTM2

B/W (白黒) モードの場合、DTM1 は "OLD" data を SRAM に転送する。DTM2 は "NEW" data を SRAM に転送する。

よくわからない。どっちも転送しておかないと、電圧かけたときに古いデータがでてきたりして謎。

What is OTP?

仕様書のところどころで出てくる OTP って何?という話。

One Time Programming 1度だけ書きこみ可能なこと。E-paper の場合 LUT を OTP して固定することができる、ということらしい。とりあえず無視して良い。

  1. トップ
  2. tech
  3. E-paper display の覚え書き (LUT ってなんだ)

mbed USBDevice ライブラリの中に USB CDC で動く USBSerial クラスが実装されている。これを Web USB から扱ってみる。これができるとブラウザ上から直接デバイスの設定などを行えるので便利になる。

LPC11U35 でも動かすことができる。

mbed USBSerial の構成

以下のような interface になっている。

CDC class (interface count = 2)
interface 0  (endpoint count = 1)
  CDC Header Functional Descriptor
  Call Management Functional Descriptor
  Abstract Control Management Functional Descriptor
  Union Functional Descriptor
  Endpoint Descriptor
    INTERRUPT IN (interval = 16)
interface 1 (endpoint count = 2)
  Endpoint Descriptor
    BULK IN (endpoint num = 2)
  Endpoint Descriptor
    BULK OUT  (endpoint num = 2)

mbed側の実装

シリアル通信まわりだけだと以下のようになる

#include "mbed.h"
#include "CircBuffer.h"
#include "stdint.h"
#include "USBCDC.h"

class MyUSBSerial: public USBCDC {
	private:
		Callback<void()> rx;
		CircBuffer<uint8_t,128> buf;

	public:
		MyUSBSerial(uint16_t vendor_id = 0x1f00, uint16_t product_id = 0x2012, uint16_t product_release = 0x0001, bool connect_blocking = true):
			USBCDC(vendor_id, product_id, product_release, connect_blocking){
			};

		bool writeBlock(void* b, uint16_t size) {
			uint8_t* buf = reinterpret_cast<uint8_t*>(b);
			if (!send(buf, size)) {
				return false;
			}
			return true;
		}

		uint8_t available() {
			return buf.available();
		}

		void read(uint16_t len, uint8_t out[]) {
			while (available() < len);
			for (uint16_t i = 0; i < len; i++) {
				buf.dequeue(&out[i]);
			}
		}

		uint8_t read() {
			uint8_t c = 0;
			while (available() < 1);
			buf.dequeue(&c);
			return c;
		}

		bool connected() const {
			return terminal_connected;
		}


	protected:
		virtual bool EPBULK_OUT_callback() {
			uint8_t c[MAX_PACKET_SIZE_EPBULK+1];
			uint32_t size = 0;

			//we read the packet received and put it on the circular buffer
			readEP(c, &size);
			for (uint32_t i = 0; i < size; i++) {
				buf.queue(c[i]);
			}

			//call a potential handlenr
			if (rx)
				rx.call();

			return true;
		}

		virtual void lineCodingChanged(int baud, int bits, int parity, int stop){
			// nothing to do
		}
};

MyUSBSerial usbserial(0xf055, 0xf001);

int main(void) {
    while (1) ;
}

Web USB からの接続

こちらもシリアル通信にあたる部分だけ。

  • interface index = 1
  • endpoint index = 2

を指定して transferIn/transferOut をすれば良い。DTR などを設定するために controlTransferOut も使うが、ただデバイスとやりとりするだけなら特に必要はない。

以下のような感じ。

const CDC_SET_LINE_CODING        = 0x20;
const CDC_GET_LINE_CODING        = 0x21;
const CDC_SET_CONTROL_LINE_STATE = 0x22;
const CLS_DTR  = (1 << 0);
const CLS_RTS  = (1 << 1);
const INTERFACE_INDEX = 1;
const ENDPOINT_INDEX = 2;

class USBCDC {
	constructor() {
		this.onread = () => {};
	}

	async connect(filters) {
		this.device = await navigator.usb.requestDevice({
			filters: filters || [
				{ 'vendorId': 0xf055, 'productId': 0xf001 }
			]
		});
		console.log(this.device);

		const read = async () => {
			const result = await this.device.transferIn(ENDPOINT_INDEX, 64);
			this.onread(result.data.buffer, result);

			if (result.status === 'stall') {
				console.warn('Endpoint stalled. Clearing.');
				await this.device.clearHalt(1);
			}
			read();
		};

		const configuration = await this.device.open();
		if (this.device.configuration === null) {
			await this.device.selectConfiguration(1);
		}

		await this.device.claimInterface(INTERFACE_INDEX);
		await this.device.controlTransferOut({
			'requestType': 'class',
			'recipient': 'interface',
			'request': CDC_SET_CONTROL_LINE_STATE,
			'value': CLS_DTR,
			'index': INTERFACE_INDEX
		});
		read();
	}

	async write(data) {
		if (typeof data === "string") {
			// string to utf-8 arraybuffer
			const arraybuffer = await new Promise( (resolve, reject) => {
				const reader = new FileReader();
				reader.onloadend = function () {
					resolve(reader.result);
				};
				reader.readAsArrayBuffer(new Blob([data]));
			});
			console.log('write ab', arraybuffer);
			await this.device.transferOut(ENDPOINT_INDEX, arraybuffer);
		} else {
			console.log('write rr', data);
			await this.device.transferOut(ENDPOINT_INDEX, data);
		}
	}

	async close() {
		await this.device.controlTransferOut({
			'requestType': 'class',
			'recipient': 'interface',
			'request': CDC_SET_CONTROL_LINE_STATE,
			'value': 0x00, // reset DTR
			'index': INTERFACE_INDEX
		});
		await this.device.close();
	}
}

const usbcdc = new USBCDC();
btnConnect.onclick = async () => {
	usbcdc.onread = (buffer, result) => {
		console.log(result);
		console.log(String.fromCharCode.apply("", new Uint8Array(buffer)));
	};
	usbcdc.connect();
};
  1. トップ
  2. tech
  3. mbed USBSerial を WebUSB から扱うには


だいぶ前に買ったのを放置してたので動かした。なぜかSDカードコネクタとかついてる。最初は 5V インターフェイスでレギュレータを使うようになっているが、ジャンパで 3.3V にすることができる。

「Adafruit_ST7735」という名前のライブラリがいくらか公開されているので、それを参考にちょっと書きかえた。ST7735 シリーズは細かい違いがたくさんあるようでなかなか難しい。

このライブラリは setRotation するときに MADCTL を発行するが、RGB と BGR の選択もコマンドで設定を行うため、setRotation をすると色が反転するみたいなことが起きて困った。

基本的には setAddrWindow して更新範囲を指定して pushColor して更新する。

	tft.setAddrWindow(offsetx, offsety, offsetx + width - 1, offsety + height - 1);
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			usbserial.read(2, buf);
			uint16_t c = (buf[0] << 8) | buf[1];
			tft.pushColor(c);
		}
	}
  1. トップ
  2. tech
  3. ST7735S 使用の 128x160 TFT SPI 液晶

Rock64 http://akizukidenshi.com/catalog/g/gM-12382/ というのを買ってみた。

とりあえず minimal Ubuntu をいれた。

ssh は最初から有効。rock64:rock64 で入れる。

CPU温度などを連続でみる

sudo /usr/local/sbin/rock64_diagnostics.sh -m

便利ツールが最初からある。

そこそこ大きめのヒートシンクをつけてもCPUにフルロードかけると78℃近くまでいく。たぶんヒートシンク必須。だけどCPUクロックに制限かかるまではいかない。というかクロック制限はあるのだろうか?

ちなみにアイドル時にはクロックダウンする。

IR Reciever

GPIO2_A2/IR_RX/POWERSTATE2_u M21 に IR Receiver が繋っているが使いかたがよくわからない。RK3328 自体で NEC フォーマットの IR コマンドをサポートしているみたいだが Linux から利用するドライバがよくわからない。

まぁ GPIO 機能もあるので、こっちで読み出しをしてみることにしてみた。


GPIO2_A2 (2_A2 の部分を変換して pin 番号を求める) は 2 * 32 + ("A".charCodeAt(0)-65) * 8 + 2 = 66

root@rock64:/home/rock64# echo 66 > /sys/class/gpio/export

試しに IRKit に使えるフォーマットで受信データを表示するようにしてみた。IRKit は 2MHz sample のカウント数で表示されるので変換している。

//#!/usr/bin/env go run
package main

import (
	"fmt"
	"time"

	"github.com/brian-armstrong/gpio"
)

type Event struct {
	th int64
	tl int64
}

func main() {
	watcher := gpio.NewWatcher()
	watcher.AddPin(66)
	defer watcher.Close()

	events := make(chan *Event)

	go func() {
		prev := time.Now()
		var th int64 = 0
		var tl int64 = 0
		for {
			now := time.Now()
			delta := now.Sub(prev).Nanoseconds()
			prev = now
			_, value := watcher.Watch()
			switch value {
			case 1:
				tl = delta
				events <- &Event{th, tl}
			case 0:
				th = delta
			default:
			}
		}
	}()

	for {
		e := <-events
		fmt.Printf("%d %d\n", e.th/5.0e2, e.tl/5.0e2)
	}
}

IR 受信にはsys/gpio だと速度がちょっと足りないですね。

ref

  1. トップ
  2. tech
  3. Rock64 を買って golang で赤外線受信を試してみた

さくらのVPSはIPv4/IPv6デュアルスタックで、双方ともにグローバルアドレスがついているため、IPv4 しかない環境から IPv6 アドレスがついたホストへ SSH する際の踏み台に使うことができる。

  • ProxyCommand でホスト名をクオートすること
    • IPv6 のブラケットがシェルで解釈されないように
Host raspberrypi
User pi
Hostname 2001:db8::40c0
ProxyCommand ssh user@vpshost -W '[%h]:%p'

自宅のIPv6環境でアクセスする場合は、ルーターなどのファイアーウォールの設定が必要。家庭用ルーターはIPv6でも無防備にならないように、デフォルトでほとんどのインカミングパケットを破棄するので、特定ホスト・ポートへのアクセスを許可する設定が別途必要となる。

  1. トップ
  2. tech
  3. さくらのVPSを踏み台にして IPv6 アドレスがついているホストへ ssh