前回、SHT45センサから湿度と温度を読み取りました。
環境
- Raspberry Pi 5 8GB
- 追加ボード;NVMe Base for Raspberry Pi 5 (NVMe Base by Pimoroni)
- Crucial クルーシャル P2シリーズ 500GB 3D NAND NVMe PCIe M.2 SSD CT500P2SSD8
- Ubuntu Desktop 24.04LTS(64-bit)
準備
Windows10のコマンドプロンプトからsshで入って作業をしています。前回のプログラムの一部です。
unsigned char data1[1], data2[6];
文字列をハンドリングしようとしているのではなく、たんに8ビットデータが使いたいだけなので、uint8_tでいいじゃないかと思います。
最近見ないですが、byteでいいんですね。
data2[6]
で、6バイトの配列を確保しました。
次の
msgP[0].buf = &data2[0];
もともと参考にしたプログラムでは、
msgP[0].buf = &data2;
と書かれ、シングル・バイトのリードだったのを、6バイトのリード・バッファに拡張したんです。この記述って、何?
わかっていないです。ポインタ・アクセス?参照アクセス?なんでしょうか。
うごいたのは、まる1日、* & []とかをつけたり外したりしていて、たまたまうまく書けただけなんです。
それじゃーイケナイ、と思っています。
i2c_msg構造体
もともとの定義は次の構造になっています。宣言かな?
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /* see above for flag definitions */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
この部分だけをアクセスするプログラムを書きます。
#include <iostream>
#include <cstdint>
#include <bitset>
uint8_t addr, flags, len, buf;
struct i2c_msg {
uint8_t addr; /* slave address */
uint8_t flags; /* see above for flag definitions */
uint8_t len; /* msg length */
const char *buf; /* pointer to msg data */
};
int main(){
// init
i2c_msg msgP = {0x44, 0, 1, "123"};
std::cout << "addr, flags, len, *buf : " << std::hex << "0x" << (int)msgP.addr << " " << (int)msgP.flags << " " << (int)msgP.len << " "
<< msgP.buf << "\n";
return 0;
}
実行します。
$ g++ ex111.cpp
$ ./a.out
addr, flags, len, *buf : 0x44 0 1 123
const char *buf;
は、C言語のようにchar *buf;
と書くと、警告が出ます。
C++では文字列リテラルは、const char 配列として扱われる
なのだそうです。
文字列は置いておいて、数値データです。
#include <iostream>
#include <cstdint>
#include <bitset>
uint8_t addr, flags, len, buf, data1;
struct i2c_msg {
uint8_t addr; /* slave address */
uint8_t flags; /* see above for flag definitions */
uint8_t len; /* msg length */
uint8_t *buf; /* pointer to msg data */
};
int main(){
// init
data1 = 32;
i2c_msg msgP = {0x44, 0, 1, data1};
std::cout << "addr, flags, len, *buf : " << std::hex << "0x" << (int)msgP.addr << " " << (int)msgP.flags << " " << (int)msgP.len << " "
<< msgP.buf << " " << (int)data1 << "\n";
return 0;
}
実行します。
$ g++ ex112.cpp
ex112.cpp: In function ‘int main()’:
ex112.cpp:18:33: error: invalid conversion from ‘uint8_t’ {aka ‘unsigned char’} to ‘uint8_t*’ {aka ‘unsigned char*’} [-fpermissive]
18 | i2c_msg msgP = {0x44, 0, 1, data1};
| ^~~~~
| |
| uint8_t {aka unsigned char}
data1はuint8_t*としていたのですが、上記のエラーが出ました。言っている意味は、無効な変換です。たぶん。でも、何を変換したのかわかっていません。
i2c_msg msgP = {0x44, 0, 1, *data1};
に変更するとエラーは、
ex112.cpp:18:33: error: invalid type argument of unary ‘*’ (have ‘uint8_t’ {aka ‘unsigned char’})
18 | i2c_msg msgP = {0x44, 0, 1, *data1};
|
にかわりました。エラーの内容は、単項 '*' の無効な型引数 ('uint8_t' があります)ですね。これもなぜそういうメッセージなのかは理解できていません。
つぎは、i2c_msg msgP = {0x44, 0, 1, &data1};
に変更するとエラーはなくなりました。
$ g++ ex112.cpp
yoshi@yoshi:~/book$ ./a.out
addr, flags, len, *buf : 0x44 0 1 20
data1=32でしたが、20で表示されました。16進表示に変わったようです。
bufはポインタ変数なので、1個だけのdata1ではなく、複数lをあつかえる?はずです。
#include <iostream>
#include <cstdint>
#include <bitset>
uint8_t addr, flags, len, buf, data1;
struct i2c_msg {
uint8_t addr; /* slave address */
uint8_t flags; /* see above for flag definitions */
uint8_t len; /* msg length */
uint8_t *buf; /* pointer to msg data */
};
int main(){
// init
uint8_t data1[2] = {32, 0xff};
i2c_msg msgP = {0x44, 0, 1, &data1[]};
std::cout << "addr, flags, len, *buf : " << std::hex << "0x" << (int)msgP.addr << " " << (int)msgP.flags> << msgP.buf << " " << (int)data1[0] << " " << (int)data1[1] << "\n";
return 0;
}
実行します。
ex113.cpp:18:40: error: expected primary-expression before ‘]’ token
18 | i2c_msg msgP = {0x44, 0, 1, &data1[]};
|
閉じかっこの前に何か必要だと言っているのかな。
&data1[0]
にしました。
実行します。
$ g++ ex114.cpp
$ ./a.out
addr, flags, len, *buf : 0x44 0 1 20 ff
動きました。
ここまでを整理します。
<1> X 無効な変換?
data1 = 32;
i2c_msg msgP = {0x44, 0, 1, data1};
<2> X 無効な型引数?
data1 = 32;
i2c_msg msgP = {0x44, 0, 1, *data1};
<3> 〇
data1 = 32;
i2c_msg msgP = {0x44, 0, 1, &data1};
<4> X 閉じかっこの前に何か必要?
uint8_t data1[2] = {32, 0xff};
i2c_msg msgP = {0x44, 0, 1, &data1[]};
<5> 〇
uint8_t data1[2] = {32, 0xff};
i2c_msg msgP = {0x44, 0, 1, &data1[0]};
1日、この結果を考えていました。OKだったのは次の二つです。
msg.buf = &data1
もしくは msg.buf = &data1[0]
これは、元の構造体の宣言の記述に置き換えると、
uint8_t *buf = &data1
もしくは uint8_t *buf = &data1[0]
これは、
<3>
uint8_t *buf;
buf = &data1; // ポインタ変数bufに変数data1のアドレスを代入
と、
<5>
uint8_t *buf;
buf = &data1[0]; // ポインタ変数bufに配列data1の先頭アドレスを代入。
// bufが配列の先頭を指すようになる
// (buf = data1; と書いてもよい?が何を書いているかわからなくなりそう)
これって、どこかで見た記述ではないですか!
<3>ではdata1で値を取り出せます。
<4>では、
[]は配列の要素番号を指定する演算子。
配列名というアドレスに足し算を行っている
なので、data1[1]で、2番目のデータを取り出せる。?当たり前なような気もしますが、配列の要素(値?)を取り出しているわけですよね。
それにしても、検索でみつかる解説は、特別な事例ばっかりのような気がする。書籍で確かめよう。
とりあえず、最初の事例の前半の問題が解決した。はず。
確認で、ex309.cpp(前回)の、
int tempCrc = CalcCrc(data2);
と
int humiCrc = CalcCrc(data2+3);
を下記のように書き換えて実行しましたが、同じように動きました。
int tempCrc = CalcCrc(&data2[0]);
と
int humiCrc = CalcCrc(&data2[3]);
暗黙にポインタに変換?
CRC-8の関数CalcCrc()を呼び出すとき、
int tempCrc = CalcCrc(data2);
と、
int humiCrc = CalcCrc(data2+3);
と記述しました。
data2[0]からdata2[5]は読み出した6バイトで、data[0]とdata[1]が温度データ、data2[3]、data2[4]が湿度データです。
CalcCrc()の引数は2バイトのデータですから、
int tempCrc = CalcCrc(data2[0]);
int humiCrc = CalcCrc(data2[3]);
と書くべき?なんですが。
data2[0]が暗黙にポインタに変換されている?ので、ポインタdata2はアドレス&data2[0]のことで、ポインタdata2+3は、あれ?アドレス&data2[3]のことになる?のでしょう。
配列のポインタ渡しにはサイズ情報を
コメントでご指摘いただいたように、配列のアクセスは、間違って、その最後のデータを通り越してアクセスするということが起こりえます。逆に言えば、配列の最後以降に意地悪なプログラムを埋め込むというのがはやりました(バッファー・オーバーラン)。
で、CRC-8の計算に、2バイトであることはわかっているのですが、サイズの情報を追加しました。ベースは、ex306.cppです。
#include <iostream>
#include <cstdint>
#include <bitset>
uint8_t CalcCrc(const uint8_t * crcdata, uint8_t size);
uint8_t reflect(uint8_t c);
int main() {
std::cout << "start \n";
uint8_t data[2] = {0xbe, 0xef};
uint8_t dataSize = sizeof(data);
uint8_t crc8 = CalcCrc(data, dataSize);
std::cout << "crc-8 0x" << std::hex << (int)crc8 << "\n";
return 0;
}
uint8_t table[256] ={
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,35, 125, 159,193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
uint8_t CalcCrc(const uint8_t * crcdata, uint8_t length){
std::cout << "in function \n new data 0x" << std::hex << (int)crcdata[0] << std::hex << (int)crcdata[1]
<< " 0b" << ((std::bitset<8>(*crcdata)).to_string()).insert(4, " ")
<< "\n data size is " << (int)length << "\n";
uint8_t crc = 0xff;
crc = table[reflect(crcdata[0]) ^ crc];
crc = table[reflect(crcdata[1]) ^ crc];
return reflect(crc);
}
uint8_t reflect( uint8_t c ){
c = ((c & 0x55) << 1) | (c >> 1) & 0x55;
c = ((c & 0x33) << 2) | (c >> 2) & 0x33;
c = ((c & 0x0f) << 4) | (c >> 4) & 0x0f;
return c;
}
実行します。データのサイズは、ただ表示しているだけです。
$ g++ ex115.cpp
$ ./a.out
start
in function
new data 0xbeef 0b1011 1110
data size is 2
crc-8 0x92
vector
機能が多い!
参照渡しにしました。&をとると値渡しになります。値渡しができるのでポインタ渡しはない?ようです。参照渡しでは、呼び出したほうの配列内容が書き変わります。が、ここで作った関数では、関係ありません。だったら、値渡しでいいですね。
#include <iostream>
#include <cstdint>
#include <bitset>
#include <vector>
uint8_t CalcCrc(const std::vector<uint8_t>& crcdata);
uint8_t reflect(uint8_t c);
int main() {
std::cout << "start \n";
std::vector<uint8_t> data1{0xbe, 0xef};
uint8_t crc8 = CalcCrc(data1);
std::cout << "crc-8 0x" << std::hex << (int)crc8 << "\n";
return 0;
}
uint8_t table[256] ={
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,35, 125, 159,193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
uint8_t CalcCrc(const std::vector<uint8_t>& crcdata){
std::cout << "in function \n new data 0x" << std::hex << (int)crcdata.at(0) << std::hex << (int)crcdata.at(1)
<< ", 0b" << ((std::bitset<8>(crcdata.at(0))).to_string()).insert(4, " ")
<< " " << ((std::bitset<8>(crcdata.at(1))).to_string()).insert(4, " ")
<< "\n data size is " << (int)crcdata.size() << "\n";
uint8_t crc = 0xff;
crc = table[reflect(crcdata[0]) ^ crc];
crc = table[reflect(crcdata[1]) ^ crc];
return reflect(crc);
}
uint8_t reflect( uint8_t c ){
c = ((c & 0x55) << 1) | (c >> 1) & 0x55;
c = ((c & 0x33) << 2) | (c >> 2) & 0x33;
c = ((c & 0x0f) << 4) | (c >> 4) & 0x0f;
return c;
}
実行します。
$ g++ ex116.cpp
$ ./a.out
start
in function
new data 0xbeef, 0b1011 1110 1110 1111
data size is 2
crc-8 0x92