C语言串口编程收发数据 并实现AT指令的收发 可变参数控制串口属性 树莓派4G模块


对于Linux,我们都知道Linux下皆为文件,这当然也包括我们今天的主角——串口。因此对串口的操作都和对文件的操作一样(涉及到了open,read, write,close 等文件的基本操作)。

一、 Linux下串口编程的流程

串口编程可以简单概括为如下几个步骤:

​ 1.打开串口
​ 2.串口初始化
​ 3.读串口或写串口
​ 4.关闭串口

1.打开串口

既然串口在linux中被看作文件,那么在对文件进行操作前必要先对其进行打开操作。

在 Linxu中,串口设备是通过串口终端设备文件来访问的,即通过访问/dev/tty***这些设备文件实现对串口的访问。

调用open()函数来代开串口设备,通常对于串口的打开操作一般使用如下参数。其中O_NOCTTY又是必不可少的。

  • O_RDWR:以可读可写的方式打开文件。

  • O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务一个输入(eg:键盘中止信号等)都将影响进程。

  • O_NDELAY:表示不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。

 open("/dev/ttyUSB5", O_RDWR | O_NOCTTY | O_NDELAY) ; //打开串口设备
2. 串口初始化

在初始化串口之前,我们不得不掌握一些必要的知识。内容比较多,我就不在这里整理了。下面是一位好心人整理的有关串口属性的一些相关知识,不是很了解的可以look look

https://blog.csdn.net/qq_37932504/article/details/121125906

2.1 常用函数总览
#include <termios.h>
#include <unistd.h>

int tcgetattr(int fd, struct termios *termios_p); //用于获取与终端相关的参数

int tcsetattr(int fd, int optional_actions, struct termios *termios_p); //用于设置终端参数

int tcdrain(int fd); //等待直到所有写入 fd 引用的对象的输出都被传输

int tcflush(int fd, int queue_selector); //刷清(扔掉)输入缓存

int tcflow(int fd, int action); //挂起传输或接受

int cfmakeraw(struct termios *termios_p);// 制作新的终端控制属性

speed_t cfgetispeed(struct termios *termios_p); //得到输入波特率

speed_t cfgetospeed(struct termios *termios_p); //得到输出波特率

int cfsetispeed(struct termios *termios_p, speed_t speed); //设置输入波特率

int cfsetospeed(struct termios *termios_p, speed_t speed) //设置输出波特率

int tcsendbreak(int fd, int duration);

这里,我们可以看到有一个结构体struct termios现身于绝大多数的函数中,它的重要性就不言而喻了。。。

struct termios
{
    tcflag_t c_iflag;	 /* input mode flags 输入模式标志*/
    tcflag_t c_oflag;	 /* output mode flags 输出模式标志*/
    tcflag_t c_cflag;	 /* control mode flags 控制模式控制终端设备的硬件特性(串口波特率、数据位、校验位、停止位等)*/
    tcflag_t c_lflag;	 /* local mode flags 本地模式用于控制终端的本地数据处理和工作模式。*/
    cc_t c_line; 		/* line discipline */
    cc_t c_cc[NCCS];	 /* control characters 特殊控制字符是一些字符组合,如 Ctrl+C、 Ctrl+Z 等, 当用户键入这样的组合键,终端会采取特殊处理方式。*/
    speed_t c_ispeed;	 /* input speed 输入波特率*/
    speed_t c_ospeed;	 /* output speed 输出波特率*/
};

注:对于这些变量尽量不要直接对其初始化,而要将其通过“按位与”、“按位或” 等操作添加标志或清除某个标志。

注:不同的终端设备,本身硬件上就存在很大的区别,所以配置参数不是对所有终端设备都是有效的。

2.2 初始化

当然这里只是简单的初始化过程,没有什么可变性,只是一种固定的串口配置,这样只是便于理解罢了

tcgetattr(fd, &oldtermios); //获取原有的串口属性,以便后面可以恢复
tcgetattr(fd, &newtermios); //获取原有的串口属性,以此为基修改串口属性

newtermios.c_cflag|=(CLOCAL|CREAD );  // CREAD 开启串行数据接收,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
 *
 * */

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
 *
 * 这样与数据位无关的部分就保留了下来,单单把数据位全部清零了
 *
 * */

newtermios.c_cflag |= CS8;            //设置8bits数据位

newtermios.c_cflag &= ~PARENB;        //无校验位

/* 设置9600波特率  */
cfsetispeed(&newtermios, B9600);
cfsetospeed(&newtermios, B9600);

newtermios.c_cflag &= ~CSTOPB;       // 设置1位停止位

newtermios.c_cc[VTIME] = 0; 		// 非规范模式读取时的超时时间
newtermios.c_cc[VMIN]  = 0; 		// 非规范模式读取时的最小字符数

tcflush(fd ,TCIFLUSH);/* tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */

tcsetattr(fd, TCSANOW, &newtermios);  //设置串口属性
3. 串口的读写

​ 串口的读写就比较简单了,像上面我们说的一样Linux下皆为文件。因此对串口调用read, write就行了。因为无论是读还是写,我们都是对同一串口进行操作的,所以在这里就不分程序操作了,而是使用select多路复用来实现自发自收的功能。

while(1)
{
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);				//文件描述符
        FD_SET(STDIN_FILENO, &fdset);  //标准输入

        rv = select(fd+1, &fdset, NULL, NULL, NULL);
        if(rv < 0)
        {
            printf("select() failed: %s\n", strerror(errno));
            goto cleanup;
        }

        if(rv == 0)
        {
            printf("select() time out!\n");
            goto cleanup;
        }
        
/* ----------写串口 -----------*/
        if(FD_ISSET(STDIN_FILENO, &fdset))
        {
                memset(wr_buf, 0, sizeof(wr_buf));
                fgets(wr_buf, sizeof(wr_buf), stdin);

                rv = write(fd, wr_buf, strlen(wr_buf));
                if(rv < 0)
                {
                        printf("Write() error:%s\n",strerror(errno));
                        goto cleanup;
                }
         }

/* -----------读串口----------- */
         if(FD_ISSET(fd, &fdset))
         {
                memset(rd_buf, 0, sizeof(rd_buf));
                rv = read(fd, rd_buf, sizeof(rd_buf));
                if(rv <= 0)
                {
                       printf("Read() error:%s\n",strerror(errno));
                       goto cleanup;
                }

                printf("Read %d bytes data from serial port: %s\n", rv, rd_buf);
          }
          sleep(5);
}
4. 串口关闭

串口关闭就比较简单了,但是不要忘记了一件重要的事情哦~

恢复原有的串口属性~~

tcsetattr(fd, TCSANOW, &newtermios);  //恢复默认的串口属性

close(fd);

二、代码——串口编程实现自发自收

将上面的代码结合如下:

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

int main(int argc, char **argv)
{
    int                 fd, rv;
    char                wr_buf[128];
    char                rd_buf[128];
    fd_set              fdset;
    struct termios      oldtermios;
    struct termios      newtermios;

    fd = open("/dev/ttyUSB5", O_RDWR | O_NOCTTY | O_NDELAY) ; //打开串口设备
    if(fd < 0)
    {
        printf("open failure: %s\n", strerror(errno));

                goto cleanup;
    }

    printf("Open sucess!\n") ;

    memset(&newtermios, 0, sizeof(newtermios)) ;

    rv = tcgetattr(fd, &oldtermios); //获取原有串口属性
    rv = tcgetattr(fd, &newtermios); //获取原有串口属性,并在此更改
    if(rv != 0)
    {
        printf("tcgetattr() failure:%s\n", strerror(errno)) ;

        goto cleanup;
    }

    newtermios.c_cflag|=(CLOCAL|CREAD );        // CREAD 开启串行数据接收,CLOCAL并打开本地连接模式

    newtermios.c_cflag &=~CSIZE;                        // 先清零数据位

    newtermios.c_cflag |= CS8;                          //设置8bits数据位

    newtermios.c_cflag &= ~PARENB;                      //无校验位

    /* 设置9600波特率  */
    cfsetispeed(&newtermios, B9600);
    cfsetospeed(&newtermios, B9600);

    newtermios.c_cflag &= ~CSTOPB;              // 设置1位停止位

    newtermios.c_cc[VTIME] = 0; // 非规范模式读取时的超时时间
    newtermios.c_cc[VMIN]  = 0; // 非规范模式读取时的最小字符数

    tcflush(fd ,TCIFLUSH);/* tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */

    if((tcsetattr(fd, TCSANOW,&newtermios))!=0)
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        goto cleanup ;
    }

    while(1)
    {
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);
        FD_SET(STDIN_FILENO, &fdset);

        rv = select(fd+1, &fdset, NULL, NULL, NULL);
        if(rv < 0)
        {
            printf("select() failed: %s\n", strerror(errno));
            goto cleanup;
        }

        if(rv == 0)
        {
            printf("select() time out!\n");

            goto cleanup;
        }
        
        /* ------写串口 ------*/
        if(FD_ISSET(STDIN_FILENO, &fdset))
        {
                memset(wr_buf, 0, sizeof(wr_buf));
                fgets(wr_buf, sizeof(wr_buf), stdin);

                rv = write(fd, wr_buf, strlen(wr_buf));
                if(rv < 0)
                {
                        printf("Write() error:%s\n",strerror(errno));
                        goto cleanup;
                }
         }

         /* ------读串口------ */
         if(FD_ISSET(fd, &fdset))
         {
                memset(rd_buf, 0, sizeof(rd_buf));
                rv = read(fd, rd_buf, sizeof(rd_buf));
                if(rv <= 0)
                {
                       printf("Read() error:%s\n",strerror(errno));
                       goto cleanup;
                }

                printf("Read %d bytes data from serial port: %s\n", rv, rd_buf);
          }
      }

cleanup:
    tcsetattr(fd, TCSANOW,&oldtermios);  //恢复默认属性
    close(fd);

    return 0;
}

如下图所示,是代码的运行结果:这里我们可以看到,收到的确实就是发送的消息,但是返回值却是比我们肉眼可见的字符长度多了“1”,这其实是因为,我们在获取标准输入的字符串的时候,在最后还接受了一个“\n”的换行符。这里可以去了解一下fgets()函数的使用方法。
在这里插入图片描述

三、可变参数控制串口属性的函数封装

但是要知道的是,我们操作串口的时候,对于串口属性的设置参数不是一尘不变的,所以为了提高代码的可重用性,我们可以使用可变参数来设置串口的属性~~

3.1 头文件——serial_port.h
#ifndef  _SERIALPORT_H_
#define  _SERIALPORT_H_

#define SERIALNAME_LEN 128

typedef struct attr_s{
        int             flow_ctrl;                      //流控制
        int             baud_rate;                      //波特率
        int             data_bits;                      //数据位
        char            parity;                         //奇偶校验位
        int             stop_bits;                      //停止位
}attr_t;

extern int serial_open(char *fname);						//打开串口
extern int serial_close(int fd, struct termios *termios_p);		//关闭串口
extern int serial_init(int fd, struct termios *oldtermios, attr_t *attr);  //串口初始化
extern int serial_send(int fd, char *msg, int msg_len);		//写数据到串口
extern int serial_recv(int fd, char *recv_msg, int size);	//接收串口数据

#endif

这里封装了一个结构体,将所有需要用到的串口属性都放在了里面,这样,我在设计后面的函数时,就可以直接传结构体指针,再根据功能的实际要求,使用自己需要的成员即可。

3.2 函数定义——serial_port.c
打开串口——serial_open
int serial_open(char *fname)
{
    int fd, rv;

    if(NULL == fname)
    {
        printf("%s,Invalid parameter\n",__func__);
        return -1;
    }

    if((fd = open(fname,O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
    {
        printf("Open %s failed: %s\n",fname, strerror(errno));

        return -1;
    }

    /* 判断串口的状态是否处于阻塞态*/
    if((rv = fcntl(fd, F_SETFL, 0)) < 0)
    {
        printf("fcntl failed!\n");

        return -2;
    }
    else
    {
        printf("fcntl=%d\n",rv);
    }

    if(0 == isatty(fd))  //是否为终端设备
    {
        printf("%s:[%d] is not a Terminal equipment.\n", fname, fd);
        return -3;
    }

    printf("Open %s successfully!\n", fname);

    return fd;
}
关闭串口——serial_close
int serial_close (int fd, struct termios *termios_p)
{
    /* 清空串口通信的缓冲区 */
    if(tcflush(fd,TCIOFLUSH))
    {
        printf("%s, tcflush() fail: %s\n", __func__, strerror(errno));
        return -1;
    }

    /* 将串口设置为原有属性, 立即生效 */
    if(tcsetattr(fd,TCSANOW,termios_p))
    {
        printf("%s, set old options fail: %s\n",__func__,strerror(errno));
        return -2;
    }

    close(fd);
        printf("close OK..............");

    return 0;
}
串口初始化——serial_init
int serial_init(int fd, struct termios *oldtermios, struct attr_s  *attr)
{
    char                  baudrate[32] = {0};
    struct termios        newtermios;

    memset(&newtermios,0,sizeof(struct termios));
    memset(oldtermios,0,sizeof(struct termios));

    if(!attr)
    {
        printf("%s invalid parameter.\n", __func__);
        return -1;
    }

    /* 获取默认串口属性 */
    if(tcgetattr(fd, oldtermios))
    {
        printf("%s, get termios to oldtermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    /* 先获取默认属性,后在此基础上修改 */
    if(tcgetattr(fd, &newtermios))
    {
        printf("%s, get termios to newtermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }


    /* 修改控制模式,保证程序不会占用串口 */
    newtermios.c_cflag |= CLOCAL;


    /* 启动接收器,能够从串口中读取输入数据 */
    newtermios.c_cflag |= CREAD;



    /*
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志,ERASE字符删除前一个所输入的字符,WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符,生成一个相应的信号
     *
     * 在原始模式下,串口输入数据是不经过处理的,在串口接口接收的数据被完整保留。

    newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

     * */

    /*
     * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush),并且如果终端为前台进程组的控制终端,则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     * ICRNL: 将输入中的CR转换为NL
     * INPCK: 允许奇偶校验
     * ISTRIP: 剥离第8个bits
     * IXON: 允许输出端的XON/XOF流控
     *
    newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
     * */


    /* --------设置数据流控制------- */
    switch(attr->flow_ctrl)
    {
        case 0:                         //不使用流控制
            newtermios.c_cflag &=~CRTSCTS;
            break;

        case 1:                         //使用硬件流控制
            newtermios.c_cflag |= CRTSCTS;
            break;

        case 2:                         //使用软件流控制
            newtermios.c_cflag |= IXON| IXOFF|IXANY;
            break;

        default:
            break;
    }


    /* 设置波特率,否则默认设置其为B115200 */
    if(attr->baud_rate)
    {
        sprintf(baudrate,"B%d",attr->baud_rate);

        cfsetispeed(&newtermios, (int)baudrate); //设置输入输出波特率
        cfsetospeed(&newtermios, (int)baudrate);
    }
    else
    {
        cfsetispeed(&newtermios, B115200);
        cfsetospeed(&newtermios, B115200);
    }


    /* ------设置数据位-------*/
    newtermios.c_cflag &= ~CSIZE;   //先把数据位清零,然后再设置新的数据位

    switch(attr->data_bits)
    {
        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 |= (PARODD | PARENB);
            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->stop_bits)
    {
        case '1':
            newtermios.c_cflag &= ~CSTOPB;
            break;

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

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

    /* OPOST: 表示处理后输出,按照原始数据输出 */
    newtermios.c_oflag &= ~(OPOST);


    newtermios.c_cc[VTIME] = 0;  //最长等待时间
    newtermios.c_cc[VMIN] = 0;  //最小接收字符

    //attr->mSend_Len = 128;  //若命令长度大于mSend_Len,则每次最多发送为mSend_Len

    /* 刷新串口缓冲区 / 如果发生数据溢出,接收数据,但是不再读取*/
    if(tcflush(fd,TCIFLUSH))
    {
        printf("%s, clear the cache failure:%s\n", __func__, strerror(errno));
        return -4;
    }

    /* 设置串口属性,立刻生效 */
    if(tcsetattr(fd,TCSANOW,&newtermios) != 0)
    {
        printf("%s, tcsetattr failure: %s\n", __func__, strerror(errno));
        return -5;
    }

    printf("Serial port Init Successfully!\n");

    return 0;
}
写数据到串口—— serial_send

​ 接下来的两个函数的定义可以有,但是没必要,因为我这里并没有多加什么东西。如果写的内容要进行封装打包,或者解析的话,就需要加上这两个定义了。

int serial_send (int fd, char *msg, int msg_len)
{
    int rv = 0;

    rv = write(fd, msg, msg_len);
    if(rv == msg_len)
    {
        return rv;
    }
    else
    {
        tcflush(fd, TCOFLUSH);

        return -1;
    }

    return rv;
}
从串口读取数据—— serial_recv
int serial_recv(int fd, char *recv_msg, int size)
{
    int     rv;    
    
    rv = read(fd, recv_msg, size);
    if(rv)
    {
        return rv;
    }
    else
    {
        return -1;
    }
    /********************************
    *
    *      这里可以自由发挥
    *
    ********************************/
}
3.3 主程序(已更新为收发AT指令)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include "serial_port.h"

#define SERIAL_DEBUG

#ifdef SERIAL_DEBUG
#define serial_print(format, args...) printf(format,##args)
#else
#define serial_print(format, args...) do{} while(0)
#endif

void sig_stop(int signum);
void usage();
void adjust_msg(char *buf);

int run_stop = 0;

int main(int argc, char **argv)
{
        int                     fd, rv, ch, i;
        char                *fname = "/dev/ttyUSB6"; //如果未指定,使用该设备节点
        char                buf[64] = {0};
        char                send_msg[128];
        char                recv_msg[128];
        fd_set              fdset;
        attr_t              attr;
        struct termios      oldtio;


        struct option    opts[] = {
                {"help"    , no_argument          , NULL, 'h'},
                {"flowctrl", required_argument, NULL, 'f'},
                {"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 }
        };

        if(argc < 2)
        {
                serial_print("WARN: without arguments!");
                usage();
                return -1;
        }

        while((ch = getopt_long(argc,argv,"hf:b:d:p:s:n:",opts,NULL)) != -1)
        {
                switch(ch)
                {
                        case 'h':
                                usage();
                                return 0;

                        case 'f':
                                attr.flow_ctrl = atoi(optarg);
                                break;

                        case 'b':
                                attr.baud_rate = atoi(optarg);
                                break;

                        case 'd':
                                attr.data_bits = atoi(optarg);
                                break;

                        case 'p':
                                attr.parity = *optarg;
                                break;

                        case 's':
                                attr.stop_bits = atoi(optarg);
                                break;

                        case 'n':
                                fname = optarg;
                                break;
                }
        }

        if((fd = serial_open(fname)) < 0)
        {
                serial_print("Open %s failure: %s\n", fname, strerror(errno));
                return -1;
        }

        if(serial_init(fd, &oldtio, &attr) < 0)
        {
                return -2;
        }

        signal(SIGINT, sig_stop);
        signal(SIGTERM, sig_stop);

        while(!run_stop)
        {
                FD_ZERO(&fdset);        //清空所有文件描述符
                FD_SET(STDIN_FILENO,&fdset);    //添加标准输入到fdset中
                FD_SET(fd,&fdset);                              //添加文件描述符fd到fdset中

                /*      使用select多路复用监听标准输入和串口fd */
                rv = select(fd + 1, &fdset, NULL, NULL, NULL);
                if(rv < 0)
                {
                        serial_print("Select failure......\n");
                        break;
                }

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

                //有事件发生
                if(FD_ISSET(STDIN_FILENO,&fdset))
                {
                        memset(send_msg, 0, sizeof(send_msg));

                        /* 从标准输入读取命令 */
                        fgets(send_msg, sizeof(send_msg), stdin);
                        
/* 处理要发送的数据,因为我们从fgets函数获取的字符串末尾是"\n",而发送AT指令需要的是"\r"*/
                        adjust_msg(send_msg);

                //      serial_print("Serial port will send: %s\n", send_msg);
                        if((rv = serial_send(fd, send_msg, strlen(send_msg))) < 0)
                        {
                                serial_print("Write failed.\n");
                                goto cleanup;
                        }


#ifndef SERIAL_DEBUG

/*  逐一打印一下发送的的数据都是什么  */
                        for(i = 0; i < rv; i++)
                        {
                                serial_print("Byte: %c\t ASCII: 0x%x\n", send_msg[i], (int)send_msg[i]);
                        }

                        serial_print("INFO:------Write success!\n\n");
#endif

                        fflush(stdin);

                }

                if(FD_ISSET(fd,&fdset))
                {
                        memset(recv_msg, 0, sizeof(recv_msg));

                        rv = serial_recv(fd, recv_msg, sizeof(recv_msg));
                        if(rv <= 0)
                        {
                                serial_print("Read failed: %s\n",strerror(errno));
                                break;
                        }

                        printf("%s", recv_msg);

#ifndef SERIAL_DEBUG
                        serial_print("Receive %d bytes data: %s",rv, recv_msg);

/*  逐一打印一下收到的数据一个一个都是什么  */
                        for(i = 0; i < rv; i++)
                        {
                                serial_print("Byte: %c\t ASCII: 0x%x\n", recv_msg[i], (int)recv_msg[i]);
                        }
#endif

                        fflush(stdout);
                }

                sleep(3);
        }

cleanup:
        serial_close(fd, &oldtio);

        return 0;
}

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

void sig_stop(int signum)
{
        serial_print("catch the signal: %d\n", signum);
        run_stop = 1;
}

void usage()
{
        serial_print("-h(--help    ): aply the usage of this file\n");
        serial_print("-f(--flowctrl): arguments: 0(no use) or 1(hard) or 2(soft)\n");
        serial_print("-b(--baudrate): arguments with speed number\n");
        serial_print("-d(--databits): arguments: 5 or 6 or 7 or 8 bits\n");
        serial_print("-p(--parity  ): arguments: n/N(null) e/E(even) o/O(odd) s/S(space)\n");
        serial_print("-s(--stopbits): arguments: 1 or 2 stopbits\n");
}

四、补充一下串口实现AT指令的收发

先简单讲一下AT指令收发的实际数据是什么,然后在最后小小的验证一波~~

使用AT指令与串口进行通信,是一种“礼尚往来”的通信方式,即当控制端输入一个AT指令后,与之通信的外部设备将会回复一个结果,就这样一对一的进行。

以最简单的AT指令为例,当串口连接好以后,使用

busybox microcom -s 115200 ttyUSB2

在这里插入图片描述
每输入一次AT设备都会回复一个OK,就可以利用不同的指令,结合设备的返回码来与设备通信。

其实,当我敲下AT 回车后,发送给设备的指令实际是

AT<CR>

也就是 “AT\r”

“\r” 是指回到行首,但不会换到下一行,而当我们收到OK时,实际上是收到了

<CR><LF><OK><CR><LF>

也就是 “\r\nOK\r\n”
" /r/n " 合起来才是Windows下的Enter,即回到行首并新建一行。从上面的图中可以看到,OK的确换到了新的一行,当我们在敲AT时,又是在新的一行。

这里就是验证的结果了~

ATE1模式下,发送的数据会接收一遍,再接收应答数据
“\r”和“\n”的ASCII值分别为十六进制的 d 和 a
![在这里插入图片描述](https://img-blog.csdnimg.cn/d26259d683554fb1a0106d6e35d23930.png

参考链接:https://blog.csdn.net/weixin_45121946/article/details/107130238

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 C 语言实现串口收发数据可以分为以下几个步骤: 1. 打开串口: 首先需要打开串口设备文件,可以使用 `open()` 函数来实现。需要提供串口设备文件路径和打开模式,例如 `open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK)`。 2. 配置串口参数: 通过调用 `tcgetattr()` 函数获取当前串口的配置参数,然后对这些参数进行修改,例如设置波特率、数据位数、奇偶校验等。然后使用 `tcsetattr()` 函数将修改后的参数设置回去。 3. 读取串口数据: 使用 `read()` 函数从串口中读取数据。这个函数会一直阻塞直到有数据可读取。可以设置循环,根据需要每次读取固定长度的数据,例如 `read(fd, buf, 256)`,其中 `fd` 是打开串口设备的文件描述符,`buf` 是存储数据的缓冲区。 4. 写入串口数据: 使用 `write()` 函数将数据写入串口。可以将需要发送的数据存储到一个缓冲区中,然后通过 `write(fd, buf, len)` 发送。其中 `len` 是需要发送的数据长度。 5. 关闭串口: 最后需要关闭打开的串口设备文件,使用 `close()` 函数即可,例如 `close(fd)`。 另外,在进行串口编程时还需要注意以下几个问题: - 需要以超级用户权限运行程序才能访问串口设备文件。 - 在读取时需要判断返回值,判断是否读取到了预期的数据。 - 在写入时需要判断返回值,判断是否成功写入了所有的数据。 - 可以使用 `select()` 函数进行串口文件描述符的等待,避免阻塞。 - 可以使用串口调试助手等工具测试串口收发的功能。 以上是使用 C 语言实现串口收发数据的简要步骤和注意事项,具体的实现细节还需要根据实际情况来进行调整和完善。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值