C语言串口编程收发数据 并实现AT指令的收发 可变参数控制串口属性 树莓派4G模块
文章目录
- 一、 Linux下串口编程的流程
- 1.打开串口
- 2. 串口初始化
- 2.1 常用函数总览
- 2.2 初始化
- 3. 串口的读写
- 4. 串口关闭
- 二、代码——串口编程实现自发自收
- 三、可变参数控制串口属性的函数封装
- 3.1 头文件——serial_port.h
- 3.2 函数定义——serial_port.c
- 打开串口——serial_open
- 关闭串口——serial_close
- 串口初始化——serial_init
- 写数据到串口—— serial_send
- 从串口读取数据—— serial_recv
- 3.3 主程序(已更新为收发AT指令)
- 四、补充一下串口实现AT指令的收发
对于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 128typedef 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; //默认数据位为8break;}/* -------设置校验方式------- */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)
#endifvoid 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");
#endiffflush(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_DEBUGserial_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]);}
#endiffflush(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://blog.csdn.net/weixin_45121946/article/details/107130238
C语言串口编程收发数据 并实现AT指令的收发 可变参数控制串口属性 树莓派4G模块相关推荐
- UART0串口编程(五):串口编程(UART0)之UC/OS(一)UC/OS下的串口发送任务编程
UART0串口编程(五) 串口编程(UART0)之UC/OS(一) 一.在UC/OS中设计串口程序所要考虑的问题 1. 串口通信数据以帧为单位进行处理,如果需要接收大量数据,则帧缓冲区规模必然 ...
- 大数据学习day16-Set接口、Map、可变参数
大数据学习day16 Set接口.Map.可变参数 今日内容 Set接口中hashSet LinkedHashSet Map集合特点 Map集合的遍历方式 方法可变参数 斗地主案例排序 总结 第一章 ...
- QT入门第十四天 串口通信协议+收发数据+波特率+数据位+停止位+奇偶校验+串口识别射频RFID的卡号
QT入门第十四天 串口通信[QT入门第十四天 串口通信协议+收发数据+波特率+数据位+停止位+奇偶校验+串口识别射频RFID的卡号 第一章 常见的硬件通信接口协议 [1]硬件通信接口协议 [2]使用串 ...
- QT5实现串口收发数据(上位机与下位机通信)
最近帮老师做一个应用程序,通过上位机与下位机进行串口通信,最后实现实时绘图,通过几天努力,成功实现蓝牙串口通信. 参考博客1 注意:代码中一些与串口无关代码,可以忽略掉 一.QT5串口基础知识 1. ...
- C语言程序周期接收虚拟串口发送的数据
背景 我之前的一篇博客讲解了怎么使用虚拟串口和串口调试助手:虚拟串口模拟器和串口调试助手使用教程,这次我们在此基础上继续来使用虚拟串口周期发送和接收功能. 我们知道,在Windows的操作系统上,将串 ...
- C——Linux下的串口编程
原 C--Linux下的串口编程 2017年06月06日 19:30:50 C_Aya 阅读数:11537 <span class="tags-box artic-tag-box&qu ...
- Qt 多线程串口编程
一.问题 以前串口编程使用第三方的CnComm.h编程,CnComm作者博客链接,使用起来还蛮好的,不过既然用qt了就想着用qt自带的QSerialPort,移植性更好一些,结果折腾了好几天,主要遇到 ...
- 单片机 串口编程之串口通信仿真实验
单片机 串口编程之串口通信仿真实验 一.简述 记--简单的使能串口,串口收发数据的例子.(使用Proteus仿真+虚拟串口调试) 代码,仿真文件打包:链接: https:/ ...
- 【linux】串口编程(一)——配置串口
目前遇到的串口编程都是用于通信,很少作为终端显示.以前没有对串口编程做深入研究,本次以libmodbus源码中对串口的设置为例,详解总结串口编程时配置的属性(struct termios) 以libm ...
最新文章
- C# where用法
- [仙剑四]仙剑四十大经典语句评析
- 开发高性能并发应用不是一件容易的事情。这类应用的例子包括高性能Web服务器、游戏服务器和搜索引擎爬虫...
- Appium定位元素的几种方法总结
- [luoguP4306][JSOI2010]连通数
- 通信基站(dfs回溯,思维)
- 芝麻HTTP:redis-py的安装
- winform窗体控件多,加载慢、卡顿的解决办法
- LOOP WITH CONTROL 用法
- 实践与反思_在行动中反思的实践
- java quartz 2.2.3_java – Spring 3 Quartz 2错误
- 用汇编的眼光看C++(之特殊函数)
- pstack: Input/output error failed to read target解决
- 计算机程序设计语言分为机器语言,汇编语言和高级语言三种,简述计算机程序设计语言(机器语言、汇编语言、高级语言)的优缺点。...
- 最新版校园招聘进大厂系列----------(4)京东篇 -----未完待续
- 进化算法之粒子群算法介绍附代码——PSO
- 鸿蒙系统是怎样一种系统,鸿蒙系统pc版怎么安装 鸿蒙系统pc版安装教程
- Problem F: Matrix Problem (III) : Array Practice Time Limit: 1 Sec Memory Limit: 4 MB Submit: 8787
- 收废品小程序的推广策略与实践
- 若依前后端分离框架去掉首页 登录后跳转至动态路由的第一个路由