Qtの基礎 - I2C通信
概要
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) とスレーブアドレスを指定する必要があることに注意する。
// I2CReader.h
#include <QObject>
#include <QI2CDevice>
#include <QFuture>
#include <QtConcurrent>
#include <QDebug>
class I2CReader : public QObject
{
Q_OBJECT
private:
QI2CDevice *m_device;
public:
explicit I2CReader(const QString &deviceName, QObject *parent = nullptr) : QObject(parent), m_device(new QI2CDevice(this))
{
if (!m_device->open(deviceName)) {
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();
}
m_device->setSlaveAddress(address);
QByteArray data(size, 0);
if (m_device->read(data.data(), size) != size) {
emit errorOccurred("データの読み込みに失敗: " + m_device->errorString());
return QByteArray();
}
emit dataReceived(data);
return data;
});
}
signals:
void dataReceived(const QByteArray &data);
void errorOccurred(const QString &error);
};
// エラーハンドリングと使用例
class I2CReaderManager : public QObject
{
Q_OBJECT
private:
I2CReader *m_reader;
public:
explicit I2CReaderManager(QObject *parent = nullptr) : QObject(parent)
{
m_reader = new I2CReader("/dev/i2c-1", this);
connect(m_reader, &I2CReader::errorOccurred, this, &I2CReaderManager::handleError);
connect(m_reader, &I2CReader::dataReceived, this, &I2CReaderManager::handleData);
}
void readData(int address, int size)
{
QFuture<QByteArray> future = m_reader->readDataAsync(address, size);
future.then([this](const QByteArray &data) {
if (data.isEmpty()) {
qDebug() << "受信に失敗";
}
else {
qDebug() << "受信に成功:" << data.toHex();
}
});
}
private slots:
void handleError(const QString &error)
{
qDebug() << "エラーが発生: " << error;
// エラーに応じて適切な処理を行う
}
void handleData(const QByteArray &data)
{
qDebug() << "データを受信しました:" << data.toHex();
// 受信したデータを処理する
}
};
上記のクラスを使用して、I2C通信でデータを受信する。
#include "I2CReader.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
I2CReaderManager readerManager;
// I2C通信で受信
readerManager.readData(0x50, 10);
return a.exec();
}
I2C通信の送信
以下の例では、非同期処理を使用して、I2C通信の送信を行っている。
適切なI2Cデバイス名 (例: /dev/i2c-1) とスレーブアドレスを指定する必要があることに注意する。
// I2CWriter.hファイル
#include <QObject>
#include <QI2CDevice>
#include <QFuture>
#include <QtConcurrent>
#include <QDebug>
class I2CWriter : public QObject
{
Q_OBJECT
private:
QI2CDevice *m_device;
public:
explicit I2CWriter(const QString &deviceName, QObject *parent = nullptr) : QObject(parent), m_device(new QI2CDevice(this))
{
if (!m_device->open(deviceName)) {
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;
}
m_device->setSlaveAddress(address);
if (m_device->write(data.constData(), data.size()) != data.size()) {
emit errorOccurred("データの書き込みに失敗: " + m_device->errorString());
return false;
}
emit writeCompleted(true);
return true;
});
}
signals:
void writeCompleted(bool success);
void errorOccurred(const QString &error);
};
// エラーハンドリングと使用例
class I2CWriterManager : public QObject
{
Q_OBJECT
private:
I2CWriter *m_writer;
public:
explicit I2CWriterManager(QObject *parent = nullptr) : QObject(parent)
{
m_writer = new I2CWriter("/dev/i2c-1", this);
connect(m_writer, &I2CWriter::errorOccurred, this, &I2CWriterManager::handleError);
connect(m_writer, &I2CWriter::writeCompleted, this, &I2CWriterManager::handleWriteCompleted);
}
void writeData(int address, const QByteArray &data)
{
QFuture<bool> future = m_writer->writeDataAsync(address, data);
future.then([this](bool success) {
if (success) {
qDebug() << "書き込み成功";
}
else {
qDebug() << "書き込みに失敗";
}
});
}
private slots:
void handleError(const QString &error)
{
qDebug() << "エラーが発生: " << error;
// エラーに応じて適切な処理を行う
}
void handleWriteCompleted(bool success)
{
qDebug() << "書き込み完了: " << (success ? "成功" : "失敗");
// 書き込み完了後の処理を行う
}
};
上記のクラスを使用して、I2C通信でデータを送信する。
#include "I2CWriter.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
I2CWriterManager writerManager;
// 書き込み操作
QByteArray dataToWrite = QByteArray::fromHex("0102030405");
writerManager.writeData(0x50, dataToWrite);
return a.exec();
}