Arduino nano を usb->I2c I/F として利用する

Debian編

前記事

で、Arduino nanoが使えるという話を書きましたが、センサーADT7410の出力を捉えて、日変化を記録しようとする試みです。Debianが動いてるマシンですが、ここにはArduino IDEはインストールしていません。一旦WindowsサイドのPCのArduino IDEからスケッチをコンパイルして、動作していれば、そのArduino nanoをUSB-I/F的に一旦切り離し、今度はDebianなマシンに接続してもちゃんとスケッチはResumeする。しかもArduino nanoの出力はシリアルポート経由で受け取れるというのがポイントです。結構便利かな?Debianマシンが直接I2Cを話さなくても、間に入れたAruduino nano経由でI2Cのセンサーが使えるということです。

さてまず接続してみましょうかね。Debian側から認識されるはずですね。システムコンソールを凝視していなくても、dmesg -Tでおけです。認識してるあたりは、

[金  7月  3 08:46:08 2026] usb 6-1: new full-speed USB device number 2 using uhci_hcd
[金  7月  3 08:46:08 2026] usb 6-1: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice= 2.54
[金  7月  3 08:46:08 2026] usb 6-1: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[金  7月  3 08:46:08 2026] usb 6-1: Product: USB2.0-Serial
[金  7月  3 08:46:09 2026] usbcore: registered new interface driver usbserial_generic
[金  7月  3 08:46:09 2026] usbserial: USB Serial support registered for generic
[金  7月  3 08:46:09 2026] usbcore: registered new interface driver ch341
[金  7月  3 08:46:09 2026] usbserial: USB Serial support registered for ch341-uart
[金  7月  3 08:46:09 2026] ch341 6-1:1.0: ch341-uart converter detected
[金  7月  3 08:46:09 2026] usb 6-1: ch341-uart converter now attached to ttyUSB0

/dev/ttyUSB0としてアクセスできるということですね。

シリアルデバイスは、cuとかscreenで読み取れます。筆者の環境では双方ともにinstallする必要がありました。といっても、screenは常用していますから、cuのみ新たにinstallしたのでした。

root@debianalt:~# cu -s 9600 -l /dev/ttyUSB0
cu: open (/dev/ttyUSB0): Permission denied
cu: /dev/ttyUSB0: Line in use

rootなんですけど…..。screenを試してみます。

root@debianalt:~# screen /dev/ttyUSB0 9600

25.7500
25.7500
25.6875
25.6875
25.6875
25.6875
25.6875
25.6875
25.7500
25.6875
25.6875
25.6875
25.6875
25.6875
25.6875
25.7500
25.6875
25.6875

動作してますね。cuでなんとかしたいわけですが、/dev/ttyUSB0のパーミッションの問題だろうということで、

chmod 666 /dev/ttyUSB0

します。で、

root@debianalt:~# cu -s 9600 -l /dev/ttyUSB0
Connected.
25.6250
25.6250
25.6250
25.6250
25.6250
25.6250
25.6250
25.6875
25.6250
25.6250
25.6250
25.6875
25.6250
25.6875

動いていますね。切断するのは、”~.”を入れます。

cu -s 9600 -l /dev/ttyUSB0
Connected.
25.6250
25.6250
25.6250
25.6250
25.6250
25.6250
~25.6250
.
Disconnected.

“~.”を入れる間に”25.6250″という出力が混ざりました。screenの場合は、ctrl-A Kです。

bashスクリプトで温度データを1個(以上)取得して戻るということをやりたいわけですが、”cu”ないし”screen”ではやりにくいので、C++でプログラム作ります。え、ほんと?って、Copilot様にお願いすれば、テキトーな例を提示してくれます。それが、

#include <iostream>
#include <string>
#include <fcntl.h>      // open()
#include <termios.h>    // termios, tcgetattr(), tcsetattr()
#include <unistd.h>     // read(), write(), close()
#include <cstring>      // memset()
#include <errno.h>      // errno

// Function to configure the serial port
bool configureSerialPort(int fd, speed_t baudRate) {
    struct termios tty;
    if (tcgetattr(fd, &tty) != 0) {
        std::cerr << "Error getting terminal attributes: " << strerror(errno) << "\n";
        return false;
    }

    // Set baud rate
    cfsetospeed(&tty, baudRate);
    cfsetispeed(&tty, baudRate);

    // Configure 8N1 (8 data bits, no parity, 1 stop bit)
    tty.c_cflag &= ~PARENB; // No parity
    tty.c_cflag &= ~CSTOPB; // 1 stop bit
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;     // 8 bits per byte

    tty.c_cflag &= ~CRTSCTS; // No hardware flow control
    tty.c_cflag |= CREAD | CLOCAL; // Enable receiver, ignore modem control lines

    // Raw input mode (no processing)
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // No software flow control
    tty.c_oflag &= ~OPOST; // Raw output

    // Set read timeout
    tty.c_cc[VMIN]  = 0;  // Minimum number of characters to read
    tty.c_cc[VTIME] = 10; // Timeout in deciseconds (1s)

    // Apply settings
    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        std::cerr << "Error setting terminal attributes: " << strerror(errno) << "\n";
        return false;
    }
    return true;
}

int main() {
    const char* portName = "/dev/ttyUSB0"; // Change to your serial port
    int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        std::cerr << "Error opening " << portName << ": " << strerror(errno) << "\n";
        return 1;
    }

    // Configure the serial port
    if (!configureSerialPort(fd, B9600)) { // Baud rate: 9600
        close(fd);
        return 1;
    }

    // Example: Write data
    std::string outData = "Hello Serial\n";
    int bytesWritten = write(fd, outData.c_str(), outData.size());
    if (bytesWritten < 0) {
        std::cerr << "Error writing to serial port: " << strerror(errno) << "\n";
    } else {
        std::cout << "Sent: " << outData;
    }

    // Example: Read data
    char buffer[256];
    int bytesRead = read(fd, buffer, sizeof(buffer) - 1);
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0'; // Null-terminate
        std::cout << "Received: " << buffer << "\n";
    } else if (bytesRead == 0) {
        std::cout << "No data received (timeout)\n";
    } else {
        std::cerr << "Error reading from serial port: " << strerror(errno) << "\n";
    }

    close(fd);
    return 0;
}

なんとも都合のいいことに、デバイスは”/dev/ttyUSB0″で、ボーレートは9600というラッキー。なので、ほぼそのまま動かせますね。上記コードが”sample.cpp”というfileならば、

g++ -o sample sample.cpp

でbuildできます。g++はGnu C++コンパイラです。Cコンパイラはgccです。動かすと、

nao@ghost:~$ ./sample
Sent: Hello Serial
Received: 25.9375

25.9375

25.9375

25.9375

25.93

ソースコードを読めばお分かりのように、256バイトのレシーブバッファが満杯になるまで/dev/ttyUSB0から読み出して、それを文字列として出力するというものです。受信の前に”Hello Serial”という文字列を送っていますが、無視されてるので、ここは削除。また1個のデータを抜き出すためのルーチンを作って足したものが、

cat ext.cpp
#include <iostream>
#include <string>
#include <fcntl.h>      // open()
#include <termios.h>    // termios, tcgetattr(), tcsetattr()
#include <unistd.h>     // read(), write(), close()
#include <cstring>      // memset()
#include <errno.h>      // errno

// Function to configure the serial port
bool configureSerialPort(int fd, speed_t baudRate) {
    struct termios tty;
    if (tcgetattr(fd, &tty) != 0) {
        std::cerr << "Error getting terminal attributes: " << strerror(errno) << "\n";
        return false;
    }

    // Set baud rate
    cfsetospeed(&tty, baudRate);
    cfsetispeed(&tty, baudRate);

    // Configure 8N1 (8 data bits, no parity, 1 stop bit)
    tty.c_cflag &= ~PARENB; // No parity
    tty.c_cflag &= ~CSTOPB; // 1 stop bit
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;     // 8 bits per byte

    tty.c_cflag &= ~CRTSCTS; // No hardware flow control
    tty.c_cflag |= CREAD | CLOCAL; // Enable receiver, ignore modem control lines

    // Raw input mode (no processing)
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // No software flow control
    tty.c_oflag &= ~OPOST; // Raw output

    // Set read timeout
    tty.c_cc[VMIN]  = 0;  // Minimum number of characters to read
    tty.c_cc[VTIME] = 10; // Timeout in deciseconds (1s)

    // Apply settings
    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        std::cerr << "Error setting terminal attributes: " << strerror(errno) << "\n";
        return false;
    }
    return true;
}

char* extract(char* input)
{
        char delimiter[] = "\n\n";
        char *start,*end;

        start = strstr(input,delimiter);

        start += 2; // skip \n\n

        end = strstr(start,delimiter);

        if( end )
                *end = '\0';
         else
                return nullptr;

        return start;

}

int main() {
    const char* portName = "/dev/ttyUSB0"; // Change to your serial port
    int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        std::cerr << "Error opening " << portName << ": " << strerror(errno) << "\n";
        return 1;
    }

    // Configure the serial port
    if (!configureSerialPort(fd, B9600)) { // Baud rate: 9600
        close(fd);
        return 1;
    }

    // Example: Write data
/*
    std::string outData = "Hello Serial\n";
    int bytesWritten = write(fd, outData.c_str(), outData.size());
    if (bytesWritten < 0) {
        std::cerr << "Error writing to serial port: " << strerror(errno) << "\n";
    } else {
        std::cout << "Sent: " << outData;
    }
*/
    // Example: Read data
    char buffer[256];
char* extdata;

    int bytesRead = read(fd, buffer, sizeof(buffer) - 1);
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0'; // Null-terminate
//        std::cout << "Received: " << buffer << "\n";
        extdata = extract(buffer);
        std::cout << extdata << "\n";
    } else if (bytesRead == 0) {
        std::cout << "No data received (timeout)\n";
    } else {
        std::cerr << "Error reading from serial port: " << strerror(errno) << "\n";
    }

    close(fd);
    return 0;
}

これを動かすと、

 ./ext
25.9375

のように温度データ1個だけが得られます。これに日付を足して、ログに書き加えていけばよさそうですね。具体的なスクリプトや、記録結果は別記事で。1日の変化が出ればいいんだけど。

コメント