Printf utils
mwxライブラリでは、書式出力をC言語で常用されるprintfの実装を用いています。いくつかの関数の関数定義が利用できます。
int mwx_printf(const char* format, ...)
int mwx_snprintf(char* buffer, size_t count, const char* format, ...)
mwx_printf()
はSerialオブジェクトに対してprintf出力を行います。Serial.printfmt()
と同じ処理になります。
mwx_snprintf()
はバッファに対してsnprintfを行います。
pack_bits()
指定したビット位置に1をセットします。
constexpr uint32_t pack_bits(...)
パラメータは可変数引数で指定でき、各パラメータはビット位置を指定する0..31の整数を指定する。例えばpack_bits(1,3,6)
と指定すると((1UL<<1)|(1UL<<3)|(1UL<<6))
を返します。
背景
IOポート(DI,DO)の状態など各種ビットマップに値を参照・設定する場面があり、その記述を簡素化するため。
collect_bits()
整数から指定したビット位置の値を取得し、指定した順番のビットマップを作成します。
constexpr uint32_t collect_bits(uint32_t bm, ...)
パラメータbmに指定する値から、その後の可変数パラメータで指定する0..31のビット位置に対応する値を取り出します。取り出した値はパラメータ順に並べビットマップとして戻り値になります。
ビットマップの並び順は、最初のパラメータを上位ビットとし末尾のパラメータがbit0になります。
uint32_t b1 = 0x12; // (b00010010)
uint32_t b2 = collect_bits(b1, 4, 2, 1, 0);
// bit4->1, bit2->0, bit1->1, bit0->0
// b2=0x10 (b1010)
例ではb1のビット4,2,1,0を取り出すと (1,0,1,0) になります。これをb1010として0x10のように計算されます。
背景
IOポート(DI,DO)の状態など各種ビットマップに値を参照・設定する場面があり、その記述を簡素化するため。
Byte array utils
読み出し
バイト配列から、uint8_t,
ビッグエンディアン並びとして、uint16_t
, uint32_t
の値を取得する。
inline uint8_t G_OCTET(const uint8_t*& p) {
return *(p)++;
}
inline uint16_t G_WORD(const uint8_t*& p) {
uint32_t r = *p++;
r = (r << 8) + *p++;
return r;
}
inline uint32_t G_DWORD(const uint8_t*& p) {
uint32_t r = *p++;
r = (r << 8) + *p++;
r = (r << 8) + *p++;
r = (r << 8) + *p++;
return r;
}
p
は読み出したバイト数分だけインクリメントされる。
書き込み
ポインタq
で指定するバイト配列にuint8_t
,ビッグエンディアンでuint16_t
,uint32_t
の値を書き込む。
inline uint8_t& S_OCTET(uint8_t*& q, uint8_t c) {
*q++ = c;
return *q;
}
inline uint8_t& S_WORD(uint8_t*& q, uint16_t c) {
*(q) = ((c) >> 8) & 0xff; (q)++;
*(q) = ((c) & 0xff); (q)++;
return *q;
}
inline uint8_t& S_DWORD(uint8_t*& q, uint32_t c) {
*(q) = ((c) >> 24) & 0xff; (q)++;
*(q) = ((c) >> 16) & 0xff; (q)++;
*(q) = ((c) >> 8) & 0xff; (q)++;
*(q) = ((c) & 0xff); (q)++;
return *q;
}
q
は書き込んだバイト数分だけインクリメントされる。
背景
無線パケットのデータペイロードの生成・分解時の操作を簡略化するため。
より簡略化したpack_bytes()
, expand_bytes()
も利用できます。
pack_bytes()
要素データを並べてバイト列を生成します。
uint8_t* pack_bytes(uint8_t* b, uint8_t* e, ...)
pack_bytes
はコンテナクラスのbegin()
,end()
イテレータをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。
可変引数パラメータに与えるデータは以下に示すとおりです。
char*
,uint8_t*
型の配列と配列長のペア。make_pair()
で生成できる。
smplbuf_u8& pack_bytes(smplbuf_u8& c, ...)
pack_bytes
はコンテナオブジェクトをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。コンテナの.push_back()
メソッドで末尾に追加します。
可変引数パラメータに与えるデータは以下に示すとおりです。
char*
,uint8_t*
型の配列と配列長のペア。make_pair()
で生成できる。
uint8_t
型のsmplbuf<>
コンテナ。コンテナ長(.size()
)のデータを格納する。
例
auto&& rx = the_twelite.receiver.read();
smplbuf<uint8_t, 128> buf;
mwx::pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // src addr (LID)
, uint8_t(0xCC) // cmd id (0xCC, fixed)
, uint8_t(rx.get_psRxDataApp()->u8Seq) // seqence number
, uint32_t(rx.get_addr_src_long()) // src addr (long)
, uint32_t(rx.get_addr_dst()) // dst addr
, uint8_t(rx.get_lqi()) // LQI
, uint16_t(rx.get_length()) // payload length
, rx.get_payload() // payload
);
この例では受信パケットの各属性やペイロードを別のバッファbuf
に再格納しています。
背景
無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t
型のバイト配列の記述を簡素化するため。
auto&& rx = the_twelite.receiver.read();
uint8_t data[128];
data[0] = rx.get_addr_src_lid();
data[1] = 0xCC;
data[2] = rx.get_psRxDataApp()->u8Seq;
data[4] = rx.get_addr_src_long() & 0x000000FF;
data[5] = (rx.get_addr_src_long() & 0x0000FF00) >> 8;
data[6] = (rx.get_addr_src_long() & 0x00FF0000) >> 16;
data[7] = (rx.get_addr_src_long() & 0xFF000000) >> 24;
...
上記はもっとも単純な記述だが、以下のようにByte array utilsを用いてバイト配列を生成できる。
auto&& rx = the_twelite.receiver.read();
uint8_t data[128], *q = data;
S_OCTET(q, rx.get_addr_src_lid());
S_OCTET(q, 0xCC);
S_OCTET(q, rx.get_psRxDataApp()->u8Seq);
S_DWORD(q, rx.get_addr_src_long());
S_DWORD(q, rx.get_addr_dst());
S_OCTET(q, rx.get_lqi());
S_WORD(q, rx.get_length());
for (auto x : rx.get_payload()) {
S_OCTET(q, x);
}
expand_bytes()
バイト列を分解し変数に格納します。
const uint8_t* expand_bytes(
const uint8_t* b, const uint8_t* e, ...)
expand_bytes()
は、パラメータにuint8_t*
型のイテレータの組み合わせを指定します。これは解析対象の先頭と末尾の次のイテレータの指定となります。e
の位置まで解析が進んだ場合はエラーとなりnullptr
を返します。
展開にエラーがない場合は、次の読み出しを行うイテレータを戻します。
可変数パラメータには以下を指定します。
char*
,uint8_t*
型の配列と配列長Nのペアmake_pair()
で生成できる
例
auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
auto&& np =
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, make_pair((uint8_t*)fourchars, 4)
);
// read rest of payload
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
expand_bytes(np, rx.get_payload().end()
, u8DI_BM_remote
, au16AI_remote[0]
, au16AI_remote[1]
, au16AI_remote[2]
, au16AI_remote[3]
, au16AI_remote[4]
);
この例では、まず4バイトの文字列を読み出しています。ここではmake_pair()
を用いて明示的に4バイト分のデータを読み出します。
戻されたイテレータnp
をもとに、次のデータを読み出します。次のデータはuint8_t
型、あとはuint16_t
型が5つ続いています。
背景
無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t
型のバイト配列の記述を簡素化するため。
auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
uint8_t *p = rx.get_payload().begin();
fourchars[0] = p[0];
fourchars[1] = p[1];
fourchars[2] = p[2];
fourchars[3] = p[3];
fourchars[4] = 0;
p += 4;
u8DI_BM_remote = (p[0] << 8) + p[1]; p+=2;
au16AI_remote[0] = (p[0] << 8) + p[1]; p+=2;
...
上記はもっとも単純な記述だが、以下のようにByte array utilsを用いてバイト配列から読み出せる。
auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
uint8_t *p = rx.get_payload().begin();
fourchars[0] = G_OCTET(p);
fourchars[1] = G_OCTET(p);
fourchars[2] = G_OCTET(p);
fourchars[3] = G_OCTET(p);
fourchars[4] = 0;
u8DI_BM_remote = G_WORD(p);
au16AI_remote[0] = G_WORD(p);
...
CRC8, XOR, LRC
チェックサムの計算で良く用いられる値です。
uint8_t CRC8_u8Calc(uint8_t *pu8Data, uint8_t size, uint8_t init=0)
uint8_t CRC8_u8CalcU32(uint32_t u32c, uint8_t init=0)
uint8_t CRC8_u8CalcU16(uint16_t u16c, uint8_t init=0)
uint8_t XOR_u8Calc(uint8_t *pu8Data, uint8_t size)
uint8_t LRC_u8Calc(uint8_t* pu8Data, uint8_t size)
CRC8, XOR, LRC(アスキー形式で使用)の計算を行います。
CRC8_u8CalcU16(), CRC8_u8CalcU32()
はu16c, u32c
をビッグエンディアン並びとして、CRC8を計算します。
背景
無線パケットのデータ列、アスキー形式のチェックサム(LRC)、各種センサーのデータチェック用に利用されるため、ライブラリ手続きとして追加した。
div100()
10、100または1000で割った商と余りを計算します。
struct div_result_i32 {
int32_t quo; // quotient
int16_t rem; // remainder
uint8_t b_neg; // true if negative
uint8_t digits_rem; // digits of remainder
};
div_result_i32 div10(int32_t val);
div_result_i32 div100(int32_t val);
div_result_i32 div1000(int32_t val);
センサー値などで100倍した値をuint16_t
型にして受け渡しする場合がありますが、除算回路がないマイコンでの計算処理には相応の時間がかかるため、加算・減算・乗算とビットシフトを用いた近似計算と補正により計算を行います。
val
に計算したい値、rem
は余りの格納変数、neg
は符号を格納する変数を渡します。
戻り値は商の値(常に正)、rem
には余りの値(常に正)、neg
は負ならtrue
が格納されます。
計算アルゴリズムの制約(桁あふれ)からdiv100()
とdiv1000()
での計算可能な値域が決まっています。div100()
は-99999~99999までの値に対応し、div1000()
は-999999~999999までの値に対応します。
使用例
auto d1 = div100(sns_val.u16temp_object);
auto d2 = div100(sns_val.u16temp_object);
Serial
<< crlf << format("..Object = %c%2d.%02d"
, d1.b_neg ? '-' : '+', d1.quo, d1.rem)
<< format(" Ambient = %c%2d.%02d"
, d2.b_neg ? '-' : '+', d2.quo, d2.rem);
計算速度
10倍程度になります。
結果の出力
// 変換オプション
struct DIVFMT {
static const int STD = 0; // displays with minimul digits (no padding, no positive sign)
static const int PAD_ZERO = 1; // set padding character as '0' instead of ' '.
static const int SIGN_PLUS = 2; // put '+' sign if value is positive or 0.
static const int PAD_ZERO_SIGN_PLUS = 3; // PAD_ZERO & SIGN_PLUS
static const int SIGN_SPACE = 4; // put ' ' sign if value is positive or 0.
static const int PAD_ZERO_SIGN_SPACE = 5; // PAD_ZERO & SIGN_SPACE
};
// 文字列変換結果を格納するためのクラス
class _div_chars {
...
const char* begin() const {...}
const char* end() const {...}
const char* c_str() const { return begin(); }
operator const char*() const { return begin(); }
};
// format()メソッド
_div_chars div_result_i32::format(
int dig_quo = 0, uint32_t opt = DIVFMT::STD) const;
// Serialへのインタフェースの実装
template <class D> class stream {
...
inline D& operator << (const mwx::_div_chars&& divc);
inline D& operator << (const mwx::div_result_i32&&);
inline D& operator << (const mwx::div_result_i32&);
};
割り算の結果を格納するdiv_result_i32
クラスにはformat()
メソッドを用い_div_chars
クラスオブジェクトを得ることが出来る。_div_chars()
クラスオブジェクトは文字列バッファを内包していてconst char*
型として文字列バッファにアクセスするメソッドが用意されている。また、Serial
オブジェクトに対する<<
演算子も実装している。
format()
メソッドのパラメータの1番目dig_quo
は出力桁数(符号部を含まず)を指定します。出力桁数に足りない場合(以下、不足桁)は空白または0
で埋めます。2番目のパラメータopt
は書式を指定します。
標準的な出力で、不足桁は空白で埋め、負の値に限り-
を付加します。
DIVFMT::PAD_ZERO_SIGN_PLUS
DIVFMT::PAD_ZERO_SIGN_SPACE
不足桁は0
で埋め、正の値の場合は+
符号の替わりに空白を付加します。
例
//// div_result_i32オブジェクトから直接出力
Serial << div100(-1234) << crlf;
// 結果: -12.34
//// 3桁で出力します
Serial << div100(3456).format(3, DIVFMT::PAD_ZERO_SIGN_PLUE) << crlf;
// 結果: +034.56
//// c_str()を使ってconst char*を得る
char str1[128];
auto x = div100(-5678);
mwx_snprintf(str1, 16, "VAL=%s", x.format.c_str()); // const char*
Serial << str1;
// 結果: VAL=-56.78
背景
TWELITE 無線マイコンでは除算はコストが高い演算であるため、目的を限定した除算アルゴリズムを追加した。
ライブラリ内では温度・湿度といった一部のセンサー値、100倍の値(25.12℃なら2512)を用いて表現しているため、100で割った商と余りを得るための簡素な手続きを定義した。
dev_result_i32::format()
については、書式出力を行う際の煩雑さを避けるためである。
Scale utils
8bit値(0..25など)の値とユーザが取り扱いやすい0..1000(千分率, ‰)値でスケール(拡縮)する。低い演算コストで実施するため、除算(x*1000/255
)の替わりに乗算とビットシフトによって近似計算します。
static inline uint8_t scale_1000_to_127u8(uint16_t x)
static inline uint16_t scale_127u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_255u8(uint16_t x)
static inline uint16_t scale_255u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_256u8(uint16_t x)
static inline uint16_t scale_256u16_to_1000(uint16_t x)
scale_1000_to_127u8()
0..1000 を 0..127 にスケールします。(16646*x+65000)>>17
を用いて近似計算します。
scale_127u8_to_1000()
0..127 を 0..1000 にスケールします。(2064000UL*x+131072)>>18
を用いて近似計算します。
scale_1000_to_255u8()
0..1000 を 0..255 にスケールします。(33423*x+65000)>>17
を用いて近似計算します。
scale_255u8_to_1000()
0..255 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18
を用いて近似計算します。
scale_1000_to_256u8()
0..1000 を 0..256 にスケールします。(33554*x+66000) >> 17
を用いて近似計算します。
注: x=999,1000は計算値が256になりますがuint8_t
の範囲として255を返します。
scale_256u16_to_1000()
0..256 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18
を用いて近似計算します。
背景
ハードウェアに設定すべき値は0..255といった2進数が前提になることが多く、ユーザアプリケーションで取り扱う数は0..1000といった10進数基準のほうが扱いやすい。これらのスケール変換に除算を行わない式を定義した。