ビットフィールドとは

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