ただのちっちゃい Linux だと思っていたけど、RTOS が入っているコプロセッサも持っていて、ホストCPUとシームレスに連携できるということがわかった。

アーキテクチャを見ると思いのほか面白かった。

https://software.intel.com/en-us/creating-applications-with-mcu-sdk-for-intel-edison-board

ホストCPU上のLinuxからは、GPIOが sysfs 経由 (または Intel の提供するSDKなど) から見える。それと同時にコプロセッサの MCU からは直接 GPIO が見える。

ホストCPUのLinuxはリアルタイムではないので、最低でも10ms程度(カーネルの割込み頻度の設定による)、処理が遅延する。GPIO を正確にコントロールする場合この遅延は大きすぎる。

一方コプロセッサのMCUはリアルタイムになっており、クロック100MHzなので、最速では 20ns ぐらいの単位でコントロールできる。

Edison はこれらをうまく協調して動かせるように設計されているみたい。MCU 側へファームウェアを書きこんで /dev/ttymcu* で連携したりとか、ホストCPUをスリープさせてMCUだけで動かしつつホストCPUをいい感じのタイミングで起こせるとか。

おもしろい!!

Intel ボードコンピューター Intel Edison Kit for Breakout Board(MM#939977) EDI2BB.AL.K - インテル

インテル

3.0 / 5.0

なお未だ買ってない。

  1. トップ
  2. tech
  3. Intel Edison のおもしろいところ

アナログ回路への理解を深めたい。まだ全然、やりたいことをすぐに実現できるレベルにならない。どうすればできるのかわからない、ということが多すぎる。

2015年もたくさんコードかきました。他人に承認されないことはせめて自分で承認しましょう。つらい

Chemr

[tech] リファレンスマニュアルをインクリメンタル検索するやつを Electron で実装した | Wed, Oct 14. 2015 - 氾濫原

ドキュメントビューワ。Mac AppStore まで出してみたが全く売れておりません。自分では便利に使ってる

Arduino-meta

[tech] Arduino の digitalWrite / pinMode / digitalRead をコンパイル時に展開する | Wed, Dec 16. 2015 - 氾濫原

digitalWrite とかを静的に解決するやつ。

go-KX3-panadapter

[tech] もう僕らは OpenGL ライブラリにリンクするビルドに悩むことはない | Thu, Oct 8. 2015 - 氾濫原

KX3(無線機)用のパナダプター(Panoramic Adapter = FFTウォーターフォール) WebGL に書きなおしたりした。便利

GrblServer

[tech] CNC フライス Grbl 制御編 | Sun, Aug 16. 2015 - 氾濫原

CNCフライス制御用のアプリケーション。最近あんまり切削してない。

KeyCast

[tech] スクリーンキャスト用のキーストローク表示アプリ | Sat, Feb 14. 2015 - 氾濫原

入力したキーを画面に表示するやつ。

CopyHook

[tech] CopyHook というペーストボードの中身をいじるツールを作った | Wed, Mar 18. 2015 - 氾濫原

クリップボードコピーしたときにフックでJS実行するやつ。なんかうまく動いてない気がするがデバッグの時間がとれない。

reedsolomon.js

[tech] JavaScript で書かれたリードソロモン符号のエンコーダ・デコーダ | Tue, Mar 31. 2015 - 氾濫原

任意のリードソロモン(誤り訂正)符号のエンコーダデコーダ。Zxing (Java) の一部ライブラリの移植

webaudio-filter-frequency-response

[tech] WebAudio の BiquadFilterNode の周波数特性をグラフにするやつ | Fri, Mar 20. 2015 - 氾濫原

WebAudio のフィルタの周波数特性を出すやつ。

dekaimoji-a4

[tech] デカい文字をA4で分割して印刷するツールをJSで書いた | Sat, Mar 7. 2015 - 氾濫原

https://cho45.stfuawsc.com/dekaimoji-a4/

A4プリンタでデカい文字が印刷できるやつ。

  1. トップ
  2. tech
  3. 2015年つくったもの

ビットフィールドとは

C/C++にはほとんど使われてないがビットフィールドという機能がある。

union {
	uint8_t raw;
	struct {
		unsigned FAULT_QUEUE      : 2;
		unsigned CT_PIN_POLARITY  : 1;
		unsigned INT_PIN_POLARITY : 1;
		unsigned INT_CT_MODE      : 1;
		unsigned OPERATION_MODE   : 2;
		unsigned RESOLUTION       : 1;
	};
} config;

このように書ける。struct 内で名前の後ろについているのが、そのフィールドで消費するビット数で、この場合合計で8bitになり、それを uint8_t と共用している。

こうすると config.OPERATION_MODE = 2; などと、マスクやシフトを伴わずに直接書けて、結果をconfig.rawでとれる。

めっちゃ便利なので使わない手はなさそうだと思いきや、実際のところ実用するのは不安がある。というのも、この struct 内のビット配置の順序は実装依存となっていて、uint8_t として評価したとき、どのような結果が返ってくるか確かなことがいえない。

コンパイラ依存

再発明

そこで、上記のようなビットフィールドを以下のように書きなおす

template <class T, uint8_t s, uint8_t e = s>
struct bits {
	T ref;
	static constexpr T mask = (T)(~( (T)(~0) << (e - s + 1))) << s;
	void operator=(const T val) { ref = (ref & ~mask) | ((val & (mask >> s)) << s); }
	operator T() const { return (ref & mask) >> s; }
};

template <uint8_t s, uint8_t e = s>
using bits8 = bits<uint8_t, s, e>;


union {
	uint8_t raw = 0;
	bits8<0, 1> FAULT_QUEUE      ;
	bits8<2>    CT_PIN_POLARITY  ;
	bits8<3>    INT_PIN_POLARITY ;
	bits8<4>    INT_CT_MODE      ;
	bits8<5, 6> OPERATION_MODE   ;
	bits8<7>    RESOLUTION       ;
} config;

uint8_t 全体を明確に共用する複数のstructという形にし、明示的にビットシフトやマスクを行っている。それぞれ、テンプレートの第一引数〜第二引数のビットを扱うクラスになっている。

用途

組み込みで他のデジタルICとやりとりをする場合、だいたいデータシートには [0:1] foobar みたいな形でビット範囲と値の説明が書いてあるので、それをその通り書きうつして union を作れば間違いなくビット操作できる状態になる。

これで安心してビットフィールドっぽいものが使える。

生成バイナリ

試した限りだと完全にインライン化される。また、1bitだけ書く場合andかorだけにまで最適化される。

int main(void) {
	asm volatile ("nop");
	config.OPERATION_MODE = 0b11;
	asm volatile ("nop");
	config.RESOLUTION = 1;
	asm volatile ("nop");
	config.FAULT_QUEUE = 1;
	asm volatile ("nop");

	for (;;) {
	}

	return 0;
}


こういうコードは

000000a0 <main>:
  a0:	00 00       	nop
  a2:	00 00       	nop
  a4:	00 00       	nop
  a6:	80 91 00 01 	lds	r24, 0x0100
  aa:	8c 71       	andi	r24, 0x1C	; 28
  ac:	81 6e       	ori	r24, 0xE1	; 225
  ae:	80 93 00 01 	sts	0x0100, r24
  b2:	00 00       	nop
  b4:	ff cf       	rjmp	.-2      	; 0xb4 <main+0x14>

こうなる

ref. https://gist.github.com/cho45/397f834791bae67166e1

テスト

#include <cstdio>
#include <stdint.h>
#include <iostream>
template <class T, class U>
void is(T got, U expected) {
if (got == expected) {
std::cout << "ok" << std::endl;
} else {
std::cout << "not ok " << got << " != " << expected << std::endl;
}
}
template <class T, uint8_t s, uint8_t e = s>
struct bits {
T ref;
static constexpr T mask = (T)(~( (T)(~0) << (e - s + 1))) << s;
void operator=(const T val) { ref = (ref & ~mask) | ((val & (mask >> s)) << s); }
operator T() const { return (ref & mask) >> s; }
};
template <uint8_t s, uint8_t e = s>
using bits8 = bits<uint8_t, s, e>;
int main () {
union {
uint8_t raw = 0;
bits8<0, 1> FAULT_QUEUE ;
bits8<2> CT_PIN_POLARITY ;
bits8<3> INT_PIN_POLARITY ;
bits8<4> INT_CT_MODE ;
bits8<5, 6> OPERATION_MODE ;
bits8<7> RESOLUTION ;
} config;
config.OPERATION_MODE = 0b11;
is((uint)config.raw, 0b01100000);
config.FAULT_QUEUE = 0b10;
is((uint)config.raw, 0b01100010);
config.RESOLUTION = 1;
is((uint)config.raw, 0b11100010);
config.OPERATION_MODE = 0;
is((uint)config.raw, 0b10000010);
config.raw = 0;
is((uint)config.OPERATION_MODE, 0b00);
config.raw = 0b01000000;
is((uint)config.OPERATION_MODE, 0b10);
config.FAULT_QUEUE = 0b111;
is((uint)config.raw, 0b01000011);
return 0;
}

  1. トップ
  2. tech
  3. C++ でビットフィールドを再発明する

I2Cセンサーとかを扱うと固定小数点表現によく出会う。が、固定小数点のままだと計算がめんどうなので、とりあえず浮動小数点に変換しときたいというケースがまぁまぁある。

そういうときに雑に使えるスニペットがほしかったので書いた。

#include <type_traits>
template <uint8_t int_bits, uint8_t fractional_bits, class T>
inline float fixed_point_to_float(const T fixed) {
	static_assert(std::is_unsigned<T>::value, "argument must be unsigned");
	constexpr uint8_t msb = int_bits + fractional_bits - 1;
	constexpr T mask = static_cast<T>(~(( static_cast<T>(~0)) << msb));
	constexpr float deno = 1<<fractional_bits;
	if (fixed & (1<<msb)) {
		// negative
		return -( ( (~fixed & mask) + 1) / deno);
	} else {
		// positive
		return fixed / deno;
	}
}

type_traits がない環境の場合、include と static_assert を消すだけで動く。これはエラーチェックにしか使ってなくて、もし消したとしても、負の signed を渡すと左シフトが不正になるのでエラーになる。

センサ出力とかの場合、8bit単位のビット数ではないことが多いので、渡された型のサイズに関わらずに処理できるようにマスクを作っている。

// usage
int main (int argc, char* argv[]) {
	// from ADT7410 datasheet
	is(fixed_point_to_float<9, 4>((uint16_t)0b0000000000001), 0.0625);
	is(fixed_point_to_float<9, 4>((uint16_t)0b0100101100000), 150.0);
	is(fixed_point_to_float<9, 4>((uint16_t)0b0000000000000), 0);
	is(fixed_point_to_float<9, 4>((uint16_t)0b1110010010000), -55.0);
	is(fixed_point_to_float<9, 4>((uint16_t)0b1111111111111), -0.0625);
	is(fixed_point_to_float<9, 4>((uint32_t)0b1111111111111), -0.0625 );
	/** compile error : argument must be unsigned
	printf("%f\n", fixed_point_to_float<9, 4>((int16_t)0b1111111111111));
	*/

	// from MCP3425 datasheet
	is(fixed_point_to_float<1, 11>((uint16_t)0x001) * (2.048), 1e-3);
	is(fixed_point_to_float<1, 13>((uint16_t)0x001) * (2.048), 250e-6);
	is(fixed_point_to_float<1, 15>((uint16_t)0x001) * (2.048), 62.5e-6);

	// from MPL115A2 datasheet
	// a0 coefficient
	is(fixed_point_to_float<13, 3>((uint16_t)0x3ECE), 2009.75);
	// b1 coefficient
	is(fixed_point_to_float<3, 13>((uint16_t)0xB3F9), -2.37585);
	// b2 coefficient
	is(fixed_point_to_float<2, 14>((uint16_t)0xC517), -0.92047);
	// c12 coefficient
	is(fixed_point_to_float<1, 15>((uint16_t)0x33C8)/(1<<9), 0.000790);

	// test dynamic variable
	volatile uint16_t x = 0x001;
	is(fixed_point_to_float<1, 11>(x) * (2.048), 1e-3);
}

テンプレートの第1引数は整数部(符号込み)のビット数・第2引数は小数点分のビット数

これはQ表記に対応する。

Q表記だと Q1.15 だと符号分1・整数部なし・15ビットの小数点桁。Q9.4 だと符号付き整数部8bit、小数部4bit。

メモ

固定小数点数用のクラス作って可能な限りは固定小数点で演算したほうがいい気はする。ヘッダ1ファイルとかで使えるの、当然もうありそうだけど見つけられてない。

  1. トップ
  2. tech
  3. 任意固定小数点→浮動小数点変換スニペット

モバイバッテリーは低電流時、充電完了と判断してパワーオフする(出力回路の動作をやめる)が、これをやらせたくない場合どうすればいいか。現時点でのメモ

Arduino に適当なプログラムを書きこんで、ポートに抵抗を繋ぎ(複数ポートにわけて) パワーオフするかどうかを調べた。測定時は電源供給経路途中に1Ωの抵抗をいれ、オシロでこの抵抗の両端電圧を測ることで間接的に実測の電流値を求めている。

Arduino のベースの消費電力は40mA程度。なので以下でさらに大きな電流を流しているが、40mA との切り替えということになる。

Anker

Anker は製品説明書に最低限充電電流が書いてある (50mA)
しかし連続で流し続けなければならないのか、パルスでいいのかはわからない。

A1208

 -

3.0 / 5.0

検知できないと30秒ぐらいでパワーオフする

  • 50mA 50ms / 1s → ダメ
  • 100mA 50ms / 1s → ダメ
  • 100mA 100ms / 1s → ダメ
  • 100mA 100ms / 10ms → ok
  • 100mA 100ms / 100ms → ok

すくなくとも短いパルスではリセットできないっぽい

  • 100mA 1s / 5s → ok
  • 50mA 1s / 5s → ng
  • 60mA 1s / 5s → ok
  • 60mA 1s / 10s → ok
  • 60mA 1s / 30s → ok
  • 60mA 500ms / 30s → ok
  • 60mA 250ms / 30s → ng

一定時間内の平均消費をみている?

Aukey

PB-T1

 -

3.0 / 5.0

説明書には特にオートパワーオフの閾値の記載なし。

約3分後にパワーオフ。電流を検知している間はバッテリーランプが点灯するっぽい?

  • 60mA 250ms / 30s → ng
  • 100mA 250ms / 30s → ng
  • 100mA 50ms / 1s → ok
  • 100mA 50ms / 10s → ng
  • 100mA 50ms / 5s → ng
  • 100mA 50ms / 2.5s → ok

ランプが5秒ぐらいで消灯するが、その前にパルスを検知すればいいっぽい… 謎

その他

100mA ぐらい常時流しといたらいいんちゃう?

→ 5V 100mA (0.5W)

3.3V 10000mAh のやつは26Whぐらいなので、とても厳しいという状態でなければそれでもいいかもしれない。

Quick Charge バッテリの場合、電流値で検出しているのか電力値で検出しているのかで大きくわかれる。12V 100mA 流すことになったら常時 1.2W 消費ということになりつらい。

  1. トップ
  2. tech
  3. 今夜は寝かさないぞモバイルバッテリー

Quick Charge 2.0 電源から 9V をとる (任意の電圧をとる) | tech - 氾濫原 で、だいたいこれで良さそうと思ったので ATTiny13A 使ってユニバーサル基板にまとめた。

できたもの

風景

回路図

3端子レギュレータの S-812C33AY は安い (12.5円/個) から以外の意味はとくにない。ピン配置が GND IN OUT と 78* 系と違うので注意

Tiny13A は8pinなのでIOが最大で6pinしかない。そしてRESETをIOにするとISP書きこみができなくなるので、ISPを使うなら、実質5ピンのIOになる。

ファームウェアなど

レポジトリ https://github.com/cho45/QCdirect

コードは Arduino 版とほぼ同じだが、AVR ネイティブで書きなおしてある。

main.cpp https://github.com/cho45/QCdirect/blob/master/firmware/main.cpp

ボードレイアウト例

ISP ピンヘッダの位置でいつも困る。AVRISP mkII の直で繋げられるようにしようとすると、意外と干渉するので、ピンヘッダまわりに十分スペースが必要

  1. トップ
  2. tech
  3. Quick Charge 2.0 から 9V/12V を出力させるデバイス (ATTiny13A)

esptool が以下で死ぬ。

warning: espcomm_send_command: didn't receive command response
warning: espcomm_send_command(FLASH_DOWNLOAD_DATA) failed
warning: espcomm_send_command: wrong direction/command: 0x01 0x03, expected 0x01 0x04

シリアルダウンロードのモードにはなっているがうまくいかなかった。

解決方法

書きこみに使う USB シリアル変換を別のもの(FT234X使用のもの)にしたらうまくいった。

使えなかったのはFT4232Hを使ったもので、このチップは4chを1つのUSBポートで通信できるものだが、どのチャンネルを使ってもダメだった。

ドライババージョンは現時点で最新

$  kextstat  | grep FTDI
   86    0 0xffffff7f80f6c000 0x7000     0x7000     com.FTDI.driver.FTDIUSBSerialDriver (2.3) ECC3AF36-431D-370D-86F2-5237785E9CF8 <85 39 5 4 3 1>

いまいち原因がわからない。

メモ

各チャンネルをループバックして screen を使って手動で通信テストする限りはうまく動いているようにみえる。壊れているわけではないっぽい。なぜこんなことになるのかわからない。

  1. トップ
  2. tech
  3. ESP-WROOM-02 書きこめなかった

Over The Air で (すなわち Wifi 経由で)、ファームウェア書きかえをするやつ。

コード

https://github.com/esp8266/Arduino/blob/master/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino

これの通りにすればコード的にはとりあえずうまくいく。

platform.ini

board は esp12e にしとかないとだめ。generic と書いてあるからといって、esp01 とか esp01_1m とかは flash のサイズが小さいので OTA_BEGIN_ERROR と言われる。

OTA の場合使える容量は半分以下になる。というのも、既存領域の余った部分にとりあえず書くからっぽい。

ref. https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/ota_updates.md#update-process---memory-view

OTA

まずアドレスを mDNS からひいてくる (シリアルに表示させてもいいけど)

$ dns-sd -B _arduino._tcp
Browsing for _arduino._tcp
DATE: ---Wed 23 Dec 2015---
17:49:14.912  ...STARTING...
Timestamp     A/R    Flags  if Domain               Service Type         Instance Name
17:49:14.913  Add        2   4 local.               _arduino._tcp.       esp8266-ee8488

$ dns-sd -G v4 esp8266-ee8488.local
DATE: ---Wed 23 Dec 2015---
17:48:48.955  ...STARTING...
Timestamp     A/R Flags if Hostname                               Address                                      TTL
17:48:48.958  Add     2  4 esp8266-ee8488.local.                  192.168.0.11                                 120

platform.ini の port に、この ip address を書いておく。これだけで自動的に OTA 経由と判別して書きこもうとする。

書きこみ時間

222K のbinファイル書きこみに4秒ぐらい。

$ ls -altrh .pioenvs/esp12e/firmware.bin 
-rw-r--r--@ 1 cho45  staff   222K 12 23 21:05 .pioenvs/esp12e/firmware.bin

$ time "/Users/cho45/.platformio/packages/framework-arduinoespressif/tools/espota.py" --debug --progress -i 192.168.0.11 -f .pioenvs/esp12e/firmware.bin
18:55:58 [DEBUG]: Options: {'esp_ip': '192.168.0.11', 'image': '.pioenvs/esp12e/firmware.bin', 'auth': '', 'esp_port': 8266, 'spiffs': False, 'debug': True, 'progress': True}
18:55:58 [INFO]: Starting on 0.0.0.0:24984
18:55:58 [INFO]: Upload size: 227280
18:55:58 [INFO]: Sending invitation to: 192.168.0.11
18:55:58 [INFO]: Waiting for device...
Uploading: [============================================================] 100% Done...

18:56:02 [INFO]: Waiting for result...
18:56:02 [INFO]: Result: OK
"/Users/cho45/.platformio/packages/framework-arduinoespressif/tools/espota.py  0.06s user 0.05s system 2% cpu 4.417 total

その他

UART Download で書きこんだ直後の起動中に OTA 書きこみをした場合、ESP.restart() が失敗するっぽい。UART Download したあとは、一旦 reset してから OTA すればいいっぽい。

  1. トップ
  2. tech
  3. ESP-WROOM-02 OTA firmware update / platformio

だいぶ忘れてたが、しばらくぶりにとりくんだらうまくいった。

やりたいこと

  • ヘッドフォン出力から UART 信号を入力する
  • (-1V〜1V) ぐらいを想定。何もしてないときは0V
  • 300mV ぐらいでも動いてほしい
  • UART へ出力する
    • 何もないときはHIGHになってほしい
    • 負を出力したときだけLOWになってほしい

シミュレーション

こんな回路で

入出力

入出力DCスイープ

回路の設計

1石で非反転にしたいのでベース接地とした。ヘッドフォン出力を入力にすることを想定しているので入力インピーダンスはそれほど高くなくても良いだろう。この回路の入力インピーダンスは約100Ω(100Ωと20kΩの並列)

そして出力インピーダンスは100kとかなり大きいがデジタル入力なのでたぶん大丈夫だろう……

バイアスは0Vから飽和になるように選び、負の出力のときだけLOWに。

実験

赤が入力(ヘッドフォン出力)、黄がUARTへの出力

4800baud

9600baud

9600baud 拡大

受信側はよくあるFTDI チップのUSBシリアル変換のもので、screen でデバイスファイルを指定して見ている。どのボーレートでも問題なく受信できた。

WebAudio 側の実装

まだ実験的なページしか作っていない。これでだいたいうまくいきそうなので汎用的に使えるようにしたい。

メモ

これ以上簡単な回路にはならない気がする。

最初オーディオ出力がハイになるときにスパイクが出てしまう。これは出力に100pFぐらいつけたらだいぶ良くなるが、別段つけなくても問題はなさそう。

なお ASUS Zenfone2 で実験を行なった。他のデバイスだとうまくいかないケースがあるかもしれない。

TODO

3.3V 版もつくる。たぶん↓でよさそう

  1. トップ
  2. tech
  3. WebAudio 直結 UART

AVR のビルドツールといえば Arduino.app 1.6.5 に含まれているのでそれ使うのが一番楽だけど、この avr-gcc は 4.8.1 で、ちょっと古い。だんだん試行錯誤が嫌になって C++ で書きたくなってくると、どうせなら C++14 で書きたいと思うのが人間でしょう。

http://www.nongnu.org/avr-libc/user-manual/install_tools.html

このページを見ながら最初から順番にやれば、基本的にはできる。

注意点は

  • avr-libc 1.8.1 と gcc 5 系の組合せだと一見ビルドがうまくいったようにみえて、実際はリンクできない
    • svn head を使うこと
  • ちまたの avr-size には AVR 用のパッチがあたっており、binutils には含まれていない
    • 別途あてる必要がある


ということをやるビルド用のスクリプトを OS X 向けにかいた (依存のインストール用に homebrew が必要)

それなりのマシンで全部ビルドにするのに30分〜40分ぐらいかかる。

  1. トップ
  2. tech
  3. OS X で avr-gcc 5.3.0 をビルドする

先日 [tech] Arduino の digitalWrite をコンパイルタイムに解決する | Tue, Dec 15. 2015 - 氾濫原 というのをコンセプト的にやってみたが割と良さそうな気がしたので、.hpp なライブラリにしてみた。

include してコンパイル時に -std=c++11 を指定すれば使える。

想定する使いかた

#include <Arduino.h>
#include "Arduino-meta.hpp"

constexpr uint8_t LED_RED = 0;
constexpr uint8_t LED_GREEN = 1;
constexpr uint8_t LED_BLUE = 2;

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    // static version of pinMode()
    pinMode(
        LED_RED, OUTPUT,
        LED_GREEN, OUTPUT,
        LED_BLUE, OUTPUT
    );
}

void loop() {
    // this is also static
    digitalWrite(LED_RED, HIGH, LED_GREEN, LOW, LED_BLUE, LOW);
    delay(1000);
    digitalWrite(LED_RED, LOW, LED_GREEN, HIGH, LED_BLUE, LOW);
    delay(1000);
    digitalWrite(LED_RED, LOW, LED_GREEN, LOW, LED_BLUE, HIGH);
    delay(1000);

    // original version of digitalWrite()
    digitalWriteDynamic(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWriteDynamic(LED_BUILTIN, LOW);
    delay(1000);
}

デフォルトの digitalWrite / pinMode / digitalRead をマクロで置き換えるようにして、完全にインターフェイスを互換にしたので、基本的に include したらあとはほぼ気にする必要がない。

ただ、静的に解決できない引数を指定するとコンパイルエラーになるので、この場合は何も考えず Dynamic をサフィックスにして元の Arduino 組込み関数をよぶようにすればいい。

基本 include するだけで最適化されたコードが生成されるようになり、必要に応じて複数ピンの同時操作もできる。

そして引数がおかしい場合コンパイルエラーになってくれるので便利。

何も失うものはない。

実装

前のエントリに書いた通り、C++ の template を使ってコンパイル時にビットマスクとかを計算しているだけ。pinMode も面倒くさいけど実装したり、digitalWrite/digitalRead 前に timer 付きピンなら無効にするような処理を入れたり (互換性のため) した。

Arduino.h を include すると、PROGMEM 指定されている const なテーブルが見えるので、本当はそれを直接コンパイル時にも使いたいのだけれど、どうしても無理っぽいので、独自にマップをコピペして持っている。無駄感

最終的には使う側の見た目をあわせるためプリプロセッサマクロを使って、マクロ関数の引数をテンプレートパラメータとして渡している。

static constexpr void (*digitalWriteDynamic)(uint8_t, uint8_t)  = *digitalWrite;
#define digitalWrite(...) digitalWriteMulti<__VA_ARGS__>()
static constexpr void (*pinModeDynamic)(uint8_t, uint8_t)  = *pinMode;
#define pinMode(...) pinModeMulti<__VA_ARGS__>()
static constexpr int (*digitalReadDynamic)(uint8_t)  = *digitalRead;
#define digitalRead(pin) digitalReadX<pin>()

テスト

一応テストも書いてあって

test(<<-'EOS')
	void expected() {
		DDRD  = (DDRD  & 0b11111100) | (0b00000000);
		PORTD = (PORTD & 0b11111100) | (0b00000010);
	}

	void result() {
		pinMode(0, INPUT, 1, INPUT_PULLUP);
	}
EOS

こんな感じで、想定する展開コードとテンプレート関数とをコンパイルしたあと obj-dump して突き合せしている。これら2つの関数は全くバイナリ的に同一になることをテストしている。

備考

最初からテンプレートを駆使するつもりで AVR のライブラリを設計すればもっと良いものができそうだが、Arduino が覇権を握っているので、Arduino のインターフェイスにあわせるほうが嬉しい人が多いだろう。Arduino の基板上にはピン番号が書いてあるので、いちいちAVRのポート名とかとのマッピングを見ながら書くよりは、このほうが楽だ。

そしてテンプレート駆使しまくりすぎたライブラリは理解がつらいのが明かなので、このぐらいのショボいラッパぐらいがちょうどいいような気もする。一方でそれはそれで反知性的であるともいえそうだしなんともいえない。

  1. トップ
  2. tech
  3. Arduino の digitalWrite / pinMode / digitalRead をコンパイル時に展開する