Qtの基礎 - I2C通信

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

I2C (Inter-Integrated Circuit) 通信は、集積回路間の短距離通信を目的として設計された同期式シリアル通信プロトコルである。
1982年にフィリップス社 (現NXPセミコンダクターズ) によって開発され、その後、多くの半導体メーカーによって採用された。

この通信方式の特徴として、シンプルな構造と低速ながら信頼性の高い通信が挙げられる。
I2Cバスは、データ線 (SDA) と クロック線 (SCL) の2本のワイヤーのみで構成されており、これらの線は双方向性を持つ。
この簡素な構造により、複数のデバイスを容易に接続できるため、多くの電子機器や組み込みシステムで広く使用されている。

I2C通信では、マスター・スレーブモデルが採用されている。
一般的に、マイコンやSBC等がマスターデバイスとなり、センサやメモリチップ等がスレーブデバイスとなる。
マスターデバイスはクロック信号を生成して、通信を開始・終了する役割を担う。
各スレーブデバイスには固有のアドレスが割り当てられており、マスターはこのアドレスを指定することで特定のスレーブと通信を行う。

通信速度に関しては、標準モードで100[kbps]、高速モードで400[kbps]、新しい規格では3.4[Mbps]までの通信が可能である。
ただし、多くの一般的な用途では標準モードや高速モードで十分な性能が得られる。

I2C通信のプロトコルは、スタート条件、スレーブアドレス、データ転送、ストップ条件という一連の流れで構成されている。
マスターがスタート条件を送信して、続いてスレーブアドレスと読み書きの指示を送信する。
その後、データの転送が行われ、最後にストップ条件で通信を終了する。

エラー検出機能としては、各バイトの転送後に受信側がACK (確認応答) ビットを送信することにより、データの正常な受信を確認する。
これにより、通信の信頼性が向上している。

I2C通信の応用範囲は非常に広く、温度センサ、加速度センサ、EEPROM等のメモリチップ、リアルタイムクロック、ADコンバータ等の様々なデバイスで使用されている。
特に、複数のセンサやアクチュエータを制御する必要がある組み込みシステムやIoTデバイスにおいて、I2C通信は重要な役割を果たしている。

ただし、I2C通信にも制限がある。
比較的低速であるため、高速なデータ転送が必要な用途には適していない。
また、通信距離も数メートル程度に限られるため、長距離通信には他のプロトコルが選択される。

プログラミングの観点からは、I2C通信の実装は比較的実直であるが、タイミングや通信プロトコルの詳細な理解が必要となる。
多くのマイコンやSBCには、I2C通信を容易に実装するためのライブラリやAPIが用意されている。
これらを利用することにより、開発者はより高レベルな機能の実装に集中することができる。


I2C通信の受信

以下の例では、非同期処理を使用して、I2C通信の受信を行っている。
適切なI2Cデバイス名 (例: /dev/i2c-1) とスレーブアドレスを指定する必要があることに注意する。

※注意
以下の例で使用しているlinux/i2c-dev.hファイルのライセンスは、GPL 2.0以降に準拠していることに注意する。

 // I2CReader.h
 
 #include <QObject>
 #include <QFile>
 #include <QFuture>
 #include <QtConcurrent>
 #include <QDebug>
 #include <sys/ioctl.h>
 #include <linux/i2c-dev.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 class I2CReader : public QObject
 {
    Q_OBJECT
 
 private:
    QFile *m_device;
    bool  m_streamingActive = false;
 
 public:
    explicit I2CReader(const QString &deviceName, QObject *parent = nullptr) : QObject(parent), m_device(new QFile(deviceName, this))
    {
       if (!m_device->open(QIODevice::ReadWrite)) {
          emit errorOccurred("デバイスのオープンに失敗: " + m_device->errorString());
       }
    }
 
    ~I2CReader()
    {
       if (m_device->isOpen()) {
          m_device->close();
       }
    }
 
    QFuture<QByteArray> readDataAsync(int address, int size)
    {
       return QtConcurrent::run([this, address, size]() {
          if (!m_device->isOpen()) {
             emit errorOccurred("デバイスが開かれていない");
             return QByteArray();
          }
 
          // スレーブアドレスの設定
          if (ioctl(m_device->handle(), I2C_SLAVE, address) < 0) {
             emit errorOccurred("スレーブアドレスの設定に失敗");
             return QByteArray();
          }
 
          QByteArray data(size, 0);
          if (m_device->read(data.data(), size) != size) {
             emit errorOccurred("データの読み込みに失敗: " + m_device->errorString());
             return QByteArray();
          }
 
          emit dataReceived(data);
 
          return data;
       });
    }
 
    void startStreaming(int address, int size, int interval)
    {
       m_streamingActive = true;
       QtConcurrent::run([this, address, size, interval]() {
          while (m_streamingActive) {
             QByteArray data = readDataAsync(address, size).result();
             if (!data.isEmpty()) {
                emit dataStreamed(data);
             }
             QThread::msleep(interval);
          }
       });
    }
 
    void stopStreaming()
    {
       m_streamingActive = false;
    }
 
 signals:
    void dataReceived(const QByteArray &data);
    void dataStreamed(const QByteArray &data);
    void errorOccurred(const QString &error);
 };


上記のクラスを使用して、I2C通信でデータを受信する。

 #include <QCoreApplication>
 #include "I2CReader.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    I2CReader reader("/dev/i2c-1");
 
    // 非同期処理で受信する場合
    QFuture<QByteArray> readFuture = reader.readDataAsync(0x50, 10);
    readFuture.then([](const QByteArray &data) {
       qDebug() << "読み込んだデータ:" << data.toHex();
    });
 
    // ストリーミング受信する場合
    reader.startStreaming(0x50, 10, 1000);  // 1秒間隔で10[byte]読み込み
 
    // エラーハンドリングの例
    QObject::connect(&reader, &I2CReader::errorOccurred, [](const QString &error) {
       qDebug() << "読み込みエラー:" << error;
    });
 
    // 10秒後にストリーミングを停止
    QTimer::singleShot(10000, &reader, &I2CReader::stopStreaming);
 
    return a.exec();
 }



I2C通信の送信

以下の例では、非同期処理を使用して、I2C通信の送信を行っている。

適切なI2Cデバイス名 (例: /dev/i2c-1) とスレーブアドレスを指定する必要があることに注意する。

※注意
以下の例で使用しているlinux/i2c-dev.hファイルのライセンスは、GPL 2.0以降に準拠していることに注意する。

 // I2CWriter.hファイル
 
 #include <QObject>
 #include <QFile>
 #include <QFuture>
 #include <QtConcurrent>
 #include <QDebug>
 #include <sys/ioctl.h>
 #include <linux/i2c-dev.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 class I2CWriter : public QObject
 {
    Q_OBJECT
 
 private:
    QFile *m_device;
    bool  m_streamingActive = false;
 
 public:
    explicit I2CWriter(const QString &deviceName, QObject *parent = nullptr) : QObject(parent), m_device(new QFile(deviceName, this))
    {
       if (!m_device->open(QIODevice::ReadWrite)) {
          emit errorOccurred("デバイスのオープンに失敗: " + m_device->errorString());
       }
    }
 
    ~I2CWriter()
    {
       if (m_device->isOpen()) {
          m_device->close();
       }
    }
 
    QFuture<bool> writeDataAsync(int address, const QByteArray &data)
    {
       return QtConcurrent::run([this, address, data]() {
          if (!m_device->isOpen()) {
             emit errorOccurred("デバイスが開かれていない");
             return false;
          }
 
          // スレーブアドレスの設定
          if (ioctl(m_device->handle(), I2C_SLAVE, address) < 0) {
             emit errorOccurred("スレーブアドレスの設定に失敗");
             return false;
          }
 
          if (m_device->write(data) != data.size()) {
             emit errorOccurred("データの書き込みに失敗: " + m_device->errorString());
             return false;
          }
 
          emit dataWritten(data);
 
          return true;
       });
    }
 
    void startStreamingWrite(int address, const QByteArray &data, int interval)
    {
       m_streamingActive = true;
       QtConcurrent::run([this, address, data, interval]() {
          while (m_streamingActive) {
             bool success = writeDataAsync(address, data).result();
             if (success) {
                emit dataStreamWritten(data);
             }
             QThread::msleep(interval);
          }
       });
    }
 
    void stopStreamingWrite()
    {
       m_streamingActive = false;
    }
 
 signals:
    void dataWritten(const QByteArray &data);
    void dataStreamWritten(const QByteArray &data);
    void errorOccurred(const QString &error);
 };


上記のクラスを使用して、I2C通信でデータを送信する。

 #include <QCoreApplication>
 #include "I2CWriter.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    I2CWriter writer("/dev/i2c-1");
 
    // 非同期処理で書き込みする場合
    QByteArray dataToWrite = QByteArray::fromHex("0102030405");
    QFuture<bool> writeFuture = writer.writeDataAsync(0x50, dataToWrite);
    writeFuture.then([](bool success) {
       qDebug() << "書き込み成功:" << success;
    });
 
    // ストリーミング書き込みする場合
    writer.startStreamingWrite(0x50, dataToWrite, 2000);  // 2秒間隔で書き込み
 
    // エラーハンドリング
    QObject::connect(&writer, &I2CWriter::errorOccurred, [](const QString &error) {
       qDebug() << "書き込みエラー: " << error;
    });
 
    // 10秒後にストリーミングを停止
    QTimer::singleShot(10000, &writer, &I2CWriter::stopStreamingWrite);
 
    return a.exec();
 }