串口与wifi模块
经过以下学习,我们掌握:
- AT指令与wifi模块的测试方法:通过CH340直接测试,研究各种AT指令下wifi模块的响应信息形式。
- 编程,使用串口中断接收wifi模块对AT指令的响应信息以及透传数据,通过判断提高指令执行的准确度。
- wifi模块的调试方法:白盒测试。
- 串口接收缓冲区字符串的获取方法有两种:①直接存在字符数组中,用strstr()函数查找子串(有效指令);②定义临时字符,保证子串(有效指令)从字符数组第0位开始存储。第二种更好,因为你要考虑有效指令的存放可能因为字符数组的长度限制而只被部分捕获。
- 了解wifi模块分别以客户端(client)以及服务器(server)模式进行工作的特点:
- 服务器和客户端联入同一个局域网,因此都知道互相的IP地址,也是数据交互的前提;
- 以客户端模式工作就是多对1的关系(8266和PC都是多,路由器是1),以服务器模式工作就是1对多的关系(8266是1,其余是多)。总之,服务器就是1,客户端就是多。
- 服务器有端口号,服务器与每个客户端进行数据交互有通道号。
- 服务器与客户端建立连接时的连接请求,一般都是由客户端发起的(通过服务器端口,向服务器IP地址发送请求),然后服务器这边就有个响应。服务器与客户端在连接期间都是保持一个握手状态。
- 服务器与客户端断开连接的请求,可以由服务器发起,也可以由客户端发起。
1.ESP8266wifi模块
1、ESP8266介绍:可以在乐鑫科技官网查询。
- ESP8266模块是面向物联网应用的高性价比、高度集成的 Wi-Fi MCU,也就是说它就是一个单片机。
- 集成了 32 位 Tensilica 处理器、标准数字外设接口、天线开关、射频 balun、功率放大器、低噪放大器、过滤器和电源管理模块等,仅需很少的外围电路,可将所占 PCB 空间降低。
- ESP8266EX 内置超低功耗 Tensilica L106 32 位 RISC 处理器,CPU 时钟速度最高可达 160 MHz,支持实时操作系统 (RTOS) 和 Wi-Fi 协议栈,可将高达 80% 的处理能力留给应用编程和开发。
- 可以将wifi模块设计到51或32的PCB板上,这样就制作成了一个可以上网的开发板。
2、AT指令:蓝牙,ESP-01s,Zigbee, NB-Iot等通信模块都是基于AT指令的设计。所有的指令没必要去记。
- 终端设备(比如PC、MCU)通过串口向终端适配器(比如说蓝牙、wifi模块)发送AT指令:AT指令集是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。
-
AT指令的功能就是驱动终端适配器干活,比如联网、数据交互等。
3、AT指令和普通数据的区别:
- AT指令对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个字符的长度(包括最后的空字符)。
- 每个AT命令行中只能包含一条AT指令;对于由终端设备主动向PC端报告的URC指示或者response响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。
- AT指令是控制类数据,都要以回车作为结尾:响应或上报以回车换行为结尾。AT指令发送时,一定需要在串口助手中勾选“发送新行”,没有此选项的话,需要手动在指令末位键盘敲一个回车键和指令一起发送给wifi模块。辨析:比如说在AT指令模式下,我第一次发送指令时忘记发送新行了,下一次发送带新行的指令后,wifi模块会响应“ERROR”,因为wifi模块在读取AT指令时是以\r\n作为结尾的,第一次没有新行的指令相当于还在缓冲区当中,也就是没有结束符,第二次带新行的指令发送过来,才读到最后的\r\n,相当于发送了两遍指令,所以wifi模块不认识这个指令,会响应“EEROR”。
4、ESP-01S初始配置和验证:ESP-01s出厂波特率正常是115200,也有可能是9600,需要连在CH340驱动上,再与电脑进行连接,进行验证。此时PC就是终端设备,ESP-01S就是终端适配器。
- 接线:交叉接线,经过测试,ESP-01S的VCC接在CH340驱动的5V针脚上,串口才能正常工作。另外,模块会存在发热现象。
- 打开ESP8266调试助手:使用安信可串口助手,实际上ESP8266的技术支持很多都是安信可做的。
- 确定波特率:经过验证是115200的波特率,因为以9600的波特率发送AT指令后,模块没有响应。
- 发送AT+RST:重启wifi模块。购买来的模块的响应信息有两种情况,如下,但是以看见ready为准(多次发送可能就看不到ready了)。
- 发送AT:测试模块功能是否正常,正常的话会响应OK。
- 发送AT+UART=9600,8,1,0,0:配置成9600的波特率,同时说明数据位是8位、停止位是1位、校验位是0位。成功wifi模块会响应“OK”。
- 点击关闭串口,切换成9600波特率:目的是以后用单片机来玩ESP-01S时,波特率是对的上的(实际上51单片机也能配置成115200,但前面我们一直在讲9600)。
2.AT指令入网设置
0、互联网的4个协议层:从上到下依次是应用层、传输层、网络层、链路层。
网络层 | 应用层 |
1发送和接受数据包时,要注明发送者和接收者的IP地址; 2IP协议版本:IPV4:四个8位二进制数(十进制下就是0~255)例如 192.168.0.123; 3连上路由器的无线终端设备会被分配独立(但每次连接不固定)的IP地址; 4查找我的电脑在WLAN中的IP地址方法:ipconfig; 5默认网关(gateway): 连上“中国移动…服务器”的路由器可以使它的局域网连接互联网,路由器(Router)的IP地址是网关,它由上述服务器分配,一个局域网下只有一个网关; 6子网掩码(subnet):255.255.255.0 子网是巨型网络的分支,用于区分IPV4中的子网地址和设备序号,二者组成了设备的IP 7实例:用连上WiFi的电脑在互联网上打开某网站的过程:已知网站IP地址 数据包传输路线:电脑(浏览器)→路由器→中国移动服务器→互联网→某网站 | 1HTTP:Hyper Text Transfer Protocol,超文本传输协议; 2HTTP请求和响应演示:用连上WiFi的电脑在互联网上打开某网站的过程: 电脑浏览器←→互联网←→网站服务器(HTTP请求数据包→、HTTP响应数据包←); 3HTTP请求数据包: ①组成:请求行 & 请求头; ②请求行:请求方法 + 网站首页 + 协议版本,例如:GET/HTTP/1.1; ③请求头: HOST:网址 ……………… 4网站IP地址的确定: 请求数据包中的网址先被送至DNS服务器进行域名解析,之后会返回网站服务器的IP地址 5HTTP响应数据包: ①组成:响应状态行 & 响应头; ②响应状态行:协议版本 + 状态码 + 状态码含义,例如:HTTP/1.1 200 OK; ③响应头: 时间信息 Content-Type:响应体内容类型;字符集 = 类型 (例如:Content-Type: text/html; charset = UTF-8) 6注:请求行、请求头以及响应行、响应头之间用 \r\n 区分开(表示隔一行), \r\n\r\n 则表示空行,头与体之间用空行分隔。 |
传输层 | |
1TCP:Transmission Control Protocol,传输控制协议; 应用:电子邮件、文件; 2UDP:User Datagram Protocol,用户数据报协议; 应用:语音、视频、网游; |
1、设置wifi模块的工作模式:这属于网络的链路层,共有三种模式可选。指令是:AT+CWMODE=(1/2/3)
- 1是设备模式,或称无线设备模式(Wirless Station):这种模式下就像手机联wifi进行上网一样让ESP8266联入家里的路由器,可与连入同一个局域网的其他设备通讯。
- 2是路由模式,或称接入点模式(Acess Point):这种模式下就像手机开热点一样将ESP8266当做一个小路由器。
- 3是双模式;
- 设置成功,wifi模块会响应“OK”
- 设置失败,wifi模块会响应“ERROR”,失败的原因可能是拼写出错。
- 没有响应的话,说明你没有发送新行。
- 设置双模式成功后,能在电脑上看到这个wifi模块的wifi名字是ESP_28DCAB
2、以设备模式联入路由器:告知wifi名字和密码。
- 在家里,开路由器,我的指令是:AT+CWJAP="CMCC-XyVF","XyVFVsrz"
- 在学校,开手机热点,我的指令是:AT+CWJAP="HUAWEI P20","abcdefgh"
- 连接成功,wifi模块会响应“WIFI CONNECTED” 、 “WIFI GOT IP” 、“OK”(有时候可能不会显示“OK”,不用担心,实际上是有的)
- 连接失败,有两种情况,①wifi模块会响应“ERROR”。失败的原因可能是双引号和逗号没写成半角(英文模式),或者指令后面多了个空格;②wifi模块会响应“+CWJAP:3”、“FAIL”,原因是wifi名字拼写错误导致这个wifi检索不到,或者wifi密码拼写错误。
- 没有响应的话,说明你没有发送新行。
- wifi模块一上电就会去自动匹配上次连接的路由器,一旦匹配上会自动连接wifi,并返回“WIFI CONNECTED”、“WIFI GOT IP”(没有“OK”);没有匹配上的话会显示“WIFI DISCONNECT”,并且wifi模块上的led指示灯会闪烁,只有连上了才会熄灭。
- 再次用上述AT指令连接wifi时,wifi模块先会响应“WIFI DISCONNECTED”,然后再进行连接,响应“WIFI CONNECTED”、“GOT IP”、“OK”(有时候可能不会显示“OK”,不用担心,实际上是有的)
- 我发现,进入透传模式再返回AT指令模式后,再次连网,第一次wifi模块会响应“CLOSED”、“OK”,之后再连网,wifi模块会响应“OK”。
3、查询ESP-01S的IP地址:指令是:AT+CIFSR
- 成功后,wifi模块会响应多行信息,包括路由模式的网关、路由模式的mac地址、设备模式的IP地址、设备模式的mac地址,最后响应“OK”。
- 设备模式的IP地址:图中,在连入家中wifi的前提下,我的ESP-01S的IP地址是192.168.1.8;
- 路由模式的网关:APIP后面的网关是当ESP-01S在路由模式下的网关,当前的值是192.168.4.1(网关的最后一位一般都是1)
- 路由模式的mac地址:APMAC后面的12个(6组,一组2个)16进制数是ESP-01S在路由模式下的mac地址(物理地址),相当于人的身份证,每台可以联网的设备都有mac地址,它是出厂就烧录好的,IP地址不是永久的,但mac地址是固定的。关于mac地址的具体解释参阅此链接。
- 设备模式的mac地址:STAMAC后面的也是mac地址,我们可以发现只有连入wifi后,我们才能看到这个设备的mac地址。并且一台设备可能有多个MAC地址,比如说这个ESP8266,它的MAC地址就有两个。
- 注意:家中路由器的网关不怎么变化,如果你是用手机当做热点的话,今天和明天的手机网关可能会变化,所以用的时候需要实时去查。
3.作为客户端连接TCP服务器
1、数据传输路线:我们接下来在PC上用网络调试助手构建一个基于TCP协议的服务器。接下来也是发送AT指令,让ESP8266当做客户端工作。这个时候ESP8266和外部的通讯方式分为两类。
- 走串口:wifi模块和PC走串口协议,PC通过USB转TTL的CH340驱动,将指令传送给wifi模块,然后wifi模块响应一些配置信息给PC。
- 走wifi:wifi模块和PC也可以走网络传输层中的TCP协议,这时候PC和wifi模块是连接在同一个局域网下的,wifi模块作为客户端,PC作为服务器。数据从wifi模块传递到路由器,再由路由器中转,将这个信息传递给PC上的TCP服务器;数据也可以从TCP服务器经过路由器中转,传递到wifi模块。
2、网络调试助手设置:像这个服务器我们以后也能自己写,基于Linux的或者基于Windows的,通过Scoket编程,用java或者C语言编程。或者说用手机上的网络助手app也是可以的。
- 协议类型选择TCP服务器
- 本地主机IP用自己电脑的IP:在家里连接路由器,查到是192.168.1.7
- 本机端口号:使用8880,尽量不要用8080。
3、wifi模块连接TCP服务器:指令是:AT+CIPSTART="TCP","192.168.1.7",8880 (这里的IPV4地址是PC的IP地址,在PC的命令提示符中通过config指令查询)
- 连接成功,wifi模块会响应“CONNECT”、“OK”。另外还能在网络调试助手中的连接对象中看见wifi模块的IP地址。
- 连接失败,wifi模块会响应“ERROR”、“CLOSED”。失败的原因可能是:没有让本机和ESP8266连在同一个局域网下,或者8266没有提前连上局域网,或者电脑需要关闭防火墙(百度看看),或者网络调试助手出问题了,没有成功建立TCP服务器(换一个)。
- 成功连接后,如果再次发送上述AT指令,wifi模块会响应“ALREADY CONNECTED”、“ERROR”。
- 成功连接后,如果在网络调试助手上手动将TCP服务器断开,那么wifi模块会响应“CLOSED”,这时候如果想再次连接,需要手动重新建立TCP服务器,并再往wifi模块发送连接该服务器的AT指令。
4、向服务器发送固定字节数据:这种模式比较蠢,每次发数据都要先设置数据的长度。
- 不要勾选网络调试助手中接收区设置中的16进制显示,否则发送来的数据是16进制的。
- 设置要发送数据的字节长度,进入数据待发状态:指令是AT+CIPSEND=n (n表示数据的字节数)
- 设置成功,wifi模块会响应“OK”,同时在安可信串口调试助手中能看见">"这个符号,代表等待数据发送。
- 如果失败,且wifi模块响应“IPMODE=1”、“ERROR”,那么其原因是你之前配置过透传模式(见下面第3.5小节)。
- 发送的数据尽量不要有新行,也就是我们不发送换行和回车符号"\r\n",因为它们占2个字节。
- 发送成功后,wifi模块会从透传模式自动回到AT指令模式。
- 如果发送的字节数超出了n字节数,wifi模块先会响应“busy s...”,再响应“Recv n bytes”、“SEND OK”,同时只会截取前面的字节发送给TCP服务器。
- 如果你发送的数据长度不足n字节,wifi模块不会有任何响应,同时网络调试助手上看不到任何发送来的数据,这个数据就好像一直存在“缓冲区”中,只有你达到或者超过了设定的n个字节数,才会显示。
5、透传模式下数据交互:我们不希望每次发数据之前都设置数据长度,同时我们也不希望受到是否发送新行的限制。在透传模式下,随便你怎么发,随便你怎么收。
- 设置透传模式:指令是:AT+CIPMODE=1 。
- 设置成功,wifi模块会响应“OK”。
- 设置失败,wifi模块会响应“ERROR”。
- 进入数据待发状态:指令是AT+CIPSEND 。
- 设置成功,wifi模块会响应“OK”。
- 设置失败,wifi模块会响应“ERROR”。
- 在透传模式下如果还继续让wifi模块给TCP服务器发送带有新行的AT指令,wifi模块自身不会有任何响应,这些数据会出现在网络调试助手上,只不过成为带新行的普通数据。
- 退出透传模式,回到AT指令模式:wifi模块发送数据时,发送"+++"(不加新行),发送一次没反应,就发送两次。
4.单片机帮你做这一切,实现wifi透传控制LED(重在调试)
1、单片机串口需要做哪些事情:终端设备从PC变成单片机(MCU),让单片机通过串口给模块发送一系列AT指令,指挥wifi模块以客户端模式工作。
重启wifi模块- 配置wifi模块的工作模式(如果之前设置过的话,这一步wifi模块会自动完成)
- 连接上局域网络(之前设置过网络连接的话,这一步wifi模块会自动完成)
- 连接上TCP服务器
- 设置透传模式
- 进入数据待发状态,正式进入透传模式
发送数据(本demo不演示发送透传指令,也就是不向服务器发送任何请求,只用来接收服务器数据)关闭透传模式(本demo不退出透传模式)
注:波特率是提前用电脑通过AT指令给wifi模块配置好的,用9600。
2、白盒测试:【项目工程文件夹】
- 白盒测试的原因:在我们的串口中断程序中,有一个从缓冲寄存器SBUF中读取字符到字符数组的这么一个过程,所以wifi模块响应回来的字符串被单片机截胡了,这导致我们没法在串口助手上看见它们,这也称为黑盒测试。在AT指令模式下,单片机通过串口给wifi模块发送指令后,wifi模块会响应信息;在透传模式下,wifi模块会接收到由TCP服务器透传来的数据,然后wifi模块会将数据响应给单片机。所以需要使用白盒测试来提前得知wifi模块的响应信息。
- 白盒测试的接线方法:不将wifi模块的TX接到单片机的RX,而将其接到电脑的RX,那么电脑就能通过安信可串口调试助手来观察“51给模块发送的指令之后,模块到底有没有去执行这些指令”,这有点像让ESP8266脚踏两只船。
- 测试原理:将由wifi模块发送的所有数据(AT指令模式下的响应信息 & 透传模式下接收到的信息)在串口助手上显示出来。在第2节习题4(PC发送字符串指令给单片机)的代码基础上,我们让单片机每隔5s给wifi模块发送上述AT指令。当串口助手上对每条指令都响应“OK” 或者 PC上的网络调试助手上能收到数据 或者 串口能查看到网络调试助手发送来的数据 时,都能说明单片机已经成功驱动wifi模块进入透传模式
- 代码心得:
- 用字符数粗保存AT指令这些字符串时,需要注意字符串当中有双引号时则需要用转义字符保留原来的含义;还需要注意AT指令需要新行,也就是字符串末尾加上\r\n
- 对于比较长AT指令的字符串,它们太占空间,程序会编译失败,所以需要在定义的字符数组前面加上“code”,这是个宏定义,目的是将程序存放在比较占空间的字符串的存储位置(关键字code放在以后介绍)。
- 白盒测试前需要验证:只让51单片机与PC进行串口通讯(先不着急把上图连线接好),在串口助手中,验证程序中写的AT指令是OK的,也就是说,检查我们写的字符串中,逗号、双引号是否有错,是否漏写\r\n。
- 测试代码:
#include "reg52.h" #include "intrins.h" #include <string.h>#define len 12 sfr AUXR = 0x8E; sbit ledD5 = P3^7; char cmd[len]; char esp01s_modeSetting[] = "AT+CWMODE=3\r\n"; //配置wifi模块的工作模式 code char esp01s_connectLAN[] = "AT+CWJAP=\"CMCC-XyVF\",\"XyVFVsrz\"\r\n"; //连接上局域网络 code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.1.7\",8880\r\n"; //连接上TCP服务器 // code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n"; //连接上局域网络 // code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.199.201\",8880\r\n";//连接上TCP服务器 char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n"; //设置透传模式 char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n"; //进入数据待发状态,正式进入透传模式/* API1. 测试,用于每隔5秒给串口缓冲寄存器发送代表AT指令的字符串 */ void Delay5000ms(); /* API2. 初始化串口 */ void UartInit(void); /* API3. 通过串口给PC发送一个字符 */ void sendByte(char data_msg); /* API4. 通过串口给PC发送一个字符串 */ void sendString(char *str);void main(void) {UartInit();ledD5 = 1;while(1){Delay5000ms();sendString(esp01s_modeSetting);Delay5000ms();sendString(esp01s_connectLAN);Delay5000ms();sendString(esp01s_connectTCPServer);Delay5000ms();sendString(esp01s_serialNet_mode);Delay5000ms();sendString(esp01s_dataSend_waiting);} }void Uart_Routine() interrupt 4 {static int i = 0; //静态全局区的变量,数组下标/* 中断处理程序中,对于接收中断的响应 */if(RI == 1){ RI = 0;cmd[i] = SBUF;i++;if(i == len){i = 0;}if(strstr(cmd,"en")!=NULL){ledD5 = 0;i = 0;memset(cmd,'\0',len);}else if(strstr(cmd,"se")!=NULL){ledD5 = 1;i = 0;memset(cmd,'\0',len);}}/* 中断处理程序中,对于发送中断的响应 */if(TI == 1){// 暂时不做任何事情} }void Delay5000ms() //@11.0592MHz {unsigned char i, j, k;_nop_();i = 36;j = 5;k = 211;do{do{while (--k);} while (--j);} while (--i); }void UartInit(void) //9600bps@11.0592MHz {AUXR = 0x01;SCON = 0x50; //8位UART,允许串口接收TMOD &= 0xDF;TMOD |= 0x20; //定时器8位重载工作模式TH1 = 0xFD;TL1 = 0xFD; //9600波特率初值TR1 = 1;EA = 1;ES = 1; //开启串口中断 }void sendByte(char data_msg) {SBUF = data_msg;// Delay10ms();while(TI == 0);TI = 0; }void sendString(char *str) {char *p = str;while(*p != '\0'){sendByte(*p);p++;} }
- 测试内容:
- 观察串口调试助手上wifi模块对AT指令的反应结果,以及从网络调试助手上透传来的数据:
- 拔掉wifi模块插在CH340模块RX上的TX引线,将这个TX引脚插到51单片机上(不重启51单片机),试一下能否通过wifi透传点亮LED。
- 我测试了一下是可以的。
- 这里留了个伏笔,为什么说不要重启51单片机呢?因为刚才的操作已经保证我们的wifi模块进入透传模式了,你把引脚换一下就能成功。你重新上电之后,网络调试助手有时候能看见透传数据,但更多时候看不到任何透传数据,也就是连接TCP服务器失败了,你没法通过wifi透传对LED进行测试。
- 观察串口调试助手上wifi模块对AT指令的反应结果,以及从网络调试助手上透传来的数据:
3、连接服务器失败的原因:关于指令响应时间的讨论
- 问题剖析:在上述白盒测试中,我们重复给单片机上电,wifi模块大概率会出现连接服务器失败的情况(表现为wifi模块能够联网,但是网络调试助手上没有数据),从而后面的AT指令也自然变成了无效操作。
- 猜测:wifi模块以设备模式联入路由器的过程可能有点久(因为会有一个自动匹配联网—>断网—>再联网的过程),最终造成了还没有联上网之前,单片机就又发送下一条AT指令,导致联入TCP服务器的指令失效。
- 测试:我把wifi模块回到本节1.4的接线方法 ,经过串口调试助手测试,在AT指令还在执行期间,如果继续向wifi模块发送数据(不论是普通数据还是AT指令),wifi模块都会响应busy p...,也就是说这些数据被吞掉了、无视掉了,最后wifi模块没有连上TCP服务器。
- 总结:只留5s时间给wifi模块联网有点稍短,导致连接TCP服务器的AT指令发送后被丢失了,同时每次重新给51单片机上电后,连接wifi的时间是不一样,不能简单用5s时间笼统概括。另外上述白盒测试代码有点残疾,一上电之后只能停留在网络调试助手页面干巴巴地等,没有指示灯告诉我成没成功,到底连不连得上心里也没底,很被动。
4、单片机处理wifi模块响应信息:
- 目的:针对上述连接TCP服务器失败的问题,我们需要考虑给5s时间联网会不会太短,考虑给5s时间联入服务器、设置工作模式、进入透传模式会不会太长。等于说我们需要优化AT指令执行的效率和准确度。
- 总体方法:
- ①跟上节课串口中断点亮LED一样,wifi模块对AT指令的响应信息也是在串口中断程序中完成读取的,此外wifi模块对于从TCP服务器透传来的数据也是在串口中断程序中完成捕获的,这两路数据共用一个接收数据缓冲寄存器SBUF。因此,我们让单片机针对上述各类信息进行关键字眼的检索,一旦检索到关键字眼说明单片机发送或者接收到了一条有效指令(或是失败指令)。
- ②需要定义wifi模块响应AT指令成功的标志位,这些标志位是全局变量,在串口中断程序中读取到了关键字眼,就对这些全局变量进行修改。标志位最终能帮助单片机判断是否发送下一条AT指令,方法是在main函数中用while空循环体进行等待,直到标志位改变为1,才继续往下执行代码,发送下一条AT指令。
- ③肯定存在联网失败的情况(在入网指令书写正确的情况下这是小概率事件)。如果联网失败,我们总不能让程序一直等待吧?答:在串口中断程序中,一旦检测到联网失败的响应信息,就发送一条联网的AT指令(或者重启wifi模块的AT指令),注:可以发送重启wifi模块的AT指令的原因是wifi模块重新上电后会自动匹配上次的wifi,wifi模块又会自动响应联网的信息。
- 确定关键字眼与标志位:在串口中断程序中不方便随手处理的有效指令需要标志位,方便随手处理的有效指令就不需要标志位。当我们读到能标志响应成功的信息时(“WIFI GOT IP”、“OK”),我们就令对应的响应成功标志位为1;当我们读到联网失败的响应信息时(“FAIL”),我们就随手重新发送一条联网的AT指令。
- AT指令响应成功的关键字眼:通过CH340直接测试,观察各种AT指令下wifi模块的响应信息形式(之前已经测试过了)。针对《4.1小节单片机需要做哪些事情》中的那5个AT指令,确定了以下关键字眼和标志位。
配置wifi模块的工作模式 连接上局域网络 连接上TCP服务器 设置透传模式 进入数据待发状态 成功:“OK” 成功:“WIFI GOT IP” & “OK”
注:一上电,自动匹配上wifi时不会响应“OK”
失败:“FAIL”成功:“OK” 成功:“OK” 成功:“OK” 成功标志位: AT_OK_Flag 成功标志位:AT_ConnectLAN_Flag 成功标志位:AT_OK_Flag 成功标志位:AT_OK_Flag 成功标志位:AT_OK_Flag - 透传有效指令的关键字眼:我用首字母'L'代表LED,我观察到在所有的AT指令的响应信息中,字符'L'出现的次数比较少,因此本demo中,我们规定指令"L-1"亮灯,指令"L-0"灭灯。即关键字眼是
灯控指令 亮灯:“L-1” 灭灯:“L-0”
- AT指令响应成功的关键字眼:通过CH340直接测试,观察各种AT指令下wifi模块的响应信息形式(之前已经测试过了)。针对《4.1小节单片机需要做哪些事情》中的那5个AT指令,确定了以下关键字眼和标志位。
- 临时字符变量的引入:对关键字眼读取的前提是让数据缓冲寄存器SBUF读取到的字符串存放在我们定义的字符数组中,我们不希望用上节课那样查找子串的方式来捕捉这些关键字眼,因为有效指令的存放可能因为字符数组的长度限制而只被部分捕获。但是存储的顺序不一定从第0位开始存放(以关键字眼“WIFI GOT IP”为例),如下图,怎么办呢?
- 答:方法是定义一个临时字符变量(temp)。用于检测是否读到字符'W',当检测到字符'W'后就让字符串从头开始存放,这样就能保证有效指令字符串全部保存在字符数组中,同时方便了我们编程。
- 通过硬件来窥探wifi模块配置情况:
- 让wifi模块在成功联入局域网后,D5亮(main函数中对标志位 AT_ConnectLAN_Flag 判断);
- 让wifi模块成功联入服务器后,D6亮(main函数中对标志位 AT_OK_Flag 判断);
- 如果入网失败我们闪烁D5(串口中断程序捕获到关键字眼“FAIL”后随手处理,但实际上中断程序中最好不要有任何耗时操作)。
习题1(优化wifi透传控制LED):【项目工程文件夹】
- 代码心得:
- 本代码的串口中断程序中多出来了一个临时字符变量temp,对temp的判断语句需要放在读取SBUF内容之前,同时要注意串口缓冲寄存器SBUF中的内容被temp读取之后,内容就不存在SBUF中了,所以得用temp来给我们的字符数组赋值。
- 字符数组下标i的偏移语句必须写在判断字符数组是否存满的判断语句前面,不能写在后面,因为我们要保证每次存满之后都从下标0位置开始存放,如果写在后面那么每次都会从下标1位置开始存放。从而可以发现我们预设的字符数组长度为12,实际上只能存放11个字符。
- 思路:
宏定义: 1. 定义符号常量len,用它代表用于接收SBUF中缓冲字符串的全局数组的长度: #define len 12 全局变量: 1. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E; //因为AUXR没有在reg52.h中声明 2. sbit指令找到P3这个I/O口组的第7位P3^7,也就是D5这个LED: sbit ledD5 = P3^7; 3. sbit指令找到P3这个I/O口组的第6位P3^6,也就是D6这个LED: sbit ledD6 = P3^6; 4. “设置wifi模块工作模式的AT指令”: char esp01s_modeSetting[] = "AT+CWMODE=3\r\n"; 5. “设置wifi模块连接网络的AT指令”: code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n"; 6. “设置wifi模块连接TCP服务器的AT指令”: code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.45.201\",8880\r\n"; //5和6都要在前面加个关键词code,因为太长了 7. “设置wifi模块透传模式的AT指令”: char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n"; 8. “设置wifi模块数据传输的AT指令”: char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n"; 9. 定义wifi模块响应AT指令"OK"的标志位: char AT_OK_Flag = 0; //AT_OK_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Config(); 10. 定义wifi模块响应AT指令"WIFI GOT IP"的标志位: char AT_ConnectLAN_Flag = 0; //AT_ConnectLAN_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Config(); 11. 定义一个用于接收串口中缓冲区字符串的全局数组serial_buffer: char serial_buffer[len]; //serial_buffer的传递路线为:SBUF ——> 串口中断(中断4)
1. 一上电先让指示灯D5和D6灭: ledD5 = ledD6 = 1; 2. 调用API1. 初始化串口: UartInit(); 3. 调用API5. 软件延时1s,给wifi模块上电后ready的准备预留时间: Delay1000ms(); 4. 调用API2. 配置wifi模块,进入透传模式: wifiModule_Config(); 5. while死循环,每隔一秒通过串口给PC发送一个字符串,当做心跳包5.1 调用API5,软件延时1s: Delay1000ms();5.2 调用API4,给输出数据缓冲寄存器SBUF发送一条字符串: sendString("hello shuaige\r\n");
中断: 中断4: 封装串口中断的中断服务程序, void Uart_Routine() interrupt 44.1 定义一个静态全局区的静态变量,用来表示数组serial_buffer的下标: static int i = 0;4.2 定义一个临时字符变量temp,用于检测关键字眼,保证我们的字符串是从字符数粗的第0位开始存放的。char temp;4.3 中断处理程序中,对于接收中断的响应,判据是RI == 14.3.1 在接受到1字节数据后,程序复位RI: RI = 0;4.3.2 串口缓冲区接收到的字符先存放在临时变量temp中: temp = SBUF;4.3.3 从数据缓冲寄存器SBUF中读到字符后,根据我们提前设计好的关键字眼,关心四件事:"WIFI GOT IP"的'W'、 "OK"的'O'、 "L-1"的'L',"FAIL"的'F',判据是temp=='W' || temp=='O' || temp=='L' || temp=='F'4.3.3.1 如果是,那么需要从头开始存放: i = 0;4.3.3.2 否则,那么什么也不做,继续往下执行4.3.4 将temp的值保存在数组serial_buffer的第i个元素中:serial_buffer[i] = temp;4.3.5 偏移数组下标: i++;4.3.6 判断字符数组serial_buffer是否存满了,判据是 i == len //内在逻辑:由于serial_buffer长度的限制,当字符串超过len时,我们需要覆盖掉原先的字符4.3.6.1 如果是,那么需要从头开始存放: i = 0;4.3.6.2 否则,那么什么也不做,继续往下执行4.3.7 通过字符数组的第0位和第5位捕捉关键字眼,判断wifi模块是否响应"WIFI GOT IP",判据是serial_buffer[0]=='W' && serial_buffer[5]=='G'4.3.7.1 如果是,令标志位为1: AT_ConnectLAN_Flag = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.7.2 否则,那么什么也不做,继续往下执行4.3.8 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"OK",判据是serial_buffer[0]=='O' && serial_buffer[1]=='K'4.3.8.1 如果是,令标志位为1: AT_OK_Flag = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.8.2 否则,那么什么也不做,继续往下执行4.3.9 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"FAIL",判据是serial_buffer[0]=='F' && serial_buffer[1]=='L'4.3.9.1 如果是,用i当做循环变量,使用for循环语句闪烁D5五次,亮一秒,灭一秒再次调用API4,通过串口发送对应入网的AT指令: sendString(esp01s_connectLAN);有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.9.2 否则,那么什么也不做,继续往下执行4.3.10 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否收到透传数据"L-1",判据是serial_buffer[0]=='L' && serial_buffer[2]=='1'4.3.10.1 如果是,点亮D5: ledD5 = 0;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.10.2 否则,如果wifi模块收到透传数据"L-0"熄灭D5: ledD5 = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.4 中断处理程序中,对于发送中断的响应,判据是TI == 1暂时不做任何事情
/* 一级函数:f1、f2、f4、f5 */ f1. 封装初始化串口的API: void UartInit(void);f1.1 禁用ALE信号: AUXR = 0X01;f1.2 让串口以方式1工作(8位UART,可变波特率),并允许串口接收: SCON = 0x50;f1.3 让定时器1以8位重载工作模式工作:TMOD &= 0xDF;TMOD |= 0x20;f1.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:TH1 = 0xFD;TL1 = 0xFD;f1.5 定时器开始数数: TR1 = 1;f1.6 开启串口中断:EA = 1;ES = 1; f2. 封装配置wifi模块以客户端模式工作的API: void wifiModule_Config();f2.1 配置wifi模块工作模式为双模式(设备模式+路由模式):调用API4,通过串口发送对应AT指令: sendString(esp01s_modeSetting);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.2 配置wifi模块,进行入网设置:调用API4,通过串口发送对应AT指令: sendString(esp01s_connectLAN);空循环体等待,直到wifi模块响应"WIFI GOT IP": while(!AT_ConnectLAN_Flag);为了不影响下一个条指令响应,复位标志位: AT_ConnectLAN_Flag = 0;空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.3 配置wifi模块,连接TCP服务器:调用API4,通过串口发送对应AT指令: sendString(esp01s_connectTCPServer);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.4 配置wifi模块,设置成透传模式:调用API4,通过串口发送对应AT指令: sendString(esp01s_serialNet_mode);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.5 配置wifi模块,进入数据待发状态,正式进入透传模式:调用API4,通过串口发送对应AT指令: sendString(esp01s_dataSend_waiting);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag); //不复位,留给后面判断f2.6 测试代码,经COM3这个端口发给PC上的串口助手看: sendString("wifi Module setting success!\r\n");f2.7 测试代码,如果AT_OK_Flag==1,就点亮D6,代表成功: ledD6 = 0; f4. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址f4.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;f4.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送f4.2.1 通过指针间接访问字符串字符,再调用API3. 发送单个字符: sendByte(*p);f4.2.2 修改循环变量p的值,让指针p偏移: p++; f5. 封装软件延时1s的API,用于每隔1秒发送心跳包,以及串口初始化后的短暂休眠: void Delay1000ms();
/* 二级函数:f3 */ f3. 封装定时给PC发送一个字符的API: void sendByte(char data_msg); //形参是字符值f3.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;f3.2 根据串口发送中断触发位TI,利用空循环体暂停程序: while(TI == 0);f3.3 程序复位TI: TI = 0;
- 代码:
#include "reg52.h" #include "intrins.h" #include <string.h>#define len 12 sfr AUXR = 0x8E; sbit ledD5 = P3^7; sbit ledD6 = P3^6; char esp01s_modeSetting[] = "AT+CWMODE=3\r\n"; //配置wifi模块的工作模式 // code char esp01s_connectLAN[] = "AT+CWJAP=\"CMCC-XyVF\",\"XyVFVsrz\"\r\n"; //连接上局域网络 // code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.1.7\",8880\r\n"; //连接上TCP服务器 code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n"; //连接上局域网络 code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.45.201\",8880\r\n";//连接上TCP服务器 char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n"; //设置透传模式 char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n"; //进入数据待发状态,正式进入透传模式 // char esp01s_reset[] = "AT+RST\r\n"; //wifi连接失败重启wifi模块 char AT_OK_Flag = 0; //关键字眼OK的标志位,用1代表AT指令响应成功,0代表AT指令响应失败 char AT_ConnectLAN_Flag = 0; //关键字眼WIFI GOT IP的标志位,用1代表AT指令响应成功,0代表AT指令响应失败 char serial_buffer[len];/* API1. 初始化串口 */ void UartInit(void); /* API2. 配置wifi模块以客户端模式工作,进入透传模式 */ void wifiModule_Client_Config(); /* API3. 通过串口给PC发送一个字符 */ void sendByte(char data_msg); /* API4. 通过串口给PC发送一个字符串 */ void sendString(char *str); /* API5. 用于每隔1秒给wifi模块发送心跳包,以及串口初始化后的短暂休眠 */ void Delay1000ms();void main(void) {ledD5 = ledD6 = 1;UartInit();Delay1000ms(); //给wifi模块上电后ready的准备时间wifiModule_Client_Config();while(1){Delay1000ms();sendString("hello shuaige\r\n"); //心跳包} }void Uart_Routine() interrupt 4 {static int i = 0; //静态全局区的变量,数组下标char temp;/* 中断处理程序中,对于接收中断的响应 */if(RI == 1){ RI = 0;//清除接收中断标志位temp = SBUF;if(temp=='W' || temp=='O' || temp=='L' || temp=='F'){ //从数据缓冲寄存器SBUF中读到字符后,关心四件事i = 0;}serial_buffer[i] = temp;i++;if(i == len){i = 0;}//wifi模块响应值的判断if(serial_buffer[0]=='W' && serial_buffer[5]=='G'){AT_ConnectLAN_Flag = 1;memset(serial_buffer, '\0', len);}if(serial_buffer[0]=='O' && serial_buffer[1]=='K'){AT_OK_Flag = 1;memset(serial_buffer, '\0', len);}if(serial_buffer[0]=='F' && serial_buffer[1]=='A'){for(i=0; i<5; i++){ //测试:入网失败后闪灯,并发送联网指令ledD5 = 0;Delay1000ms();ledD5 = 1;Delay1000ms();}// sendString(esp01s_reset);sendString(esp01s_connectLAN);memset(serial_buffer, '\0', len);}//灯控指令if(serial_buffer[0]=='L' && serial_buffer[2]=='1'){ledD5 = 0;memset(serial_buffer, '\0', len);}else if(serial_buffer[0]=='L' && serial_buffer[2]=='0'){ledD5 = 1;memset(serial_buffer, '\0', len);}}/* 中断处理程序中,对于发送中断的响应 */if(TI == 1){// 暂时不做任何事情} }void UartInit(void) //9600bps@11.0592MHz {AUXR = 0x01;SCON = 0x50; //8位UART,允许串口接收TMOD &= 0xDF;TMOD |= 0x20; //定时器8位重载工作模式TH1 = 0xFD;TL1 = 0xFD; //9600波特率初值TR1 = 1;EA = 1;ES = 1; //开启串口中断 }void wifiModule_Client_Config() {/* 经过白盒测试以及优化后,以下AT指令只需要发送一遍所以不需要反复配置,暂停程序直到AT指令响应成功 */sendString(esp01s_modeSetting);while(!AT_OK_Flag); //等待,直到wifi模块响应OKAT_OK_Flag = 0;sendString(esp01s_connectLAN);while(!AT_ConnectLAN_Flag); //等待,直到wifi模块响应WIFI GOT IPledD5 = 0; //测试,点亮D5,代表入网成功while(!AT_OK_Flag); //等待,直到wifi模块响应OKAT_OK_Flag = 0;sendString(esp01s_connectTCPServer);while(!AT_OK_Flag); //等待,直到wifi模块响应OKAT_OK_Flag = 0;sendString(esp01s_serialNet_mode);while(!AT_OK_Flag); //等待,直到wifi模块响应OKAT_OK_Flag = 0;sendString(esp01s_dataSend_waiting);while(!AT_OK_Flag); //等待,直到wifi模块响应OKsendString("wifi Module setting success!\r\n"); //测试,经COM3这个端口发给PC上的串口助手看if(AT_OK_Flag == 1){ledD6 = 0; //测试,点亮D6,代表连接服务器并打开透传模式} }void sendByte(char data_msg) {SBUF = data_msg;while(TI == 0);TI = 0; }void sendString(char *str) {char *p = str;while(*p != '\0'){sendByte(*p);p++;} }void Delay1000ms() //@11.0592MHz {unsigned char i, j, k;_nop_();i = 8;j = 1;k = 243;do{do{while (--k);} while (--j);} while (--i); }
5、白盒调试跟踪代码:优化代码写完之后,再按照白盒测试的套路进行调试,跟踪代码执行过程。
我们这个代码烧进去之后,由于51单片机的RX引脚没有接在wifi模块的TX引脚上,所以51单片机从接收缓冲寄存器SBUF中接收不到任何数据。所以白盒测试中的串口助手(COM6端口:CH340和wifi模块的串口)一定会卡着。所以需要我们在另一个串口助手(COM3端口:51单片机和PC的串口)中手动模拟wifi模块本来应该响应给51单片机的数据。最后测试成功后,就可以认为代码是可靠的了,可以烧录进单片机中。
注:白盒测试下不能够用透传来测试wifi控制LED,也得用串口手动模拟。
5.ESP-01S当做服务器
1、数据传输路线:前面4小节我们都是让wifi以客户端模式进行工作。我们接下来在PC上用网络调试助手构建一个基于TCP协议的客户端。也是给wifi模块发送AT指令,让ESP8266在路由模式下以TCP服务器模式工作。然后让PC上的TCP客户端联入wifi模块构建的局域网。这个时候ESP8266和外部的通讯方式也分为两类。
- 走串口:wifi模块和PC走串口协议,PC通过USB转TTL的CH340驱动,将AT指令传送给wifi模块,然后wifi模块响应一些配置信息给PC。
- 走wifi:wifi模块和PC也可以走网络传输层中的TCP协议,这时候PC和wifi模块是连接在同一个局域网下的,wifi模块作为服务器,PC作为客户端。数据可以从TCP客户端传递到wifi模块。数据也可以从wifi模块透传到TCP客户端。
2、串口测试时的AT指令及响应(测试截图略,总的响应信息见5.3中的图):
- wifi模块工作模式设置:AT+CWMODE=3(也可以写成AT+CWMODE=2)
wifi模块联网:AT+CWJAP="HUAWEI P20","abcdefgh"(这种模式下是路由模式不需要联网)- 查阅wifi模块以AP模式工作时的网关:AT+CIFSR
- 目的是用于确认服务器的IP地址(就是wifi模块的网关,有的网络调试助手上会写成远程主机地址,一个意思),将它写在网络调试助手的对应位置。
- 也需要在命令提示符中,明确当前PC的IPV4地址,输入网络调试助手中。
- 配置wifi模块成使能多链接:AT+CIPMUX=1
- 配置成功,wifi模块会响应“OK”
- 配置失败,wifi模块会响应“ERROR”
- 如果再次发送该AT指令,wifi模块会响应“OK”
- 如果在wifi模块已经与TCP客户端建立连接的前提下再次发送该AT指令,wifi模块会响应“link is builded”、“ERROR”
- 让wifi模块建立TCP服务器(端口号默认是333):AT+CIPSERVER=1
- 建立成功,wifi模块会响应“OK”
- 建立失败,wifi模块会响应“ERROR”
- 建立成功后,重复发送上述AT指令,wifi模块会响应“no change”、“OK”
- 在PC上的网络调试助手上配置好之后,网络调试助手上点击连接后,如果连接成功,wifi模块会响应“0,CONNECT”(逗号后无空格,这里0代表连接的通道,一个IP地址对应一个通道,第1个联入的IP地址的通道是0);
- 网络调试助手上点击连接后,如果连不上,可能的原因是你的PC还没有联入wifi模块构建的局域网(我的8266构建的局域网的名字是ESP_28DCAB),或者网络调试助手出问题了,没有成功建立TCP客户端(换一个)。
- TCP客户端和wifi模块成功建立连接后,如果在网络调试助手上点击断开后,也就是TCP客户端断开连接时,wifi模块会响应“0,CLOSED”(逗号后无空格)。
- 让wifi模块进入数据待发状态:AT+CIPSEND=0,n (0是通道号、n是待发数据字节数,逗号后无空格)
- 当你数据手动发送成功后,wifi模块会响应“Recv n bytes”、“SEND OK”
- 如果在没有与TCP客户端建立连接时就立马发送上述AT指令,wifi模块会响应“link is not valid”、“ERROR”
- 让wifi模块主动断开TCP连接(用的很少):AT+CIPCLOSE=0 (0是通道号)
- 在TCP客户端和wifi模块成功建立连接的前提下,发送上述指令并断开后,wifi模块会响应“0,CLOSED”、“OK” (0是通道号,逗号后无空格)。同时网络调试助手上会显示未连接的状态。
- 重复发送断开的AT指令,也就是TCP服务器和客户端已经断开的状态下继续发送该AT指令,wifi模块会响应“UNLINK”、“ERROR”。
- 注意上述指令只是让wifi模块断开TCP连接,并不是取消构建TCP服务器,下次连接时,仍然还是由TCP客户端发起的。
3、与客户端的数据交互:
- 接收客户端发来的数据:让wifi模块配置到上述第5步(建立TCP服务器)就能接收TCP客户端发送来的数据了。
- 但实际上这还不是透传模式。
- 接收的数据具有固定的格式:“+IPD,0,n:xxx”(逗号,冒号后均无空格),其中+IPD是固定字符串,0是连接的通道,n是数据的字节数,xxx才是从客户端透传来的数据
- 往客户端透传数据:需要让wifi模块配置到上述第6步(进入数据待发状态),才正式进入透传模式。
- 透传时尽量不要发送新行,因为新行占两个字节。
- 发送完毕后会自动退出透传模式。
- 如果发送的字节数超出了n字节数,wifi模块先会响应“busy s...”,再响应“Recv n bytes”、“SEND OK”,同时只会截取前面的字节发送。
- 如果你发送的数据长度不足n字节,wifi模块不会有任何响应,同时网络调试助手上看不到任何发送来的数据,这个数据就好像一直存在“缓冲区”中,只有你达到或者超过了设定的n个字节数,才会显示。
4、单片机配置服务器模式:这里的步骤和4.1~4.5一致。
- 确认单片机要发送的AT指令:终端设备是单片机(MCU),让单片机通过串口给模块发送一系列AT指令,指挥wifi模块以服务器模式工作。
重启wifi模块- 配置wifi模块的工作模式(如果之前设置过的话,这一步wifi模块会自动完成)
- 设置成使能多链接模式
- 建立TCP服务器
- 进入数据待发状态,正式进入透传模式(发送完毕后会自动退回到AT指令模式)
波特率是提前用电脑通过AT指令给wifi模块配置好的,用9600。
- 单片机处理wifi模块响应信息:
AT指令响应成功的关键字眼及标志位 配置wifi模块为路由模式 使能多链接 建立TCP服务器 与TCP客户端建立连接 进入数据待发状态(5字节、通道0) 成功:“OK” 成功:“OK” 成功:“OK” 成功:“0,CONNECT” 成功:“OK” 成功标志位: AT_OK_Flag 成功标志位: AT_OK_Flag 成功标志位: AT_OK_Flag 成功标志位: Client_Connect_Flag 不检测 透传有效指令的关键字眼 灯控指令 亮灯:“:op”
灭灯:“:cl”- 对于进入数据待发状态的AT指令来说,不要去检测“OK”,因为我们要在main函数的while(1)循环中把这个指令用于给TCP客户端发送心跳包(通过串口也能看见发送的心跳包),如果说以后一旦检测不到OK了(比如说TCP客户端主动与TCP服务器断开连接),那么程序就会卡死在while(1)循环中,那么就连串口上也无法监测到心跳包,会让我们误以为单片机死掉了了。
- 我在用白盒测试跟踪代码运行情况时,灯控指令设置成了“:L-1”和“:L-0”,出现了大问题:①第一个OK需要发送两次才能被接收到,②后面发送0,CONNECT也很难被接收到,③即使与TCP客户端建立起了连接,灯控指令也不能正常工作。经过我不断的调试(心累,其实从问题描述中也能看出),终于发现了问题所在:灯控指令不能设置成“:L-1”和“:L-0”。所以我选用了“:op”与“:cl”。
- 通过硬件来窥探wifi模块配置情况:
- 让wifi模块在成功建立TCP服务器后,D5亮(main函数中对标志位 AT_OK_Flag 判断);
- 让wifi模块与客户端建立连接后,D6亮(main函数中对标志位 Client_Connect_Flag 判断);
- 白盒测试跟踪代码:代码见习题2。注意白盒测试下不能够测试wifi控制LED,也得用串口手动模拟。
习题2(wifi模块作为服务器):在习题1的代码上做适当修改【项目工程文件夹】
- 思路:
宏定义: 1. 定义符号常量len,用它代表用于接收SBUF中缓冲字符串的全局数组的长度: #define len 12 全局变量: 1. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E; //因为AUXR没有在reg52.h中声明 2. sbit指令找到P3这个I/O口组的第7位P3^7,也就是D5这个LED: sbit ledD5 = P3^7; 3. sbit指令找到P3这个I/O口组的第6位P3^6,也就是D6这个LED: sbit ledD6 = P3^6; 4. “设置wifi模块工作模式的AT指令”: char esp01s_modeSetting[] = "AT+CWMODE=2\r\n"; 5. “设置wifi模块使能多链接的AT指令”: char esp01s_multiLink[] = "AT+CIPMUX=1\r\n"; 6. “设置wifi模块建立TCP服务器的AT指令”: char esp01s_setTCPServer[] = "AT+CIPSERVER=1\r\n"; 7. “设置wifi模块数据传输的AT指令”: char esp01s_dataSend_waiting[] = "AT+CIPSEND=0,5\r\n"; 8. 定义wifi模块响应AT指令"OK"的标志位: char AT_OK_Flag = 0; //AT_OK_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Server_Config(); 9. 定义wifi模块响应AT指令"0,CONNECT"的标志位: char Client_Connect_Flag = 0; //Client_Connect_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Server_Config(); 10. 定义一个用于接收串口中缓冲区字符串的全局数组serial_buffer: char serial_buffer[len]; //serial_buffer的传递路线为:SBUF ——> 串口中断(中断4)——> 临时字符变量temp
1. 一上电先让指示灯D5和D6灭: ledD5 = ledD6 = 1; 2. 调用API1. 初始化串口: UartInit(); 3. 调用API5. 软件延时1s,给wifi模块上电后ready的准备预留时间: Delay1000ms(); 4. 调用API2. 配置wifi模块以TCP服务器模式工作: wifiModule_Server_Config(); 5. while死循环,每隔4秒以两种方式(串口和透传)给PC发送一个字符串,当做心跳包5.1 调用API4,通过串口发送数据传输的AT指令: sendString(esp01s_0Channel_dataSend);5.2 调用API5,软件延时2s: Delay1000ms();Delay1000ms();5.3 调用API4,给输出数据缓冲寄存器SBUF发送一条字符串: sendString("hello");5.2 调用API5,软件延时2s: Delay1000ms();Delay1000ms();
中断: 中断4: 封装串口中断的中断服务程序, void Uart_Routine() interrupt 44.1 定义一个静态全局区的静态变量,用来表示数组serial_buffer的下标: static int i = 0;4.2 定义一个临时字符变量temp,用于检测关键字眼,保证我们的字符串是从字符数粗的第0位开始存放的。char temp;4.3 中断处理程序中,对于接收中断的响应,判据是RI == 14.3.1 在接受到1字节数据后,程序复位RI: RI = 0;4.3.2 串口缓冲区接收到的字符先存放在临时变量temp中: temp = SBUF;4.3.3 从数据缓冲寄存器SBUF中读到字符后,根据我们提前设计好的关键字眼,关心四件事:"OK"的'O'、 "0,CONNECT"的'0'、 "+IPD,0,n:op"的':',判据是temp=='O' || temp=='0' || temp==':'4.3.3.1 如果是,那么需要从头开始存放: i = 0;4.3.3.2 否则,那么什么也不做,继续往下执行4.3.4 将temp的值保存在数组serial_buffer的第i个元素中:serial_buffer[i] = temp;4.3.5 偏移数组下标: i++;4.3.6 判断字符数组serial_buffer是否存满了,判据是 i == len //内在逻辑:由于serial_buffer长度的限制,当字符串超过len时,我们需要覆盖掉原先的字符4.3.6.1 如果是,那么需要从头开始存放: i = 0;4.3.6.2 否则,那么什么也不做,继续往下执行4.3.7 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"OK",判据是serial_buffer[0]=='O' && serial_buffer[1]=='K'4.3.7.1 如果是,令标志位为1: AT_OK_Flag = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.7.2 否则,那么什么也不做,继续往下执行4.3.8 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否响应"0,CONNECT",判据是serial_buffer[0]=='0' && serial_buffer[2]=='C'4.3.8.1 如果是,令标志位为1: Client_Connect_Flag = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.8.2 否则,那么什么也不做,继续往下执行4.3.9 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否收到透传数据"+IPD,0,n:open",判据是serial_buffer[0]==':' && serial_buffer[1]=='o' && serial_buffer[2]=='p'4.3.9.1 如果是,点亮D5: ledD5 = 0;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.3.9.2 否则,如果wifi模块收到透传数据"+IPD,0,n:close"熄灭D5: ledD5 = 1;有效指令后清空字符数组: memset(serial_buffer,'\0',len);4.4 中断处理程序中,对于发送中断的响应,判据是TI == 1暂时不做任何事情
/* 一级函数:f1、f2、f4、f5 */ f1. 封装初始化串口的API: void UartInit(void);f1.1 禁用ALE信号: AUXR = 0X01;f1.2 让串口以方式1工作(8位UART,可变波特率),并让REN使能允许串口接收: SCON = 0x50;f1.3 让定时器1以8位重载工作模式工作:TMOD &= 0xDF;TMOD |= 0x20;f1.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:TH1 = 0xFD;TL1 = 0xFD;f1.5 定时器开始数数: TR1 = 1;f1.6 开启串口中断:EA = 1;ES = 1; f2. 封装配置wifi模块以客户端模式工作的API: void wifiModule_Server_Config();f2.1 配置wifi模块工作模式为路由模式:调用API4,通过串口发送对应AT指令: sendString(esp01s_modeSetting);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.2 配置wifi模块,使能多链接模式:调用API4,通过串口发送对应AT指令: sendString(esp01s_multiLink);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;f2.3 配置wifi模块,建立TCP服务器:调用API4,通过串口发送对应AT指令: sendString(esp01s_setTCPServer);空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;测试代码:点亮D5,代表成功建立TCP服务器: ledD5 = 0;f2.4 空循环体等待,直到wifi模块响应"0,CONNECT": while(!Client_Connect_Flag); //不复位,留给后面判断测试代码,如果Client_Connect_Flag==1,就点亮D6,代表成功与客户端建立链接: ledD6 = 0; f4. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址f4.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;f4.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送f4.2.1 通过指针间接访问字符串字符,再调用API3. 发送单个字符: sendByte(*p);f4.2.2 修改循环变量p的值,让指针p偏移: p++; f5. 封装软件延时1s的API,用于每隔1秒发送心跳包,以及串口初始化后的短暂休眠: void Delay1000ms();
/* 二级函数:f3 */ f3. 封装定时给PC发送一个字符的API: void sendByte(char data_msg); //形参是字符值f3.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;f3.2 根据串口发送中断触发位TI,利用空循环体暂停程序: while(!TI);f3.3 程序复位TI: TI = 0;
- 代码:
#include "reg52.h" #include "intrins.h" #include <string.h>#define len 12 sfr AUXR = 0x8E; sbit ledD5 = P3^7; sbit ledD6 = P3^6; char serial_buffer[len]; char esp01s_modeSetting[] = "AT+CWMODE=2\r\n"; //配置wifi模块的工作模式 char esp01s_multiLink[] = "AT+CIPMUX=1\r\n"; //使能多链接 char esp01s_setTCPServer[] = "AT+CIPSERVER=1\r\n"; //建立TCP服务器 char esp01s_0Channel_dataSend[] = "AT+CIPSEND=0,5\r\n"; //发送5个字节数据在连接0通道上 char AT_OK_Flag = 0; //关键字眼OK的标志位,用1代表AT指令响应成功,0代表AT指令响应失败 char Client_Connect_Flag = 0; //关键字眼0,CONNECT的标志位,用1代表客户端接入成功,0代表客户端接入失败/* API1. 初始化串口 */ void UartInit(void); /* API2. 配置wifi模块以服务器模式工作 */ void wifiModule_Server_Config(); /* API3. 通过串口给PC发送一个字符 */ void sendByte(char data_msg); /* API4. 通过串口给PC发送一个字符串 */ void sendString(char *str); /* API5. 用于每隔1秒给wifi模块发送心跳包,以及串口初始化后的短暂休眠 */ void Delay1000ms();void main() {ledD5 = ledD6 = 1;//灭状态灯UartInit();Delay1000ms();//给espwifi模块上电时间wifiModule_Server_Config();while(1){sendString(esp01s_0Channel_dataSend);Delay1000ms(); //虽然说发送也是需要时间,但是不要用检测“OK”来暂停程序Delay1000ms();sendString("Hello"); //服务器给客户端的心跳包(串口助手上也能看见)Delay1000ms();Delay1000ms();} }void Uart_Routine() interrupt 4 {static int i = 0;//静态变量,被初始化一次char temp;/* 中断处理程序中,对于接收中断的响应 */if(RI)//中断处理函数中,对于接收中断的响应{RI = 0;//清除接收中断标志位temp = SBUF;if(temp == 'O' || temp == '0' || temp == ':'){i = 0;}serial_buffer[i] = temp;i++;if(i == len) i = 0;//wifi模块响应值的判断if(serial_buffer[0] == 'O' && serial_buffer[1] == 'K'){AT_OK_Flag = 1;memset(serial_buffer, '\0', len);}if(serial_buffer[0] == '0' && serial_buffer[2] == 'C'){Client_Connect_Flag = 1;memset(serial_buffer, '\0', len);}//灯控指令if(serial_buffer[0] == ':' && serial_buffer[1] == 'o' && serial_buffer[2]=='p'){ledD5 = 0;//点亮D5memset(serial_buffer, '\0', len);}else if(serial_buffer[0] == ':' && serial_buffer[1] == 'c' && serial_buffer[2]=='l'){ledD5 = 1;//熄灭D5memset(serial_buffer, '\0', len);}}/* 中断处理程序中,对于发送中断的响应 */if(TI == 1){// 暂时不做任何事情} }void UartInit(void) //9600bps@11.0592MHz {AUXR = 0x01;SCON = 0x50; //8位UART,允许串口接收TMOD &= 0xDF;TMOD |= 0x20; //定时器8位重载工作模式TH1 = 0xFD;TL1 = 0xFD; //9600波特率初值TR1 = 1; //启动定时器EA = 1;ES = 1; //开启串口中断 }void wifiModule_Server_Config() {sendString(esp01s_modeSetting);while(!AT_OK_Flag);AT_OK_Flag = 0;sendString(esp01s_multiLink);while(!AT_OK_Flag);AT_OK_Flag = 0;sendString(esp01s_setTCPServer);while(!AT_OK_Flag);AT_OK_Flag = 0; ledD5 = 0; //点亮D5,代表成功建立TCP服务器while(!Client_Connect_Flag);if(Client_Connect_Flag){ledD6 = 0; //点亮D6,代表有客户端接入} }void sendByte(char data_msg) {SBUF = data_msg;while(!TI);TI = 0; }void sendString(char *str) {char *p = str;while(*p != '\0'){sendByte(*p);p++;} }void Delay1000ms() //@11.0592MHz {unsigned char i, j, k;_nop_();i = 8;j = 1;k = 243;do{do{while (--k);} while (--j);} while (--i); }
相关文章:

串口与wifi模块
经过以下学习,我们掌握: AT指令与wifi模块的测试方法:通过CH340直接测试,研究各种AT指令下wifi模块的响应信息形式。编程,使用串口中断接收wifi模块对AT指令的响应信息以及透传数据,通过判断提高指令执行的…...

上财黄烨:金融科技人才的吸引与培养
“金融科技企业在吸引人才前,应先完善人才培养机制,建立员工画像,有针对性地培训提高成员综合素质。” ——上海金融智能工程技术研究中心上海财经大学金融科技研究院秘书长&院长助理黄烨老师 01.何为数字人才? 目前大多数研…...

利用MQ事务消息实现分布式事务
MQ事务消息使用场景 消息队列中的“事务”,主要解决的是消息生产者和消息消费者的数据一致性问题。 拿我们熟悉的电商来举个例子。一般来说,用户在电商 APP 上购物时,先把商品加到购物车里,然后几件商品一起下单,最后…...
C++面向对象设计:深入理解多态与抽象类实现技巧
面向对象的多态 一、概念二、实现1. 静态多态1.1 函数重载1.2 运算符重载 2. 动态多态2.1 虚函数2.2 纯虚函数 三、虚函数1. 定义2. 实现3. 注意 四、纯虚函数1. 定义2. 作用 五、虚析构函数1. 定义2. 作用 六、 抽象类七、实现多态的注意事项1. 基类虚函数必须使用 virtual 关…...

长三角生物医药产业加速跑,飞桨螺旋桨为创新药企、医药技术伙伴装上AI大模型引擎...
生物医药是国家“十四五”规划中明确的战略性新兴产业之一。长三角地区是中国生物医药产业的排头兵,也是《“十四五”生物经济发展规划》的“生物经济先导区”之一。据《上海市生物医药产业投资指南》显示,2022 年上海市生物医药产业在 I 类国产创新药数…...

orin Ubuntu 20.04 配置 Realsense-ROS
librealsense安装 sudo apt-get install libudev-dev pkg-config libgtk-3-dev sudo apt-get install libusb-1.0-0-dev pkg-config sudo apt-get install libglfw3-dev sudo apt-get install libssl-dev sudo apt-get install ros-noetic-ddynamic-reconfigure二进制安装libr…...

MyBatis基础知识点总结
MyBatis了解 MyBatis 是什么? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架 MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集 MyBatis 可以使用简单的XML或注解用于配置和原始映射,将接口和Java的 POJO&#x…...

校园企业车辆维修报修管理系统设计与开发
本研究课题重点主要包括了下面几大模块:在本基于.net平台的车辆系统中分为管理员和用户2个模块,主要功能包括管理员信息管理,车辆信息管理,驾驶员信息管理,事故信息管理,维修信息管理,维修点管理…...

【企业信息化】第1集 免费开源ERP: Odoo 16 CRM客户关系管理系统
文章目录 前言一、概览二、使用功能1.加快销售速度2.销售线索3.机会4.客户5.高效沟通6.报告7.集成 三、总结 前言 世界排名第一的免费开源ERP: Odoo 16 CRM客户关系管理系统。真正以客户为中心的CRM。 一、概览 获得准确预测 使用可操作数据,以做出更好的决定。 获…...
Flink创建Hudi的Sink动态表
工厂类 HoodieTableFactory 提供的创建动态表接口 createDynamicTableSource 和 createDynamicTableSink,对应的源码文件为:https://github.com/apache/hudi/blob/master/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/table/HoodieT…...
人脸识别技术的安全性及其应用探讨
随着科技的不断发展,人脸识别技术已经成为了一个热门话题。人脸识别系统的出现,给人们的生活带来了极大的便利,同时也为一些犯罪分子提供了方便。因此,人脸识别技术的安全性和可靠性一直备受关注。 一、人脸识别技术的原理 人脸识…...

老域名查询工具- 在线域名批量查询工具
域名批量查询工具 域名批量查询工具是一种帮助用户快速查询多个域名信息的工具,通常能够自动扫描一组域名的WHOIS信息、DNS、IP地址、服务器等各种信息,并提供快速的结果反馈。 以下是域名批量查询工具主要的优点: 提高工作效率:…...

JimuReport - 积木报表(一款免费Web报表工具)
一款免费的数据可视化报表,含报表和大屏设计,像搭建积木一样在线设计报表!功能涵盖,数据报表、打印设计、图表报表、大屏设计等! Web 版报表设计器,类似于excel操作风格,通过拖拽完成报表设计。…...

01-数据操作+数据预处理
1.n维数组,也称为张量(tensor):tensor和ndarray没有本质区别。tensor是有数学上的严格定义,ndarray是计算机描述的;张量表示一个由数值组成的数组,这个数组可能有多个维度; 无论使用…...

macOS本地python环境/vscode/导入python包/设置python解释器
查看macbook本地是否有python环境 输入python或者python3,退出python环境使用exit(),别忘了括号 没有的话去官网安装https://www.python.org/ 2. 安装vscode 官网https://code.visualstudio.com/ 3. 安装插件 点击左边的“插件”按钮,安装…...

【转存】Go语言设计模式
导语| 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题,提高开发效率࿰…...
第十一章 升级与定制
第十一章 升级与定制 一、 RPM 包安装操作 RPM(Redhat Packet Manager)。 ①安装 rpm –i rpm 文件名 (注:⑴常见用法:-ivh 参数显示安装过程和 hash 符#; ⑵覆盖安装:使用- -force 选项。…...
代码随想录算法训练营第二十二天|235. 二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点
目录 235. 二叉搜索树的最近公共祖先 1、递归实现 2、迭代法实现 701.二叉搜索树中的插入操作(递归实现) 450.删除二叉搜索树中的节点(递归实现) 235. 二叉搜索树的最近公共祖先 相对于 二叉树的最近公共祖先 本题就简单一些了…...
hbase表出现RIT删除方案
1.删除zookeeper中对应表注册信息 cd /opt/cloudera/parcels/CDH/lib/zookeeper/bin ./zkCli.sh -server node2:2181 --node2为仿真节点,生产需改 deleteall /hbase/table/表名 2.删除hdfs对应表数据 hadoop dfs -rm -r /hbase/data/default/表名 3.删除hbase:met…...
SQL学习(3)
SELECT 语句用于从表中选取数据。 SELECT 列名称 FROM 表名称 SELECT * FROM 表名称关键词 DISTINCT 用于返回唯一不同的值 SELECT DISTINCT 列名称 FROM 表名称WHERE 子句用于规定选择的标准 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句。 S…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...