(Java です)

例えばクローラみたいなのを書こうと思うと、

  1. リモートからダウンロードしてくるタスク
  2. ストレージに格納するタスク
  3. メタデータを更新するタスク

みたいなのが大まかにあって、リモートからダウンロードしてくるのは2並列で、ストレージに格納するのも2並列で (ダウンロード先とストア先は別のところなので別々に並列にしたい)、メタデータ更新するのはコリジョンさせたくないから1スレッドで、みたいなことをやりたくなることがある。そういうイメージでいきます。

CompletableFuture

Java8 から CompletableFuture というのが入って、これが JavaScript の Promise みたいなやつにあたるようだ。名前が長くてウザいが我慢するしかない。

Java には ExecutorService というスレッドプールを管理していい感じに実行してくれる仕組みがあり、Executors.newXXX() というのにいくつか実装がある。

これらを組合せるとマルチスレッドで同期処理を非同期して結果を集めて同期したりみたいなのが強力に書けそう。同期にしたいのか非同期にしたいのかどっちやねん感がでてくる。

いっぱいメソッドがあるが、とりあえず CompletableFuture.runAsync() と thenRunAsync() あたりでなんとかなりそう。

コード

実際こういう感じでできる。

並列で行われる部分は当然スレッドセーフでなければならないが、このコードの場合 metaExecutor はシングルスレッドなので metaExecutor 内では考えることが減る (downloadExecutor/storeExecutor/metaExecutor それぞれは並列で動くけど)

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {
        new Main().doMain(args);
    }

    private ExecutorService downloadExecutor;
    private ExecutorService storeExecutor;
    private ExecutorService metaExecutor;

    private void heavyProcess(String name) {
        System.out.println(String.format("%s -- START", name));
        try {
            Thread.currentThread().sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(String.format("%s -- END", name));
    }

    public void doMain(String[] args) {
        downloadExecutor = Executors.newFixedThreadPool(2, r -> new Thread(r, "download"));
        storeExecutor = Executors.newFixedThreadPool(2, r -> new Thread(r, "store"));
        metaExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "meta"));

        final List<String> urls = Arrays.asList(
                "http://example.com/1",
                "http://example.com/2",
                "http://example.com/3",
                "http://example.com/4",
                "http://example.com/5",
                "http://example.com/6",
                "http://example.com/7",
                "http://example.com/8",
                "http://example.com/9"
        );


        Thread.currentThread().setName("Main");
        System.out.println(String.format("[%s] START", Thread.currentThread().getName()));

        final List<CompletableFuture<Void>> futures = urls.stream().map(
                url -> CompletableFuture
                        .runAsync(() -> {
                            heavyProcess(String.format("[%s] downloading for %s", Thread.currentThread().getName(), url));
                        }, downloadExecutor)
                        .thenRunAsync(() -> {
                            heavyProcess(String.format("[%s] storing for %s", Thread.currentThread().getName(), url));
                        }, storeExecutor)
                        .thenRunAsync(() -> {
                            System.out.println(String.format("[%s] updating meta for %s", Thread.currentThread().getName(), url));
                        }, metaExecutor)
        ).collect(Collectors.toList());

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).thenRun(() -> {
            System.out.println(String.format("[%s] FINISH", Thread.currentThread().getName()));
        });

        // .join() しなければ即座にこの関数は終わる.
        System.out.println(String.format("[%s] END OF MAIN", Thread.currentThread().getName()));
    }
}

これとは別に BlockingQueue を使ったり、各タスクが次のタスクを自力で次の Executor に投げるみたいな実装を書いてみたが、CompletableFuture のほうが簡潔に書けた。

出力

こんな感じで動く

[Main] START
[download] downloading for http://example.com/1 -- START
[download] downloading for http://example.com/2 -- START
[Main] END OF MAIN
[download] downloading for http://example.com/2 -- END
[download] downloading for http://example.com/3 -- START
[store] storing for http://example.com/2 -- START
[download] downloading for http://example.com/1 -- END
[download] downloading for http://example.com/4 -- START
[store] storing for http://example.com/1 -- START
[download] downloading for http://example.com/4 -- END
[download] downloading for http://example.com/5 -- START
[download] downloading for http://example.com/3 -- END
[download] downloading for http://example.com/6 -- START
[store] storing for http://example.com/2 -- END
[store] storing for http://example.com/4 -- START
[Main Executor] updating meta for http://example.com/2
[store] storing for http://example.com/1 -- END
[store] storing for http://example.com/3 -- START
[Main Executor] updating meta for http://example.com/1
[store] storing for http://example.com/3 -- END
[Main Executor] updating meta for http://example.com/3
[download] downloading for http://example.com/6 -- END
[download] downloading for http://example.com/7 -- START
[store] storing for http://example.com/6 -- START
[download] downloading for http://example.com/5 -- END
[download] downloading for http://example.com/8 -- START
[store] storing for http://example.com/6 -- END
[store] storing for http://example.com/5 -- START
[Main Executor] updating meta for http://example.com/6
[store] storing for http://example.com/4 -- END
[Main Executor] updating meta for http://example.com/4
[store] storing for http://example.com/5 -- END
[Main Executor] updating meta for http://example.com/5
[download] downloading for http://example.com/8 -- END
[download] downloading for http://example.com/9 -- START
[store] storing for http://example.com/8 -- START
[download] downloading for http://example.com/9 -- END
[store] storing for http://example.com/9 -- START
[download] downloading for http://example.com/7 -- END
[store] storing for http://example.com/8 -- END
[Main Executor] updating meta for http://example.com/8
[store] storing for http://example.com/7 -- START
[store] storing for http://example.com/9 -- END
[Main Executor] updating meta for http://example.com/9
[store] storing for http://example.com/7 -- END
[Main Executor] updating meta for http://example.com/7
[Main Executor] FINISH

ハマった点

知らない機能がいっぱいあるのでもっと簡潔に書ける気がします。

  1. トップ
  2. tech
  3. 数種類のタスクをタスクごとに別々の並列性ポリシー使いつつ、順次実行する
  1. トップ
  2. java
  3. 数種類のタスクをタスクごとに別々の並列性ポリシー使いつつ、順次実行する

前のエントリでいい感じで書けそうみたいなことを書いたが、例外処理について言及していなかった。

CompletableFuture は Promise 同様エラー処理用にcompleteExceptionally(Throwable ex) というメソッドを持っている。これを呼ぶと後続の exceptionally() にエラー処理が移る (そしてリカバリできる) 。

では普通の、時間のかかり、なおかつ検査例外を出す同期メソッドを CompletableFuture として実行する場合例外はどうすればいいだろうか?

runAsync などの中では CompletableFuture インスタンスは見えないので、catch して明示的に completeExceptionally() することができない。

いくつか方法がありそうだけど、よくわかってない

CompletableFuture を明示的に扱うラッパメソッドを作る

package com.company;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
new Main().doMain(args);
}
private static <T> CompletableFuture<T> async(Consumer<CompletableFuture<T>> function) {
final CompletableFuture<T> future = new CompletableFuture<>();
function.accept(future);
return future;
}
public void doMain(String[] args) {
final CompletableFuture<Void> future1 = async(f -> {
try {
f.complete("Foobar");
} catch (Exception e) {
f.completeExceptionally(e);
}
}).thenAccept(x -> {
System.out.println(x);
});
final CompletableFuture<Void> future2 = async(f -> {
try {
throw new RuntimeException("exception");
} catch (Exception e) {
f.completeExceptionally(e);
}
}).exceptionally(ex -> {
System.out.println("exception!!");
return ex;
}).thenAccept(x -> {
System.out.println(x);
});
CompletableFuture.allOf(future1, future2).thenRun(() -> {
System.out.println("All done");
});
}
}
view raw Main.java hosted with ❤ by GitHub

こういう感じで、JS でいうところの new Promise( (resolve, reject) => {}) に相当するメソッドを作って、明示的に同期コードでの終了時/例外時の処理を行う。

JS の流れだとわかりやすいけど、ラッパメソッドとかいちいち書きたくはないし、せっかくマルチスレッドによって同期的に書ける処理を非同期っぽくなおすのはダサい。

全部 RuntimeException にしてしまう

runAsync 内で全例外をキャッチして非検査例外にしてしまう。

この場合投げられた例外は java.util.concurrent.CompletionException (これも非検査例外) にさらにラップされて exceptionally に渡される。

全例外 catch して completeExceptionally するのも実質非検査への変換なのでこれでもよさそう。実質はラッパメソッドでキャッチして completeExceptionally するのと変わりない。

その他

検査例外をうまく生かしつつ処理するというのが難しそう。各ステージで検査例外は全て処理するというのが想定されているのだろうか? とりあえず全部 catch して uncheck にするというのをやっていると、当然検査例外をまともに処理しようという気にはならなくなる。

検索例外を投げるメソッドは、直接メソッド参照のスタイルで runAsync(this::FooBar) 等と渡せない。

  1. トップ
  2. tech
  3. CompletableFuture と例外
  1. トップ
  2. java
  3. CompletableFuture と例外

(実質的な) 非接続状態のことをハイインピーダンス (Hi-Z) 状態 (Tri-state) と言う。久しぶりに触ると、どうすれば Hi-Z になるんだっけ??となるのでメモしておく

Arduino 風にいうと、

  • DDxn は pinMode(XX, INPUT | OUTPUT);
  • PORTxn は digitalWrite(XX, LOW | HIGH);
  • PUD (in MCUCR) はプルアップの有効化を決めるフラグだけど普通は有効にしとくので気にしない (Pull-up Disable フラグ、1でdisableされる)

プルアップ有効のケースだと、pinMode が INPUT かつ digitalWrite で LOW したピンはハイインピーダンス

  1. トップ
  2. tech
  3. Arduino / AVR でコード上からピンを非接続状態とする

続きをかきました 500 Can't connect to lowreal.net:443 (certificate verify failed)

仕様はちょっと前に調べて、先日対応バッテリーがきたので試してみました。

 -

3.0 / 5.0

回路図

12V を出す場合必要なのは D+ 0.6V / D- 0.6V なので、非常に簡単な構成。

VBUS には QC時に 5V〜12V の電圧がかかる。直接 Arduino の VIN に繋いでいるが、Arduino 側で 5V レギュレータが入っているので問題ない (ref. https://www.arduino.cc/en/uploads/Main/ArduinoNano30Schematic.pdf )。当然 5V ピンに直接繋いではならない (燃える)

コード

#include <Arduino.h>
#include <avr/sleep.h>

const int D_PLUS  = 11;
const int D_MINUS = 12;

/**
 * Arduino nano has 5V regulated power, so required voltages are generated with:
 * 3.3V: 4.7kΩ / 9.1kΩ
 * 0.6V: 1.1kΩ / 150Ω
 *
 */

void setup() {
	// wait until stable connection
	delay(1000);

	// reset line
	pinMode(D_PLUS, OUTPUT);
	pinMode(D_MINUS, OUTPUT);
	digitalWrite(D_PLUS, LOW);
	digitalWrite(D_MINUS, LOW);
	delay(100);

	// D+ and D- to 0.6V for 1.25s
	digitalWrite(D_PLUS, HIGH);
	digitalWrite(D_MINUS, HIGH);
	delay(1500);

	// D- to 0V for 1ms
	digitalWrite(D_MINUS, LOW);
	delay(2);

	// Set voltage
	digitalWrite(D_MINUS, HIGH);

	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	sleep_mode();
}

void loop() {
}

結果

12V とれたよ〜 (赤が VBUS、黄は D-)

備考

9V に対応する場合 D+ を途中から 0.6V -> 3.3V とする必要があるので、もう少し回路とコードが必要。

  1. トップ
  2. tech
  3. Quick Charge 2.0 電源から 12V とる

とりあえずとれるところまではやっていたが、ちゃんと負荷をかけていなかったのでかけたメモ

 -

5.0 / 5.0

例によってこのバッテリーで、結論からいうとスペック通り(12V 1.35A) の出力はとれ、ノイズ(リプル)も定格内だと全く問題なさそう。

  • 実際は 1.5A超ぐらいまでは出力され、1.6Aぐらいでシャットダウンされる。電源ボタンを押すとすぐリセットされるのでポリスイッチ以外での制御もしている? ショートされた場合はしばらくリセットもできないのでポリスイッチも入ってそうだけど
  • QucikCharge モードに入っている場合でも、低電流状態が続くと自動シャットダウンされる。1分ぐらい。閾値は面倒なので調べていない 。スペックに表記はない。

ちなみにこれ、安い割に外装がちゃんとアルミで、しっかりしている。ただしパワーが入っている間は常にコイル鳴きが聞こえる。負荷があがるとコイルもさらによく鳴く。

  1. トップ
  2. tech
  3. Quick Charge 2.0 バッテリーから12Vをとる(追試)

前行におく

promise.
  then(function () { }).
  catch(function () { });

後行におく

promise
  .then(function () { } )
  .catch(function () { });

どっちもいいんだけど、僕は前行においております

  • JS には自動セミコロン挿入があるので閉じ括弧で行が終わっていると不安になる
  • 上から順に読んでいく場合は行継続のマークが行の最後にあったほうが読みやすい気がする
  • then 〜 . で、ピリオドを文の終わりのように見せられる

行頭を重点的に見ながらコード読む人にとっては前の行との繋がりがわかりやすいのは、後行にドットなので (前行に置いた場合に行頭だけ読んでいくとただの関数コールに見える)、難しいところですね。

JS の場合セミコロン自動挿入があるのが嫌で、これが一番の理由で前行につけているフシがある。(文法違反になってはじめてセミコロン挿入がくるので、この場合どちらもセミコロン自動挿入は起きませんが……)

then 〜 . で1行にするとセミコロンと対応する感じになるので良いような気がする。

条件演算子で

var piyo = foo ? bar:
           fuu ? baz:
                 buz;

みたいに書くとき : と ; が対応していい感じなので、そういうイメージでいます

  1. トップ
  2. tech
  3. JS のメソッドチェインでドットを前行に置くか後行に置くか


500 Can't connect to lowreal.net:443 (certificate verify failed) というのを書いたが 、これだと12V だけしかとれない。

12Vとれれば個人的にはいいんだけど、9V もとるには(というより任意の電圧をとる) にはどうするかなと思って考えた。

 -

3.0 / 5.0

ルートアール QC2.0対応 USB簡易電圧・電流チェッカー RT-USBVA3HV - ルートアール

ルートアール

3.0 / 5.0

回路図

追記: 特に3段にする必要もないので計算が簡単で定数が簡単な回路にかえた

古い回路図:

D12をハイ(5V)にすると D- に 0.6V、 D11 をハイにすると 3.3V がかかるように分圧抵抗を選び、このポートをコントロールする。D+ 側も同様 (D10, D9)

どっちかがハイのときはどっちかはHi-Zにして回路を切断する。

ファームウェア

例によって Arduino で試した

#include <Arduino.h>
#include <avr/sleep.h>

const int D_PLUS_0V6 = 12;
const int D_PLUS_3V3 = 11;
const int D_MINUS_0V6 = 10;
const int D_MINUS_3V3 = 9;

enum EVoltage {
	E_12V,
	E_9V,
	E_5V
};

void setup() {
	EVoltage voltage = E_9V;

	// init with hi-Z
	pinMode(D_PLUS_3V3, INPUT);
	pinMode(D_PLUS_0V6, INPUT);
	pinMode(D_MINUS_3V3, INPUT);
	pinMode(D_MINUS_0V6, INPUT);
	digitalWrite(D_PLUS_3V3, LOW);
	digitalWrite(D_PLUS_0V6, LOW);
	digitalWrite(D_MINUS_3V3, LOW);
	digitalWrite(D_MINUS_0V6, LOW);

	// wait until stable connection
	delay(1000);

	// reset line (D+ = 0V, D- = 0V)
	pinMode(D_PLUS_0V6, OUTPUT);
	pinMode(D_MINUS_0V6, OUTPUT);
	delay(100);

	// D+ = 0.6V, D- = 0.6V for 1.25s
	digitalWrite(D_PLUS_0V6, HIGH);
	digitalWrite(D_MINUS_0V6, HIGH);
	delay(1500);

	// D- to 0V for 1ms
	digitalWrite(D_MINUS_0V6, LOW);
	delay(2);

	// Set voltage
	// (now: D+ = 0.6V, D- = 0)
	// D+ must not be low; it cause reset.
	switch (voltage) {
		case E_12V:
			// D+ = 0.6V, D- = 0.6V
			digitalWrite(D_MINUS_0V6, HIGH);
			break;
		case E_9V:
			// D+ = 3.3V, D- = 0.6V
			digitalWrite(D_PLUS_3V3, HIGH);
			pinMode(D_PLUS_3V3, OUTPUT);
			pinMode(D_PLUS_0V6, INPUT);
			digitalWrite(D_PLUS_0V6, LOW);

			digitalWrite(D_MINUS_0V6, HIGH);
			break;
		default:
			// nothing to do
			break;
	}

	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	sleep_mode();
}

void loop() {
}

出力

赤は VBUS, 黄は D-

9V とれたよ〜

9V 時の D+, D-

12V 時の D+, D-

懸念点

0.6V 出力時かなりドライブ能力が低いのが心配。

メモ

スイッチ切り替えで任意電圧とれるアダプタみたいなのがあると便利そうですね。

  1. トップ
  2. tech
  3. Quick Charge 2.0 電源から 9V をとる (任意の電圧をとる)

digitalWrite(pin, state) の引数はほとんど固定で使うことが多い (と思いますがどうでしょうか? http://lowreal.net/2015/12/14/1 こういうケースでは常に固定ですね) が、digitalWrite は実装上、実行時にいちいちテーブルを使って、ピン番号からどのポートのどのビットかをひいている。たいへん無駄である。

そして、digitalWrite() は1度の1つのビットしか設定できないという制約がある。これは大変こまる場合がある (同時に複数のビットを設定して出力を変化させようとしても、タイムラグができてしまう)

ということで、これらを解決できないかと思った結果 C++ のテンプレートメタプログラミングに手を出すことになった

前提

Arduino が使っている 8bit AVR では PORTB, PORTC, PORTD などと最大8bitのレジスタがあり、これら単位では同時に操作が可能になっている。

Arduino ではこれらのポート単位を隠蔽し、ピン番号という一連の番号によって区別するように抽象化されている。具体的には 0〜7 が PD, 8〜14 が PB というような感じ (standard なArduino の場合)

仕様

  • digitalWrite() とインターフェイス互換であること (静的であること以外)
  • 複数ビットを同時に操作できること
  • 指定したピン番号同士のポートが同一でない場合エラーとすること
// オリジナル。0ピンをLOWにする
digitalWrite(0, LOW);

// 作ったもの。0ピンをLOW, 1ピンをHIGH... とする (同時に)
WRITE_MULTI(
  0, LOW,	
  1, HIGH,
  2, LOW,	
  3, HIGH
);

コード

Gist

//#!ano build -f="-std=c++11 -Os" && /Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-objdump -d .build_ano/uno/firmware.elf ;:
#include <Arduino.h>
// ARDUINO_MAIN で ifdef されているので外からみえない
constexpr uint8_t PB = 2;
constexpr uint8_t PC = 3;
constexpr uint8_t PD = 4;
// template version of Arduino builtin functions
constexpr uint16_t port_to_mode[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &DDRB,
(uint16_t) &DDRC,
(uint16_t) &DDRD,
};
constexpr uint16_t port_to_output[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &PORTB,
(uint16_t) &PORTC,
(uint16_t) &PORTD,
};
constexpr uint16_t port_to_input[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &PINB,
(uint16_t) &PINC,
(uint16_t) &PIND,
};
constexpr uint8_t digital_pin_to_port[] = {
PD, /* 0 */
PD,
PD,
PD,
PD,
PD,
PD,
PD,
PB, /* 8 */
PB,
PB,
PB,
PB,
PB,
PC, /* 14 */
PC,
PC,
PC,
PC,
PC,
};
constexpr uint8_t digital_pin_to_bit_mask[] = {
_BV(0), /* 0, port D */
_BV(1),
_BV(2),
_BV(3),
_BV(4),
_BV(5),
_BV(6),
_BV(7),
_BV(0), /* 8, port B */
_BV(1),
_BV(2),
_BV(3),
_BV(4),
_BV(5),
_BV(0), /* 14, port C */
_BV(1),
_BV(2),
_BV(3),
_BV(4),
_BV(5),
};
template<uint8_t port>
constexpr volatile uint8_t* portModeRegisterX() {
static_assert(port < sizeof(port_to_mode), "invalid port number");
static_assert(port_to_mode[port] != NOT_A_PORT, "invalid port number");
return (volatile uint8_t*)port_to_mode[port];
}
template<uint8_t port>
constexpr volatile uint8_t* portOutputRegisterX() {
static_assert(port < sizeof(port_to_output), "invalid port number");
static_assert(port_to_output[port] != NOT_A_PORT, "invalid port number");
return (volatile uint8_t*)port_to_output[port];
}
template<uint8_t port>
constexpr volatile uint8_t* portInputRegisterX() {
static_assert(port < sizeof(port_to_input), "invalid port number");
static_assert(port_to_input[port] != NOT_A_PORT, "invalid port number");
return (volatile uint8_t*)port_to_input[port];
}
template <uint16_t pin>
constexpr uint8_t digitalPinToBitMaskX() {
static_assert(pin < sizeof(digital_pin_to_bit_mask), "invalid pin number");
return digital_pin_to_bit_mask[pin];
};
template <uint16_t pin>
constexpr uint8_t digitalPinToPortX() {
static_assert(pin < sizeof(digital_pin_to_port), "invalid pin number");
return digital_pin_to_port[pin];
};
// multiple bit set function with cheking port consistency
template<uint8_t port, uint8_t mask, uint8_t vals>
constexpr void digitalWriteMulti_() {
volatile uint8_t *out = portOutputRegisterX<port>();
*out = (*out & ~mask) | vals;
}
template<uint8_t port, uint8_t mask, uint8_t vals, uint16_t pin, uint8_t val, uint16_t... Rest>
constexpr void digitalWriteMulti_() {
static_assert(digitalPinToPortX<pin>() == port, "all port must be same");
constexpr uint8_t bit = digitalPinToBitMaskX<pin>();
digitalWriteMulti_<
port,
mask | bit,
vals | (val ? bit : 0),
Rest...
>();
}
template<uint16_t pin, uint8_t val, uint16_t... Rest>
constexpr void digitalWriteMulti() {
constexpr uint8_t port = digitalPinToPortX<pin>();
return digitalWriteMulti_<
port,
0,
0,
pin,
val,
Rest...
>();
}
// hide template magic from surface
#define WRITE_MULTI(...) digitalWriteMulti<__VA_ARGS__>()
void setup() {
PORTD = (PORTD & 0b11110000) | (0b1010);
asm volatile ("nop"); // marker
// same as above
WRITE_MULTI(
0, LOW,
1, HIGH,
2, LOW,
3, HIGH
);
asm volatile ("nop");
// another port
WRITE_MULTI(
8, LOW,
9, HIGH,
10, LOW,
11, HIGH
);
asm volatile ("nop");
/** following is invalid
* static_assert(digitalPinToPortX<pin>() == port, "all port must be same");
* pin=0 -> PORTB, pin=8 -> PORTC error
digitalWriteMulti<
0, LOW,
1, HIGH,
2, LOW,
8, HIGH
>();
*/
asm volatile ("nop");
/** builtin dynamic method
* this is very slow
*/
digitalWrite(0, LOW);
}
void loop() {
}
.build_ano/uno/firmware.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 61 00 jmp 0xc2 ; 0xc2 <__ctors_end>
4: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
8: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
10: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
14: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
18: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
1c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
20: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
24: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
28: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
2c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
30: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
34: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
38: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
3c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
40: 0c 94 71 01 jmp 0x2e2 ; 0x2e2 <__vector_16>
44: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
48: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
4c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
50: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
54: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
58: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
5c: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
60: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
64: 0c 94 73 00 jmp 0xe6 ; 0xe6 <__bad_interrupt>
00000068 <__trampolines_end>:
68: 00 00 nop
6a: 00 08 sbc r0, r0
6c: 00 02 muls r16, r16
6e: 01 00 .word 0x0001 ; ????
70: 00 03 mulsu r16, r16
72: 04 07 cpc r16, r20
...
0000007c <digital_pin_to_bit_mask_PGM>:
7c: 01 02 04 08 10 20 40 80 01 02 04 08 10 20 01 02 ..... @...... ..
8c: 04 08 10 20 ...
00000090 <digital_pin_to_port_PGM>:
90: 04 04 04 04 04 04 04 04 02 02 02 02 02 02 03 03 ................
a0: 03 03 03 03 ....
000000a4 <port_to_input_PGM>:
a4: 00 00 00 00 23 00 26 00 29 00 ....#.&.).
000000ae <port_to_output_PGM>:
ae: 00 00 00 00 25 00 28 00 2b 00 ....%.(.+.
000000b8 <port_to_mode_PGM>:
b8: 00 00 00 00 24 00 27 00 2a 00 ....$.'.*.
000000c2 <__ctors_end>:
c2: 11 24 eor r1, r1
c4: 1f be out 0x3f, r1 ; 63
c6: cf ef ldi r28, 0xFF ; 255
c8: d8 e0 ldi r29, 0x08 ; 8
ca: de bf out 0x3e, r29 ; 62
cc: cd bf out 0x3d, r28 ; 61
000000ce <__do_clear_bss>:
ce: 21 e0 ldi r18, 0x01 ; 1
d0: a0 e0 ldi r26, 0x00 ; 0
d2: b1 e0 ldi r27, 0x01 ; 1
d4: 01 c0 rjmp .+2 ; 0xd8 <.do_clear_bss_start>
000000d6 <.do_clear_bss_loop>:
d6: 1d 92 st X+, r1
000000d8 <.do_clear_bss_start>:
d8: a9 30 cpi r26, 0x09 ; 9
da: b2 07 cpc r27, r18
dc: e1 f7 brne .-8 ; 0xd6 <.do_clear_bss_loop>
de: 0e 94 b8 02 call 0x570 ; 0x570 <main>
e2: 0c 94 cc 02 jmp 0x598 ; 0x598 <_exit>
000000e6 <__bad_interrupt>:
e6: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
000000ea <setup>:
ea: 8b b1 in r24, 0x0b ; 11
ec: 80 7f andi r24, 0xF0 ; 240
ee: 8a 60 ori r24, 0x0A ; 10
f0: 8b b9 out 0x0b, r24 ; 11
f2: 00 00 nop
f4: 8b b1 in r24, 0x0b ; 11
f6: 80 7f andi r24, 0xF0 ; 240
f8: 8a 60 ori r24, 0x0A ; 10
fa: 8b b9 out 0x0b, r24 ; 11
fc: 00 00 nop
fe: 85 b1 in r24, 0x05 ; 5
100: 80 7f andi r24, 0xF0 ; 240
102: 8a 60 ori r24, 0x0A ; 10
104: 85 b9 out 0x05, r24 ; 5
106: 00 00 nop
108: 00 00 nop
10a: 60 e0 ldi r22, 0x00 ; 0
10c: 80 e0 ldi r24, 0x00 ; 0
10e: 0c 94 c8 00 jmp 0x190 ; 0x190 <digitalWrite>
00000112 <loop>:
112: 08 95 ret
00000114 <pinMode>:
114: cf 93 push r28
116: df 93 push r29
118: 90 e0 ldi r25, 0x00 ; 0
11a: fc 01 movw r30, r24
11c: e4 58 subi r30, 0x84 ; 132
11e: ff 4f sbci r31, 0xFF ; 255
120: 24 91 lpm r18, Z
122: fc 01 movw r30, r24
124: e0 57 subi r30, 0x70 ; 112
126: ff 4f sbci r31, 0xFF ; 255
128: 84 91 lpm r24, Z
12a: 88 23 and r24, r24
12c: b9 f0 breq .+46 ; 0x15c <pinMode+0x48>
12e: 90 e0 ldi r25, 0x00 ; 0
130: 88 0f add r24, r24
132: 99 1f adc r25, r25
134: fc 01 movw r30, r24
136: e8 54 subi r30, 0x48 ; 72
138: ff 4f sbci r31, 0xFF ; 255
13a: a5 91 lpm r26, Z+
13c: b4 91 lpm r27, Z
13e: 82 55 subi r24, 0x52 ; 82
140: 9f 4f sbci r25, 0xFF ; 255
142: fc 01 movw r30, r24
144: c5 91 lpm r28, Z+
146: d4 91 lpm r29, Z
148: 9f b7 in r25, 0x3f ; 63
14a: 66 23 and r22, r22
14c: 51 f0 breq .+20 ; 0x162 <pinMode+0x4e>
14e: 62 30 cpi r22, 0x02 ; 2
150: a1 f0 breq .+40 ; 0x17a <pinMode+0x66>
152: f8 94 cli
154: 8c 91 ld r24, X
156: 82 2b or r24, r18
158: 8c 93 st X, r24
15a: 9f bf out 0x3f, r25 ; 63
15c: df 91 pop r29
15e: cf 91 pop r28
160: 08 95 ret
162: f8 94 cli
164: 8c 91 ld r24, X
166: 20 95 com r18
168: 82 23 and r24, r18
16a: 8c 93 st X, r24
16c: 88 81 ld r24, Y
16e: 82 23 and r24, r18
170: 88 83 st Y, r24
172: 9f bf out 0x3f, r25 ; 63
174: df 91 pop r29
176: cf 91 pop r28
178: 08 95 ret
17a: f8 94 cli
17c: 8c 91 ld r24, X
17e: 32 2f mov r19, r18
180: 30 95 com r19
182: 83 23 and r24, r19
184: 8c 93 st X, r24
186: 88 81 ld r24, Y
188: 82 2b or r24, r18
18a: 88 83 st Y, r24
18c: 9f bf out 0x3f, r25 ; 63
18e: e6 cf rjmp .-52 ; 0x15c <pinMode+0x48>
00000190 <digitalWrite>:
190: 90 e0 ldi r25, 0x00 ; 0
192: fc 01 movw r30, r24
194: e8 59 subi r30, 0x98 ; 152
196: ff 4f sbci r31, 0xFF ; 255
198: 24 91 lpm r18, Z
19a: fc 01 movw r30, r24
19c: e4 58 subi r30, 0x84 ; 132
19e: ff 4f sbci r31, 0xFF ; 255
1a0: 34 91 lpm r19, Z
1a2: fc 01 movw r30, r24
1a4: e0 57 subi r30, 0x70 ; 112
1a6: ff 4f sbci r31, 0xFF ; 255
1a8: 84 91 lpm r24, Z
1aa: 88 23 and r24, r24
1ac: 99 f0 breq .+38 ; 0x1d4 <digitalWrite+0x44>
1ae: 21 11 cpse r18, r1
1b0: 16 c0 rjmp .+44 ; 0x1de <digitalWrite+0x4e>
1b2: e8 2f mov r30, r24
1b4: f0 e0 ldi r31, 0x00 ; 0
1b6: ee 0f add r30, r30
1b8: ff 1f adc r31, r31
1ba: e2 55 subi r30, 0x52 ; 82
1bc: ff 4f sbci r31, 0xFF ; 255
1be: a5 91 lpm r26, Z+
1c0: b4 91 lpm r27, Z
1c2: 8f b7 in r24, 0x3f ; 63
1c4: f8 94 cli
1c6: 9c 91 ld r25, X
1c8: 61 11 cpse r22, r1
1ca: 05 c0 rjmp .+10 ; 0x1d6 <digitalWrite+0x46>
1cc: 30 95 com r19
1ce: 93 23 and r25, r19
1d0: 9c 93 st X, r25
1d2: 8f bf out 0x3f, r24 ; 63
1d4: 08 95 ret
1d6: 93 2b or r25, r19
1d8: 9c 93 st X, r25
1da: 8f bf out 0x3f, r24 ; 63
1dc: fb cf rjmp .-10 ; 0x1d4 <digitalWrite+0x44>
1de: 23 30 cpi r18, 0x03 ; 3
1e0: 29 f1 breq .+74 ; 0x22c <digitalWrite+0x9c>
1e2: 60 f0 brcs .+24 ; 0x1fc <digitalWrite+0x6c>
1e4: 27 30 cpi r18, 0x07 ; 7
1e6: e1 f0 breq .+56 ; 0x220 <digitalWrite+0x90>
1e8: 28 30 cpi r18, 0x08 ; 8
1ea: a1 f0 breq .+40 ; 0x214 <digitalWrite+0x84>
1ec: 24 30 cpi r18, 0x04 ; 4
1ee: 09 f7 brne .-62 ; 0x1b2 <digitalWrite+0x22>
1f0: 90 91 80 00 lds r25, 0x0080
1f4: 9f 7d andi r25, 0xDF ; 223
1f6: 90 93 80 00 sts 0x0080, r25
1fa: db cf rjmp .-74 ; 0x1b2 <digitalWrite+0x22>
1fc: 21 30 cpi r18, 0x01 ; 1
1fe: 31 f0 breq .+12 ; 0x20c <digitalWrite+0x7c>
200: 22 30 cpi r18, 0x02 ; 2
202: b9 f6 brne .-82 ; 0x1b2 <digitalWrite+0x22>
204: 94 b5 in r25, 0x24 ; 36
206: 9f 7d andi r25, 0xDF ; 223
208: 94 bd out 0x24, r25 ; 36
20a: d3 cf rjmp .-90 ; 0x1b2 <digitalWrite+0x22>
20c: 94 b5 in r25, 0x24 ; 36
20e: 9f 77 andi r25, 0x7F ; 127
210: 94 bd out 0x24, r25 ; 36
212: cf cf rjmp .-98 ; 0x1b2 <digitalWrite+0x22>
214: 90 91 b0 00 lds r25, 0x00B0
218: 9f 7d andi r25, 0xDF ; 223
21a: 90 93 b0 00 sts 0x00B0, r25
21e: c9 cf rjmp .-110 ; 0x1b2 <digitalWrite+0x22>
220: 90 91 b0 00 lds r25, 0x00B0
224: 9f 77 andi r25, 0x7F ; 127
226: 90 93 b0 00 sts 0x00B0, r25
22a: c3 cf rjmp .-122 ; 0x1b2 <digitalWrite+0x22>
22c: 90 91 80 00 lds r25, 0x0080
230: 9f 77 andi r25, 0x7F ; 127
232: 90 93 80 00 sts 0x0080, r25
236: bd cf rjmp .-134 ; 0x1b2 <digitalWrite+0x22>
00000238 <digitalRead>:
238: 90 e0 ldi r25, 0x00 ; 0
23a: fc 01 movw r30, r24
23c: e8 59 subi r30, 0x98 ; 152
23e: ff 4f sbci r31, 0xFF ; 255
240: 24 91 lpm r18, Z
242: fc 01 movw r30, r24
244: e4 58 subi r30, 0x84 ; 132
246: ff 4f sbci r31, 0xFF ; 255
248: 34 91 lpm r19, Z
24a: fc 01 movw r30, r24
24c: e0 57 subi r30, 0x70 ; 112
24e: ff 4f sbci r31, 0xFF ; 255
250: 84 91 lpm r24, Z
252: 88 23 and r24, r24
254: 91 f0 breq .+36 ; 0x27a <digitalRead+0x42>
256: 21 11 cpse r18, r1
258: 13 c0 rjmp .+38 ; 0x280 <digitalRead+0x48>
25a: e8 2f mov r30, r24
25c: f0 e0 ldi r31, 0x00 ; 0
25e: ee 0f add r30, r30
260: ff 1f adc r31, r31
262: ec 55 subi r30, 0x5C ; 92
264: ff 4f sbci r31, 0xFF ; 255
266: a5 91 lpm r26, Z+
268: b4 91 lpm r27, Z
26a: 2c 91 ld r18, X
26c: 23 23 and r18, r19
26e: 81 e0 ldi r24, 0x01 ; 1
270: 90 e0 ldi r25, 0x00 ; 0
272: 09 f0 breq .+2 ; 0x276 <digitalRead+0x3e>
274: 08 95 ret
276: 80 e0 ldi r24, 0x00 ; 0
278: 08 95 ret
27a: 80 e0 ldi r24, 0x00 ; 0
27c: 90 e0 ldi r25, 0x00 ; 0
27e: 08 95 ret
280: 23 30 cpi r18, 0x03 ; 3
282: 29 f1 breq .+74 ; 0x2ce <digitalRead+0x96>
284: 60 f0 brcs .+24 ; 0x29e <digitalRead+0x66>
286: 27 30 cpi r18, 0x07 ; 7
288: e1 f0 breq .+56 ; 0x2c2 <digitalRead+0x8a>
28a: 28 30 cpi r18, 0x08 ; 8
28c: a1 f0 breq .+40 ; 0x2b6 <digitalRead+0x7e>
28e: 24 30 cpi r18, 0x04 ; 4
290: 21 f7 brne .-56 ; 0x25a <digitalRead+0x22>
292: 90 91 80 00 lds r25, 0x0080
296: 9f 7d andi r25, 0xDF ; 223
298: 90 93 80 00 sts 0x0080, r25
29c: de cf rjmp .-68 ; 0x25a <digitalRead+0x22>
29e: 21 30 cpi r18, 0x01 ; 1
2a0: 31 f0 breq .+12 ; 0x2ae <digitalRead+0x76>
2a2: 22 30 cpi r18, 0x02 ; 2
2a4: d1 f6 brne .-76 ; 0x25a <digitalRead+0x22>
2a6: 94 b5 in r25, 0x24 ; 36
2a8: 9f 7d andi r25, 0xDF ; 223
2aa: 94 bd out 0x24, r25 ; 36
2ac: d6 cf rjmp .-84 ; 0x25a <digitalRead+0x22>
2ae: 94 b5 in r25, 0x24 ; 36
2b0: 9f 77 andi r25, 0x7F ; 127
2b2: 94 bd out 0x24, r25 ; 36
2b4: d2 cf rjmp .-92 ; 0x25a <digitalRead+0x22>
2b6: 90 91 b0 00 lds r25, 0x00B0
2ba: 9f 7d andi r25, 0xDF ; 223
2bc: 90 93 b0 00 sts 0x00B0, r25
2c0: cc cf rjmp .-104 ; 0x25a <digitalRead+0x22>
2c2: 90 91 b0 00 lds r25, 0x00B0
2c6: 9f 77 andi r25, 0x7F ; 127
2c8: 90 93 b0 00 sts 0x00B0, r25
2cc: c6 cf rjmp .-116 ; 0x25a <digitalRead+0x22>
2ce: 90 91 80 00 lds r25, 0x0080
2d2: 9f 77 andi r25, 0x7F ; 127
2d4: 90 93 80 00 sts 0x0080, r25
2d8: c0 cf rjmp .-128 ; 0x25a <digitalRead+0x22>
000002da <atexit>:
2da: 80 e0 ldi r24, 0x00 ; 0
2dc: 90 e0 ldi r25, 0x00 ; 0
2de: 08 95 ret
000002e0 <initVariant>:
2e0: 08 95 ret
000002e2 <__vector_16>:
2e2: 1f 92 push r1
2e4: 0f 92 push r0
2e6: 0f b6 in r0, 0x3f ; 63
2e8: 0f 92 push r0
2ea: 11 24 eor r1, r1
2ec: 2f 93 push r18
2ee: 3f 93 push r19
2f0: 8f 93 push r24
2f2: 9f 93 push r25
2f4: af 93 push r26
2f6: bf 93 push r27
2f8: 80 91 00 01 lds r24, 0x0100
2fc: 90 91 01 01 lds r25, 0x0101
300: a0 91 02 01 lds r26, 0x0102
304: b0 91 03 01 lds r27, 0x0103
308: 30 91 08 01 lds r19, 0x0108
30c: 23 e0 ldi r18, 0x03 ; 3
30e: 23 0f add r18, r19
310: 2d 37 cpi r18, 0x7D ; 125
312: 68 f1 brcs .+90 ; 0x36e <__vector_16+0x8c>
314: 26 e8 ldi r18, 0x86 ; 134
316: 23 0f add r18, r19
318: 02 96 adiw r24, 0x02 ; 2
31a: a1 1d adc r26, r1
31c: b1 1d adc r27, r1
31e: 20 93 08 01 sts 0x0108, r18
322: 80 93 00 01 sts 0x0100, r24
326: 90 93 01 01 sts 0x0101, r25
32a: a0 93 02 01 sts 0x0102, r26
32e: b0 93 03 01 sts 0x0103, r27
332: 80 91 04 01 lds r24, 0x0104
336: 90 91 05 01 lds r25, 0x0105
33a: a0 91 06 01 lds r26, 0x0106
33e: b0 91 07 01 lds r27, 0x0107
342: 01 96 adiw r24, 0x01 ; 1
344: a1 1d adc r26, r1
346: b1 1d adc r27, r1
348: 80 93 04 01 sts 0x0104, r24
34c: 90 93 05 01 sts 0x0105, r25
350: a0 93 06 01 sts 0x0106, r26
354: b0 93 07 01 sts 0x0107, r27
358: bf 91 pop r27
35a: af 91 pop r26
35c: 9f 91 pop r25
35e: 8f 91 pop r24
360: 3f 91 pop r19
362: 2f 91 pop r18
364: 0f 90 pop r0
366: 0f be out 0x3f, r0 ; 63
368: 0f 90 pop r0
36a: 1f 90 pop r1
36c: 18 95 reti
36e: 01 96 adiw r24, 0x01 ; 1
370: a1 1d adc r26, r1
372: b1 1d adc r27, r1
374: d4 cf rjmp .-88 ; 0x31e <__vector_16+0x3c>
00000376 <millis>:
376: 2f b7 in r18, 0x3f ; 63
378: f8 94 cli
37a: 60 91 00 01 lds r22, 0x0100
37e: 70 91 01 01 lds r23, 0x0101
382: 80 91 02 01 lds r24, 0x0102
386: 90 91 03 01 lds r25, 0x0103
38a: 2f bf out 0x3f, r18 ; 63
38c: 08 95 ret
0000038e <micros>:
38e: 3f b7 in r19, 0x3f ; 63
390: f8 94 cli
392: 80 91 04 01 lds r24, 0x0104
396: 90 91 05 01 lds r25, 0x0105
39a: a0 91 06 01 lds r26, 0x0106
39e: b0 91 07 01 lds r27, 0x0107
3a2: 26 b5 in r18, 0x26 ; 38
3a4: a8 9b sbis 0x15, 0 ; 21
3a6: 05 c0 rjmp .+10 ; 0x3b2 <micros+0x24>
3a8: 2f 3f cpi r18, 0xFF ; 255
3aa: 19 f0 breq .+6 ; 0x3b2 <micros+0x24>
3ac: 01 96 adiw r24, 0x01 ; 1
3ae: a1 1d adc r26, r1
3b0: b1 1d adc r27, r1
3b2: 3f bf out 0x3f, r19 ; 63
3b4: 66 27 eor r22, r22
3b6: 78 2f mov r23, r24
3b8: 89 2f mov r24, r25
3ba: 9a 2f mov r25, r26
3bc: 62 0f add r22, r18
3be: 71 1d adc r23, r1
3c0: 81 1d adc r24, r1
3c2: 91 1d adc r25, r1
3c4: 66 0f add r22, r22
3c6: 77 1f adc r23, r23
3c8: 88 1f adc r24, r24
3ca: 99 1f adc r25, r25
3cc: 66 0f add r22, r22
3ce: 77 1f adc r23, r23
3d0: 88 1f adc r24, r24
3d2: 99 1f adc r25, r25
3d4: 08 95 ret
000003d6 <delay>:
3d6: cf 92 push r12
3d8: df 92 push r13
3da: ef 92 push r14
3dc: ff 92 push r15
3de: 0f 93 push r16
3e0: 1f 93 push r17
3e2: cf 93 push r28
3e4: df 93 push r29
3e6: 00 d0 rcall .+0 ; 0x3e8 <delay+0x12>
3e8: 00 d0 rcall .+0 ; 0x3ea <delay+0x14>
3ea: cd b7 in r28, 0x3d ; 61
3ec: de b7 in r29, 0x3e ; 62
3ee: 3f b7 in r19, 0x3f ; 63
3f0: f8 94 cli
3f2: c0 90 04 01 lds r12, 0x0104
3f6: d0 90 05 01 lds r13, 0x0105
3fa: e0 90 06 01 lds r14, 0x0106
3fe: f0 90 07 01 lds r15, 0x0107
402: 26 b5 in r18, 0x26 ; 38
404: a8 9b sbis 0x15, 0 ; 21
406: 07 c0 rjmp .+14 ; 0x416 <delay+0x40>
408: 2f 3f cpi r18, 0xFF ; 255
40a: 29 f0 breq .+10 ; 0x416 <delay+0x40>
40c: 4f ef ldi r20, 0xFF ; 255
40e: c4 1a sub r12, r20
410: d4 0a sbc r13, r20
412: e4 0a sbc r14, r20
414: f4 0a sbc r15, r20
416: 3f bf out 0x3f, r19 ; 63
418: fe 2c mov r15, r14
41a: ed 2c mov r14, r13
41c: dc 2c mov r13, r12
41e: cc 24 eor r12, r12
420: c2 0e add r12, r18
422: d1 1c adc r13, r1
424: e1 1c adc r14, r1
426: f1 1c adc r15, r1
428: cc 0c add r12, r12
42a: dd 1c adc r13, r13
42c: ee 1c adc r14, r14
42e: ff 1c adc r15, r15
430: cc 0c add r12, r12
432: dd 1c adc r13, r13
434: ee 1c adc r14, r14
436: ff 1c adc r15, r15
438: 86 01 movw r16, r12
43a: 61 15 cp r22, r1
43c: 71 05 cpc r23, r1
43e: 81 05 cpc r24, r1
440: 91 05 cpc r25, r1
442: 09 f4 brne .+2 ; 0x446 <delay+0x70>
444: 41 c0 rjmp .+130 ; 0x4c8 <delay+0xf2>
446: 69 83 std Y+1, r22 ; 0x01
448: 7a 83 std Y+2, r23 ; 0x02
44a: 8b 83 std Y+3, r24 ; 0x03
44c: 9c 83 std Y+4, r25 ; 0x04
44e: 0e 94 b7 02 call 0x56e ; 0x56e <yield>
452: 3f b7 in r19, 0x3f ; 63
454: f8 94 cli
456: c0 90 04 01 lds r12, 0x0104
45a: d0 90 05 01 lds r13, 0x0105
45e: e0 90 06 01 lds r14, 0x0106
462: f0 90 07 01 lds r15, 0x0107
466: 26 b5 in r18, 0x26 ; 38
468: 69 81 ldd r22, Y+1 ; 0x01
46a: 7a 81 ldd r23, Y+2 ; 0x02
46c: 8b 81 ldd r24, Y+3 ; 0x03
46e: 9c 81 ldd r25, Y+4 ; 0x04
470: a8 9b sbis 0x15, 0 ; 21
472: 07 c0 rjmp .+14 ; 0x482 <delay+0xac>
474: 2f 3f cpi r18, 0xFF ; 255
476: 29 f0 breq .+10 ; 0x482 <delay+0xac>
478: ef ef ldi r30, 0xFF ; 255
47a: ce 1a sub r12, r30
47c: de 0a sbc r13, r30
47e: ee 0a sbc r14, r30
480: fe 0a sbc r15, r30
482: 3f bf out 0x3f, r19 ; 63
484: fe 2c mov r15, r14
486: ed 2c mov r14, r13
488: dc 2c mov r13, r12
48a: cc 24 eor r12, r12
48c: c2 0e add r12, r18
48e: d1 1c adc r13, r1
490: e1 1c adc r14, r1
492: f1 1c adc r15, r1
494: cc 0c add r12, r12
496: dd 1c adc r13, r13
498: ee 1c adc r14, r14
49a: ff 1c adc r15, r15
49c: cc 0c add r12, r12
49e: dd 1c adc r13, r13
4a0: ee 1c adc r14, r14
4a2: ff 1c adc r15, r15
4a4: 96 01 movw r18, r12
4a6: 20 1b sub r18, r16
4a8: 31 0b sbc r19, r17
4aa: 28 3e cpi r18, 0xE8 ; 232
4ac: 33 40 sbci r19, 0x03 ; 3
4ae: 28 f2 brcs .-118 ; 0x43a <delay+0x64>
4b0: 61 50 subi r22, 0x01 ; 1
4b2: 71 09 sbc r23, r1
4b4: 81 09 sbc r24, r1
4b6: 91 09 sbc r25, r1
4b8: 08 51 subi r16, 0x18 ; 24
4ba: 1c 4f sbci r17, 0xFC ; 252
4bc: 61 15 cp r22, r1
4be: 71 05 cpc r23, r1
4c0: 81 05 cpc r24, r1
4c2: 91 05 cpc r25, r1
4c4: 09 f0 breq .+2 ; 0x4c8 <delay+0xf2>
4c6: bf cf rjmp .-130 ; 0x446 <delay+0x70>
4c8: 0f 90 pop r0
4ca: 0f 90 pop r0
4cc: 0f 90 pop r0
4ce: 0f 90 pop r0
4d0: df 91 pop r29
4d2: cf 91 pop r28
4d4: 1f 91 pop r17
4d6: 0f 91 pop r16
4d8: ff 90 pop r15
4da: ef 90 pop r14
4dc: df 90 pop r13
4de: cf 90 pop r12
4e0: 08 95 ret
000004e2 <delayMicroseconds>:
4e2: 82 30 cpi r24, 0x02 ; 2
4e4: 91 05 cpc r25, r1
4e6: 38 f0 brcs .+14 ; 0x4f6 <delayMicroseconds+0x14>
4e8: 88 0f add r24, r24
4ea: 99 1f adc r25, r25
4ec: 88 0f add r24, r24
4ee: 99 1f adc r25, r25
4f0: 05 97 sbiw r24, 0x05 ; 5
4f2: 01 97 sbiw r24, 0x01 ; 1
4f4: f1 f7 brne .-4 ; 0x4f2 <delayMicroseconds+0x10>
4f6: 08 95 ret
000004f8 <init>:
4f8: 78 94 sei
4fa: 84 b5 in r24, 0x24 ; 36
4fc: 82 60 ori r24, 0x02 ; 2
4fe: 84 bd out 0x24, r24 ; 36
500: 84 b5 in r24, 0x24 ; 36
502: 81 60 ori r24, 0x01 ; 1
504: 84 bd out 0x24, r24 ; 36
506: 85 b5 in r24, 0x25 ; 37
508: 82 60 ori r24, 0x02 ; 2
50a: 85 bd out 0x25, r24 ; 37
50c: 85 b5 in r24, 0x25 ; 37
50e: 81 60 ori r24, 0x01 ; 1
510: 85 bd out 0x25, r24 ; 37
512: ee e6 ldi r30, 0x6E ; 110
514: f0 e0 ldi r31, 0x00 ; 0
516: 80 81 ld r24, Z
518: 81 60 ori r24, 0x01 ; 1
51a: 80 83 st Z, r24
51c: e1 e8 ldi r30, 0x81 ; 129
51e: f0 e0 ldi r31, 0x00 ; 0
520: 10 82 st Z, r1
522: 80 81 ld r24, Z
524: 82 60 ori r24, 0x02 ; 2
526: 80 83 st Z, r24
528: 80 81 ld r24, Z
52a: 81 60 ori r24, 0x01 ; 1
52c: 80 83 st Z, r24
52e: e0 e8 ldi r30, 0x80 ; 128
530: f0 e0 ldi r31, 0x00 ; 0
532: 80 81 ld r24, Z
534: 81 60 ori r24, 0x01 ; 1
536: 80 83 st Z, r24
538: e1 eb ldi r30, 0xB1 ; 177
53a: f0 e0 ldi r31, 0x00 ; 0
53c: 80 81 ld r24, Z
53e: 84 60 ori r24, 0x04 ; 4
540: 80 83 st Z, r24
542: e0 eb ldi r30, 0xB0 ; 176
544: f0 e0 ldi r31, 0x00 ; 0
546: 80 81 ld r24, Z
548: 81 60 ori r24, 0x01 ; 1
54a: 80 83 st Z, r24
54c: ea e7 ldi r30, 0x7A ; 122
54e: f0 e0 ldi r31, 0x00 ; 0
550: 80 81 ld r24, Z
552: 84 60 ori r24, 0x04 ; 4
554: 80 83 st Z, r24
556: 80 81 ld r24, Z
558: 82 60 ori r24, 0x02 ; 2
55a: 80 83 st Z, r24
55c: 80 81 ld r24, Z
55e: 81 60 ori r24, 0x01 ; 1
560: 80 83 st Z, r24
562: 80 81 ld r24, Z
564: 80 68 ori r24, 0x80 ; 128
566: 80 83 st Z, r24
568: 10 92 c1 00 sts 0x00C1, r1
56c: 08 95 ret
0000056e <yield>:
56e: 08 95 ret
00000570 <main>:
570: 0e 94 7c 02 call 0x4f8 ; 0x4f8 <init>
574: 0e 94 70 01 call 0x2e0 ; 0x2e0 <initVariant>
578: 0e 94 75 00 call 0xea ; 0xea <setup>
57c: 80 e0 ldi r24, 0x00 ; 0
57e: 90 e0 ldi r25, 0x00 ; 0
580: 89 2b or r24, r25
582: 29 f0 breq .+10 ; 0x58e <main+0x1e>
584: 0e 94 89 00 call 0x112 ; 0x112 <loop>
588: 0e 94 00 00 call 0 ; 0x0 <__vectors>
58c: fb cf rjmp .-10 ; 0x584 <main+0x14>
58e: 0e 94 89 00 call 0x112 ; 0x112 <loop>
592: 0e 94 89 00 call 0x112 ; 0x112 <loop>
596: fb cf rjmp .-10 ; 0x58e <main+0x1e>
00000598 <_exit>:
598: f8 94 cli
0000059a <__stop_program>:
59a: ff cf rjmp .-2 ; 0x59a <__stop_program>
view raw obj-dump hosted with ❤ by GitHub

解説

C++11

コンパイラは Arduino.app に入っている avr-gcc を使っているが -std=c++11 を指定して C++11 の機能を有効にしている (constexpr 関数) なので素の Arduino.app からだとコンパイルできない。

Arduino ビルトイン関数の再実装

Arduino 側で portOutputRegister digitalPinToBitMask digitalPinToPort といった関数が定義されており、これらは PROGMEM からテーブルをひいてくる実装になっているので、まずこれをコンパイル時にできるように constexpr 関数で再実装した。

C++14 ならもっと簡単に書けるはずなんだけど、C++11 相当だと関数本体に return 以外書けないのでキモい実装になっている。

digitalWriteMulti

ここがキモで、まず digitalWriteMulti でうけて、digitalWriteMulti_ で再帰的に処理している。digitalWriteMulti_ の最初の3引数は、持ってまわっている状態で、残りは元の digitalWriteMulti に渡された引数。

C++ のテンプレートメタプログラミングではこのようにパターンマッチでしか可変長引数を扱えないみたい。だけど思ったより綺麗に実装できた気がする。

WRITE_MULTI() マクロ

これはプリプロセッサマクロで単に置換している。テンプレート引数として渡すのが気持ち悪かったので(慣れかもしれない)マクロでラップしている。

その他

C++ はほとんど書いたことがないので、きっともっといい方法がありそうだと思う。特にテンプレートメタプログラミングで可変長引数を処理するとき、状態を全てテンプレート引数の頭にフラットに渡しているのがダサい。うまくコンパイル時だけ使えるコンテナみたいなのを持ちまわしていければよかったが、おもいつかなかった。

pinMode にも応用できると思うが pinMode は PORTx もいじるのでとりあえず省略した。

大きなメリット

同じようなことはテンプレートを使わない場合(素のCとか)だとまずできない。

特に「指定しなかった部分のビットはかえない」を実現しようとすると、どうしてもビットマスクと実際に設定するビット値の2つを扱わないといけないので、かなりコードが煩雑になる。テンプレート化することで表面上の可読性がかなり上がる (テンプレート部分はアレだけど)

そしてコンパイル時にチェックができる点はおおきい。マイコンはどうしてもフィードバックがすくないので、実行時エラーというのは実際気付くのが難しい。

速度的にも当然メリットがある。これの場合はコンパイルしたあとは素で書く場合と完全に同じバイナリになる。

000000ea <setup>:
; PORTD = (PORTD & 0b11110000) | (0b1010);
  ea:	8b b1       	in	r24, 0x0b	; 11
  ec:	80 7f       	andi	r24, 0xF0	; 240
  ee:	8a 60       	ori	r24, 0x0A	; 10
  f0:	8b b9       	out	0x0b, r24	; 11
; asm volatile ("nop");	// marker
  f2:	00 00       	nop
; WRITE_MULTI(...)
  f4:	8b b1       	in	r24, 0x0b	; 11
  f6:	80 7f       	andi	r24, 0xF0	; 240
  f8:	8a 60       	ori	r24, 0x0A	; 10
  fa:	8b b9       	out	0x0b, r24	; 11

素の digitalWrite の場合

; digitalWrite(0, LOW);
 10a:	60 e0       	ldi	r22, 0x00	; 0
 10c:	80 e0       	ldi	r24, 0x00	; 0
 10e:	0c 94 c8 00 	jmp	0x190	; 0x190 <digitalWrite>

で関数コールして

00000190 <digitalWrite>:
 190:	90 e0       	ldi	r25, 0x00	; 0
 192:	fc 01       	movw	r30, r24
 194:	e8 59       	subi	r30, 0x98	; 152
 196:	ff 4f       	sbci	r31, 0xFF	; 255
 198:	24 91       	lpm	r18, Z
 19a:	fc 01       	movw	r30, r24
 19c:	e4 58       	subi	r30, 0x84	; 132
 19e:	ff 4f       	sbci	r31, 0xFF	; 255
 1a0:	34 91       	lpm	r19, Z
 1a2:	fc 01       	movw	r30, r24
 1a4:	e0 57       	subi	r30, 0x70	; 112
 1a6:	ff 4f       	sbci	r31, 0xFF	; 255
 1a8:	84 91       	lpm	r24, Z
 1aa:	88 23       	and	r24, r24
 1ac:	99 f0       	breq	.+38     	; 0x1d4 <digitalWrite+0x44>
 1ae:	21 11       	cpse	r18, r1
 1b0:	16 c0       	rjmp	.+44     	; 0x1de <digitalWrite+0x4e>
 1b2:	e8 2f       	mov	r30, r24
 1b4:	f0 e0       	ldi	r31, 0x00	; 0
 1b6:	ee 0f       	add	r30, r30
 1b8:	ff 1f       	adc	r31, r31
 1ba:	e2 55       	subi	r30, 0x52	; 82
 1bc:	ff 4f       	sbci	r31, 0xFF	; 255
 1be:	a5 91       	lpm	r26, Z+
 1c0:	b4 91       	lpm	r27, Z
 1c2:	8f b7       	in	r24, 0x3f	; 63
 1c4:	f8 94       	cli
 1c6:	9c 91       	ld	r25, X
 1c8:	61 11       	cpse	r22, r1
 1ca:	05 c0       	rjmp	.+10     	; 0x1d6 <digitalWrite+0x46>
 1cc:	30 95       	com	r19
 1ce:	93 23       	and	r25, r19
 1d0:	9c 93       	st	X, r25
 1d2:	8f bf       	out	0x3f, r24	; 63
 1d4:	08 95       	ret
 1d6:	93 2b       	or	r25, r19
 1d8:	9c 93       	st	X, r25
 1da:	8f bf       	out	0x3f, r24	; 63
 1dc:	fb cf       	rjmp	.-10     	; 0x1d4 <digitalWrite+0x44>
 1de:	23 30       	cpi	r18, 0x03	; 3
 1e0:	29 f1       	breq	.+74     	; 0x22c <digitalWrite+0x9c>
 1e2:	60 f0       	brcs	.+24     	; 0x1fc <digitalWrite+0x6c>
 1e4:	27 30       	cpi	r18, 0x07	; 7
 1e6:	e1 f0       	breq	.+56     	; 0x220 <digitalWrite+0x90>
 1e8:	28 30       	cpi	r18, 0x08	; 8
 1ea:	a1 f0       	breq	.+40     	; 0x214 <digitalWrite+0x84>
 1ec:	24 30       	cpi	r18, 0x04	; 4
 1ee:	09 f7       	brne	.-62     	; 0x1b2 <digitalWrite+0x22>
 1f0:	90 91 80 00 	lds	r25, 0x0080
 1f4:	9f 7d       	andi	r25, 0xDF	; 223
 1f6:	90 93 80 00 	sts	0x0080, r25
 1fa:	db cf       	rjmp	.-74     	; 0x1b2 <digitalWrite+0x22>
 1fc:	21 30       	cpi	r18, 0x01	; 1
 1fe:	31 f0       	breq	.+12     	; 0x20c <digitalWrite+0x7c>
 200:	22 30       	cpi	r18, 0x02	; 2
 202:	b9 f6       	brne	.-82     	; 0x1b2 <digitalWrite+0x22>
 204:	94 b5       	in	r25, 0x24	; 36
 206:	9f 7d       	andi	r25, 0xDF	; 223
 208:	94 bd       	out	0x24, r25	; 36
 20a:	d3 cf       	rjmp	.-90     	; 0x1b2 <digitalWrite+0x22>
 20c:	94 b5       	in	r25, 0x24	; 36
 20e:	9f 77       	andi	r25, 0x7F	; 127
 210:	94 bd       	out	0x24, r25	; 36
 212:	cf cf       	rjmp	.-98     	; 0x1b2 <digitalWrite+0x22>
 214:	90 91 b0 00 	lds	r25, 0x00B0
 218:	9f 7d       	andi	r25, 0xDF	; 223
 21a:	90 93 b0 00 	sts	0x00B0, r25
 21e:	c9 cf       	rjmp	.-110    	; 0x1b2 <digitalWrite+0x22>
 220:	90 91 b0 00 	lds	r25, 0x00B0
 224:	9f 77       	andi	r25, 0x7F	; 127
 226:	90 93 b0 00 	sts	0x00B0, r25
 22a:	c3 cf       	rjmp	.-122    	; 0x1b2 <digitalWrite+0x22>
 22c:	90 91 80 00 	lds	r25, 0x0080
 230:	9f 77       	andi	r25, 0x7F	; 127
 232:	90 93 80 00 	sts	0x0080, r25
 236:	bd cf       	rjmp	.-134    	; 0x1b2 <digitalWrite+0x22>

となっていて、1bit 書くだけでかなり大層なことをしていることがわかる。

  1. トップ
  2. tech
  3. Arduino の digitalWrite をコンパイルタイムに解決する

先日 [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 をコンパイル時に展開する

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 をビルドする

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

やりたいこと

  • ヘッドフォン出力から 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

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

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)

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

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. 今夜は寝かさないぞモバイルバッテリー

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. 任意固定小数点→浮動小数点変換スニペット

ビットフィールドとは

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++ でビットフィールドを再発明する