概要
前回はレジスタに書き込めるようにしました。今回はunionを使ってレジスタの中身にアクセスしやすいようにしてみたいと思います。
unionとは?
C言語の構造体で、複数の定義でアクセスできるようにする方法です。この機能を使ってバイト単位でのアクセスと、特定ビット単位のアクセスを両立させることができます。
ESP32だとIOポートなどを直接触る場合のメモリアクセスなどに利用されています。
typedef union {
struct {
uint32_t byte_num: 8; /*Byte_num represent the number of data need to be send or data need to be received.*/
uint32_t ack_en: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/
uint32_t ack_exp: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/
uint32_t ack_val: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/
uint32_t op_code: 3; /*op_code is the command 0:RSTART 1:WRITE 2:READ 3:STOP . 4:END.*/
uint32_t reserved14: 17;
uint32_t done: 1; /*When command0 is done in I2C Master mode this bit changes to high level.*/
};
uint32_t val;
} I2C_COMMAND_t;
上記はI2Cですが、uint32_tのvalを分割してbyte_num、ack_en、ack_exp、ack_val、op_code、reserved14、doneの変数としてアクセスが可能になります。:1などは1ビットという意味になりまして、32ビット変数を指定したビット区切りでアクセス可能になります。
速度的にはunionを使わない方が早いのですが、ビットで区切られているハードウエア系のレジスタや通信データなどには非常に便利です。
SLG46826のレジスタ構造
256バイトのデータで、フラッシュは16バイト単位が16個ありますが、フラッシュの書き込み制約なので、通常の256バイトか2048ビットのデータになります。
typedef struct {
union {
struct {
union {
uint8_t reg_00;
};
// (省略)
union {
uint8_t reg_ff;
};
};
uint8_t reg_data[256];
};
} slg_register_t;
基本構造としては、slg_register_t型をつくりました。中身はreg_dataで256バイトにバイト単位でアクセス可能です。union型を利用してreg_00からreg_ffまでの256個の変数も宣言しています。
union {
struct {
uint8_t i2c_slave_address: 4; // [CAH(0), 1619:1616]
uint8_t slave_address_selection_a4: 1; // [CAH(4), 1620:1620]
uint8_t slave_address_selection_a5: 1; // [CAH(5), 1621:1621]
uint8_t slave_address_selection_a6: 1; // [CAH(6), 1622:1622]
uint8_t slave_address_selection_a7: 1; // [CAH(7), 1623:1623]
};
uint8_t reg_ca;
};
さらに、reg_ca(0xCA)をみてみるとI2Cの設定をするアドレスです。ここは4バイトのコントロールコードと、GPIOの状態で可変にするかを設定する変数に分離しています。
Serial.printf("%3d(0x%02X) : [CAH(0) 1619:1616(4)] i2c_slave_address\n", slgReg.i2c_slave_address, slgReg.i2c_slave_address);
Serial.printf("%3d(0x%02X) : [CAH(4) 1620:1620(1)] slave_address_selection_a4\n", slgReg.slave_address_selection_a4, slgReg.slave_address_selection_a4);
Serial.printf("%3d(0x%02X) : [CAH(5) 1621:1621(1)] slave_address_selection_a5\n", slgReg.slave_address_selection_a5, slgReg.slave_address_selection_a5);
Serial.printf("%3d(0x%02X) : [CAH(6) 1622:1622(1)] slave_address_selection_a6\n", slgReg.slave_address_selection_a6, slgReg.slave_address_selection_a6);
Serial.printf("%3d(0x%02X) : [CAH(7) 1623:1623(1)] slave_address_selection_a7\n", slgReg.slave_address_selection_a7, slgReg.slave_address_selection_a7);
さらに、デバッグ用としてdebug_print_slg46826()という関数を用意しました。上記のようにunionで定義されている変数全部を出力します。
制限事項
実は先頭には回路のマトリクス設定があるのですが、ここは対応していません。見てもわからないってこともあるのですが、なんと6ビット単位で並んでいます。

上記みたいな構造になっており、非常にunion定義が難しい構造です。3バイト単位で読んでいけばいいんですが、非常にトリッキーな構造にしないとだめですね。

あと、マルチファンクション系も対応していません。ビットの順番がばらばら、、、。説明しやすい順番に並び替えている気がします。

あとこれです。なんと離れたビットを参照しています。あとで拡張したんですね。。。
実際の処理が上記にあります。
サンプル実装
// Read Register
slg_register_t slgReg;
;
htmlItem = "<h3>Read Register</h3>\n";
htmlItem += "<pre>\n";
memset(slgReg.reg_data, 0, sizeof(slgReg.reg_data));
if (slg.readSlg(controlCode, slgReg.reg_data, 0)) {
debug_print_slg46826(slgReg);
WebSLGのサンプルですが、slg_register_tで確保して、slgReg.reg_dataが256バイトの配列ですので今まで通りに使えます。debug_print_slg46826(slgReg)でシリアル経由にずらずらっとレジスタの内容を出力します。
書き込みもできますので、レジスタやNVMの情報を読み出してI2C設定などを書き換えてから書き込むってこともできると思います。
まとめ
設定値を個別に書き換える関数群を作るのはちょっと大変そうなので、unionで実装してみました。とはいえ、このunionも今後編集したくないぐらいの分量になっています。。。
ざっくりライブラリで作れそうなところはできてきたので、あとはサンプルの充実とか他のチップへの対応かな。回路をかんたんに書き換えれるようになったので、もう少し実機を使った実験に戻ろうかなとも思っています。
コメント