ESP8266 (ESP-WROOM-02) の Deep Sleep でデータを引き継ぐ | tech - 氾濫原 を試しているとき、起動時に必ず一定のゴミっぽいのがつくので気になっていた。

が、実はこれブート時に必ずでるメッセージのようで、ボーレートがあっていない。ボーレートを 74880 にすると (書きこむコードでも 74880 を使うように変更)

と、完全なメッセージがみれる。ちなみに rst cause (リセット原因) は RTC メモリのシステム用の領域に入っているみたい。

そういうわけなので、デバッグ用に UART 使うなら 74880 にあわせておいたほうが気持ち悪くない。

  1. トップ
  2. tech
  3. ESP8266 起動時にシリアルにでるゴミっぽいもの

ESP8266 の deep sleep モードは CPU などの電源を全て落として、RTC (時刻を持ってるわけではないっぽいけど内蔵RTCがあるらしい) だけ生かして設定時刻になったら RTC にリセットさせるという挙動をする。

そういうわけで、ディープスリープ後はリセット直後と同じ状態になり、メモリなどは引き継げない。

もちろん deep sleep 時には wifi の接続も切れてしまうので、毎回繋ぎなおしになる。自分の環境だとAPに接続するまで5秒ぐらいかかるし、接続中は電流が80mA(データシート的には最大で180mA)ぐらい流れる。高い頻度で起動すると初期化のコストがかなり大きい。

ユースケース

センサーデータは数秒に一度取得したいが、データ送信はさらに少ない頻度でどこかに送信したい、というような場合。

system_rtc_mem_read / system_rtc_mem_write

ESP8266 自体にこういうAPIがあり、RTC にあるメモリ(揮発性)を読み書きすることができる。RTC は deep sleep でも有効なので、これは電源が接続されている限り保持されるメモリのようだ (電源を切ると初期化される)。ユーザ領域として最大512bytes使えることになっている。

ということで、これを使って起動回数を記録しつつ、何回かに一度 WiFi 接続するようなサンプルを書いてみた (実際接続はしてないけど)

未初期化の system_rtc_mem_read は不定値がかえってくるみたいなので、CRC なりなんなりでハッシュをとって明示的に初期化をしたほうがよさそう。

#include <Arduino.h>
extern "C" {
#include <user_interface.h>
};
	
// system_rtc_mem_write() 先のブロックアドレス。
// 4 bytes で align されており、先頭256bytes はシステム予約領域
// 64 から書けるはずだが、65 以降でないとうまくいかなかった。。
static const uint32_t USER_DATA_ADDR = 66;

// ハッシュ関数 (FNV) CRC でいいけどコード的に短いのでFNV
static uint32_t fnv_1_hash_32(uint8_t *bytes, size_t length) {
	static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U;
	static const uint32_t FNV_PRIME_32 = 16777619U;
	uint32_t hash = FNV_OFFSET_BASIS_32;;
	for(size_t i = 0 ; i < length ; ++i) hash = (FNV_PRIME_32 * hash) ^ (bytes[i]);
	return hash;
}

// struct の hash (先頭にあることを想定) を除くデータ部分のハッシュを計算する
template <class T>
uint32_t calc_hash(T& data) {
	return fnv_1_hash_32(((uint8_t*)&data) + sizeof(data.hash), sizeof(T) - sizeof(data.hash));
}

struct {
	// retain data
	uint32_t hash;
	uint16_t count;
	uint8_t  send;
	uint16_t etc2;
} retain_data;

void post_sensor_data();

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

	Serial.begin(9600);
	Serial.println("Initializing...");

	// データ読みこみ
	bool ok;
	ok = system_rtc_mem_read(USER_DATA_ADDR, &retain_data, sizeof(retain_data));
	if (!ok) {
		Serial.println("system_rtc_mem_read failed");
	}
	Serial.print("retain_data.count = ");
	Serial.println(retain_data.count);

	// ハッシュが一致していない場合、初期化されていないとみなし、初期化処理を行う
	uint32_t hash = calc_hash(retain_data);
	if (retain_data.hash != hash) {
		Serial.println("retain_data may be uninitialized");
		retain_data.count = 0;
		retain_data.send = 0;
	}

	// データの変更処理(任意)
	retain_data.count++;
	if (!retain_data.send) {
		// 4回に1度送信する
		retain_data.send = retain_data.count % 4 == 0;
	} else {
		Serial.println("send data");
		retain_data.send = 0;
		// なんか定期的に書きこみたい処理
		post_sensor_data();
	}

	// 書きこみ処理。hash を計算していれておく
	retain_data.hash = hash = calc_hash(retain_data);
	ok = system_rtc_mem_write(USER_DATA_ADDR, &retain_data, sizeof(retain_data));
	if (!ok) {
		Serial.println("system_rtc_mem_write failed");
	}

	// 動作確認用のダミー
	digitalWrite(13, HIGH);
	delay(1000);
	digitalWrite(13, LOW);

	if (retain_data.send) {
		ESP.deepSleep(1e6, WAKE_RF_DEFAULT);
	} else {
		// sendしない場合は WIFI をオフで起動させる
		ESP.deepSleep(1e6, WAKE_RF_DISABLED);
	}
}

void loop() {
}

// dummy
void post_sensor_data() {
	for (uint i = 0; i < 5; i++) {
		digitalWrite(13, HIGH);
		delay(300);
		digitalWrite(13, LOW);
		delay(300);
	}
}
  1. トップ
  2. tech
  3. ESP8266 (ESP-WROOM-02) の Deep Sleep でデータを引き継ぐ

こんな感じで遊べる。

HttpClient (portformio だと id=66) の使いかたで微妙にハマった。

こんな感じの GrowthForecastClient クラスをつくっておいて、

HttpClient とホスト名や認証情報を渡して

WiFiClient wifiClient;
HttpClient http(wifiClient);
GrowthForecastClient gf(http, GF_HOST, GF_USER, GF_PASS);

https://github.com/cho45/esp-wroom-02-sketch/blob/master/src/main.cpp#L24

post する

	float temp = adt7410.read();
	Serial.print("adt7410 = ");
	Serial.println(temp);
	gf.post("/home/test/temp", (int32_t)(temp * 1000));

https://github.com/cho45/esp-wroom-02-sketch/blob/master/src/main.cpp#L165

GF は整数しか扱えないので、1000倍してpostして、GFのグラフの設定で ÷ 1000 を選択する。

HttpClient 以外はとくにハマりどころはない。適当にグラフ化するならこれ以上簡単なものはない。

ただ、結構 post に時間がかかる。特に request body にパラメータを書こうとするとあまりにも遅いのでクエリパラメータにしている。なんかどっかですごい効率が悪いことをしていそうだがよくわからない。

  1. トップ
  2. tech
  3. ESP8266 から GrowthForecast へセンサーデータをPOST

cmake で CGI (blosxom クローンぽいやつ) を書きました。(実用のためというわけではないです)

cmake は -P をつけるとスクリプトモードになるので、それでふつうにCGIのコードを書くだけです。

データ型として文字列しかないのがつらい感じです。リストみたいなのも使えるけど、これもセミコロン区切りの文字列をリストとみなすという仕様になっている。シェルスクリプトのノリに近い。

ただ、正規表現がある程度使えるので、文字列操作は割と楽に書くことができる。

なお、cmakeで普通使うような命令を使わない(使えない)のでまったく通常ユースのためのノウハウはほとんど溜まらず、学習という意味ではあんまり意味がないというオチでした。

  1. トップ
  2. tech
  3. cmake で CGI を書く

ただのちっちゃい 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