2012年9月30日日曜日

C言語でシリアル通信



ANDROID,LINUX,WINDOWS,MAC
それぞれの環境でUSBシリアル通信(組み込み的にはUART通信)のプログラムを
つくったのでその感想を述べたい。

まず、androidだが、これはLINUXの一部なのだが、ドライバがほとんど対応してない。だからLINUXで/dev/ttyUSBのファイルができるのを確認しているドングル、ケーブルなどをUSBミニとUSBとの変換器経由で接続しても、ほとんど/dev/ttyUSBのファイルができない。これはUSBの変換器の不具合ではなく、androidのカーネルにドライバがないため。これをクリアするためにカーネルのフラグを立ててビルドしなおしてもだめ。理由はandroid appからアクセスするときアクセス権で拒否られるため。結局カーネル、シェル両方書き換えてOSごとインストールする必要あり。それでデバイスファイルができアクセス権も取れ、アプリからアクセス可能となる。

このあたりの顛末は
”google nexusにandroidを再インストール”
でかきましたんで興味がある人は読んで

 ただBluetooth用のUARTのチップを使った装置との通信なら上のようなことをする必要ないらしい。(したことないので分からないが)

次LINUX
一番素直なシリアル通信コードがかける。さすがC言語オリジナルという感じ
QTでコーデイングすればそのままMACでも同じコーデイングが使用可能。上の処理をすればそのままandroidでも動作する。

やり方。まずケーブル、ドングルなどシリアル通信するハードを接続。
ここで /dev/ttyUSB? というファイルが出来るのを確認。
何もほかに差してないなら、/dev/ttyUSB0 です。たぶん。

でC言語コーディングの話に移ります。
QTで私は作成しましたがなんでも同じです。
①ポートオープン

# include <stdio.h>
# include <stdlib.h>
# include <termio.h>
# include <unistd.h>
# include <fcntl.h>
# include <getopt.h>
# include <time.h>
# include <errno.h>
# include <string.h>
#include <sys/time.h>
 #include <unistd.h>

    struct termios TtyAttr;

    int DeviceSpeed = B9600;
    int ByteBits = CS8;
    char DeviceName[32];//="/dev/ttyUSB0";
    fd_set ReadSetFD;

int ncommFd=open(DeviceName, O_RDWR, 0);
    if (fcntl(ncommFd F_SETFL, O_NONBLOCK) < 0)
    {
            Error("Unable set to NONBLOCK mode");
            return ERROR_FCNTL;
    }
    memset(&TtyAttr, 0, sizeof(struct termios));
    TtyAttr.c_iflag = IGNPAR;
    TtyAttr.c_cflag = DeviceSpeed | HUPCL | ByteBits | CREAD|CLOCAL;
    TtyAttr.c_cc[VMIN] = 1;

    if (tcsetattr(ncommFd, TCSANOW, &TtyAttr) < 0)
    {
         Warning("Unable to set comm port");
    }
    FD_ZERO(&g_ReadSetFD);
    FD_SET(ncommFd, ReadSetFD);
    return 0;

②send
write(ncommFd, バッファ, 長さ);
③recv
read(ncommFd, バッファ, 長さ);

最後Windows
WindowsでC言語で上のコードを通しても失敗する(コンパイルエラー)
さらに/dev/ttyUSB0というファイルもできないし。
WINDOWSでシリアルポートの情報は実はレジストリからとりそのあとCreateFileでオープンするのだがそんなことはやってられない。
そこでQtでコーディングする方法をしらべると
qextserialportというものが公開されているのが分かる。
これは超便利なクラスでQtのシリアルアクセスをカプセル化します。
http://code.google.com/p/qextserialport/
そこでこれを使って上記コードを書き換えます

環境整備
上記からファイルを落とし展開
C:\qextserialport-1.2beta1\
に展開したとする。
プロジェクトの*.proに
include(C:\qextserialport-1.2beta1\src\qextserialport.pri)
を一行追加して保存

①ポートオープン
#include "qextserialenumerator.h"
//検索
    QList<QextPortInfo> ports = QextSerialEnumerator::getPorts();
    QString enumName;
    char ch_enumName[20];
    char ch_portname[20];
    char ch_com9[20]="COM9";
    printf("List of ports:\n");
    for (int i = 0; i < ports.size(); i++) {
            printf("port name: %s\n", ports.at(i).portName.toLocal8Bit().constData());
            printf("friendly name: %s\n", ports.at(i).friendName.toLocal8Bit().constData());
            printf("physical name: %s\n", ports.at(i).physName.toLocal8Bit().constData());
            printf("enumerator name: %s\n", ports.at(i).enumName.toLocal8Bit().constData());
            printf("===================================\n\n");
            sprintf(ch_enumName,"%s",ports.at(i).enumName.toLocal8Bit().constData());
            if(strcmp(ch_enumName,"USB")==0)
            {
                sprintf(ch_portname,"%s",ports.at(i).portName.toLocal8Bit().constData());
                if(strcmp(ch_portname,ch_com9))
                {
                    sprintf(portname,"\\\\.\\%s",ch_portname);
                }
                break;
            }else{
                sprintf(portname,"%s",ports.at(i).portName.toLocal8Bit().constData());
            }
    }
//open
    QextSerialPort *port = new QextSerialPort(portname);
    port->open(QIODevice::ReadWrite | QIODevice::Unbuffered);
    qDebug("is open: %d", port->isOpen());
    if(port->isOpen() != 1)
    {
        return;
    }
    port->setBaudRate(BAUD9600);
    port->setFlowControl(FLOW_OFF);
    port->setParity(PAR_NONE);
    port->setDataBits(DATA_8);
    port->setStopBits(STOP_1);
    //set timeouts to 500 ms
    port->setTimeout(0);

②send
port->write(バッファ, ながさ);
③recv
port->read(バッファ, ながさ);