(二)树莓派Linux环境串口通信编程--AT指令集的发送和接收

一、前言

在开始本阶段之前,我们需要了解串口通信的知识,常见的几要素:起始位、数据位、校验、停止位以及波特率。尚未了解的,也可以看我之前写的关于串口通信的博客:串口通信 。在前面我们实现了串口驱动,在/dev/目录下可以查看ttyUSB等多个设备文件,使用ifconfig命令之后也可以看到usb0网卡,也可以使用Linux下的软件busybox microcom实现AT指令的发送和接收。但是我们怎么接收来自EC200U模块的信息呢?难道要写个程序启动busybox microcom软件再读取运行AT指令返回的信息?并不是,现在我们需要自己用C代码写一个类似busybox microcom的软件,实现AT指令集的发送和接收,这样我们就可以在C代码里面接收返回的信息了,把它们存放到我们创建的buffer里面,就可以对数据进行操作了。

二、要了解的知识

2.1 termios结构体

该结构体主要用于对串口属性的修改,常见的如起始位、数据位、校验、停止位以及波特率等,通过与或等位相应的标志常量就可以对成员的值进行修改,也就是对串口属性的修改。

struct termios  
{  
    unsigned short c_iflag; /* 输入模式标志*/  
    unsigned short c_oflag; /* 输出模式标志*/  
    unsigned short c_cflag; /* 控制模式标志*/  
    unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/  
    unsigned char c_line; /*行控制line discipline */  
    unsigned char c_cc[NCC]; /* 控制字符特性*/  
};  

对于该结构体成员可以设置哪些标准常量,可以看这篇博客,总结很详细:termios 详解

于此结构体相关的函数:
(1)tcgetattr()
函数原型

#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);

参数:

  • int fd:打开串口文件的文件描述符
  • struct termios *termios_p:指向termios 的结构体指针

返回值:成功返回0,失败返回-1
函数功能: 获取文件描述符对应串口的原始属性,并保存在第二个参数中,通常获取的原始属性需要进行备份,在程序退出之前要将其修改回来,否则无法继续使用串口。

(2)tcsetattr()
函数原型

#include <termios.h>
#include <unistd.h>
int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);

参数:

  • 打开串口文件的文件描述符
  • int actions: 设置属性时,可以控制属性生效的时刻,actions可以取下面几个值:
    TCSANOW: 立即生效
    TCADRAIN: 改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变)
    TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。
  • struct termios *termios_p:指向termios 的结构体指针

返回值:成功返回 0 ,失败返回-1
函数功能:设置打开的文件描述符对应的串口的属性

2.2 tcflush()

函数原型

int tcflush(int fd,int quene)

参数

  • fd: 要操作的文件描述符
  • quene: 操作位置,可以取下面三个值:
    TCIFLUSH:清空输入队列
    TCOFLUSH:清空输出队列
    TCIOFLUSH:清空输入输出队列

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

函数功能:
在打开串口后,串口其实已经可以开始读取 数据了 ,这段时间用户如果没有读取,将保存在缓冲区里,如果用户不想要开始的一段数据,或者发现缓冲区数据有误,可以使用这个函数清空缓冲需要注意,如果是在任务中,需要不停地写入数据到串口设备,千万不能在每次写入数据到设备前,进行flush以前数据的操作,因为两次写入的间隔是业务控制的,内核不会保证在两次写入之间一定把数据发送成功。flush操作一般在打开或者复位串口设备时进行操作。

2.3 cfsetispeed()与cfsetospeed()

函数原型

#include <termios.h>
#include <unistd.h>

int cfsetispeed(struct termios *termios_p, speed_t speed);

int cfsetospeed(struct termios *termios_p, speed_t speed);

函数参数:

  • struct termios *termios_p:指向termios结构体类型的指针
  • speed_t speed:因为串口通信没有时钟线,是一种异步的通信,想要通信双方收发信息实现统一,就需要设置输入输出波特率相同,通过man命令就可以查看可以设置的波特率,常用的是B9600和B115200。
    在这里插入图片描述

函数功能:设置输入和输出的波特率

三、流程图设计与代码实现

其中有5个代码文件:
serial_init.h:头文件,用来声明自己定义的函数,同时定义一个串口属性结构体,以存放解析命令参数的数据。
serial_init.c:源文件,实现对串口设备的IO操作,以及属性设置的函数封装,包括串口打开、串口初始化、读串口、写串口和关闭串口五个函数。
main.c:主函数,实现命令行解析、信号注册,以及采用多路复用select监听串口文件描述符获取标准输入的数据和接收来自串口的数据。
main.h:导入头文件
Makefile:实现自动编译,生成可执行代码

Linux有三类设备:字符设备,块设备,网络设备。那么串口设备属于字符设备。所以串口设备的命名一般为/dev/ttySn(n = 0、1、2…),如果该串口为USB转串口,可能名称为/dev/ttyUSBn(n = 0、1、2…),不同的平台下串口的名称是不同的,且串口的名称也是可以更改的。

serial_init.h

#ifndef _UART_INIT_H_
#define _UART_INIT_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>
#include <string.h>
#include <time.h>



#define SERIAL_NAME 128

typedef struct ttyusb_ctx_s{
    int     fd;//文件描述符
    int     baudrate;//波特率
    int     databits;//数据位
    char    parity;//奇偶校验位
    int     stopbits;//停止位
    char    serial_name[SERIAL_NAME];//设备文件名
    int     msend_len;//单次最大发送长度
    int     timeout;//读数据时的最长延时
    struct  termios old_termios;//保存串口初始属性
}ttyusb_ctx_t;


int tty_open(ttyusb_ctx_t *ttyusb_ctx);

int tty_close(ttyusb_ctx_t *ttyusb_ctx);

int tty_init(ttyusb_ctx_t *ttyusb_ctx);

int tty_send(ttyusb_ctx_t *ttyusb_ctx, char *send_buf, int sbuf_len);

int tty_recv(ttyusb_ctx_t *ttyusb_ctx, char *recv_buf, int rbuf_len);

#endif

serial_init.c

Linux系统下一般用负数表示执行函数出错返回值,返回0表示正常,在这里我也是引用了这一种方式。

(1) tty_open()

在对串口操作之前,先打开串口设备文件

//打开串口文件
#include "serial_init.h"

int tty_open(ttyusb_ctx_t *ttyusb_ctx)
{
    if (!ttyusb_ctx)
    {
        printf("The argument invalid!\n");
        return -1;
    }

    ttyusb_ctx->fd = open(ttyusb_ctx->serial_name, O_RDWR|O_NOCTTY|O_NONBLOCK);
    if (ttyusb_ctx->fd < 0)
    {
        printf("Open serial file failure:%s\n", strerror(errno));
        return -2;
    }

    if (!isatty(ttyusb_ctx->fd))
    {
        printf("%s is not a terminal equipment!\n", ttyusb_ctx->serial_name);
        return -3;
    }

    printf("[%s]Open %s successfully!\n", __func__, ttyusb_ctx->serial_name);
    return 0;
}

(2) tty_close()

在使用串口完毕的时候,我们需要进行相应的处理,即清空输入输出的缓冲,并且恢复串口原来的属性。

//关闭串口文件
int tty_close(ttyusb_ctx_t *ttyusb_ctx)
{
    int retval = -1;

    if (!ttyusb_ctx)
    {
        printf("The argument invalid!\n");
        return -1;
    }

    retval = tcflush(ttyusb_ctx->fd, TCIOFLUSH);//清空输入输出
    if (retval < 0)
    {
        printf("Failed to clear the input/output buffer:%s\n", strerror(errno));
        return -2;
    }
    
    retval = tcsetattr(ttyusb_ctx->fd, TCSANOW, &(ttyusb_ctx->old_termios));//恢复串口原始属性,TCSANOW
    if (retval < 0)
    {
        printf("Set old termios failure:%s\n", strerror(errno));
        return -3;
    }

    close(ttyusb_ctx->fd);
    
    printf("Excute tty_close() successfully!\n");

    return 0;
}

(3) tty_init()

对串口进行初始化

int tty_init(ttyusb_ctx_t *ttyusb_ctx)
{
    int     retval = -1;
    char    baudrate_buf[32] = {0};
    struct  termios new_termios;

    if (!ttyusb_ctx)
    {
        printf("The argument invalid!\n");
        return -1;
    }

    memset(&new_termios, 0, sizeof(new_termios));
    memset(&(ttyusb_ctx->old_termios), 0, sizeof(ttyusb_ctx->old_termios));

    retval = tcgetattr(ttyusb_ctx->fd, &(ttyusb_ctx->old_termios));
    if (retval < 0)
    {
        printf("Failed to obtain the current serial port properties!\n");
        return -2;
    }

    retval = tcgetattr(ttyusb_ctx->fd, &new_termios);
    if (retval < 0)
    {
        printf("Failed to obtain the current serial port properties!\n");
        return -3;
    }

    new_termios.c_cflag |= CLOCAL;//忽略解调器线路状态

    new_termios.c_cflag |= CREAD;

    new_termios.c_cflag &= ~CSIZE;//启动接收器,能够从串口中读取输入数据

    new_termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /* 
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志,ERASE字符删除前一个所输入的字符,WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符,生成一个相应的信号
     *
     * */

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

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

    if (ttyusb_ctx->baudrate)
    {
        snprintf(baudrate_buf, sizeof(baudrate_buf), "B%d", ttyusb_ctx->baudrate);
        cfsetispeed(&new_termios, (int)baudrate_buf);//输入波特率
        cfsetospeed(&new_termios, (int)baudrate_buf);//输出波特率
    }
    else
    {
        cfsetispeed(&new_termios, B115200);//默认波特率
        cfsetospeed(&new_termios, B115200);
    }

    switch (ttyusb_ctx->databits)//数据位
    {
        case 5:
        {
            new_termios.c_cflag |= CS5;//5位
            break;
        }

        case 6:
        {
            new_termios.c_cflag |= CS6;//6位
            break;
        }

        case 7:
        {
            new_termios.c_cflag |= CS7;//7位
            break;
        }

        case 8:
        {
            new_termios.c_cflag |= CS8;//8位
            break;
        }

        default:
        {
            new_termios.c_cflag |= CS8;//默认
            break; 
        }
    }

#if 1
    switch(ttyusb_ctx->parity)//奇偶校验位
    {
        case 'n':
        case 'N':
        {
            new_termios.c_cflag &= ~PARENB;//无校验
            break;
        }

        case 'o':
        case 'O':
        {
            new_termios.c_cflag |= (PARODD | PARENB);//奇校验
            break;
        }

        case 'e':
        case 'E':
        {
            new_termios.c_cflag &= ~PARENB;//偶校验
            new_termios.c_cflag &= ~PARODD;
        }

        case 'b':
        case 'B':
        {
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_cflag &= ~CSTOP;//空格
        }

        default:
        {
            new_termios.c_cflag &= ~PARENB;//默认无校验
            break;
        }
    }

    switch(ttyusb_ctx->stopbits)//停止位
    {
        case 1:
        {
            new_termios.c_cflag &= ~CSTOPB;//1位停止位
            break;
        }

        case 2:
        {
            new_termios.c_cflag |= CSTOPB;//2位停止位
            break;
        }

        default:
        {
            new_termios.c_cflag &= ~CSTOPB;//默认1位停止位
            break;
        }
    }
#endif


    //MIN =0 TIME =0 时,如果有数据可读,则read最多返回所要求的字节数,如果无数据可用,则read立即返回0;
    new_termios.c_cc[VTIME] = 0;
    new_termios.c_cc[VMIN] = 0;

    ttyusb_ctx->msend_len = 128;//最长数据发送长度

    retval = tcflush(ttyusb_ctx->fd, TCIOFLUSH);//清空输入输出
    if (retval < 0)
    {
        printf("Failed to clear the input/output buffer:%s\n", strerror(errno));
        return -3;
    }

    retval = tcsetattr(ttyusb_ctx->fd, TCSANOW, &new_termios);//启用新的串口文件属性
    if(retval < 0)
    {
        printf("Failed to set new properties of the serial port:%s\n", strerror(errno));
        return -4;
    }

    printf("[%s]Successfully set new properties of the serial port!\n", __func__);
    return 0;
}


(4)tty_send()

向串口发送信息

int tty_send(ttyusb_ctx_t *ttyusb_ctx, char *send_buf, int sbuf_len)
{
    int     retval = -1;
    int     write_rv = 0;
    char    *ptr = NULL, *end = NULL;


    if (!ttyusb_ctx || !send_buf || (sbuf_len < 0))
    {
        printf("[%s]The argument invalid!\n", __func__);
        return -1;
    }
	
	//对能发送的最长的信息和需要发送信息的长度作比较
    if (sbuf_len > ttyusb_ctx->msend_len)
    {
        ptr = send_buf;
        end = send_buf + sbuf_len;
        do
        {   
            if(ttyusb_ctx->msend_len <(end-ptr))
            {
                retval = write(ttyusb_ctx->fd, ptr, ttyusb_ctx->msend_len);

                if ((retval <= 0) || (retval != ttyusb_ctx->msend_len))
                {
                    printf("[%s]Write data to fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
                    return -2;
                }

                write_rv += retval;
                ptr += ttyusb_ctx->msend_len;
            }
            else
            {
                retval = write(ttyusb_ctx->fd, ptr, (end - ptr));

                if ((retval <= 0) || (retval != (end - ptr)))
                {
                    printf("[%s]Write data to fd[%d] failure:%s\n",__func__, ttyusb_ctx->fd, strerror(errno));
                    return -3;
                }

                write_rv += retval;
                ptr += (end - ptr);
            }
            
        }while(ptr < end);
        
    }
    else
    {
        retval = write(ttyusb_ctx->fd, send_buf, sbuf_len);
        if((retval <= 0) || (retval != sbuf_len))
        {
            printf("[%s]Write data to fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
            return -4;
        }
        write_rv += retval;
    }

    printf("[%s]send_buf:%s\n", __func__ , send_buf);
    printf("[%s]write_rv: %d\n", __func__ , write_rv);

    return write_rv;
}

(5) tty_recv()

在发送AT指令给4G模块之后,4G模块需要时间进行一定的处理,才能返回数据给树莓派,所以我这里采用了select进行延时阻塞,在一定时间内没读到信息就超时退出,在规定时间内读到立即返回。

//这里不固定接收buffer的大小,因为不同的AT指令返回的字符串大小不一样,如果要查看所有SMS信息的话,就可能很大
int tty_recv(ttyusb_ctx_t *ttyusb_ctx, char *recv_buf, int rbuf_len)
{
    int     read_rv = -1;
    int     rv_fd = -1;
    fd_set  rdset;
    struct  timeval time_out;

    if (!ttyusb_ctx || (rbuf_len < 0))
    {
        printf("[%s]The argument invalid!\n", __func__);
        return -1;
    }
    
    if (ttyusb_ctx->timeout)
    {
        time_out.tv_sec = (time_t)ttyusb_ctx->timeout;
        time_out.tv_usec = 0;
        FD_ZERO(&rdset);
        FD_SET(ttyusb_ctx->fd, &rdset);

        rv_fd = select(ttyusb_ctx->fd + 1, &rdset, NULL, NULL, &time_out);
        if(rv_fd < 0)
        {
            printf("[%s]Select() listening for file descriptor error!\n", __func__);
            return -2;
        }
        else if(rv_fd == 0)
        {
            printf("[%s]Select() listening for file descriptor timeout!\n", __func__);
            return -3;
        }
    }
    
    usleep(1000);
    read_rv = read(ttyusb_ctx->fd, recv_buf, rbuf_len);
    if (read_rv <= 0)
    {
        printf("[%s]Read data from fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
        return -4;
    }
    
    printf("[%s]recv_buf:%s\n", __func__, recv_buf);
    
    return read_rv;
}

流程图

在这里插入图片描述

main.h

#ifndef _MAIN_H_
#define _MAIN_H_

#include <termios.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include "serial_init.h"

void print_usage(char *program_name);
void install_signal(void);
void handler(int sig);

#endif

main.c

#include "main.h"

int g_stop = 0;

int main(int argc, char *argv[])
{
    
    int rv = - 1;
    int rv_fd = -1;
    char send_buf[128] = {0};
    char recv_buf[128] = {0};
    fd_set rdset;
    ttyusb_ctx_t ttyusb_ctx;
    ttyusb_ctx_t *ttyusb_ctx_ptr;
    ttyusb_ctx_ptr = &ttyusb_ctx;
    int ch;
    int i;


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

    while((ch = getopt_long(argc, argv, "b:d:p:s:m:h", opts, NULL)) != -1)
    {
        switch(ch)
        {
            case 'b':
            {
                ttyusb_ctx_ptr->baudrate = atoi(optarg);
                break;
            }
            case 'd':
            {
                ttyusb_ctx_ptr->databits = atoi(optarg);
                break;
            }
            case 'p':
            {
                ttyusb_ctx_ptr->parity = optarg[0];
                break;
            }
            case 's':
            {
                ttyusb_ctx_ptr->stopbits = atoi(optarg);
                break;
            }
            case 'm':
            {
                strncpy(ttyusb_ctx_ptr->serial_name, optarg, SERIAL_NAME);
                break;
            }
            case 'h':
            {
                print_usage(argv[0]);
                return 0;
            }
            default:
            {
                printf("%s input invalid argument!\n", __func__);
                return -1;
            }
        }
    }

    if(0 == strlen(ttyusb_ctx_ptr->serial_name))
    {
        printf("Failed to obtain the device name!\n");
        return -1;
    }

    install_signal();

    if(tty_open(ttyusb_ctx_ptr) < 0)
    {
        printf("Failed to open the device file");
        return -2;
    }

    if(tty_init(ttyusb_ctx_ptr) < 0)
    {
        printf("Failed to initialize the serial port\n");
        return -3;
    }

    while(!g_stop)
    {
        FD_ZERO(&rdset);//清空文件描述符集合
        FD_SET(ttyusb_ctx_ptr->fd, &rdset);//将串口文件fd加入集合
        FD_SET(STDIN_FILENO, &rdset);//将标准输入文件fd加入集合
		//select多路复用非阻塞监听文件描述符
        rv_fd = select(ttyusb_ctx_ptr->fd + 1, &rdset, NULL, NULL, NULL);
        if(rv_fd < 0)
        {
            printf("Select listening for file descriptor error!\n");
            rv = -4;
            goto CleanUp;
        }
        else if(0 == rv_fd)
        {
            printf("Select listening for file descriptor timeout!\n");
            rv = -5;
            goto CleanUp;
        }
        else
        {
            if(FD_ISSET(STDIN_FILENO, &rdset))//判断是否是标准输入响应
            {
                memset(send_buf, 0, sizeof(send_buf));//清空buffer

                fgets(send_buf, sizeof(send_buf), stdin);
                i = strlen(send_buf);
                strcpy(&send_buf[i-1], "\r");//发送AT指令时,需要在指令后面加上\r
                if(tty_send(ttyusb_ctx_ptr, send_buf, strlen(send_buf)) < 0)
                {
                    printf("Failed to send data through the serial port\n");
                    rv = -6;
                    goto CleanUp;
                }
                printf("Succeeded in send data serial port data:%s\n", recv_buf);
                fflush(stdin);//冲洗输入流
            }
            else if(FD_ISSET(ttyusb_ctx_ptr->fd, &rdset))//判断是否是串口文件描述符响应
            {
                memset(recv_buf, 0, sizeof(recv_buf));
				//读串口发来的信息
                if(tty_recv(ttyusb_ctx_ptr, recv_buf, sizeof(recv_buf), 0) < 0)
                {
                    printf("Failed to receive serial port data!\n");
                    rv = -7;
                    goto CleanUp;
                }
               	printf("Succeeded in receiving serial port data:%s\n", recv_buf);
                fflush(stdout);//冲洗输出流
            }
        }
    }

    return 0;
CleanUp: 
    tty_close(ttyusb_ctx_ptr);
    return rv;
}

//打印帮助信息
void print_usage(char *program_name)
{
    printf("Usage:%s[OPTION]\n\n", program_name);
    printf("-b[baudrate]:Select baud rate, for example 115200 and 9600.\n");
    printf("-p[parity]:Select parity check, for example n N e E o O.\n");
    printf("-s[stopbits]:Select stop bit, for example 1 and 2.\n");
    printf("-m[serial_name]:Select device file, for example /dev/ttyUSB0.\n");
    printf("-h[help]:Printing Help Information.\n"); 
    printf("For example:./SMS -b 115200 -p n -s 1 -m /dev/ttyUSB0 \n\n");

}

//对信号进行处理
void handler(int sig)
{
    switch(sig)
    {
        case SIGINT:
        {
            printf("Process captured SIGINT signal!\n");
            g_stop = 1;
            break;
        }
        case SIGTERM:
        {
            printf("Process captured SIGTERM signal!\n");
            g_stop = 1;
            break;
        }
        case SIGSEGV:
        {
            printf("Process captured SIGSEGV signal!\n");
            g_stop = 1;
            exit(0);
            break;
        }
        case SIGPIPE:
        {
            printf("Process captured SIGPIPE signal!\n");
            g_stop = 1;
            break;
        }
        default:
            break;
    }

    return ;
}

//注册信号
void install_signal(void)
{
    struct sigaction sigact;

    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigact.sa_handler = handler;

    sigaction(SIGINT, &sigact, 0);
    sigaction(SIGTERM, &sigact, 0);
    sigaction(SIGPIPE, &sigact, 0);
    sigaction(SIGSEGV, &sigact, 0);

    return ;
}

Makefile

我将.h文件都放到了inc目录下,Makefile和.c文件都放到了src目录下,执行make编译将会生成bin目录,里面生成了可执行文件。

APPNAME = SMS
OBJDIR = `pwd`/../
APPPATH = ${OBJDIR}bin/${APPNAME}
CC = gcc
CFLAGS += -I ${OBJDIR}inc/
#LIBPATH = ${OBJDIR}lib/
#LDFLAGS += -L ${LIBPATH} -lsqlite3 -lmosquitto  -ldl -lpthread


server:create_file
	${CC} `pwd`/*.c -o ${APPPATH}   ${CFLAGS} 
	
create_file:
	$(shell if [ ! -d $(OBJDIR)bin ]; then mkdir -p $(OBJDIR)bin; fi)

.PHONY:distclean
distclean:
	rm -rf ${OBJDIR}bin

四、运行结果

在这里插入图片描述

五、遇到的问题及解决办法

1、上面的代码相当于模拟了一个串口调试助手,但是我们只能发送AT指令,而不能向特定的号码发送消息,这是因为我们需要对发送的信息进行UTF8到Unicode的转码。
2、如果不将输入输出缓存区清空的话,可能会造成一直在发送,所以要用发fflush()清空。
3、每一个AT指令后面都要以\r或者是\r\n结束。这是AT标准AT指令集 里面规定的。
4、fgets()和gets()都是从标准输入读取一行数据,但是不同的是fgets()在读完数据之后会自动加上换行,就是\n,而且其需要指定写入缓冲区的大小,更为安全

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

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值