Linux環境アプリケーションプログラミング(9):シリアルポート

1つ:シリアルポートの概要

         UARTの動作は、データ送信、データ受信、割り込み生成、ボーレート設定、ループバックモード、赤外線モード、ハードフロー制御モードとソフトフロー制御モードに分けることができます。Linuxでは、すべてのデバイスファイルは通常「/ dev」の下にあります。シリアルポート1とシリアルポート2に対応するデバイス名は、順番に「/ dev / ttyS0」と「/ dev / ttyS1」であり、USBからシリアルへデバイス名前は通常「/ dev / ttyUSB0」と「/ dev / ttyUSB1」です。「/ dev」の下のファイルで確認できます。Linuxでのデバイスの操作方法はファイルの操作方法と同じです。 。シリアルポートは、単純なread()およびwrite()関数を使用して読み取りおよび書き込みが可能です。唯一の違いは、シリアルポートの他のパラメーターを個別に構成する必要があることです。

2:シリアルポート設定

シリアルポートの設定は、主にstructtermios構造体の各メンバーの値を設定するためのものです。

#include<termios.h>
struct termios
{
    unsigned short c_iflag; /* 输入模式标志 */
    unsigned short c_oflag; /* 输出模式标志 */
    unsigned short c_cflag; /* 控制模式标志*/
    unsigned short c_lflag; /* 本地模式标志 */
    unsigned char c_line; /* 线路规程 */
    unsigned char c_cc[NCC]; /* 控制特性 */
    speed_t c_ispeed; /* 输入速度 */
    speed_t c_ospeed; /* 输出速度 */
};

        termiosは、POSIX仕様で定義されている標準インターフェースであり、端末機器(仮想端末、シリアルポートなどを含む)を意味します。ポートは端末デバイスであり、通常、端末プログラミングインターフェイスを介して構成および制御されます。シリアルポート関連のプログラミングを詳細に説明する前に、まず端末関連の知識を理解してください。端末には、カノニカルモード、非カノニカルモード、およびrawモードの3つの動作モードがあります。

        termios構造体のc_lflagでICANNONフラグを設定することにより、端末がカノニカルモード(ICANNONフラグを設定)で動作するか、非カノニカルモード(ICANNONフラグをクリアする)で動作するかを定義できます。デフォルトはカノニカルモードです。正規モードでは、すべての入力は行ベースで処理されます。ユーザーが行末文字(キャリッジリターン、EOFなど)を入力する前に、システムはread()関数を呼び出し、ユーザーが入力した文字を読み取ることはできません。EOF以外の行末文字(キャリッジリターンなど)は、通常の文字と同じように、read()関数によってバッファに読み込まれます。標準モードでは、行編集が可能であり、read()関数を呼び出すことで最大1行のデータを読み取ることができます。read()関数で読み取るように要求されたデータバイト数が現在の行で読み取ることができるバイト数より少ない場合、read()関数は要求されたバイト数と残りのバイトのみを読み取ります。次回使用しますもう一度お読みください。非標準モードでは、すべての入力が瞬時に有効になり、ユーザーは行末文字を個別に入力する必要がなく、行の編集は許可されません。非標準モードでは、パラメーターMIN(c_cc [VMIN])およびTIME(c_cc [VTIME])の設定により、read()関数の呼び出しメソッドが決まります。設定には4つの異なる状況があります

MIN= 0およびTIME = 0:read()関数はすぐに戻ります。読み取り可能なデータがある場合は、データを読み取り、読み取ったバイト数を返します。ない場合、読み取りは失敗し、0を返します。
MIN> 0およびTIME = 0:read()関数は、MINバイトのデータを読み取ることができるまでブロックされます。
MIN= 0かつTIME> 0:読み取るデータがあるか、10分の1秒が経過している限り、read()関数はすぐに戻り、戻り値は読み取られたバイト数です。タイムアウトしてデータが読み取られない場合、read()関数は0を返します。
MIN> 0およびTIME> 0:read()関数は、読み取るMINバイトがある場合、または2つの入力文字間の時間間隔がTIMEの10分の1秒を超える場合にのみ戻ります。システムは最初の文字が入力されるまでタイマーを開始しないため、この場合、read()関数は少なくとも1バイトを読み取った後に戻ります。

       厳密に言えば、元のモードは特別な非標準モードです。元のモードでは、すべての入力データはバイト単位で処理されます。このモードでは、端末をエコーすることはできず、特定の端末入出力制御処理をすべて使用することはできません。cfmakeraw()関数を呼び出すことにより、端末を元のモードに設定できます。具体的な実装は次のとおりです。

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8

        シリアルポートの最も基本的な設定には、ボーレート設定、パリティビット、ストップビット設定が含まれます。この構造で最も重要なのはc_cflagです。値を割り当てることで、ユーザーはボーレート、文字サイズ、データビット、ストップビット、パリティビット、ハードフロー制御とソフトフロー制御を設定できます。ここでは、c_cflagメンバーを直接初期化することはできませんが、一部のオプションは「and」および「or」操作で使用する必要があります。

c_cflagでサポートされる定数名:

CBAUDボーレートのビットマスク
B0 0ボーレート(DTRが放棄された)
......
B1800 1800ボーレート
B2400 2400ボーレートB4800 4800ボーレート
B9600 9600ボーレート
B19200 19200ボー・
レート
B38400 38400ボーレートB57600 57600ボーレート
B115200 115200ボーレート
EXTA外部クロックレート
EXTB外部クロックレート
CSIZEデータは、マスクビット
CS5 5データビット
CS6 6データビットを
CS7 7データビット
CS8 8データビット
(設定されていない場合、1つのストップビット)CSTOPB 2ストップビットは
CREADイネーブル受信
PARENBパリティビットがPARODDを有効
代わり偶数パリティの奇数パリティ使用し
、最終的にハングアップ閉じHUPCLを(DTRを放棄)
(CLOCALローカル接続をポート所有者を変更しないでください)
CRTSCTSハードウェアフロー制御

入力モードフラグc_iflagは、ポートの受信側での文字入力処理を制御するために使用されます。

c_iflagでサポートされる定数名:

INPCKパリティチェックイネーブル
IGNPAR
パリティエラーを無視PARMRKパリティエラーマスク
ISTRIP8番目のビットをカットオフ
IXON出力ソフトウェアフロー制御を
開始IXOFF入力ソフトウェアフロー制御を開始
IXANY出力を再開する任意の文字を入力(デフォルトは入力)出力は開始文字の後でのみ再開します)
IGNBRK入力終了条件を無視する
BRKINT入力終了条件を検出したら、SIGINT信号
INLCRを送信して、受信したNL(改行)をCR(キャリッジリターン)に
変換します。IGNCR受信したCR(キャリッジリターン)Fu)
と両方のICRNLを無視します。受信したCR(キャリッジリターン)からNL(改行)へ
の受信した大文字から小文字への文字マッピング
IMAXBELのときに入力キューがいっぱいのときにIUCLCリング

出力モードフラグc_oflagは、制御ポートから送信された文字を処理するために使用されます。

c_oflagでサポートされる定数名:

OPOSTは出力処理機能を有効にします。このフラグが設定されていない場合、他のフラグ
無視されます。OLCUCは出力の大文字を小文字
に変換します。ONLCRは出力の改行文字( '\ n')をキャリッジに変換しますreturn( '\ r')
ONOCR現在の列番号が0の場合、キャリッジリターンは出力されません
。OCRNLは、出力のキャリッジリターン( '\ r')を改行( '\ n')に変換します。ONLRETは変換しません。キャリッジリターン
OFILLを出力し、パディング文字を送信して遅延
OFDELを提供します。このフラグが設定されている場合は、塗りつぶし文字がDEL文字であることを意味し、それ以外の場合はNUL文字です
。NLDLY改行遅延マスク
CRDLYキャリッジリターン遅延マスク
TABDLYタブ遅延マスク
BSDLY水平バックスペース遅延マスク
VTDLY垂直バックスペース遅延マスク
FFLDYページフィード遅延マスク                                                                                                                              

c_lflagは、制御端末のローカルデータ処理と動作モードを制御するために使用されます。

c_lflagでサポートされる定数名:

ISIGが信号文字(INTR、QUITなど)を受信すると、対応する信号
ICANONを生成します
通常モードのECHOを有効にします。ローカルエコー機能を有効にします
。ECHOE。ICANONが設定されている場合、バックスペース操作が許可されます
。ECHOK。ICANONが設定すると、KILL文字は現在のライン
エコーを削除します。ICANONが設定されている場合、ラインフィードを
エコーできます。ECHOCTLECHOが設定されている場合、制御文字(タブ、改行など)は「^ X」として表示されます。ここで、XのASCIIコードは、対応する制御文字Add0x40のASCIIコードと同じです。例:バックスペース文字(0x08)は「^ H」と表示されます(「H」のASCIIコードは0x48です)
ECHOPRT ICANONとIECHOが設定されている場合、削除された文字(バックスペースなど)と削除された文字は
ECHOKEとして表示ICANONが設定されている場合、ECHOEおよび
ECHOPRTに設定されているKILL文字NOFLSHをエコーできます。通常、INTR、QUIT、およびSUSP制御文字を受信すると、入力キューと出力キューがクリアされます。このフラグが設定されている場合、すべてのキューが空になることはありません
。TOSTOPバックグラウンドプロセスがその制御端末に書き込もうとすると、システムはSIGTTOUシグナルをバックグラウンドプロセスのプロセスグループに送信します。この信号は通常、プロセスの実行を終了します
IEXTENは入力処理機能を有効にします

c_ccは、特別な制御特性を定義します。

c_ccでサポートされる定数名:

VINTR割り込み制御文字、対応するキーはCTRL + C
VQUIT終了演算子、対応するキーは
CRTL + Z VERASE削除演算子、対応するキーはバックスペース(BS)
VKILL削除行文字、対応するキーはCTRL + U
VEOFファイル終了文字、対応するキーはCTRL + D
VEOL追加行終了文字、対応するキーはキャリッジリターン(CR)
VEOL2 2番目の行終了文字、対応するキーは改行(LF)
VMINは
読み取る最小文字数を指定ますVTIMEは各読み取りタイムアウトを指定します文字間

1.元のシリアルポート構成を保存します

       まず、安全性と将来のプログラムのデバッグの便宜のために、最初に元のシリアルポート構成を保存できます。ここでは、関数tcgetattr(fd、&old_cfg)を使用できます。この関数は、fdが指す端末の構成パラメーターを取得し、それらをtermios構造変数old_cfgに保存します。この関数は、構成が正しいかどうか、シリアルポートが使用可能かどうかなどをテストすることもできます。呼び出しが成功した場合、関数の戻り値は0であり、呼び出しが失敗した場合、関数の戻り値は-1です。

if (tcgetattr(fd, &old_cfg) != 0)
{
    perror("tcgetattr");
    return -1;
}

2.アクティベーションオプション

CLOCALとCREADは、それぞれローカル接続とアクセプタンスイネーブルに使用されるため、最初にビットマスクを使用して2つのオプションをアクティブにする必要があります。

newtio.c_cflag |= CLOCAL | CREAD;

cfmakeraw()関数を呼び出すことにより、端末を元のモードに設定できます。次の例では、元のモードをシリアルデータ通信に使用しています。

cfmakeraw(&new_cfg);

3.ボーレートを設定します

       ボーレートを設定するための特別な機能があり、ユーザーはビットマスクを介して直接操作することはできません。ボーレートを設定する主な関数は、cfsetispeed()とcfsetospeed()です。通常、ユーザーは端末の入力ボーレートと出力ボーレートを同じに設定する必要があります。これらの関数は、成功すると0を返し、失敗すると1を返します。

cfsetispeed(&new_cfg, B115200);
cfsetospeed(&new_cfg, B115200);

4.文字サイズを設定します

ボーレートの設定とは異なり、文字サイズを設定するためのすぐに使用できる機能はなく、ビットマスクが必要です。通常、データビットのビットマスクは最初に削除され、次に必要に応じて再設定されます。

new_cfg.c_cflag &= ~CSIZE; /* 用数据位掩码清空数据位设置 */
new_cfg.c_cflag |= CS8

5.パリティビットを設定します

パリティビットを設定するには、termiosにc_cflagとc_iflagの2つのメンバーが必要です。まず、c_cflagでパリティ有効化フラグPARENBをアクティブにし、偶数チェックを実行するかどうかを確認すると同時に、c_iflagで入力データのパリティチェック有効化(INPCK)をアクティブにします。

使能奇校验:
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;

使能偶校验:
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除偶校验标志,则配置为奇校验*/
new_cfg.c_iflag |= INPCK;

6.ストップビットを設定します

ストップビットの設定は、c_cflagでCSTOPBをアクティブにすることによって実現されます。ストップビットが1つある場合は、CSTOPBがクリアされ、ストップビットが2つある場合は、CSTOPBがアクティブになります。

new_cfg.c_cflag &= ~CSTOPB; /* 将停止位设置为一个比特 */
new_cfg.c_cflag |= CSTOPB; /* 将停止位设置为两个比特 */

7.最小文字数を設定し、イベントを待ちます

受信した文字と待機時間に特別な要件がない場合は、0に設定でき、read()関数はどのような場合でもすぐに戻ります。

new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;

8.シリアルポートバッファをクリアします

       シリアルポートがリセットされた後、現在のシリアルデバイスを適切に処理する必要があります。次に、tcdrain()、tcflow()、tcflush()、および<termios.h>で宣言されたその他の関数を呼び出して、現在のシリアルバッファデータを処理できます。に。

int tcdrain(int fd); /* 使程序阻塞,直到输出缓冲区的数据全部发送完毕*/
int tcflow(int fd, int action) ; /* 用于暂停或重新开始输出 */
int tcflush(int fd, int queue_selector); /* 用于清空输入/输出缓冲区*/

        バッファで送信されていないデータ、または受信されたが読み取られていないデータのtcflush()関数、その処理方法はqueue_selectorの値に依存し、可能な値は次のとおりです。
TCIFLUSH:受信したが読み取られなかったデータをクリアします。
TCOFLUSH:正常に送信されなかった出力データをクリアします。
TCIOFLUSH:最初の2つの関数を含みます。つまり、未処理の入力データと出力データをクリアします。
最初のメソッドを使用する場合:tcflush(fd、TCIFLUSH);

9.構成をアクティブ化します

すべてのシリアルポートの構成が完了したら、今すぐ構成をアクティブにして、構成を有効にします。ここで使用される関数はtcsetattr()であり、その関数プロトタイプは次のとおりです。

tcsetattr(int fd, int optional_actions, const struct termios *termios_p);


options_actionsパラメーターの可能な値は次のとおりです。TCSANOW:構成の変更はすぐに有効になります。
TCSADRAIN:構成の変更は、fdに書き込まれたすべての出力が送信された後に有効になります。
TCSAFLUSH:変更が有効になる前に、受け入れられたが読み取られなかった入力はすべて破棄されます。
この関数は、呼び出しが成功した場合は0を返し、失敗した場合は1を返します。コードは次のとおりです。

if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0)
{
    perror("tcsetattr");
    return -1;
}

3:構成テンプレート

int set_com_config(int fd,int baud_rate, int data_bits, char parity, int stop_bits)
{
    struct termios new_cfg,old_cfg;
    int speed;
    /*保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
    if (tcgetattr(fd, &old_cfg) != 0)
    {
        perror("tcgetattr");
        return -1;
    }
    /* 设置字符大小*/
    new_cfg = old_cfg;
    cfmakeraw(&new_cfg); /* 配置为原始模式 */
    new_cfg.c_cflag &= ~CSIZE;
    /*设置波特率*/
    switch (baud_rate)
    {
        case 2400:
        {
            speed = B2400;
        }
        break;
        case 4800:
        {
            speed = B4800;
        }
        break;
        case 9600:
        {
            speed = B9600;
        }
        break;
        case 19200:
        {
            speed = B19200;
        }
        break;
        case 38400:
        {
            speed = B38400;
        }
        break;
        default:
        case 115200:
        {
            speed = B115200;
        }
        break;
    }
    cfsetispeed(&new_cfg, speed);
    cfsetospeed(&new_cfg, speed);
    /*设置数据位*/
    switch (data_bits)
    {
        case 7:
        {
            new_cfg.c_cflag |= CS7;
        }
        break;
        default:
        case 8:
        {
            new_cfg.c_cflag |= CS8;
        }
        break;
    }
    /*设置奇偶校验位*/
    switch (parity)
    {
        default:
        case 'n':
        case 'N':
        {
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_iflag &= ~INPCK;
        }
        break;
        case 'o':
        case 'O':
        {
            new_cfg.c_cflag |= (PARODD | PARENB);
            new_cfg.c_iflag |= INPCK;
        }
        break;
        case 'e':
        case 'E':
        {
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;
            new_cfg.c_iflag |= INPCK;
        }
        break;
        case 's': /*as no parity*/
        case 'S':
        {
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_cflag &= ~CSTOPB;
        }
        break;
    }
    /*设置停止位*/
    switch (stop_bits)
    {
        default:
        case 1:
        {
            new_cfg.c_cflag &= ~CSTOPB;
        }
        break;
        case 2:
        {
            new_cfg.c_cflag |= CSTOPB;
        }
    }
    /*设置等待时间和最小接收字符*/
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN] = 1;
    /*处理未接收字符*/
    tcflush(fd, TCIFLUSH);
    /*激活新配置*/
    if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0)
    {
        perror("tcsetattr");
        return -1;
    }
    return 0;
}

4:シリアルポートの例

         シリアルポートの関連するプロパティを構成した後、シリアルポートを開いて読み取りおよび書き込みを行うことができます。使用する関数は、open()、write()、read()である通常のファイルの読み取りおよび書き込み関数と同じです。それらの違いは、シリアルポートが端末デバイスであるため、関数の特定のパラメータを選択するときにいくつかの違いがあります。さらに、ここではいくつかの追加機能を使用して、端末デバイスの接続をテストします。

1.シリアルポートを開きます

シリアルポートを開くことは、以下に示すように、open()関数を使用して通常のファイルを開くことと同じです。

fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);

ご覧のとおり、通常の読み取りおよび書き込みパラメーターに加えて、2つのパラメーターO_NOCTTYおよびO_NDELAYがあります。
O_NOCTTYフラグは、このパラメーターが開かれたファイルをこのプロセスの制御端末にしないことをLinuxシステムに通知するために使用されます。このフラグが指定されていない場合、入力(キーボードの中止信号など)はユーザーのプロセスに影響します。
O_NDELAYフラグは、このプログラムがDCD信号線のステータス(ポートのもう一方の端がアクティブ化されているか停止されているか)を考慮しないことをLinuxシステムに通知します。ユーザーがこのフラグを指定すると、プロセスはDCD信号ラインがアクティブになるまでスリープ状態のままになります。
次に、シリアルポートの状態をブロック状態に復元できます。これは、シリアルポートデータが読み込まれるのを待つために使用されます。次のように、fcntl()関数を使用して実装できます。

fcntl(fd, F_SETFL, 0);

次に、開いているファイル記述子が端末デバイスに接続されているかどうかをテストして、シリアルポートが正しく開かれているかどうかをさらに確認できます。関数呼び出しは、成功した場合は0を返し、失敗した場合は-1を返します。次のように:

isatty(STDIN_FILENO);
/*打开串口函数*/
int open_port(int com_port)
{
    int fd;
#if (COM_TYPE == GNR_COM) /* 使用普通串口 */
    char *dev[] = {"/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2"};
#else /* 使用 USB 转串口 */
    char *dev[] = {"/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2"};
#endif
    if ((com_port < 0) || (com_port > MAX_COM_NUM))
    {
        return -1;
    }
    /* 打开串口 */
    fd = open(dev[com_port - 1], O_RDWR|O_NOCTTY|O_NDELAY);
    if (fd < 0)
    {
        perror("open serial port");
        return(-1);
    }
    /*恢复串口为阻塞状态*/
    if (fcntl(fd, F_SETFL, 0) < 0)
    {
        perror("fcntl F_SETFL\n");
    }
    /*测试是否为终端设备*/
    if (isatty(STDIN_FILENO) == 0)
    {
        perror("standard input is not a terminal device");
    }
    return fd;
}

2.シリアルポートの読み取りと書き込み

シリアルポートの読み取りと書き込みの操作は、通常のファイルの読み取りと書き込みと同じです。次に示すように、read()関数とwrite()関数を使用するだけです。

write(fd, buff, strlen(buff));
read(fd, buff, BUFFER_SIZE);

次の2つの例は、シリアルポートの読み取りと書き込みの2つの手順を示しています。これらの手順では、上記のopen_port()関数とset_com_config()関数を使用します。シリアルポートを書き込むプログラムはホスト上で実行され、シリアルポートを読み取るプログラムはターゲットボード上で実行されます。
シリアルポートを書き込む手順は次のとおりです。

/* com_writer.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "uart_api.h"

int main(void)
{
    int fd;
    char buff[BUFFER_SIZE];
    if((fd = open_port(HOST_COM_PORT)) < 0) /* 打开串口 */
    {
        perror("open_port");
        return 1;
    }
    if(set_com_config(fd, 115200, 8, 'N', 1) < 0) /* 配置串口 */
    {
        perror("set_com_config");
        return 1;
    }
    do
    {
        printf("Input some words(enter 'quit' to exit):");
        memset(buff, 0, BUFFER_SIZE);
        if (fgets(buff, BUFFER_SIZE, stdin) == NULL)
        {
            perror("fgets");
            break;
        }
        write(fd, buff, strlen(buff));
    } while(strncmp(buff, "quit", 4));
    close(fd);
    return 0;
}

シリアルポートを読み取る手順は次のとおりです。

/* com_reader.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "uart_api.h"

int main(void)
{
    int fd;
    char buff[BUFFER_SIZE];
    if((fd = open_port(TARGET_COM_PORT)) < 0) /* 打开串口 */
    {
        perror("open_port");
        return 1;
    }
    if(set_com_config(fd, 115200, 8, 'N', 1) < 0) /* 配置串口 */
    {
        perror("set_com_config");
        return 1;
    }
    do
    {
        memset(buff, 0, BUFFER_SIZE);
        if (read(fd, buff, BUFFER_SIZE) > 0)
        {
            printf("The received words are : %s", buff);
        }
    } while(strncmp(buff, "quit", 4));
    close(fd);
    return 0;
}

プログラムを実行してホストコンピュータのシリアルポートを書き込み、プログラムを実行してターゲットボードのシリアルポートを読み取ります。結果を以下に示します。

/* 宿主机 ,写串口*/
$ ./com_writer
Input some words(enter 'quit' to exit):hello, Reader!
Input some words(enter 'quit' to exit):I'm Writer!
Input some words(enter 'quit' to exit):This is a serial port testing program.
Input some words(enter 'quit' to exit):quit
/* 目标板 ,读串口*/
$ ./com_reader
The received words are : hello, Reader!
The received words are : I'm Writer!
The received words are : This is a serial port testing program.
The received words are : quit

 

おすすめ

転載: blog.csdn.net/qq_34968572/article/details/111287260