文章目錄
一. 前言
在拿到我的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個功能模塊,
- ComportOpen.c
該模塊包含了串口的打開與關閉兩個函數: comport_open ,comport_close. - ComportInit.c
通過命令行參數傳入的值,來設置串口的 波特率,停止位,奇偶校驗,數據位以及一些爲了通信必須設置的串口屬性,以達初始化串口的所有功能,初始化後的串口便可立即使用了。 - 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:
通過參數傳入設置:
五. 討論
- 主程序中爲了讓數據成功發送,我使用了一個 adjust_buf的函數,其目的實在命令尾部加上一個 \r ,保證串口可以正常接收,可是,當我們從標準輸入輸入完成後,會敲 Enter 鍵,這時,其實是把 \n(親測)一併加到了要發送的buf中,而c_oflag中有一個功能就是將輸入的換行符全部替換成 \r (CR),不過我做過嘗試還是無法完成指令的發送,才發現他把吧\n 轉換成了 \r\n,也就是<CR><LF>,有沒有其他方法呢?
- 就目前,程序還不能直接實現發送短信這一功能,關於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 持續更新…