画面外になる部分が partial update でもチラチラ更新されてしまうので、これを隠すためにフレームをつくってつけた。
builderscon の電子名札に家庭のメトリクス表示
やってること
- 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 が何をしてるのかさっぱりわからない。
コード
https://github.com/cho45/electronic_badge_2018/blob/partialupdate/sketch/sketch.py
lib/epd4in2.py とかにもかなり変更を入れてる。LUT まわりが↑のエントリの epd4in2.cpp からのコピペなのでライセンス的に微妙。
E-paper display の覚え書き (LUT ってなんだ)
Waveshare 4.2 Black/White EPD
- https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf
- LUT の仕様は書いてない
- https://www.electrodragon.com/w/images/b/b4/IL0373.pdf
- LUT の仕様がちょっと書いてある
全体的な動作の説明がスペックに書いてないので正確にわかることがあんまりない。
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 して固定することができる、ということらしい。とりあえず無視して良い。
このエントリを参照するエントリ
mbed USBSerial を WebUSB から扱うには
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();
};
ST7735S 使用の 128x160 TFT SPI 液晶
だいぶ前に買ったのを放置してたので動かした。なぜか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); } }