物联网实战--入门篇之(六)嵌入式-WIFI驱动(ESP8266)
目录
一、WIFI简介
二、基础网络知识
三、思路讲解
四、代码分析
4.1 状态机制
4.2 客户端连接
4.3 应用数据接收处理
4.4 数据发送
4.5 主函数调用
4.6 网络连接ID分配
五、总结
一、WIFI简介
WIFI在我们生活中太常见了,手机电脑都可以用WiFi连接路由器进行上网,那么在单片机领域又是基于什么物理器件联网的呢?最常见的WIFI模块是ESP8266,以及性能更好的ESP32,还有比较新的BL602等等,种类比较多,那么我们净化器这个项目选择的是成熟稳定且便宜的ESP8266。它可以标准模式下连接路由器,自身也可以作为热点供别人连接,性能还是很强悍的。
ESP8266文档中心在这儿ESP8266文档中心 | 安信可科技,在这里我们采用AT指令的方式对齐进行驱动,具体文档可以按下图方式下载。AT指令是一个比较规范的底层通讯协议,也没什么神秘的,就是一个比较固定的格式,AT+具体指令=参数 这种模式,AT指令的好处是简单易懂,字符串的形式比较明了,对应的缺点就是没有很灵活,要根据输出内容处理字符串信息,有时候返回的信息不充分或者不完整,对开发人员的程序稳定性有一定的考验。
二、基础网络知识
这里简要说明下网络的基础知识,主要协议分为TCP和UDP,TCP是比较可靠的连接,数据包会有重发机制,发送方没收到确认就会重新发送,而UDP就不管那么多了,按照目标地址发过去就是了,有没有收到就不管了。
通常要连接一个服务器需要的信息有目标服务器的IP地址、要连接的端口以及所使用的协议三个,如下图所示。其中IP地址也可以用域名代替,这样模块内部就是需要多个步骤把域名发往域名服务器解析成具体的IP地址;目标端口就是一个数值,服务器需要打开这个端口客户端才能连接成功和发送数据,否则会一直连接错误;协议就是上面所说的TCP和UDP了,我们这里一般都是使用TCP的,后面会讲解的MQTT是基于TCP连接的,用UDP的也有,比如NB-Iot的Coap协议。
基本的网络知识就这样了,没有很复杂,会用就行;如果要深入整个网络知识体系,那就学海无涯了,一本TCP/IP协议知识的书比枕头还厚,个人学习推荐LWIP。
三、思路讲解
既然是驱动程序必然要有比较好的通用性和移植性。ESP8266的基本使用流程是配置WIFI模式以及SSID和密码,然后等到模块连接到指定的路由器上;连接完成后再进行网络方面的设置,比如可以多连接、非透传模式和TCP服务器的建立等等,AT手册里有很多,不一定全用,根据自己的需求增删;最后就是根据应用层的目标服务器信息进行连接和收发数据了。
整体来讲逻辑不会很复杂,但是细节很多。比如:
1、ESP8266主体流程要怎么运行,这个过程最好不要有阻塞(就是延时了),这样会影响其它部分代码的运行;
2、WIFI的名称和密码以及热点名称和密码要如何设置,保存方式下比较耗时的;
3、如何确定当前的网络状态以及不同的状态要执行什么动作,比如WIFI突然断开了怎么办;
4、作为TCP客户端连接时候,如何确保连接成功,并且不会重复连接;
5、如何解决TCP本质上已经断开了,但是模块没有提示的问题,即假连接,此时没法收发数据的;
6、ESP8266最多只有5个连接资源,客户端和服务端如何分配;
7、如何处理AT指令返回的信息。
针对以上提出的一些问题,通过代码分析进行逐一解答。
四、代码分析
4.1 状态机制
首先从整体思路上来讲,就是利用状态机的方式去执行不同的网络状态下的动作,也就是C语言里的switch语句了,这里定义了下图所示的一些状态,具体有注释。
然后就是设计不同状态下的动作了,也就是网络注册过程,这里使用switch语句进行状态跳转,函数内部间隔运行时间wait_time可以自定义,正常是2秒。
从起始状态开始,配置一些固定的参数,比如STA+AP两种模式都启用,上电自动连接WIFI以及配置WIFI的用户名和密码,这里参数都是存储到模块的内部FLASH的,比较耗时,所以热点AP的用户名和密码到下一个状态去设置。设置完后再次复位下模块,进入初始化阶段。
/*
================================================================================
描述 : 网络注册函数
输入 :
输出 :
================================================================================
*/
void drv_esp8266_reg_process(void)
{static u32 last_sec_time=0, wait_time=2;static char cmd_buff[100]={0};u32 now_sec_time=drv_get_sec_counter();if(now_sec_time-last_sec_time>wait_time){switch(g_sEsp8266Work.state){case ESP8266_STATE_START:{delay_os(2000);drv_esp8266_uart_send("ATE0\r\n");delay_os(200); drv_esp8266_send_at("CWMODE_DEF=3");//WiFi模式 STA+APdelay_os(200); drv_esp8266_send_at("CWAUTOCONN=1");//上电自动连接delay_os(200); if(strlen(g_sEsp8266Work.sta_ssid)>0){sprintf(cmd_buff, "CWJAP_DEF=\"%s\",\"%s\"", g_sEsp8266Work.sta_ssid, g_sEsp8266Work.sta_passwd);drv_esp8266_send_at(cmd_buff);delay_os(1000); } drv_esp8266_send_at("RST");//复位模块 g_sEsp8266Work.state=ESP8266_INIT; wait_time=3; break;}case ESP8266_INIT:{drv_esp8266_uart_send("ATE0\r\n");delay_os(200); if(strlen(g_sEsp8266Work.ap_ssid)>0){sprintf(cmd_buff, "CWSAP_DEF=\"%s\",\"%s\",5,3,4,0", g_sEsp8266Work.ap_ssid, g_sEsp8266Work.ap_passwd);drv_esp8266_send_at(cmd_buff);delay_os(1000); } wait_time=3;g_sEsp8266Work.state=ESP8266_WIFI_CONNECT; break;} case ESP8266_WIFI_CONNECT://等待WIFI连接成功{drv_esp8266_send_at("CIPSTATUS");//查询网络连接信息wait_time=5;break;}case ESP8266_NET_CFG://网络配置{printf("### ESP8266_NET_CFG\n");drv_esp8266_send_at("CIPMODE=0");//非透传模式delay_os(200); drv_esp8266_send_at("CIPMUX=1");//使能多连接delay_os(200);if(g_sEsp8266Work.listen_port>0){sprintf(cmd_buff, "CIPSERVER=1,%d", g_sEsp8266Work.listen_port);drv_esp8266_send_at(cmd_buff); //建立TCP服务器delay_os(200); } g_sEsp8266Work.state=ESP8266_STATE_OK; wait_time=2;break;} case ESP8266_STATE_OK:{drv_esp8266_connect_process();drv_esp8266_send_at("CIPSTATUS");//查询网络连接信息wait_time=5;break;} }last_sec_time=drv_get_sec_counter();}
}
初始化阶段主要设置热点AP的用户名和密码,剩下的就是等待模块自己连接上路由器了,如果没有指定名称的路由器,那就只能一直在这里等待了。在这期间,驱动会主动去查询网络状态,即drv_esp8266_send_at("CIPSTATUS"),我们需要根据模块的返回信息自己去判断网络状态,具体手册说明和代码解析如下图所示。
WIFI连接成功后就是配置一些网络信息了,在这里根据自己的需求配置了非透传模式、多连接和建立服务器三个内容。
至此,整个网络注册流程也就完成了,最后就是间隔查询网络状态,如果变化去做相对应的动作就行了。
4.2 客户端连接
网络可以用后,最麻烦的还是TCP的连接了,首先要说明的是客户端连接的结构体定义,如下所示。ESP8266最多只有5个连接,我们这里就定义了5个客户端的数组,结构体内部除了必要的网络信息外还有连接状态、心跳周期和保活时间等参数,这是为了连接的稳定而设计了,当网络因为不可控因素断开后模块又没有具体返回信息,这时可以根据应用层的保活时间来判断是否需要重新连接。
具体代码如下,有连接需求的就进行连接操作,在这里,先对模块的返回信息做个及时处理,这样多个连接时才不会分不清是哪个连接的返回信息,比如"ALREADY CONNECTED"信息它没有具体的识别参数。
/*
================================================================================
描述 : 客户端连接管理任务
输入 :
输出 :
================================================================================
*/
void drv_esp8266_connect_process(void)
{u32 now_sec_time=drv_get_sec_counter();for(u8 i=0; i<MAX_LINK_NUM; i++){Esp8266ClientStruct *pClient=&g_sEsp8266Work.client_list[i];if(pClient->dst_port>0)//有连接需求{if(pClient->conn_state==0){drv_esp8266_client_connect(pClient->sock_id, pClient->type, pClient->dst_addr, pClient->dst_port);delay_os(1000);char *pData=(char*)g_sEsp8266Work.pUART->pBuff;
// printf("***8266 recv=%s\n", pData); if(strstr(pData, "ALREADY CONNECTED")!=NULL){printf("sock_id=%d, already connected!\n", i);pClient->conn_state=1;pClient->keep_time=now_sec_time;UART_Clear(g_sEsp8266Work.pUART);//清理串口数据 }else if(strstr(pData, ",CONNECT")!=NULL){printf("sock_id=%d, new connected!\n", i);pClient->conn_state=1;pClient->keep_time=now_sec_time;UART_Clear(g_sEsp8266Work.pUART);//清理串口数据 }else if(strstr(pData, ",CLOSED")!=NULL){printf("sock_id=%d, error close!\n", i);pClient->conn_state=0;UART_Clear(g_sEsp8266Work.pUART);//清理串口数据 } }else{int det_time=now_sec_time-pClient->keep_time;if(det_time>pClient->heart_time)//心跳超时{printf("sock_id=%d, heart time out!\n", i);drv_esp8266_close(pClient->sock_id);}}} } }
同时,已经连接成功的就要检测是否保活超时,超时就要重连了,保活的keep_time在收到数据时都会自动更新为最新时间。
这个连接函数是在网络状态正常的状态下调用的,调用间隔是5秒。
4.3 应用数据接收处理
数据接收部分首先涉及的就是UART篇章的串口接收了,首先也是利用接收长度判断是否接收完成,完了之后通过关键字"+IPD,"判断是否为接收的数据,随即一步步解析连接ID和数据长度,最后就可以把应用层数据拿去接收处理了。
这里要重点讲下接收处理函数,它的定义如下图所示,属于回调函数,这样做的好处是应用层可以根据具体需求设计自己的处理函数,保证了驱动程序的通用性。
净化器这个项目的WIFI接收处理函数是在应用层的MQTT文件内注册的,具体如下,因为我们在应用层是采用ESP8266的网络连接ID=3来建立MQTT连接的,所以这里在app_esp8266_recv函数中把连接ID为3的数据保存进MQTT的环形缓冲区内,这个环形缓冲区是MQTT使用的内容,这里暂时给他理解成一个缓存空间即可。至于后续怎么处理,那是MQTT的事情了,至此,ESP8266的数据接收任务也就完成了。
4.4 数据发送
ESP8266的数据发送较为简单,先发送相关AT指令,然后立即发送数据内容即可。
4.5 主函数调用
剩下的就是提供运行主程序供应用层调用就行了,这里的运行节奏比较快,正常20ms运行一次。
4.6 网络连接ID分配
细心的同学会发现,这个项目我的连接ID是3,不是从0开始,这是为什么呢?因为我们模块是有热点的,可以作为服务器,那么其它设备连接我们的时候模块内部会自动占用一个连接ID,而且正常是从0开始的,如果有作为服务器的需求,那么就要提前做好准备,自己的使用的连接ID最好从高位开始,ESP8266的连接ID是0~4,总的5个,为了保险起见,我们可以选择3或4,这样就比较不会冲突了。这里的核心还是开发者自己要提前规划好使用的连接ID。
五、总结
ESP8266总体来讲不复杂,就是细节比较多,驱动程序要做到稳定好用不容易,像使用AT指令的通讯模块都有这个特点,比如以后可能会用到的4G模块,那个相对更复杂些。
在这里主要是想通过代码解读的方式让大家理解背后的设计思想,既然是以项目为中心的教程,那稳定通用是我们考虑的主要因素,在其它教程应该比较少有考虑到后续问题,大多只是带大家连个阿里云或者其他什么平台就完事了,对网络不稳定、连接意外断开、代码阻塞等问题都没有过多说明,这里主要就是要让大家学会技术以外的设计思想。
本项目的交流QQ群:701889554
写于2024-3-31
相关文章:
物联网实战--入门篇之(六)嵌入式-WIFI驱动(ESP8266)
目录 一、WIFI简介 二、基础网络知识 三、思路讲解 四、代码分析 4.1 状态机制 4.2 客户端连接 4.3 应用数据接收处理 4.4 数据发送 4.5 主函数调用 4.6 网络连接ID分配 五、总结 一、WIFI简介 WIFI在我们生活中太常见了,手机电脑都可以用WiFi连接路由器进行上…...
Java并发编程基础面试题详细总结
1. 什么是线程和进程? 1.1 何为进程? 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。 在 Java 中,当我们启动 main 函数时其实就是启动了一个…...
EKO / 砍树
暴力是不行的,还得是二分吧 题目描述 伐木工人 Mirko 需要砍 M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。 Mirko 的伐木机工作流程如下&a…...
Kafka面试宝典
1 Kafka基础面试篇 Kafka的那些设计让它有如此高的性能? 1.partition,producer和consumer端的批处理:提高并行度;2.页缓存:大量使用页缓存,内存操作比磁盘操作快很多,数据写入直接写道页缓存,由操作系统负责刷盘,数据读取也是直接命中页缓存,从内存中直接拿到数据;…...
Redis性能管理
目录 1、内存碎片如何产生的? 2、跟踪内存碎片率对理解Redis实例的资源性能是非常重要的 3、解决碎片率大的问题 二、内存使用率 1、避免内存交换发生的方法 2、内回收key 三、缓存的穿透、击穿、雪崩 #查看Redis内存使用方法 info memory #进入数据库查看 re…...
计算机网络:局域网的数据链路层
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
Linux常见命令简介
Linux运行级别 六种运行级别: 0、关机 1、单用户(可用来找回密码) 2、多用户无网络 3、多用户有网络(多用于工作环境) 4、预留 5、图形界面(多用于学习环境) 6、重…...
34-SDK设计(下):IAM项目GoSDK设计和实现
比如 Kubernetes的 client-go SDK设计方式。IAM项目参考client-go,也实现了client-go风格的SDK:marmotedu-sdk-go。 ,client-go风格的SDK具有以下优点: 大量使用了Go interface特性,将接口的定义和实现解耦࿰…...
基于Matlab的血管图像增强算法,Matlab实现
博主简介: 专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188) 个人主页:Matlab_ImagePro-CSDN博客 原则:代码均由本人编写完成,非中介,提供…...
LeetCode每日一题之专题一:双指针 ——复写零
复写零OJ链接:1089. 复写零 - 力扣(LeetCode) 题目: 解法(原地复写-双指针): 算法思路: 如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致…...
Golang基础-9
Go语言基础 介绍 基础 结构体 自定义类型 结构体定义 结构体声明 结构体初始化 字段访问与修改 匿名结构体 结构体嵌套 初始化函数定义 介绍 本文介绍Go语言中自定义类型、结构体定义、结构体声明、结构体初始化、字段访问与修改、匿名结构体、结构体嵌套、初始化…...
Vue基础知识:路由的封装抽离,路由模块的封装抽离的好处是什么?,如何快速的引入组件,基于@指代src目录,从src目录出发找组件
如果将所有的路由配置都存放在main.js中,是非常有问题的,杂且乱。所以我们要将路由模块进行抽离,这样有利于:拆分模块,利于维护。大致的做法就是将路由相关的东西放到router这个文件夹的index.js中,而将来只…...
插入排序---算法
1、算法概念 插入排序:它的工作原理是通过构建有序排序,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置插入。 2、算法步骤 将第一待排序序列第一个元素看作一个有序序列,把第二个元素到最后一个元素当成是…...
Vue3 Vite 整合组件脚手架笔记
序号更新时间备注12024.04.03初始化整理笔记 目录 一、安装运行命令二、相关依赖内容 1、http客户端 - alova2、国际化 - I18n3、时间管理 - moment4、pdf预览 - pdfjs-dist5、doc预览 - docx-preview6、请求参数处理 - qs7、全局状态管理 - Pinia8、路由管理 - vue-router9、…...
续二叉搜索树递归玩法
文章目录 一、插入递归二、寻找递归(非常简单,走流程就行)三、插入递归(理解起来比较麻烦) 先赞后看,养成习惯!!!^ _ ^<3 ❤️ ❤️ ❤️ 码字不易,大家的…...
DDD 的四层领域模型是怎样的?包含哪些基础概念?
DDD的四层领域模型如下所示: 展现层:这一层负责向用户显示信息和解释用户命令,完成前端界面逻辑。并将用户请求传递给应用层。应用层:这一层是很薄的一层,负责协调领域层中的领域对象,组成具体应用场景。应…...
AI 在医疗保健领域的应用:技术、趋势和前景
人工智能(AI)在医疗保健领域的应用已经成为引人瞩目的发展方向,其在医学影像分析、疾病诊断和个性化治疗等方面展现出了巨大潜力。本文将深入探讨这些技术应用和未来的发展趋势。 医学影像分析 医学影像分析是AI在医疗领域中应用最广泛的领…...
SVG XML 格式定义图形入门介绍
SVG SVG means Scalable Vector Graphics. SVG 使用 XML 格式定义图形SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失SVG 是万维网联盟的标准 Hello World Use SVG in html and you can see: Link to the SVG file You can use <a> tag to link to the svg…...
MYSQL数据库的故障排除与优化
目录 一.MySQL单实例故障排查 故障现象1 故障现象 2 故障现象 3 故障现象 4 故障现象 5 故障现象 6 故障现象 7 故障现象 8 二.主从环境常见故障 1.故障一 2. 故障二 3. 故障三 三. 优化 1.SQL优化 2. 架构优化 3.硬件方面 1.1 关于CPU 1.2 关于内存 1.3 关…...
C++从入门到精通——入门知识
1. C关键字(C98) C总计63个关键字,C语言32个关键字 2. 命名空间 在C/C中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称都将存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的就是对标识符的名…...
一些题目学习
1.打开文件添加helloworld public class Saier {public static void main(String[] args){String path"C:\\Users\\sjg\\Desktop\\abc.txt";String text"hello world";try {File file new File(path);FileWriter fileWriter new FileWriter(file,true);…...
Linux上管理文件系统
Linux上管理文件系统 机械硬盘 机械硬盘由多块盘片组成,它们都绕着主轴旋转。每块盘片上下方都有读写磁头悬浮在盘片上下方,它们与盘片的距离极小。在每次读写数据时盘片旋转,读写磁头被磁臂控制着不断的移动来读取其中的数据。 所有的盘片…...
【Linux】寿司线程池{单例模式之懒汉模式下的线程池}
文章目录 回顾单例模式0.多线程下的单例模式的意义1.什么是单例模式1.0设计模式1.1C单例模式的介绍及原理1.2拷贝构造和赋值重载的处理1.3if (nullptr ptr),nullptr放在比较运算符的前面?1.4实现单例模式的方式 2.实现懒汉方式的单例模式2.1单线程的单例模式2.2多…...
Docker资源管理和分配指南
什么是cgroup? cgroups其名称源自控制组群(control groups)的简写,是Linux内核的一个功能,用来限制、控制与分离一个进程组(如CPU、内存、磁盘输入输出等)。 什么是Docker资源限制?…...
为什么索引的底层结构是B+树
B树 1.数据库与数据交互的单位是page,而B树的每个节点都是一个page,访问一个节点,就相当于进行了一次I/O操作。所以访问的节点越少,查找效率越大。而B树是矮胖的,查找深度也不会太大。 2.B树中的节点是有序存储的,对于范围查询、排…...
NLP学习路线指南总结
当然可以,以下是一份较为详细的NLP学习路线指南,帮助你逐步掌握自然语言处理的核心技术和应用。 一、基础知识与技能 语言学基础: 语言学基本概念:语音、语法、语义等。语言的层次与分类:语音学、音系学、句法学、语…...
试过了,ChatGPT确实不用注册就可以使用了!
看到官网说不用登录也可以直接使用ChatGPT 我们来试一下 直接打开官网 默认是直接进入了chatgpt3.5的聊天界面 之前是默认进的登录页面 聊一下试试 直接回复了,目前属于未登录状态,挺好! 来试下ChatGPT4 跳转到了登录页面 目前来看gpt4还…...
CANoe自带的TCP/IP协议栈中TCP的keep alive机制是如何工作的
TCP keep alive机制我们已经讲过太多次,车内很多控制器的TCP keep alive机制相信很多开发和测试的人也配置或者测试过。我们今天想知道CANoe软件自带的TCP/IP协议栈中TCP keep alive机制是如何工作的。 首先大家需要知道TCP keep alive的参数有哪些?其实就三个参数:CP_KEEP…...
【C++练级之路】【Lv.18】哈希表(哈希映射,光速查找的魔法)
快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、哈希1.1 哈希概念1.2 哈希函数1.3 哈希冲突 二、闭散列2.1 数据类型2.2 成员变量2.3 默认成员函数2.…...
「PHP系列」If...Else语句/switch语句
文章目录 一、If...Else语句1. 基本语法2. 带有 elseif 的语法3. 示例示例 1:基本 if...else 结构示例 2:使用 elseif示例 3:嵌套 if...else 结构 4. 注意事项 二、switch语句1. 基本语法2. 示例示例 1:基本 switch 结构示例 2&am…...
大型购物网站建设/百度资源搜索平台官网
今日凌晨,李笑来在微博表示:“从今往后,李笑来个人不会做任何项目投资(不管是不是区块链,不管是不是早期)。因此,若是你再看到李笑来被站台(之前就长期被站台无数,说99%事…...
营销型网站效果/seo网站关键词优化快速官网
本词条缺少概述图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧!非线性编辑【Nonlinear Edit】,简称非编,是相对于传统上以时间顺序进行线性编辑而言。传统线性视频编辑是按照信息记录顺序…...
wordpress 菜单管理系统/任何东西都能搜出来的软件
他通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类的性能。 adaboost提高那些被前一轮弱分类器错误分类样本的权重,而降低那些被正确分类样本的权重,这样使得,那些没有得到正确…...
wordpress炫酷背景图片/怎么样关键词优化
作为开源中国点赞狂魔,必须有特殊的点赞技巧:账号登录OSC,并获取首页右侧第一个动弹的属性后点击"赞",如果第一个用户被点击过则不会重复点击配合定时任务效果更佳//点赞狂魔class LikeDemon{public function index(){$…...
建设小说网站首页/全能优化大师
作者: 阮一峰 日期: 2016年9月18日 一年半前,我写了《React 入门实例教程》,介绍了 React 的基本用法。 但是,React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。也就是说,只用 Reac…...
网站网站优化/长沙网站建设公司
1、什么是Mybatis?(1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格…...