物联网实战--入门篇之(六)嵌入式-WIFI驱动(ESP8266)

news2024/11/28 23:46:46

目录

一、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+AP
				delay_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1560305.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MIT最新研究成果 机器人能够从错误中纠偏 无需编程介入和重复演示

目前科学家们正在努力让机器人变得更加智能&#xff0c;教会他们完成诸如擦拭桌面&#xff0c;端盘子等复杂技能。以往机器人要在非结构化环境执行这样的任务&#xff0c;需要依靠固定编程进行&#xff0c;缺乏场景通用性&#xff0c;而现在机器人的学习过程主要在于模仿&#…

开关恒流源简介

目录 工作原理 设计要点 应用场景 初步想法&#xff0c;为参加活动先占贴&#xff08;带家人出去玩没时间搞~~&#xff09;&#xff0c;后面优化 开关恒流源是一种基于开关电源技术的恒流输出电源设备。它采用开关管进行高速的开关动作&#xff0c;通过控制开关管的导通和截…

linux 一些命令

文章目录 linux 一些命令fdisk 磁盘分区parted 分区文件系统mkfs 格式化文件系统fsck 修复文件系统 mount 挂载swap 交换分区清除linux缓存df du 命令raid 命令基本原理硬raid 和 软raid案例raid 10 故障修复&#xff0c;重启与卸载 lvm逻辑卷技术LVM的使用方式LVM 常见名词解析…

数据库---------完全备份和增量备份的数据恢复,以及断点恢复

目录 一、在数据库表中&#xff0c;分三次录入学生考试成绩 1.1先创建库&#xff0c;创建表&#xff0c;完成三次数据的录入 1.2首次录入成绩后&#xff0c;做该表的完全备份 1.3第二次插入后 做增量备份 1.4第三次插入后 做增量备份 二、模拟数据丢失&#xff0c;并使用…

大文件上传做断点续传(有详细的代码内容)

文章目录 一、是什么分片上传断点续传 二、实现思路三、使用场景小结参考文献 一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用户…

标题:Vue3 中父组件向子组件通信的方式

标题&#xff1a;Vue3 中父组件向子组件通信的方式 在 Vue3 中&#xff0c;父组件和子组件之间可以通过一些方式进行通信。其中&#xff0c;父组件向子组件通信主要有两种方式&#xff1a;传值和调用子组件的方法。 一、父组件向子组件传值 当父组件需要向子组件传递数据时&a…

快速排序---算法

1、算法概念 快速排序&#xff1a;通过一趟排序将待排记录分隔成独立的两部分&#xff0c;其中一部分记录的数据均比另一部分的数据小&#xff0c;则可分别对这两部分记录继续进行排序&#xff0c;以达到震哥哥序列有序。 快速排序的最坏运行情况是O()&#xff0c;比如说顺序数…

设计一个动物声音“模拟器”,希望模拟器可以模拟许多动物的叫声。

设计一个动物声音“模拟器”&#xff0c;希望模拟器可以模拟许多动物的叫声。要求如下&#xff1a; &#xff08;1&#xff09;编写接口Animal Animal接口有2个抽象方法cry()和getAnimaName()&#xff0c;即要求实现该接口的各种具体动物类给出自己的叫声和种类名称。 &…

设计模式 - 中介器模式

中介者模式使得组件通过一个中心点——中介者进行交互。组件不需要直接进行通信&#xff0c;而是将请求发送给中介者&#xff0c;由中介者进行转发&#xff01;在JavaScript中&#xff0c;中介者往往只是一个对象字面量或一个函数。 你可以将这种模式与空中交通管制员和飞行员…

科技革新,OTG充电新纪元!

在科技日新月异的今天&#xff0c;数据交互已经渗透到我们生活的每个角落&#xff0c;无论是工作还是娱乐&#xff0c;它都发挥着不可替代的作用。OTG技术的出现&#xff0c;极大地简化了设备间的联接与数据交换过程&#xff0c;但随之而来的接口有限和续航问题&#xff0c;也让…

产品经理的进阶之路

点击下载《产品经理的进阶之路》 1. 前言 本文深入剖析了产品经理这一职业从产品专员起步,逐步晋升为产品经理、高级产品经理,直至产品总监的整个职业发展路径。在每个阶段,产品经理都需承担不同的工作职责,展现出独特的职业特点。 2. 产品专员 关键词【产品需求/原型/文…

计算机服务器中了rmallox勒索病毒怎么办?rmallox勒索病毒解密数据恢复

网络技术的不断发展与应用&#xff0c;大大提高了企业的生产运营效率&#xff0c;越来越多的企业开始网络开展各项工作业务&#xff0c;网络在为人们提供便利的同时&#xff0c;也会存在潜在威胁。近日&#xff0c;云天数据恢复中心接到多家企业的求助&#xff0c;企业的计算机…

设计模式-概述篇

1. 掌握设计模式的层次 第1层&#xff1a;刚开始学编程不久&#xff0c;听说过什么是设计模式第2层&#xff1a;有很长时间的编程经验&#xff0c;自己写了很多代码&#xff0c;其中用到了设计模式&#xff0c;但是自己却不知道第3层&#xff1a;学习过了设计模式&#xff0c;…

Vue 3.0生命周期:深入理解与用法

Vue 3.0生命周期&#xff1a;深入理解与用法 摘要&#xff1a; 本文将深入探讨Vue 3.0的生命周期&#xff0c;解释每个生命周期钩子的含义和用法&#xff0c;以及它们在开发过程中的重要性。我们将详细解析每个钩子的工作原理&#xff0c;并提供一些示例和最佳实践&#xff0c…

python爬取B站视频

参考&#xff1a;https://cloud.tencent.com/developer/article/1768680 参考的代码有点问题&#xff0c;请求头需要修改&#xff0c;上代码&#xff1a; import requests import re # 正则表达式 import pprint import json from moviepy.editor import AudioFileClip, Vid…

常见贪心问题详解

目录 贪心算法应用条件 常见贪心问题 活动安排问题&#xff08;区间调度问题&#xff09; 区间覆盖问题 最优装载问题1 最优装载问题2 多机调度问题 例题&#xff1a;翻硬币 例题&#xff1a;快乐司机 例题&#xff1a;防御力 例题&#xff1a;答疑 贪心算法应用条件…

【通信原理笔记】【三】模拟信号调制——3.1 模拟信号调制基本模型与思路

文章目录 前言一、模拟信号二、模拟调制系统模型三、模拟调制的三种方式四、调制的评价指标总结 前言 一般常见的信号的频带均集中在基带附近&#xff0c;如果要通过无线地方式传输&#xff0c;其较长的波长需要大型的天线才能传输&#xff0c;难以实现。另一方面基带的带宽资…

ruoyi-nbcio-plus基于vue3的flowable执行监听器的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

Linux基础命令篇之——压缩与解压(tar、gzip、bzip2、zip和unzip)

linux基础命令——解压与压缩 以下是关于Linux命令tar、gzip、bzip2、zip和unzip的详细介绍&#xff1a; 1. tar 这个是Linux用的最多的解压缩命令 tar是Linux系统中用于创建和处理归档文件的命令。归档文件是一个包含多个文件和/或目录的单一文件。常与压缩命令gzip或bzip2结…

图像处理_积分图

目录 1. 积分图算法介绍 2. 基本原理 2.1 构建积分图 2.2 使用积分图 3. 举个例子 1. 积分图算法介绍 积分图算法是图像处理中的经典算法之一&#xff0c;由Crow在1984年首次提出&#xff0c;它是为了在多尺度透视投影中提高渲染速度。 积分图算法是一种快速计算图像区域和…