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<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  ComportOpen.c
 *    Description:  This file Open or close the serial port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"    
 *   
 ********************************************************************************/
#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<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  ComportInit.c
 *    Description:  This file is Init COm Port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"   
 *   
 ********************************************************************************/
#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<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  ComporSwap.c
 *    Description:  This file is communicate with com port
 *                 
 *        Version:  1.0.0(03/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "03/07/20 17:50:09"    
 *   
 ********************************************************************************/
#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<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(04/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      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/onion_lwl/article/details/81293266
https://blog.csdn.net/morixinguan/article/details/80898172
https://blog.csdn.net/wumenglu1018/article/details/53098794/

2020.7.5 持续更新…

  • 33
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值