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

やりたいこと

  • ヘッドフォン出力から 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 をコンパイル時に展開する