5年ごとの更新日がきたので旧免許状を返納した。新免許状は4月ぐらいに届いてたのでやることは返納のみ。しかし返納先がいつもいつも謎。

新免許に返納先の案内を入れてくれればいいのに。

もしかして返納しなくてもいいんだっけ??ってぐらいの雑さだけど、30日以内に返納しないと30万円以下の過料が課される。なんやねん……

  1. トップ
  2. tech
  3. アマチュア無線 無線局免許更新

↑ に一応資料をあげときます。めっちゃ酔っぱらってこのエントリを書いております。

mbed 環境は Arduino の次あるいはプログラマなら最初の一歩として本当にお勧めです。若干価格が高いのが玉に傷ではありますが、LPC11U35 Quickstart Board は比較的導入しやすく、mbed 入門・USB デバイス入門に最高だと思っています。ぜひ mbed 環境を試してみてほしいと思います。

もともとキーボードに使っていた BLE Nano も mbed です。これも BLE デバイスとしては非常に安価な部類だと思います。僕は今のところ挫折していますが、ぜひ安定したBLEデバイスを作ってノウハウを教えてください……

builderscon は本当に良くて、今回はスピーカーディナー(スピーカーといろんな人同士の交流)という新しい試みも大変楽しかったです。前夜祭はやっぱりオフレコの闇の話が最高に楽しいですね。

builderscon はリアルイベントなのにインターネットの夜明けのような希望があります。こういうイベントは本当に貴重で、運営のかたには頭が上がりません。まだ builderscon はこれからですが楽しみたい!!

  1. トップ
  2. tech
  3. builderscon tokyo 2018 前夜祭「自作 BLE キーボードのその後」で発表しました

まず BME280 と BMP280 というよく似たやつがあるので注意…。 BMP は気圧と気温しか測れない。

初期設定

リセット直後の BME280 は圧力・温度・湿度すべての測定がスキップかつスリープモードになっているので、まず測定開始するように設定する必要がある。いくつかモードがあるが、とりあえず NORMAL モードにして定期的に値を更新するようにしとけば良い。

なお用途に応じて最適と思われる設定がデータシートで示されているので、それに従えば良い。

FORCED モード (ワンショットモード) で測定する場合、9. Appendix B: Measurement time and current calculation を参照して tmeasure を求める必要がある。FORCED モードは MODE に書きこむと測定が始まり、終わると SLEEP モードになるという挙動をする。

Raspberry Piで動作確認する場合

ググるとほかにも実装が出てくるが、まずはリファレンス実装で試すのが筋。

https://github.com/BoschSensortec/BME280_driver

メーカーがリファレンス実装をつくってる。

$ git clone git@github.com:BoschSensortec/BME280_driver.git
$ cd BME280_driver
$ gcc -DBME280_FLOAT_ENABLE -I. -o foo examples/linux_userspace.c bme280.c
$ ./foo /dev/i2c-1 
Temperature, Pressure, Humidity
temp 31.10, p 100605.10, hum 48.97
temp 31.10, p 100605.08, hum 49.01
temp 31.11, p 100605.11, hum 48.97
temp 31.12, p 100605.34, hum 48.98
temp 31.13, p 100605.46, hum 49.02

Rubyでの実装

https://github.com/cho45/ruby-i2c-devices/blob/master/lib/i2c/device/bme280.rb

に書いた。calibration データの扱いに地味にハマってなかなかうまくいかなかった。

  1. トップ
  2. tech
  3. 気圧・温度・湿度 I2C センサー BME280

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

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 液晶

やってること

  • 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 ってなんだ)

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

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 の割込みを扱うには?

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(青)もそこそこ高い値が出ていて良い。

このときのスペクトル

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 のメモ書き

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 を試す