月途中から固定回線契約
一ヶ月ぶん
ESP8266 (ESP-WROOM-02) の Deep Sleep でデータを引き継ぐ | tech - 氾濫原 を試しているとき、起動時に必ず一定のゴミっぽいのがつくので気になっていた。
が、実はこれブート時に必ずでるメッセージのようで、ボーレートがあっていない。ボーレートを 74880 にすると (書きこむコードでも 74880 を使うように変更)
と、完全なメッセージがみれる。ちなみに rst cause (リセット原因) は RTC メモリのシステム用の領域に入っているみたい。
そういうわけなので、デバッグ用に UART 使うなら 74880 にあわせておいたほうが気持ち悪くない。
ESP8266 の deep sleep モードは CPU などの電源を全て落として、RTC (時刻を持ってるわけではないっぽいけど内蔵RTCがあるらしい) だけ生かして設定時刻になったら RTC にリセットさせるという挙動をする。
そういうわけで、ディープスリープ後はリセット直後と同じ状態になり、メモリなどは引き継げない。
もちろん deep sleep 時には wifi の接続も切れてしまうので、毎回繋ぎなおしになる。自分の環境だとAPに接続するまで5秒ぐらいかかるし、接続中は電流が80mA(データシート的には最大で180mA)ぐらい流れる。高い頻度で起動すると初期化のコストがかなり大きい。
センサーデータは数秒に一度取得したいが、データ送信はさらに少ない頻度でどこかに送信したい、というような場合。
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);
}
} こんな感じで遊べる。
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 にパラメータを書こうとするとあまりにも遅いのでクエリパラメータにしている。なんかどっかですごい効率が悪いことをしていそうだがよくわからない。
cmake で CGI (blosxom クローンぽいやつ) を書きました。(実用のためというわけではないです)
cmake は -P をつけるとスクリプトモードになるので、それでふつうにCGIのコードを書くだけです。
データ型として文字列しかないのがつらい感じです。リストみたいなのも使えるけど、これもセミコロン区切りの文字列をリストとみなすという仕様になっている。シェルスクリプトのノリに近い。
ただ、正規表現がある程度使えるので、文字列操作は割と楽に書くことができる。
なお、cmakeで普通使うような命令を使わない(使えない)のでまったく通常ユースのためのノウハウはほとんど溜まらず、学習という意味ではあんまり意味がないというオチでした。
ただのちっちゃい 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 cho45
なお未だ買ってない。
アナログ回路への理解を深めたい。まだ全然、やりたいことをすぐに実現できるレベルにならない。どうすればできるのかわからない、ということが多すぎる。
2015年もたくさんコードかきました。他人に承認されないことはせめて自分で承認しましょう。つらい
[tech] リファレンスマニュアルをインクリメンタル検索するやつを Electron で実装した | Wed, Oct 14. 2015 - 氾濫原
ドキュメントビューワ。Mac AppStore まで出してみたが全く売れておりません。自分では便利に使ってる
[tech] Arduino の digitalWrite / pinMode / digitalRead をコンパイル時に展開する | Wed, Dec 16. 2015 - 氾濫原
digitalWrite とかを静的に解決するやつ。
[tech] もう僕らは OpenGL ライブラリにリンクするビルドに悩むことはない | Thu, Oct 8. 2015 - 氾濫原
KX3(無線機)用のパナダプター(Panoramic Adapter = FFTウォーターフォール) WebGL に書きなおしたりした。便利
[tech] CNC フライス Grbl 制御編 | Sun, Aug 16. 2015 - 氾濫原
CNCフライス制御用のアプリケーション。最近あんまり切削してない。
[tech] スクリーンキャスト用のキーストローク表示アプリ | Sat, Feb 14. 2015 - 氾濫原
入力したキーを画面に表示するやつ。
[tech] CopyHook というペーストボードの中身をいじるツールを作った | Wed, Mar 18. 2015 - 氾濫原
クリップボードコピーしたときにフックでJS実行するやつ。なんかうまく動いてない気がするがデバッグの時間がとれない。
[tech] JavaScript で書かれたリードソロモン符号のエンコーダ・デコーダ | Tue, Mar 31. 2015 - 氾濫原
任意のリードソロモン(誤り訂正)符号のエンコーダデコーダ。Zxing (Java) の一部ライブラリの移植
[tech] WebAudio の BiquadFilterNode の周波数特性をグラフにするやつ | Fri, Mar 20. 2015 - 氾濫原
WebAudio のフィルタの周波数特性を出すやつ。
[tech] デカい文字をA4で分割して印刷するツールをJSで書いた | Sat, Mar 7. 2015 - 氾濫原
ー https://cho45.stfuawsc.com/dekaimoji-a4/
A4プリンタでデカい文字が印刷できるやつ。
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>
こうなる
| #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; | |
| } |
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ファイルとかで使えるの、当然もうありそうだけど見つけられてない。
モバイバッテリーは低電流時、充電完了と判断してパワーオフする(出力回路の動作をやめる)が、これをやらせたくない場合どうすればいいか。現時点でのメモ
Arduino に適当なプログラムを書きこんで、ポートに抵抗を繋ぎ(複数ポートにわけて) パワーオフするかどうかを調べた。測定時は電源供給経路途中に1Ωの抵抗をいれ、オシロでこの抵抗の両端電圧を測ることで間接的に実測の電流値を求めている。
Arduino のベースの消費電力は40mA程度。なので以下でさらに大きな電流を流しているが、40mA との切り替えということになる。
Anker は製品説明書に最低限充電電流が書いてある (50mA)
しかし連続で流し続けなければならないのか、パルスでいいのかはわからない。
A1208
検知できないと30秒ぐらいでパワーオフする
すくなくとも短いパルスではリセットできないっぽい
一定時間内の平均消費をみている?
PB-T1
説明書には特にオートパワーオフの閾値の記載なし。
約3分後にパワーオフ。電流を検知している間はバッテリーランプが点灯するっぽい?
ランプが5秒ぐらいで消灯するが、その前にパルスを検知すればいいっぽい… 謎
100mA ぐらい常時流しといたらいいんちゃう?
→ 5V 100mA (0.5W)
3.3V 10000mAh のやつは26Whぐらいなので、とても厳しいという状態でなければそれでもいいかもしれない。
Quick Charge バッテリの場合、電流値で検出しているのか電力値で検出しているのかで大きくわかれる。12V 100mA 流すことになったら常時 1.2W 消費ということになりつらい。
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 の直で繋げられるようにしようとすると、意外と干渉するので、ピンヘッダまわりに十分スペースが必要