C++ の勉強 ⑨ i2cライブラリのまとめ。その2

　C++ の勉強の集大成、Ubuntuで動作する作成したi2cバスをアクセスする関数の利用解説編です。作った関数は全部で三つあります。ハンドリングする配列の大きさは1～32です。書き込み時の配列には、書き込むレジスタのアドレスを含めます。
 　読み出し用のライブラリが二つあります。違いは、読み出し用のレジスタを指定するかしないかの部分です。読み出し用のレジスタを指定することが多いですが、このとき書き込み動作が入り、そのあと読み出します。読み出したデータは配列に入ります。ポインタ渡しです。配列のサイズも別途渡します。

　smbusのライブラリでは、１バイト用にbyte_writeとbyte_read、２バイト用にword_readとword_writeが用意されており、２バイト以上にはblock_readとblock_writeがあります。
 　今回作成したのはblock_readとblock_writeで、ほとんどすべて？の読み書きに対応します。
  
- read_block_data(デバイスのアドレス, 読み出すレジスタの配列, 配列のサイズ, 読み出したデータ用配列, 配列のサイズ );
- read_block_data_noResister(デバイスのアドレス,  読み出したデータ用配列, 配列のサイズ );
- write_block_data(デバイスのアドレス, 書き込むデータの配列, 配列のサイズ);

（※）read_block_data()関数の「読み出すレジスタの配列」は、1バイトが主流ですが、2バイトにも対応しました。

 ### 環境

- 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)


### 2バイトの読み出し事例 tmp117

　温度センサtmp117をi2cバスにつなぎます。
 　このデバイスは初期化は不要です。パワーオン・リセット後に連続変換モードになっているので、温度が入っているレジスタを読み出すだけで、温度が取得できます。
  　読み出した2バイトのデータを16ビットに直し、2の補数形式なの~~に対処して~~、1LSBが0.0078125`Cなので乗じて摂氏の温度を得ます。
（※）マイナスの温度には対応し忘れました。

  ```ex201.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>

#define deviceAdr 0x48

int fd, data;
int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount );

int main(){
    fd = open("/dev/i2c-1", O_RDWR);
    unsigned char readBuf[2] = {0,0};
    unsigned char readResisterAdr[1] = {0x00} ;
    read_block_data(deviceAdr, readResisterAdr, sizeof(readResisterAdr), readBuf, sizeof(readBuf) );
    std::cout << "read data: 0x" << std::hex << (int)readBuf[0] << " 0x" << std::hex << (int)readBuf[1] << "\n";
    int temp = (readBuf[0] << 8 ) | readBuf[1];
    std::cout << "tmp117 temp is " << float(temp * 0.0078125) << "`C\n";

    return 0;
}

int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount ){
    struct i2c_msg msg[2];
    struct i2c_rdwr_ioctl_data packets;
    unsigned char resisterData[length], readData[byteCount];

    msg[0].addr =  address;    // device address
    msg[0].flags = 0;          // write
    msg[0].len = length;
    msg[0].buf = &resisterData[0];
    if (length == 2) {
        resisterData[1] = resister[1];
    }
    resisterData[0] = resister[0];

    msg[1].addr =  address;    // device address
    msg[1].flags = I2C_M_RD;   // read
    msg[1].len = byteCount;
    msg[1].buf = &readData[0];

    packets.msgs = msg;
    packets.nmsgs = 2;
    int ret = ioctl(fd, I2C_RDWR, &packets);

    for (int i = 0; i < byteCount; i++){
        readDataBuf[i] =  readData[i];
    }

    return 0;
}
```
　実行します。
 ```
$ g++ ex201.cpp
$ ./a.out
read data: 0xe 0x19
tmp117 temp is 28.1953`C
 ```

### 1バイトの読み出し事例 hts221
　温湿度センサのhts221をi2cバスに接続します。スレーブ・アドレスは0x5fです。
 　データシート；https://www.st.com/resource/en/datasheet/hts221.pdf
  WHO_AM_Iレジスタを読み出します。
  
```ex203.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>
#include <bitset>

#define deviceAdr 0x5f

int fd, data;
int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount);

int main(){
    fd = open("/dev/i2c-1", O_RDWR);
    unsigned char readBuf[1] = {0};
    unsigned char readResisterAdr[1] = {0x0f | 0x80};
    read_block_data(deviceAdr, readResisterAdr, sizeof(readResisterAdr), readBuf, sizeof(readBuf) );
    std::cout << "read data(hts221 WHO_AM_I 0xbc): 0x" << std::hex << (int)readBuf[0] << "\n";

    return 0;
}

//　int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount ){
// は省略。上記と同じ
```
　実行します。正しく読み出せています。

 ```
$ g++ ex203.cpp
$ ./a.out
read data(hts221 WHO_AM_I 0xbc): 0xbc
```

　同じく1バイトを読み出します。レジスタはAV_CONF (10h)です。
```ex204.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>
#include <bitset>

#define deviceAdr 0x5f

int fd, data;
int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount);

int main(){
    fd = open("/dev/i2c-1", O_RDWR);
    unsigned char readBuf[1] = {0};
    unsigned char readResisterAdr[1] = {0x10 | 0x80};
    read_block_data(deviceAdr, readResisterAdr, sizeof(readResisterAdr), readBuf, sizeof(readBuf) );
    std::cout << "read data(resolution mode): 0x" << std::hex << (int)readBuf[0]
              << " 0b" << ((std::bitset<8>(readBuf[0])).to_string()).insert(4, " ")
              << "\n";

    return 0;
}

//　int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount ){
// は省略。上記と同じ
```
　実行します。温度、湿度共に最高分解能に設定されているようです。

 ```
$ g++ ex204.cpp
$ ./a.out
read data(resolution mode): 0x3f 0b0011 1111
```

　このセンサは、湿度が16ビット、温度が16ビットのデータが得られます。2バイトずつ読み出してもいいのですが、連続しているので、4バイトをまとめて読み出します。
 　ですが、失敗しました。レジスタではなくポインタ・レジスタのようです。1バイトずつ読み出して合成します。
  　このセンサの出力は、1次方程式を解かないといけないので、試行錯誤してうまくできたら別途記事を作る予定です。

### 読み出しアドレスが2バイト事例　scd4x
　二酸化炭素センサのscd4xの48ビットのシリアルナンバを読み出します。データは9バイトです。

 データシート；https://sensirion.com/media/documents/48C4B7FB/66E05452/CD_DS_SCD4x_Datasheet_D1.pdf
 
```ex310.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>

#define deviceAdr 0x62

int fd, data;
int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount);

int main(){
    fd = open("/dev/i2c-1", O_RDWR);
    unsigned char readBuf[9] = {0,0,0,0,0,0,0,0,0};
    unsigned char readResisterAdr[2] = {0x36, 0x82} ;  // get_serial_number
    read_block_data(deviceAdr, readResisterAdr, sizeof(readResisterAdr), readBuf, sizeof(readBuf) );
    std::cout << "read data: 0x" << std::hex << (int)readBuf[0] << " 0x" << std::hex << (int)readBuf[1]
              << " 0x" << std::hex << (int)readBuf[2] << " 0x" << std::hex << (int)readBuf[3]
              << " 0x" << std::hex << (int)readBuf[4] << " 0x" << std::hex << (int)readBuf[5]
              << " 0x" << std::hex << (int)readBuf[6] << " 0x" << std::hex << (int)readBuf[7]
              << " 0x" << std::hex << (int)readBuf[8]
              << "\n";
//  unsigned char  readBuf[9]={0xf8,0x96,0x31,0x9f,0x07,0xc2,0x3b,0xbe,0x89};
    int64_t serialNumber = ((int64_t)((readBuf[0] << 8) | readBuf[1]) << 32) | ((int64_t)((readBuf[3] << 8) | readBuf[4]) << 16) |
                        ((int64_t)((readBuf[6] << 8) | readBuf[7]));
    std::cout << " serial number is " << (int64_t)serialNumber << "\n";

    return 0;
}
//　int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount ){
// は省略。上記と同じ
```
　実行します。
 ```
 $ g++ ex310.cpp
$ ./a.out
read data: 0x1e 0xba 0xe6 0xbb 0x7 0x9d 0x3b 0x3f 0xc2
 serial number is 1ebabb073b3f
 ```
 


### 2バイトの書き込み/読み出し事例　scd4x
　二酸化炭素センサのscd4xでは、スタート用コマンド2バイトを書き込むと、測定が開始されます。
　2バイトのリード・コマンドを送ると、9バイトのデータが返ってきます。
  　CO2、温度、湿度がそれぞれ3バイトずつで構成されていて、最後のバイトはCRCです。今回は読み捨てます。

```ex311.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>

#define deviceAdr 0x62

int fd, data;
int write_block_data(uint8_t address, uint8_t * sendData, uint8_t byteCount);
int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount);

int main(){
    fd = open("/dev/i2c-1", O_RDWR);

    uint8_t sendData[] = {0x21, 0xb1}; // start_periodic_measurement
    write_block_data(deviceAdr, sendData, sizeof(sendData));
    sleep(5);

    unsigned char readBuf[9] = {0,0,0,0,0,0,0,0,0};
    unsigned char readResisterAdr[2] = {0xec, 0x05} ;  // read_measurement
    read_block_data(deviceAdr, readResisterAdr, sizeof(readResisterAdr), readBuf, sizeof(readBuf) );
    std::cout << "read data: 0x" << std::hex << (int)readBuf[0] << " 0x" << std::hex << (int)readBuf[1]
              << " 0x" << std::hex << (int)readBuf[2] << " 0x" << std::hex << (int)readBuf[3]
              << " 0x" << std::hex << (int)readBuf[4] << " 0x" << std::hex << (int)readBuf[5]
              << " 0x" << std::hex << (int)readBuf[6] << " 0x" << std::hex << (int)readBuf[7]
              << " 0x" << std::hex << (int)readBuf[8]
              << "\n";
    int co2 = (readBuf[0] << 8) | readBuf[1];
    int tempOrg = (readBuf[3] << 8) | readBuf[4];
    float temp = -45 + 175 * tempOrg / 65535.0;
    int humiOrg = (readBuf[6] << 8) | readBuf[7];
    float humi = 100 * humiOrg / 65535.0;
    std::cout << " CO2, Temp, Humi is " << std::dec << co2 << "ppm  " << temp << "`C  " << humi << "%"  << "\n";

    return 0;
}

int write_block_data(uint8_t address, uint8_t * sendData, uint8_t byteCount){
  struct i2c_msg msg[1];
  struct i2c_rdwr_ioctl_data packets;

  std::cout << "\nsend data is 0x" << std::hex << (int)address << "\t";
  for ( int i=0; i  <  byteCount; i++){
    std::cout << i << ": 0x"  << std::hex << (int)sendData[i] << ", ";
  }
  std::cout << "\n";

  msg[0].addr =  address;  // device address
  msg[0].flags = 0;        // write
  msg[0].len = byteCount;
  msg[0].buf = sendData;

  packets.msgs = msg;
  packets.nmsgs = 1;       // データ書き込みでmsgは1
  int ret = ioctl(fd, I2C_RDWR, &packets);

  return 0;
}

int read_block_data(uint8_t address, unsigned char * resister, uint8_t length, unsigned char * readDataBuf, uint8_t byteCount ){
    struct i2c_msg msg[2];
    struct i2c_rdwr_ioctl_data packets;
    unsigned char resisterData[length], readData[byteCount];

    msg[0].addr =  address;    // device address
    msg[0].flags = 0;          // write
    msg[0].len = length;
    msg[0].buf = &resisterData[0];
    if (length == 2) {
        resisterData[1] = resister[1];
    }
    resisterData[0] = resister[0];

    msg[1].addr =  address;    // device address
    msg[1].flags = I2C_M_RD;   // read
    msg[1].len = byteCount;
    msg[1].buf = &readData[0];

    packets.msgs = msg;
    packets.nmsgs = 2;
    int ret = ioctl(fd, I2C_RDWR, &packets);

    for (int i = 0; i < byteCount; i++){
        readDataBuf[i] =  readData[i];
    }

    return 0;
}
```
　実行します。

 ```
$ g++ ex311.cpp
$ ./a.out

send data is 0x62       0: 0x21, 1: 0xb1,
read data: 0x3 0x3d 0x25 0x66 0xfc 0x71 0x9d 0x25 0xff
 CO2, Temp, Humi is 829ppm  25.4005`C  61.3855%
 ```


### リード時にレジスタ指定なし事例　sht45
　何度も登場していますが、温湿度センサsht45をi2cバスにつなぎます。
 　CRCデータは読み捨てています。

```ex312.cpp
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>

#define deviceAdr 0x44

int fd, data;
int write_block_data(uint8_t address, uint8_t * sendData, uint8_t byteCount);
int read_block_data_noResister(uint8_t address, unsigned char * readBuf, uint8_t byteCount );

int main(){
    fd = open("/dev/i2c-1", O_RDWR);

    uint8_t sendData[] = {0xfd}; // high  precision
    write_block_data(deviceAdr, sendData, sizeof(sendData));
    usleep(10000);

    unsigned char readBuf[] = {0,0,0,0,0,0};
    read_block_data_noResister(deviceAdr, readBuf, sizeof(readBuf));
    std::cout << "all data      : 0x" << std::hex << (int)readBuf[0] << " , 0x"
    << std::hex << (int)readBuf[1] << " , 0x" << std::hex << (int)readBuf[2] << " , 0x"
    << std::hex << (int)readBuf[3] << " , 0x" << std::hex << (int)readBuf[4] << " , 0x"
    << std::hex << (int)readBuf[5] << "\n";

    int tempOrg = (readBuf[0] << 8) | readBuf[1];
    float temp = -45 + 175 * tempOrg / 65535.0;
    int humiOrg = (readBuf[3] << 8) | readBuf[4];
    float humi = 100 * humiOrg / 65535.0;
    std::cout << " Temp, Humi is " << temp << "`C  " << humi << "%"  << "\n";

    return 0;
}

int write_block_data(uint8_t address, uint8_t * sendData, uint8_t byteCount){
  struct i2c_msg msg[1];
  struct i2c_rdwr_ioctl_data packets;

  std::cout << "\nsend data is 0x" << std::hex << (int)address << "\t";
  for ( int i=0; i  <  byteCount; i++){
    std::cout << i << ": 0x"  << std::hex << (int)sendData[i] << ", ";
  }
  std::cout << "\n";

  msg[0].addr =  address;  // device address
  msg[0].flags = 0;        // write
  msg[0].len = byteCount;
  msg[0].buf = sendData;

  packets.msgs = msg;
  packets.nmsgs = 1;       // データ書き込みでmsgは1
  int ret = ioctl(fd, I2C_RDWR, &packets);

  return 0;
}

int read_block_data_noResister(uint8_t address, unsigned char * readBuf, uint8_t byteCount ){
   struct i2c_msg msg[1];
   struct i2c_rdwr_ioctl_data packets;
   unsigned char readData[byteCount];

   msg[0].addr =  address;    // device address
   msg[0].flags = I2C_M_RD;   // read
   msg[0].len = byteCount;
   msg[0].buf = &readData[0];

   packets.msgs = msg;
   packets.nmsgs = 1;        // データ読み出しでmsgは1
   int ret = ioctl(fd, I2C_RDWR, &packets);

   for (int i = 0; i <byteCount; i++){
       readBuf[i] =  readData[i];
   }

   return 0;
}
```
　実行します。

```
$ g++ ex312.cpp
$ ./a.out

send data is 0x44       0: 0xfd,
all data      : 0x69 , 0x72 , 0x8d , 0x7d , 0xaf , 0x28
 Temp, Humi is 27.0829`C  49.0959%
```


 （※）ほとんどのI2Cデバイスの読み書きができるようになったと思います。今回作った三つの関数で対応できないデバイスが見つかったときには、改良します。
 　関数の中で、読み出したデータなどを印字している部分がありますが、動作確認用なので、利用時にはコメントアウトしてください。