Linux樹莓派串口通信編程 —— C語言實現AT指令集的發送與接收

一. 前言

 在拿到我的EC20 4G模塊後,可謂是迫不及待的去辦了一張新的電話卡,可是在插上卡以後,登錄我的樹莓派,卻始終存在一個問題,插上卡以後,使用AT命令

AT+CSQ

查看信號強度,一切正常,

AT+CPIN?

一切也都就緒,但是在使用

AT+CREG?

時,卻出現了(0,2)的錯誤,我上一篇博客有提到,使用這個命令第二個參數出現2說明卡還沒有註冊上,但是處於正在註冊狀態,可是無論我怎麼等,仍然處於這個狀態,換了幾張卡依然如此,能檢查出卡,卻大不了電話發不了短信,在網上找了各種解決方法,csdn,找了EC20 4G模塊的淘寶客戶,提供的方法都解決不了,在經過一天努力未果後,我決定放棄了,於是我拔下了模塊的天線,可就是這時候,我看到了天線插口寫着一個DIV,另一個插口寫着一個MAIN(超級小),內心一顫,心想會不會是天線的原因,果不其然,在我跟換天線的插口位置以後,一切的問題全都解決了…
原來模塊分爲兩個天線口,一個是主集天線,一個是分集天線,後者只能用來接收數據,所以在查看信號時時沒問題的,讓我壓根沒想到這倆天線還有差別,所以如果你也遇到AT+CREG?返回 +CREG: 0,2 不妨也試試 ……

二. 要掌握的知識

廢話不多說,這次要寫的,是一個用於串口通信的程序,類似於我們Linux中busybox的microcom工具,該工具用來實現與串口之間的通信,如圖:
在這裏插入圖片描述
因爲的我們在使用microcom的時候,接收到的數據與答覆,都是通過肉眼來觀察判斷的,但如果通過程序實現,我們可以將接收到的數據進行處理並利用進程間通信或是socket發送至其他地方,又或者可以設置每隔多少時間發送一條短信,並且,在學習這個程序的編程中,將會慢慢的對串口通信更加了解,我之前的博客介紹了串口通信的基本知識,在掌握這些後,我們一起來看程序吧~

2.1 struct termios 結構體

通過termios結構體,我們可以更好地控制串口的屬性,例如輸入,輸出或者一些特殊的要求我們都可以通過設置這個值來實現:

struct termios
{
       tcflag_t c_iflag;           //輸入模式標誌
       tcflag_t c_oflag;           //輸出模式標誌
       tcflag_t c_cflag;           //控制模式標誌
       tcflag_t c_lflag;           //本地模式標誌
       cc_t    c_cc[NCCS];        //控制字符

       speed_t c_isspeed;         //輸入波特率
       speed_t c_ospedd;          //輸出波特率
}

通過設定對應功能的結構體成員以達到控制串口屬性的目的,屬性的設置是通過標誌位來實現的,通過與,或和取反等方式,來將對應的功能模塊的標誌位置0或者置1,從而告訴系統是否要有此功能,關於屬性設置,分爲輸入,輸出,控制等屬性,下面一一來看他們都有哪些鍵值設置吧:

2.2 c_iflag 輸入模式標誌

  • IGNBRK
    忽略BREAK鍵輸入

  • BRKINT
    如果設置了IGNBRK,BREAK鍵的輸入將被忽略,如果設置了BRKINT ,將產生SIGINT中斷

  • IGNPAR
    忽略奇偶校驗錯誤

  • PARMRK
    標識奇偶校驗錯誤

  • INPCK
    允許輸入奇偶校驗

  • ISTRIP
    去除字符的第8個比特

  • INLCR
    將輸入的NL(換行)轉換成CR(回車)

  • IGNCR
    忽略輸入的回車

  • ICRNL
    將輸入的回車轉化成換行(如果IGNCR未設置的情況下)

  • IUCLC
    將輸入的大寫字符轉換成小寫字符(非POSIX)

  • IXON
    允許輸入時對XON/XOFF流進行控制

  • IXANY
    輸入任何字符將重啓停止的輸出

  • IXOFF
    允許輸入時對XON/XOFF流進行控制

  • IMAXBEL
    當輸入隊列滿的時候開始響鈴,Linux在使用該參數而是認爲該參數總是已經設置

2.2 c_oflag 輸出模式標誌

  • OPOST
    處理後輸出

  • OLCUC
    將輸入的小寫字符轉換成大寫字符(非POSIX)

  • ONLCR
    將輸入的NL(換行)轉換成CR(回車)及NL(換行)

  • OCRNL
    將輸入的CR(回車)轉換成NL(換行)

  • ONOCR
    第一行不輸出回車符

  • ONLRET
    不輸出回車符

  • OFILL
    發送填充字符以延遲終端輸出

  • OFDEL
    以ASCII碼的DEL作爲填充字符,如果未設置該參數,填充字符將是NUL(‘\0’)(非POSIX)

  • NLDLY
    換行輸出延時,可以取NL0(不延遲)或NL1(延遲0.1s)

  • CRDLY
    回車延遲,取值範圍爲:CR0、CR1、CR2和 CR3

  • TABDLY
    水平製表符輸出延遲,取值範圍爲:TAB0、TAB1、TAB2和TAB3

  • BSDLY
    空格輸出延遲,可以取BS0或BS1

  • VTDLY
    垂直製表符輸出延遲,可以取VT0或VT1

  • FFDLY
    換頁延遲,可以取FF0或FF1

2.3 c_cflag 控制模式標誌

  • CBAUD
    波特率(4+1位)(非POSIX)

  • CBAUDEX
    附加波特率(1位)(非POSIX)

  • CSIZE
    字符長度,取值範圍爲CS5、CS6、CS7或CS8

  • CSTOPB
    設置兩個停止位

  • CREAD
    使用接收器

  • PARENB
    使用奇偶校驗

  • PARODD
    對輸入使用奇偶校驗,對輸出使用偶校驗

  • HUPCL
    關閉設備時掛起

  • CLOCAL
    忽略調制解調器線路狀態

  • CRTSCTS
    使用RTS/CTS流控制

2.4 c_lflag 本地控制模式

  • ISIG
    當輸入INTR、QUIT、SUSP或DSUSP時,產生相應的信號

  • ICANON
    使用標準輸入模式

  • XCASE
    在ICANON和XCASE同時設置的情況下,終端只使用大寫。如果只設置了XCASE,則輸入字符將被轉換爲小寫字符,除非字符使用了轉義字符(非POSIX,且Linux不支持該參數)

  • ECHO
    顯示輸入字符

  • ECHOE
    如果ICANON同時設置,ERASE將刪除輸入的字符,WERASE將刪除輸入的單詞

  • ECHOK
    如果ICANON同時設置,KILL將刪除當前行

  • ECHONL
    如果ICANON同時設置,即使ECHO沒有設置依然顯示換行符

  • ECHOPRT
    如果ECHO和ICANON同時設置,將刪除打印出的字符(非POSIX)

  • TOSTOP
    向後臺輸出發送SIGTTOU信號

2.5 c_cc[VTIME] , c_cc[VMIN]

調用read()函數讀取串口數據時,返回讀取數據的數量需要考慮兩個變量: MIN和TIME。MIN和TIME在termios結構的c_ cc成員的數組下標名爲VMIN和VTIME。
MIN是指一次read調用期望返回的最小字節數。VTIME說明等待數據到達的分秒數(秒的1/10爲分秒)。TIME與MIN組合使用的具體含義分爲以下四種情形:

當MIN>0,TIME>0時
計時器在收到第-一個字節後啓動,在計時器超時之前(TIME的時間到),若己收到MIN個字節,則read返回MIN個字節,否則,在計時器超時後返回實際接收到的字節。
注意:因爲只有在接收到第一個字節時纔開始計時,所以至少可以返回1個字節。這種情形中,在接到第一個字節之前,調用者阻塞。如果在調用read時數據已經可用,則如同在read後數據立即被接到一樣。

當MIN>0,TIME=0時
MIN個字節完整接收後,read 才返回,這可能會造成read無限期地阻塞。

當MIN=0,TIME>0時
TIME爲允許等待的最大時間,計時器在調用read時立即啓動,在串口接到1字節數據或者計時器超時後即返回,如果是計時器超時,則返回0。

當MIN=0,TIME= 0時
如果有數據可用,則read最多返回所要求的字節數,如果無數據可用,則read立即返回0。

2.6 tcgetattr() 與 tcsetattr()

函數原型:

int tcgetattr(int fd,struct termios &termios_p);
int tcsetattr(int fd,int actions,const struct termios *termios_p);

tcgetattr()

  • 參數
    int fd: 打開串口文件後,獲取到的文件描述符
    struct termios &termios_p: termios 類型的結構體,包含在 <termios.h> 頭文件中,這裏需要傳地址或指針

  • 返回值:成功返回 0,失敗返回 -1

  • 函數功能: 獲取文件描述符對應串口的原始屬性,並保存在第二個參數中,通常獲取的原始屬性需要進行備份,在程序退出之前要將其修改回來,否則無法繼續使用串口。

tcsetattr()

  • 參數
    int fd: 要設置屬性的文件描述符
    int actions: 設置屬性時,可以控制屬性生效的時刻,actions可以取下面幾個值:
    TCSANOW: 立即生效
    TCADRAIN: 改變在所有寫入fd 的輸出都被傳輸後生效。這個函數應當用於修改影響輸出的參數時使用。(當前輸出完成時將值改變)
    TCSAFLUSH :改變在所有寫入fd 引用的對象的輸出都被傳輸後生效,所有已接受但未讀入的輸入都在改變發生前丟棄(同TCSADRAIN,但會捨棄當前所有值)。
    *termios termios_p: 用來設置的串口屬性的結構體指針,通過目錄2.2之後的屬性設置好termios後,傳入函數即可
  • 返回值: 成功返回 0 ,失敗返回-1.

2.7 tcflush()

函數原型

int tcflush(int fd,int quene)
  • 參數
    fd: 要操作的文件描述符
    quene: 操作位置,可以取下面三個值:
    TCIFLUSH:清空輸入隊列
    TCOFLUSH:清空輸出隊列
    TCIOFLUSH:清空輸入輸出隊列

在打開串口後,串口其實已經可以開始讀取 數據了 ,這段時間用戶如果沒有讀取,將保存在緩衝區裏,如果用戶不想要開始的一段數據,或者發現緩衝區數據有誤,可以使用這個函數清空緩衝
需要注意,如果是在任務中,需要不停地寫入數據到串口設備,千萬不能在每次寫入數據到設備前,進行flush以前數據的操作,因爲兩次寫入的間隔是業務控制的,內核不會保證在兩次寫入之間一定把數據發送成功。flush操作一般在打開或者復位串口設備時進行操作。

返回值:成返回 0 ,失敗返回 -1

2.8 cfsetispeed() 與 cfsetospeed()

函數原型

int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
  • 參數
    termios_p: 通過結構體來設置串口通信的屬性,這是指向該結構體的指針
    speed: 因爲串口通信沒有時鐘線,是一種異步通信,要想達到通信雙發收發信息的統一,就需要設置輸入輸出波特率相同,通過man手冊,可以看到:
    在這裏插入圖片描述
    這些就是波特率可以選擇的值。

三. 繪製流程圖與設計代碼

我所寫的程序包含了3個功能模塊,

  1. ComportOpen.c
    該模塊包含了串口的打開與關閉兩個函數: comport_open ,comport_close.
  2. ComportInit.c
    通過命令行參數傳入的值,來設置串口的 波特率,停止位,奇偶校驗,數據位以及一些爲了通信必須設置的串口屬性,以達初始化串口的所有功能,初始化後的串口便可立即使用了。
  3. ComportSwap.c
    用於與串口通信,封裝了write() 和 read() 函數。

下面一起來看各個功能模塊的流程圖吧~

3.1 串口的打開和關閉

打開串口:
在這裏插入圖片描述
通過傳入的文件名打開獲取到文件描述符,最終成功後返回文件描述符。

關閉串口

在這裏插入圖片描述
關閉串口不能簡單地close(fd),否則第二次運行程序將無法打開串口,需要重啓模塊才能再次使用。

ComportOpen.h

#ifndef  _COMPORTOPEN_H_
#define  _COMPORTOPEN_H_

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

#define SERIALNAME_LEN 128

typedef struct _st_MyAttr {

    int               fd;        //串口文件描述符
    int               BaudRate;  //波特率
    int               DataBits;  //數據位
    char              Parity;    //奇偶校驗位
    int               StopBits;  //停止位
    int               mSend_Len; //單次最大發送長度
    char              SerialName[SERIALNAME_LEN];  //串口名稱
    struct termios    OldTermios;  //串口的原始屬性
}MyAttr;

int comport_open(MyAttr *attr);
int comport_close(MyAttr *attr);

#endif   /* ----- #ifndef _COMPORTOPEN_H_  ----- */

我將所有需要用到的串口屬性封裝在了一個結構體中,這樣,我在設計後面的函數時,就可以直接傳結構體指針,再根據功能的實際要求,使用自己需要的成員即可。

ComportOpen.c

/*********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  ComportOpen.c
 *    Description:  This file Open or close the serial port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"
 * 
 *      blog address:https://blog.csdn.net/weixin_45121946        
 *   
 ********************************************************************************/
#include "ComportOpen.h"

int comport_open(MyAttr *attr)
{
    int                i;
    int                retval = -1;

    if(NULL == attr)
    {
        printf("%s,Invalid parameter\n",__func__);
        return retval;
    }

    /* O_NOCTTY表示打開的是一個終端設備,程序不會成爲該
     * 端口的控制終端,O_NONBLOCK使得read處於非阻塞模式 */
    attr->fd = open(attr->SerialName,O_RDWR | O_NOCTTY | O_NONBLOCK);
    if(attr->fd < 0)
    {
        printf("%s,Open %s failed:%s\n",__func__,attr->SerialName,strerror(errno));
        return -1;
    }

    /* 檢查串口是否處於阻塞態 */
    if((retval = fcntl(attr->fd,F_SETFL,0)) < 0)
    {
        printf("%s,Fcntl check faile.\n",__func__);
        return -2;
    }

    printf("Starting serial communication process ");

    for(i = 0;i < 6;i++)
    {
        printf(" . ");
        fflush(stdout);
        sleep(1);
    }
    printf("\n");  //這部分純屬搞笑


    if(0 == isatty(attr->fd))  //是否爲終端設備
    {
        printf("%s:[%d] is not a Terminal equipment.\n",attr->SerialName,attr->fd);
        return -3;
    }

    printf("Open %s successfully.\n",attr->SerialName);

    return 0;
}

int comport_close(MyAttr *attr)
{
    if(tcflush(attr->fd,TCIOFLUSH))  //清零用於串口通信的緩衝區
    {
        printf("%s,Tcflush faile:%s\n",__func__,strerror(errno));
        return -1;
    }

    /* 將串口設置爲原有屬性 */
    if(tcsetattr(attr->fd,TCSANOW,&(attr->OldTermios)))
    {
        printf("%s,Set old options failed:%s\n",__func__,strerror(errno));
        return -2;
    }

    close(attr->fd);

    free(attr);

    return 0;
}

3.2 串口的初始化

在這裏插入圖片描述
根據思路來寫函數

ComportInit.h

#ifndef  _COMPORTINIT_H_
#define  _COMPORTINIT_H_

#include "ComportOpen.h"
#include <string.h>


int comport_init(MyAttr *attr);

#endif   /* ----- #ifndef _COMPORTINIT_H_  ----- */

因爲我將串口設置的各項參數都進行了封裝,所以函數的參數只用傳一個結構體指針或者傳結構體地址即可。

ComportInit.c

/*********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  ComportInit.c
 *    Description:  This file is Init COm Port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"
 * 
 *      blog address:https://blog.csdn.net/weixin_45121946        
 *   
 ********************************************************************************/
#include "ComportInit.h"


int comport_init(MyAttr *attr)
{
    int                   retval;
    char                  baudrate[32] = {0};
    struct termios        NewTermios;


    memset(&NewTermios,0,sizeof(struct termios));
    memset(&(attr->OldTermios),0,sizeof(struct termios));
    if(!attr)
    {
        printf("Invalid parameter.\n");
        return -1;
    }

    if(tcgetattr(attr->fd,&(attr->OldTermios)))
    {
        printf("%s,Get termios to OldTermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    if(tcgetattr(attr->fd,&NewTermios))
    {    
        printf("%s,Get termios to NewTermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }  


    /* 修改控制模式,保證程序不會佔用串口 */
    NewTermios.c_cflag |= CLOCAL;

/*  For example:
 *   
 *      c_cflag:   0 0 0 0 1 0 0 0
 *      CLOCAL:  | 0 0 0 1 0 0 0 0
 *              --------------------
 *                 0 0 0 1 1 0 0 0
 *                
 *  Finally:
 *
 *     c_flag = 0 0 0 1 1 0 0 0;
 *
 * */


    /* 啓動接收器,能夠從串口中讀取輸入數據 */
    NewTermios.c_cflag |= CREAD;


    /*  CSIZE字符大小掩碼,將與設置databits相關的標緻位置零 */
    NewTermios.c_cflag &= ~CSIZE;


/*  For example:
 *
 *      CSIZE = 0 1 1 1 0 0 0 0 ---> ~CSIZE = 1 0 0 0 1 1 1 1
 *
 *      c_cflag:    0 0 1 0 1 1 0 0
 *      ~CSIZE:  &  1 0 0 0 1 1 1 1     
 *              -----------------------
 *                  0 0 0 0 1 1 0 0
 *
 * Finally:
 *
 *     c_cflag = 0 0 0 0 1 1 00
 *
 * */

    NewTermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /* 
     * ICANON: 標準模式
     * ECHO: 回顯所輸入的字符
     * ECHOE: 如果同時設置了ICANON標誌,ERASE字符刪除前一個所輸入的字符,WERASE刪除前一個輸入的單詞
     * ISIG: 當接收到INTR/QUIT/SUSP/DSUSP字符,生成一個相應的信號
     *
     * */

    NewTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* 
     * BRKINT: BREAK將會丟棄輸入和輸出隊列中的數據(flush),並且如果終端爲前臺進程組的控制終端,則BREAK將會產生一個SIGINT信號發送到這個前臺進程組
     * ICRNL: 將輸入中的CR轉換爲NL
     * INPCK: 允許奇偶校驗
     * ISTRIP: 剝離第8個bits
     * IXON: 允許輸出端的XON/XOF流控
     *
     * */

    /* OPOST: 表示處理後輸出,按照原始數據輸出 */ 
    NewTermios.c_oflag &= ~(OPOST);

    if(attr->BaudRate)
    {
        sprintf(baudrate,"B%d",attr->BaudRate);
        cfsetispeed(&NewTermios,(int)baudrate); //設置輸入輸出波特率
        cfsetospeed(&NewTermios,(int)baudrate);
    }
    else 
    {
        cfsetispeed(&NewTermios,B115200);
        cfsetospeed(&NewTermios,B115200);
    }

    /* 設置數據位 */
    switch(attr->DataBits)
    {
        case '5':
            NewTermios.c_cflag |= CS5;
            break;

        case '6':
            NewTermios.c_cflag |= CS6;
            break;

        case '7':
            NewTermios.c_cflag |= CS7;
            break;

        case '8':
            NewTermios.c_cflag |= CS8;
            break;

        default:
            NewTermios.c_cflag |= CS8;  //默認數據位爲8
            break;
    }

    /* 設置校驗方式 */
    switch(attr->Parity)
    {
        /* 無校驗 */
        case 'n':
        case 'N':
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_iflag &= ~INPCK;
            break;

        /* 偶校驗 */
        case 'e':
        case 'E':
            NewTermios.c_cflag |= PARENB;
            NewTermios.c_cflag &= ~PARODD;
            NewTermios.c_iflag |= INPCK;
            break;

        /* 奇校驗 */
        case 'o':
        case 'O':
            NewTermios.c_cflag |= PARENB;
            NewTermios.c_cflag |= PARODD;
            NewTermios.c_iflag |= INPCK;

        /* 設置爲空格 */
        case 's':
        case 'S':
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_cflag &= ~CSTOPB;

        /* 默認無校驗 */
        default:
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_iflag &= ~INPCK;
            break;


    }

    /* 設置停止位 */
    switch(attr->StopBits)
    {
        case '1':
            NewTermios.c_cflag &= ~CSTOPB;
            break;

        case '2':
            NewTermios.c_cflag |= CSTOPB;
            break;

        default:
            NewTermios.c_cflag &= ~CSTOPB;
            break;
    }

    NewTermios.c_cc[VTIME] = 0;  //最長等待時間
    NewTermios.c_cc[VMIN] = 0;  //最小接收字符 

    attr->mSend_Len = 128;  //若命令長度大於mSend_Len,則每次最多發送爲mSend_Len

    if(tcflush(attr->fd,TCIFLUSH))
    {
        printf("%s,Failed to clear the cache:%s\n",__func__,strerror(errno));
        return -4;
    }

    if(tcsetattr(attr->fd,TCSANOW,&NewTermios) != 0)
    {
        printf("%s,tcsetattr failure:%s\n",__func__,strerror(errno));
        return -5;
    }

    printf("Comport Init Successfully......\n");

    return 0;

}

3.3 AT指令的發送及串口數據的接收

和串口通信其實就是write 和read,通過獲取到的文件描述符,在加上一些串口所特有的屬性,從而完成通信。
發送:
在這裏插入圖片描述
這裏要注意了,我在前面一篇博客總結說明了AT指令集發送的實質,例如,當我們發送AT時,其實是發送了" AT\r ",所以,當我們獲取到要發送的指令時,需要在指令的最後面加上一個 \r(CR) ,串口才能接收到信息,後面的討論會提到一個問題。
接收的程序較爲簡單,這裏就直接貼代碼了。

ComportSwap.h

#ifndef  _COMPORTSWAP_H_
#define  _COMPORTSWAP_H_

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "ComportOpen.h"

int comport_send(MyAttr *attr,char *sbuf,int sbuf_len);
int comport_recv(MyAttr *attr,char *rbuf,int rbuf_len,int timeout);

#endif   /* ----- #ifndef _COMPORTSWAP_H_  ----- */

ComportSwap.c

/*********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  ComporSwap.c
 *    Description:  This file is communicate with com port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"
 * 
 *      blog address:https://blog.csdn.net/weixin_45121946        
 *   
 ********************************************************************************/
#include "ComportSwap.h"

int comport_send(MyAttr *attr,char *sbuf,int sbuf_len)
{
    char     *ptr,*end;
    int       retval;

    if(!attr || !sbuf || sbuf_len <= 0)
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    if(sbuf_len > attr->mSend_Len)
    {
        ptr = sbuf;
        end = sbuf + sbuf_len;

        do
        {
            if(attr->mSend_Len < (end - ptr))
            {
                retval = write(attr->fd,ptr,attr->mSend_Len);
                if(retval <= 0 || retval != attr->mSend_Len)
                {
                    printf("Write to com port[%d] failed:%s\n",attr->fd,strerror(errno));
                    return -2;
                }
           
                ptr += attr->mSend_Len;
            }
            else 
            {
                retval = write(attr->fd,ptr,(end - ptr));
                if(retval <= 0 || retval != (end - ptr))
                {
                    printf("Write to com port[%d] failed:%s\n",attr->fd,strerror(errno));
                    return -3;
                }

                ptr += (end - ptr);
            }
        }while(end > ptr);
       
    }  
       
    else 
    {  
        retval = write(attr->fd,sbuf,sbuf_len);
        if(retval <= 0 || retval != sbuf_len)
        {
            printf("Write to com port[[%d] failed:%s\n",attr->fd,strerror(errno));
            return -4;
        }
    }  
       
    return retval;
}      
       
int comport_recv(MyAttr *attr,char *rbuf,int rbuf_len,int timeout)
{      
    int                   retval;
    fd_set                rset;
    struct timeval        time_out;
       
    if(!rbuf || rbuf_len <= 0)
    {  
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }  

    if(timeout) //指定延時等待
    {    
        time_out.tv_sec = (time_t)(timeout / 1000);
        time_out.tv_usec = 0;

        FD_ZERO(&rset);
        FD_SET(attr->fd,&rset);

        retval = select(attr->fd + 1,&rset,NULL,NULL,&time_out);
        if(retval < 0)
        {
            printf("%s,Select failed:%s\n",strerror(errno));
            return -2;
        }

        else if(0 == retval)
        {
            printf("Time Out.\n");
            return 0;
        }

    }

    usleep(1000);

    retval = read(attr->fd,rbuf,rbuf_len);
    if( retval <= 0)
    {
        printf("%s,Read failed:%s\n",__func__,strerror(errno));
        return -3;
    }

    return retval;
                         
}     

3.4 主程序

接下來就是寫主程序了,因爲要不斷和串口通信,所以應該使用一個while循環,從標準輸入獲取命令,加上\r 發送,同時還要讀fd,所以這裏採用select多路複用來實現監聽標準輸入與用於與串口通信的fd。

在這裏插入圖片描述
comport.c

/*********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(04/07/20)
 *         Author:  LuXiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "04/07/20 03:02:28"
 *                 
 ********************************************************************************/
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include "ComportOpen.h"
#include "ComportInit.h"
#include "ComportSwap.h"


int g_stop = 0;

void sig_handler(int sig_num)
{
    if(sig_num == SIGINT)
        g_stop = 1;
}

void adjust_buf(char* buf);
void help_information();

int main(int argc, char *argv[])
{
    int              retval;
    int              ch;
    char             sbuf[128] = {0};
    char             rbuf[128] = {0};
    fd_set           rset;
    MyAttr*          attr = NULL;

    struct option    options[] = {
        {"help",no_argument,NULL,'h'},
        {"baudrate",required_argument,NULL,'b'},
        {"databits",required_argument,NULL,'d'},
        {"parity",required_argument,NULL,'p'},
        {"stopbits",required_argument,NULL,'s'},
        {"name",required_argument,NULL,'n'},
        {NULL,0,NULL,0}
    };

    attr = (MyAttr*)malloc(sizeof(MyAttr));
    memset(attr,0,sizeof(MyAttr));
    
    while((ch = getopt_long(argc,argv,"hb:d:p:s:n:",options,NULL)) != -1)
    {
        switch(ch)
        {
            case 'h':
                help_information();
                return 0;

            case 'b':
                attr->BaudRate = atoi(optarg);
                break;

            case 'd':
                attr->DataBits = atoi(optarg);
                break;

            case 'p':
                attr->Parity = optarg[0];
                break;

            case 's':
                attr->StopBits = atoi(optarg);
                break;

            case 'n':
                strncpy(attr->SerialName,optarg,SERIALNAME_LEN);
                break;
                
        }
    }

    if(strlen(attr->SerialName) == 0)
    {
        printf("Parameter warning:\n");
        printf("\tAt least need to enter the serial port name,You can specify the serial port name with -n.\n");
        return 0;
    }

    if(comport_open(attr) != 0)
    {
        printf("Open %s failed!\n",attr->SerialName);
        return -1;
    }

    retval = comport_init(attr);
    if(retval < 0)
        goto cleanup;

    signal(SIGINT,sig_handler);
    
    fflush(stdin);
    printf("Start to communicate with com port......\n");

    while(!g_stop)
    {
        FD_ZERO(&rset);
        FD_SET(STDIN_FILENO,&rset);
        FD_SET(attr->fd,&rset);

        /*  使用多路複用監聽標準輸入和串口fd */
        retval = select(attr->fd + 1,&rset,NULL,NULL,NULL);
        if(retval < 0)
        {
            printf("Program exit......\n");
            break;
        }

        if(retval == 0)
        {
            printf("Time Out.\n");
            goto cleanup;
        }

        if(FD_ISSET(STDIN_FILENO,&rset))
        {
            memset(sbuf,0,sizeof(sbuf));

            /* 從標準輸入讀取命令 */
            fgets(sbuf,sizeof(sbuf),stdin);
            /* 處理要發送的數據 */  
            adjust_buf(sbuf);
            
            if(comport_send(attr,sbuf,strlen(sbuf)) < 0)
            {
                printf("Write failed.\n");
                goto cleanup;
            }
            fflush(stdin);

        }

        if(FD_ISSET(attr->fd,&rset))
        {
            memset(rbuf,0,sizeof(rbuf));

            retval = comport_recv(attr,rbuf,sizeof(rbuf),0);
            if(retval <= 0)
            {
                printf("Read failed:%s\n",strerror(errno));
                break;
            }

            printf("%s",rbuf);
            fflush(stdout);
        }

        
    }

cleanup:
    comport_close(attr);

    return 0;

}

void adjust_buf(char *buf)
{
    int i = strlen(buf);
    strcpy(&buf[i-1],"\r");
}

void help_information()
{
    printf("\t-b   Set BaudRate\n");
    printf("\t-d   Set Databits\n");
    printf("\t-p   Set Parity,0 for no parity,1 for Odd parity,2 for Evev parity\n");
    printf("\t-s   Set StopBits\n");
    printf("\t-n   Set the name of the serial port you want to use\n");
    printf("\t     Ctrl + c to exit the program\n");
    printf("\n\tIf you do not specify parameters,The default parameters of the program are as follows:\n");
    printf("\tBaudRate: 1115200\n\tDatabits: 8bits\n\tParity:   no parity\n\tStopBits: 1bits\n");
}

makefile

(CC) = gcc

comport: comport.c ComportOpen.o ComportInit.o ComportSwap.o ComportSwap.o
	$(CC) comport.c ComportOpen.o ComportInit.o ComportSwap.o -o comport -Werror -Wall

ComportOpen.o: ComportOpen.c
	$(CC) -c ComportOpen.c

ComportInit.o: ComportInit.c
	$(CC) -c ComportInit.c

ComportSwap.o: ComportSwap.h
	$(CC) -c ComportSwap.c


clear:
	rm *.o comport

四. 運行截圖

在這裏插入圖片描述
help_msg:
在這裏插入圖片描述
通過參數傳入設置:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

五. 討論

  1. 主程序中爲了讓數據成功發送,我使用了一個 adjust_buf的函數,其目的實在命令尾部加上一個 \r ,保證串口可以正常接收,可是,當我們從標準輸入輸入完成後,會敲 Enter 鍵,這時,其實是把 \n(親測)一併加到了要發送的buf中,而c_oflag中有一個功能就是將輸入的換行符全部替換成 \r (CR),不過我做過嘗試還是無法完成指令的發送,才發現他把吧\n 轉換成了 \r\n,也就是<CR><LF>,有沒有其他方法呢?
  2. 就目前,程序還不能直接實現發送短信這一功能,關於Ctrl+Z發送短信這一指令還正在研究,將會持續更新代碼,如果想完成收發短信,定時發送短信,查看信息並解析獲取有用信息,可以修改comport.c文件,在瞭解AT指令是如何發短信後,直接將要發送給串口的命令寫進程序裏,像串口發送要發短信的幾條命令,是可以實現短信發送的。

參考:
https://blog.csdn.net/weixin_45121946/article/details/107032711
https://blog.csdn.net/onion_lwl/article/details/81293266
https://blog.csdn.net/morixinguan/article/details/80898172
https://blog.csdn.net/wumenglu1018/article/details/53098794/

2020.7.5 持續更新…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章