基于正点原子STM32F103ZET6的ESP8266应用

基于正点原子STM32F103ZET6的ESP8266应用

前言

本次测试所使用的是正点原子的ATK-ESP8266 WIFI模块。直接使用官方提供的固件使用AT指令来配置模块并使用。

模块默认为AT指令状态,模拟波特率为115200(8bit数据位,1bit停止位)。

硬件连接

使用USB转TTL方式(电脑需要安装CH340驱动)连接ESP8266,接线图如下:

Wifi模块ESP8266简介

ESP8266 是串口型WIFI,速度比较低,不能用来传输图像或者视频这些大容量的数据,主要应用于数据量传输比较少的场合,比如温湿度信息,一些传感器的开关量等。

ESP8266与单片机串口连接。一方面单片机可以通过串口发送AT命令给ESP8266,ESP8266会返回给单片机一个返回值;另一方面,当手机与ESP8266连接后,APP通过Wifi发送命令给ESP8266模块,ESP8266接收并通过串口将命令发送至单片机。

ESP8266模块的优势

WiFi由于速度快、范围广、广播数据量大,且其耗电量大的劣势也随着低功耗WiFi模块的推出被扭转,也因此低功耗WiFi透传模块正逐步成为物联网低功耗数据传输的宠儿。

WiFi透传模块的面世,可快速实现对传统产品的智能化转型升级。适用于智能穿戴式设备、智能家居、汽车电子、休闲玩具、安防监控等行业领域,以最短的开发周期整合现有的方案,帮助客户迅速占领市场,赢得先机。

WiFi透传模块跟主控模块是有区别的,它的主控程序还是在电器本身上,模块只是作为一个传输通道,不需对数据做任何处理,同时保证传输的质量,原封不动地到了最终接收者手里。

UDP&TCP协议简介

① 简单的介绍:

UDP是一种广播式的通信模式,在网络中向所有可以接收到其信号的设备进行信息传输,UDP通信没有像TCP协议那种的严格的收发检验,根据UDP协议,设备只管发送信息,另一端设备接受到与否我一概不管,我依旧发我的。因此UDP是一种非阻塞的通信协议。

相较于UDP来说,TCP是一种带有收发检查措施的通信协议,即收发双发信息每交互一次就要检查一次,因此效率低了一些,但是安全性上去了。比如:相较于UDP来说TCP有缓存区,当我发的信息后对方无应答,我就从TCP缓冲区内不断地读这一次的数据不断地重复的发送直至对方应答成功。这样就导致了一次数据交互失败致使剩余数据交互受阻,也就是我们常常说的“卡死了“。

② TCP和UDP协议的不同点:

⑴ TCP和UDP都属于socket通信协议,前者是以100个数据流的方式进行通信,后者是以数据包的方式进行通信;

⑵ TCP是有向连接协议,UDP是无向连接协议;

⑶ 当TCP Client和服务器建立连接时,它们需要三个握手协议。UDP不需要握手,直接发送数据包;

⑷ TCP通信不会丢失数据,UDP通信会丢失数据包;

⑸ 在通信可靠性方面,TCP比UDP更可靠;

⑹ 安全性上,TCP安全保密要比UDP高;

⑺ UDP采用的是非阻塞异步通信模式,而TCP是采用的是阻塞异步通信模式。

WIFI的两种模式

AP模式: Access Point,提供无线接入服务,允许其它无线设备接入,提供数据访问,一般的无线路由/网桥工作在该模式下。AP和AP之间允许相互连接。

AP是access point的缩写,译为“可接入的节点“,我们简称”热点“。AP(例如:路由器等)担当的是一个”集线器“,在有线网络中,每个设备都是一个用户,当我们的用户只有两个人我们可以将他们两个直接相连不通过中间媒介,但是如果在这个网络中有10000个人,那么两两互联效率极低而且no possible,因此我们需要将这个无线网络中所有的终端用户全部连入一个节点当中统一规划统一管理。这个重要的在网络中担当核心地位的点就是AP。

Sta模式: Station, 类似于无线终端,sta本身并不接受无线的接入,它可以连接到AP,一般无线网卡即工作在该模式。

STA是station的缩写,从名字也可以看出“站“,我们中文译为”终端“。我们的网络由无数个基本单元组成,我们的网络不可能无限延伸肯定有个终点,这个终点就是我们的终端用户设备(我们的智能手机,智能手表,PC电脑等)。

这里需要提到的是“整个大的网络由许多局域网组成,但是这些局域网又由许多子局域网组成,这些子局域网是构成网络的最小单元网络,其中就包含一个无线连接点AP和数个STA无线终端“。

几个特殊的参数

① SSID(Service Set Identifier):SSID,每个无线AP都应该有一个标示用于用户识别,SSID就是这个用于用户识别的的名字,也就是我们经常说到的wifi名;

② BSSID:每一个网络设备都有其用于识别的物理地址,这个东西呢就叫MAC地址,这个东西一般情况下出厂会有一个默认值,可更改,也有其固定的命名格式,也是设备识别的标识符。这个BSSID呢是针对设备说的,对于STA的设备来说,拿到AP接入点的MAC地址就是这个BSSID;

③ ESSID是一个比较抽象的概念,它实际上就和ssid相同(本质也是一串字符),只是能如果有好几个无线路由器都叫这个名字,那么我们就相当于把这个ssid扩大了,所以这几个无线路由器共同的这个名字就叫ESSID。举个例子,一家公司面积比较大,安装了若干台无线接入点(AP或者无线路由器),公司员工只需要知道一个SSID就可以在公司范围内任意地方接入无线网络。BSSID其实就是每个无线接入点的MAC地址。当员工在公司内部移动的时候,SSID是不变的。但BSSID随着你切换到不同的无线接入点,是在不停变化的;

④ RSSI:就是通过STA扫描到AP站点的信号强度;

⑤ IP:IP与MAC本质的区别在于,MAC用于区别一个局域网中的各台设备,IP用于区分广域网(网络)中的不同局域网,就比如:以某个路由器为AP以数个WIFI设备为STA的一个局域网中各台设备的IP地址是一样的。我们一般在ESP8266中提及的全是私有IP,私有IP和公有IP的最大区别是:在现在的网络中,IP地址分为公网IP地址和私有IP地址。公网IP是在Internet使用的IP地址,而私有IP地址则是在局域网中使用的IP地址。私有IP地址是一段保留的IP地址。只使用在局域网中,无法在Internet上使用,即ESP8266无法提供上网服务;

⑥ TCP客户端的ID号和TCP服务器的端口号:在子局域网中,TCP客户端的ID号使其区别于网络中的其他TCP客户端,同理,TCP服务器的端口号使其区别于网络中的其他TCP服务器。

服务器VS客户端

在使用TCP通讯建立连接时采用客户端服务器模式,这种模式又常常被称为主从式架构,简称为C/S结构,属于一种网络通讯架构,将通讯的双方以客户端(Client )与服务器 (Server) 的身份区分开来。只能客户端主动连接服务器,不可以服务器主动连接客户端,其实这也好理解,如果我的服务器给每个客户端都发送一遍连接请求,这还叫低功耗吗?低功耗的意思是“按需供给“,因此低功耗要求我们务必”按照客户端的需求来提供服务“而非”主动去每个客户端询问是否需要提供服务“。

服务器的特征:被动角色,等待来自客户端的连接请求,处理请求并回传结果;

客户端的特征:主动角色,发送连接请求,等待服务器的响应。

其实说白了,服务器就是将从客户端接收的数据统一分配统一管理,例如:QQ有2种,一种是服务器中转消息,一种是P2P消息。对于中转消息,如果只是单独的来说,当客户端连接上服务器,服务器会对客户端消息做处理,比如安排INDEX之类。那么客户端ID01准备向客户端ID02发送消息。那么协议中应该包括了客户端对象和消息内容。那么客户端ID01向服务器发送这条请求消息。服务器端接收到ID01向ID02发送消息的请求,并且同意,那么服务器端就将消息内容发送到对应目标,也就是客户端ID02。。对于群发消息,那么只是在发送消息的时候不需要指定目标,而是更改成服务器广播消息。也就是,服务器接收到发送消息请求的服务器端来说,面向所有以连接客户端进行消息广播群发。

透传模式

① 透传模式是什么?

透传,又称透明传输,具体来说就是“输入即输出”「如从WiFi模块串口输入的字符会透传到服务器端」,数据不改变,不同协议之间的转换(如串口到WiFi)由模块完成。使用者无需关心内部具体实现,因此模块对于使用者是“透明的”、似乎不存在的「因为可无视中间的实现原理」。 这就好比邮寄信件,信件有可能通过自行车、汽车、火车、飞机等多种组合运输方式到达您的手上,但您不用关心它们中间经历了哪些。

注意:ATK_ESP8266 模块仅在 TCP Client和UDP,支持透传模式。

② 透传模式有什么用?

透传一般都是用来读取远程的串口数据。例如:网吧内每个上网者都要刷身份证才能上网,但身份证数据库不可能放在每个网吧内。所以就将读卡器的串口数据通过透传回传到公安局,在公安局的平台上来比对身份证号码。

ESP8266的三种工作模式

ESP8266支持 STA / AP / STA+AP 这三种工作模式:

① STA模式:ESP8266模块通过路由器连接互联网,可以通过互联网实现对设备的远程控制。类似于无线网卡;

② AP模式:ESP8266模块作为热点,可以实现手机或者其他联网设备通过WIFI直接与模块进行通信,实现局域网无线控制。类似于路由或者网桥;

③ STA+AP模式:两种模式都支持。

通俗来讲,STA模式就是一个联网设备,需要通过wifi连接在其他的无线路由器上。AP模式就是一个无线路由器,其他联网设备可以通过wifi接入。

WIFI工作模式与通信网络中从属关系的搭配

模块TCP Server多连接模式

多连接模式:一个TCP客户端可以向多个TCP服务器发送信息。

① 请求与通信:

STA模式是说模块直接连接AP(手机热点或者路由器),进入局域网中和其他无线设备通信,由于ESP8266在STA工作模式下作TCP客户端,因此ESP8266在局域网作为客户端,其他设备作为服务端。

两个终端(STA工作模式下的WIFI设备)之间建立连接的过程:

② 建立连接时的重要参数:

⑴ 配置SSID和PassWord:

这是连接局域网中无线连接点的AP的必需品,其中SSID和PassWord是AP的固有属性也是配网的重要手段:

1. SSID技术可以将一个无线局域网分为几个需要不同身份验证的子网络;

2. 每一个子网络都需要独立的身份验证即密码PassWord,只有通过身份验证的用户才可以进入相应的子网络,防止未被授权的用户进入本网络。

③ AT指令的配置流程:

⑴ 测试ESP8266是否可以接收AT指令:

⑵ 恢复ESP8266的出厂设置:

这里,恢复出厂设置一般需要3s左右。

⑶ 配置ESP8266为STA模式:

⑷ 扫描当前可用AP:

⑸ 连接指定AP:

⑹ 查询本地IP:

⑺ 设置为多连接模式:

⑻ 配置完WIFI的工作模式后,在配置ESP8266在TCP协议中的主从关系和端口名称:

⑼ ESP8266的工作模式和TCP从属关系设置完毕,现在该配置PC端/手机端了:

首先,我们要使ESP8266和PC端电脑这两个STA设备共同连接一个AP,即同一个路由器:

这里一定要注意:这个无线网络中AP是由“Tenda_352640“这个WIFI名称的路由器担当的,这个AP并不是ESP8266,ESP8266只是一个STA工作模式下的WIFI设备而已。

其次,我们要打开网络调试助手去配置PC端电脑设备的TCP模式:

选择TCP Client模式并且设置所要连接的TCP服务器端口为5678(ESP8266在TCP协议下的工作端口)

只有PC端电脑是TCP服务器时我们才关注其TCP端口号。

最终,连接成功后,ESP8266会向串口调试助手中回传“所有工作在STA模式下的TCP客户端的ID号”:

由于与这个“AP无线网络连接节点”连接的用作TCP客户端的STA终端设备只有一台(PC端电脑),因此这里仅仅显示了ESP8266的TCP 客户端的ID号:0。

⑽ 我们想想:我们从TCP客户端的角度来看,我们已知TCP服务器的端口号,我们可以配置TCP客户端向指定TCP服务器发送数据,但是我们从TCP服务器的角度来看,TCP服务器接收的数据是不是指定TCP客户端发送的呢?TCP客户端的ID号可以告诉我们答案。

这表示:PC端电脑接收ID0客户端(ESP8266)传输的2个字节数据,这里配置ESP8266只接收从PC端传来的两个字节的数据。

⑾ 此时可以开始发送数据,因为我们开启的是STA工作模式下的透传模式,因此我们只需发送数据即可(一共可发送两个数据),注意:无需点击“发送新行“:

⑿ 当我们传输完数据后,我们需要退出透传模式:

这里切记无需点击“发送新行”。

模块TCP Client透传模式(单连接模式)

透传模式:是一种单连接模式,可以实现一个TCP服务器和一个TCP客户端之间的数据传输。注意:ATK_ESP8266 模块仅在 TCP Client和UDP,支持透传模式。

⑴ 测试AT指令是否正常:

⑵ 恢复ESP8266的出厂设置:

这里,恢复出厂设置一般需要3s左右。

⑶ 配置ESP8266为STA模式:

⑷ 扫描当前可用AP,相当于寻找所在地点的可用局域网:

⑸ 连接可用的局域网:

这一步就是将ESP8266连接到某一个可用局域网中。

⑹ 查询本地IP:

STA和AP两种工作模式对应着两种不同的MAC和IP地址。

⑺ 由于透传模式为单连接模式,因此我们关闭多连接模式:

⑻ 设置为透传模式:

由于此时ESP8266工作在STA模式下,而且处于透传模式,我们可知此时ESP8266在子局域网中为“STA工作模式下的TCP客户端“。

⑼ 配置PC端电脑为TCP服务器,设置PC端电脑的TCP端口号并且指定IP地址:

⑽ 配置ESP8266所连接的TCP服务器的IP地址和TCP端口号:

这样可以实现快速的终端对终端快速连接,就如同我们的蓝牙耳机一样,开机自动配对特定的手机。

⑾ 开始发送数据:

我们需注意:

关键程序解析(正点原子例程)

串口配置

① 串口初始化

⑴ 程序示例:

//初始化IO 串口3
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率   
void usart3_init(u32 bound)
{  

 NVIC_InitTypeDef NVIC_InitStructure;
 GPIO_InitTypeDef GPIO_InitStructure;
 USART_InitTypeDef USART_InitStructure;

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOB时钟
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //串口3时钟使能

  USART_DeInit(USART3);  //复位串口3
    
    //USART3_TX   PB10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB10
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
    GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB10

    //USART3_RX   PB11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PB11
 
 USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
  
 USART_Init(USART3, &USART_InitStructure); //初始化串口 3  

 USART_Cmd(USART3, ENABLE);                    //使能串口 
 
 //使能接收中断
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启中断   
 
 //设置中断优先级
 NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //子优先级3
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能
 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
 
 TIM7_Int_Init(1000-1,7200-1);  //TIM7 10ms触发中断
 USART3_RX_STA=0;  //清零
 TIM_Cmd(TIM7,DISABLE);   //关闭定时器7

}

 

注意:USART3接收数据时触发中断执行中断服务函数。

② 串口3的中断服务函数

⑴ 程序示例

//串口接收缓存区  
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN];     //接收缓冲,最大USART3_MAX_RECV_LEN个字节.
u8  USART3_TX_BUF[USART3_MAX_SEND_LEN];    //发送缓冲,最大USART3_MAX_SEND_LEN字节

//[15]:0:没有接收到数据;1:接收数据的缓冲区已经装满数据
//[14:0]:接收到的数据长度
vu16 USART3_RX_STA=0;    


//注意:通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.
/*中断服务函数实现功能:
如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到任何数据,则表示此次接收完毕.*/
void USART3_IRQHandler(void)
{
 u8 res;       
 if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到数据
 {  
  res =USART_ReceiveData(USART3);   
  if((USART3_RX_STA&(1<<15))==0)//接收完的一批数据,还没有被处理,则不再接收其他数据
  { 
   if(USART3_RX_STA<USART3_MAX_RECV_LEN) //还可以接收数据
   {
    TIM_SetCounter(TIM7,0);//计数器清空           
    if(USART3_RX_STA==0)     //使能定时器7的中断 
    {
     TIM_Cmd(TIM7,ENABLE);//使能定时器7
    }
    USART3_RX_BUF[USART3_RX_STA++]=res; //记录接收到的值  
   }
     else 
   {
    USART3_RX_STA|=1<<15;    //强制标记接收完成
   } 
  }
 }                   
}

 

⑵ 关键参数:

接收缓冲区状态参数:

USART3_RX_STA

15

14:0

含义

接受缓冲区是否已满

接收缓冲区接受的数据的实际长度

接收缓冲区&发送缓冲区的长度:

预定义名称

USART3_MAX_RECV_LEN

600

USART3_MAX_SEND_LEN

600

⑶ 程序执行流程:

1. 当ESP8266向开发板发送数据时,开发板的USART3_RX引脚会接受数据从而触发中断;

2. 查看USART3_RX_STA的第15位是否为1,如果为1说明接收缓冲区已经满了进而说明上一次装入的数据还未处理;如果为0则继续将数据装入缓冲区内;

3. 直至装满后将USART3_RX_STA的第15位置为1;

这个函数的含义在于:将“每发送一条AT指令,ESP8266回传给串口的所有信息“存入接收缓冲区内。

③ 串口打印函数

⑴ 代码示例:

//串口3,printf 函数
//确保一次发送数据不超过USART3_MAX_SEND_LEN字节
void u3_printf(char* fmt,...)  
{  
 u16 i,j; 
 va_list ap; 
 va_start(ap,fmt);
 vsprintf((char*)USART3_TX_BUF,fmt,ap);
 va_end(ap);
 i=strlen((const char*)USART3_TX_BUF);  //此次发送数据的长度
 for(j=0;j<i;j++)       //循环发送数据
 {
   while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   
  USART_SendData(USART3,USART3_TX_BUF[j]); 
 } 
}

 

注意:这里的u8*代表着char*.

⑵ 程序执行流程:

1. 将输入字符串使用vsprintf函数装入发送缓冲区内;

2. 循环发送直至发送完毕.

定时器与串口搭配使用的函数

⑴ 定时器配置函数

//通用定时器7中断初始化,这里时钟选择为APB1的2倍
//arr:自动重装值 psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz 
//通用定时器中断初始化 
void TIM7_Int_Init(u16 arr,u16 psc)
{ 
 NVIC_InitTypeDef NVIC_InitStructure;
 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);//TIM7时钟使能    
 
 //定时器TIM7初始化
 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 
 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
 TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
 TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE ); //使能指定的TIM7中断,允许更新中断
 
 TIM_Cmd(TIM7,ENABLE);//开启定时器7
 
 NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //子优先级2
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能
 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
 
}

 

注意:TIM7开启了定时器更新中断。

⑵ TIM7中断服务函数

extern vu16 USART3_RX_STA;

//定时器7中断服务程序      
void TIM7_IRQHandler(void)
{  
 if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中断
 {        
  USART3_RX_STA|=1<<15; //标记接收完成
  TIM_ClearITPendingBit(TIM7, TIM_IT_Update  );  //清除TIM7更新中断标志    
  TIM_Cmd(TIM7, DISABLE);  //关闭TIM7 
 }     
}

 

我们还记得前面我们提到过:如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到。这里我们USART3-TIM7搭配的流程如下:

1. 当信号传输至USART3中时,如果上一次接受的数据处理完毕那么就启动TIM7定时器更新中断,定时10ms;

2. 当数据传输持续10ms之后,我们截断数据传输,我们认为10ms是数据传输的最大持续时长,此时我们将USART3_RX_STA的第15位置1,即认为本次传输圆满完成。

注意:传输圆满完成标志被设置的途径有两个:一是回传给串口的字符串过长超出了接收缓冲区规定的最大长度,此时被迫停止接收更多的字符;二是回传时长超过10ms,我们认为此时信息回传已经结束,强制的将标志位置1。

基于串口信息传输的几个重要指令传输函数

① 指令传输函数

⑴ 代码示例:

//功能:向ESP8266发送命令
//cmd:发送的命令字符串;ack:期待的应答结果,如果为空,则表示不需要等待应答;waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果);1,发送失败
u8 esp8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{
 u8 res=0; 
 USART3_RX_STA=0;
 u3_printf("%s\r\n",cmd); //发送命令
 if(ack&&waittime)  //需要等待应答
 {
  while(--waittime) //等待倒计时
  {
   delay_ms(10);
   if(USART3_RX_STA&0X8000)//接收到期待的应答结果
   {
    if(esp8266_check_cmd(ack))
    {
     printf("ack:%s\r\n",(u8*)ack);
     break;//得到有效数据 
    }
    USART3_RX_STA=0;
   } 
  }
  if(waittime==0)res=1; 
 }
 return res;
}

 

⑵ 参数说明

参数名称

参数数据类型

含义

cmd

char*

AT指令

ack

char*

我们期待的AT指令对应的应答结果

waittime

short int

最大轮询等待时间

⑶ 函数执行流程

1. 当我们重新发送一条指令时,我们要进行初始化工作:将接收缓冲区的初始接受数据个数和接收缓冲区的状态置为零(USART3_RX_STA=0);

2. 当我们使用封装好的us_printf函数将数据通过串口发送出去的时候,回传的数据会触发串口接收中断进而使得数据传输完成标志位置1,即USART3_RX_STA的第15位置1;

3. 当接收不到我们想要的应答,就不接收任何数据,即USART3_RX_STA置0.

② “检查接收应答是否是我们想要的“函数

⑴ 代码示例:

//ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果;其他,期待应答结果的位置(str的位置)
u8* esp8266_check_cmd(u8 *str)
{
 char *strx=0;
 if(USART3_RX_STA&0X8000)  //接收到一次数据了
 { 
  USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
  strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
 } 
 return (u8*)strx;
}

 

⑵ 参数说明:

参数名称

参数数据类型

含义

返回值strx

char*

我们期望的应答字符串的首地址

输入值str

char*

0(NULL):没有得到期待的应答结果;

地址:期待应答结果的位置(str的位置)

⑶ 函数执行流程:

我们知道一个字符串以’\0’为结尾,否则我们在内存中读出的字符串会带有一部分乱码。因此我们要在缓冲区中的最后一个非’\0’字符元素的后面添加’\0’字符串结束符。然后,我们使用string.h头文件中的strstr函数在接收缓冲区中查找我们想要的应答字符串,并且返回应答字符串在接收缓冲区中的第一个字符出现的地址。

③ 向ESP8266发送数据的函数

1. 代码示例:

//向ESP8266发送数据
//cmd:发送的命令字符串;waittime:等待时间(单位:10ms)
//返回值:发送数据后,服务器的返回验证码
u8* esp8266_send_data(u8 *cmd,u16 waittime)
{
 char temp[5];
 char *ack=temp;
 USART3_RX_STA=0;
 u3_printf("%s",cmd); //发送命令,切记“这里没有发送换行符(‘\n\r’)“
 if(waittime)  //需要等待应答
 {
  while(--waittime) //等待倒计时
  {
   delay_ms(10);
   if(USART3_RX_STA&0X8000)//接收到期待的应答结果
   {
    USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
    ack=(char*)USART3_RX_BUF;
    printf("ack:%s\r\n",(u8*)ack);
    USART3_RX_STA=0;
    break;//得到有效数据 
   } 
  }
 }
 return (u8*)ack;
} 

 

2. 参数说明:

参数名称

参数数据类型

含义

cmd

char*

AT指令

waittime

short int

最大轮询等待时间

3. 函数执行流程:

当我们使用us_printf向串口发送数据后,回传的串口数据会触发USART_RX接收中断,回传的数据会源源不断地装入接收缓冲区内,当回传信息传输完成之后,传输完成标志位会被置1,此时我们需要在最后一个非’\0’字符之后添加’\0’字符串结束标志。然后将接收缓冲区的字符串打印至屏幕上,然后将初始接收缓冲区的索引和接收缓冲区状态标志位置零,即USART3_RX_STA置零。

4. 对比“用于发送命令函数“的区别:

用于发送命令的函数需要检查回传字符串是否是自己想要的,也就是说我们需要检查自己发送的指令对不对,为了检查回传字符串是否与我们想要的一致,我们专门定义了“检查回传字符串是否与期望字符串相匹配“的函数。

④ 进入透传模式的函数

⑴ 函数示例:

//ESP8266模块和PC进入透传模式
void esp8266_start_trans(void)
{
 //设置工作模式 1:station模式   2:AP模式  3:兼容 AP+station模式
 esp8266_send_cmd("AT+CWMODE=1","OK",50);
 
 //让Wifi模块重启的命令
 esp8266_send_cmd("AT+RST","ready",20);
 
 delay_ms(1000);         //延时3S等待重启成功
 delay_ms(1000);
 delay_ms(1000);
 delay_ms(1000);
 
 //让模块连接上自己的路由
 while(esp8266_send_cmd("AT+CWJAP=\"xxxx\",\"xxxx\"","WIFI GOT IP",600));
 
 //=0:单路连接模式     =1:多路连接模式
 esp8266_send_cmd("AT+CIPMUX=0","OK",20);
 
 //建立TCP连接  这四项分别代表了 要连接的ID号0~4   连接类型  远程服务器IP地址   远程服务器端口号
 while(esp8266_send_cmd("AT+CIPSTART=\"TCP\",\"xxx.xxx.xxx.xxx\",xxxx","CONNECT",200));
 
 //是否开启透传模式  0:表示关闭 1:表示开启透传
 esp8266_send_cmd("AT+CIPMODE=1","OK",200);
 
 //透传模式下 开始发送数据的指令 这个指令之后就可以直接发数据了
 esp8266_send_cmd("AT+CIPSEND","OK",50);
}

 

⑵ 函数执行流程:

1. 配置ESP8266为STA模式;

2. 将ESP8266恢复出厂设置,并且等待3s;

3. 等待连接到指定路由;

4. 配置ESP8266为单连接模式;

5. 开启透传模式;

6. 发送“开始传输数据“的指令。

执行完这个函数之后,就可以发送数据了。

⑤ 退出透传模式的函数

⑴. 代码示例:

//ESP8266退出透传模式   返回值:0,退出成功;1,退出失败
//配置wifi模块,通过想wifi模块连续发送3个+(每个+号之间 超过10ms,这样认为是连续三次发送+)
u8 esp8266_quit_trans(void)
{
 u8 result=1;
 u3_printf("+++"); // 这里没有发送换行符(‘\n\r')
 delay_ms(1000);     //等待500ms太少 要1000ms才可以退出
 result=esp8266_send_cmd("AT","OK",20);//退出透传判断.
 if(result)
  printf("quit_trans failed!");
 else
  printf("quit_trans success!");
 return result;
}

 

⑵ 函数执行流程:

1. 向串口传输”退出透传模式的指令(‘+++’)”,切记“不换行“;

2. 等待接收执行成功的回传信息。

(物联网实验)C语言中几个常用函数解析

STM32的USARTx中u3_printf函数解析

我们在STM32中常常使用printf来将信息打印至PC端控制台上。但是有这样一种函数,它可以把数据以我们指定的格式装进字符串中——sprinft和vsprintf。

⑴ sprintf函数使用的方式如下:

① 将多个参数以指定格式写入字符串

int a=1,b=2;  
char s[10];  
sprintf(s,"a=%d,b=%d",1,2);  
puts(s);  

 

输出结果:a=1,b=2

② 错误使用:在函数封装中使用

void Myprintf(const char* fmt,...)  // 传递个数不定的参数
{  
  char s[10];  
  sprintf(s,fmt);  // 出现错误
  puts(s);  
}  

 

封装函数的使用:

int a=1,b=2;  
Myprintf("a=%d,b=%d",a,b);  

 

输出结果:

a=?,b=? // 不确定值

 

这是因为可变参数在入栈时如下列方式排列:

Param1

Param2

……

Paramx

但是sprintf只接收了Param1作为自己的参数,这样的话,我们调用的封装函数等价如下:

int a=1,b=2;  
char s[10];  
sprintf(s,"a=%d,b=%d");  // 这里的a,b根本没有值
puts(s);  

 

⑵ vsprintf粉墨登场了,它解决了sprintf无法读取可变参数的缺陷

我们要知道可变参数在栈中的排列是连续的,而且占用了一段栈的内存空间,我们首先要知道栈区中可变形参的排列顺序:

int func(int num,...)
{
     ......
}

 

其对应可变参数在栈区中的排列顺序是:

参数名称

num

参数1

参数N

栈中顺序

N+1

N

1

这里要注意:入栈的顺序是“先入后出-FILO”,因此这里的元素在栈中是倒序排列的,即栈区的末端地址就等于可变参数列表中首个参数的地址。

这里我们要将不定个数的参数传递给函数vsprintf函数,我们就必须要借助这N个参数(参数N~参数1)的地址来进行传参。我们一定要清楚:可变参数指的是参数1~N不包括第一个参数num,这个参数是已知的不算可变参数。

函数vsprintf原型如下:

// 函数功能:将函数地址->ParamEndAddr地址之间的所有参数以Format指定的格式转化为字符串进而赋给以StringFirstAddr为首地址的字符数组
// ParamEndAddr:参数列表中首个参数的地址(栈区中可变参数列表的末端地址)
// Format:指定转化为字符串的格式(详见:printf函数打印字符串的格式)
// StringFirstAddr:用于接受转换后字符串的字符型数组首地址
vpsrintf(char* StringFirstAddr, Format, ParamEndAddr);

 

这里我们要注意:函数在内存中的分布分为RAM和FLASH两大块,其中参数存在RAM中函数的执行内容存在FLASH中,这里的函数名就是参数列表所在栈区在RAM中的首地址,当我们提供一个RAM中参数列表所在栈区的末端地址,我们就可以将整个可变参数列表提取出来。

这里我们介绍几个函数:

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

参数类型

char*

int

int

程序如下:

void func(char *fmt, ...)
{
     va_list ap; // 指向参数列表(栈区)中某个元素的指针类型

     va_start(ap, fmt);
     va_arg(ap, int);
     va_end(va);
}

 

① 确定“栈区末端地址”的函数——va_start()

函数原型:va_start(va_list ap, 参数列表中的第一个元素)

函数功能:va_start(va_list ap, char* fmt)

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

   

ap指向的对象

   

函数的调用使得ap指针指向可变参数列表中的首个元素也是栈区中的最后一个元素地址。

② 根据栈区末端地址和参数类型提取可变参数列表中的指定元素——va_arg()

函数原型:va_arg(va_list ap, 元素的数据类型)

函数功能:va_arg(va_list ap, int)

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

参数类型

char*

int

int

   

ap指向的对象

   

va_arg()所做的就是根据ap指向的地址,和第二个参数所确定的类型,将这个参数的中的数据提取出来,作为返回值,同时让ap指向下一个参数。arg是argument参数的缩写。

③ 将指针置空(NULL)的函数——va_end()

函数原型:va_end(va_list ap)

函数功能:va_end()所做的就是让ap这个指针指向0。

函数vsprintf使用方式如下:

void Myprintf(const char* fmt,...)  
{  
  char s[10];  
  va_list ap;  // 定义指向栈区某个元素的指针
  va_start(ap,fmt);  // ap指向可变参数列表的首地址(栈区的末端元素地址)
  vsprintf(s,fmt,ap);  // 将函数首地址~ap之间的可变形参列表赋值给vsprintf函数形参列表
  va_end(ap); // ap指针置空(NULL)
  puts(s);  // 打印字符串至屏幕上
}  

 

但是,这里将一段封装函数Myprintf的参数列表赋值给vsprintf函数当作参数,如果我们操作单个可变参数列表中的元素,我们该如何做呢?

相较于前面一段一段的使用参数列表的元素的最大不同在于“我们需要指定输入参数的个数”,因为我们输入的参数的数据类型未知,个数未知,因此我们要知道我们每个输入参数的数据类型和输入参数的总个数才可以。

// 这里我们指定了可变形参个数为num个
float Average(int num,...) 
{
       int i=0;
       float sum;
       va_list valist;
       va_start(valist,num); // 通过参数列表中收个元素(int num)找到可变参数列表中的收个元素地址并赋值给valist
       for (i = 0; i < num; i++)
       {
             // 根据输入元素的数据类型在栈区中读取数据
             sum+=(float)va_arg(valist,int); // 假设所有输入元素全文int类型的参数,当不断循环时不断改变valist指针指向元素,进而不断轮询访问参数列表中的所有元素
       }
       va_end(valist); // 置空(NULL)valist指针
       return sum/(float)num;
}

 

注意:我们要使用这些函数必须要加载“#include <stdarg.h>”头文件。

字符串查找函数——strstr()

① 功能:

c语言中“strstr(str1,str2)”函数用于判断字符串“str2”是否是“str1”的子串;如果是,则该函数返回“str2”在“str1”中首次出现的地址;否则返回NULL。其语法为“* strstr(str1,str2)”。

② 函数原型:extern char *strstr(char *str1, const char *str2);

③ 参数说明:

str1: 被查找目标 string expression to search.

str2: 要查找对象 The string expression to find.

返回值:若str2是str1的子串,则返回str2在str1的首次出现的地址;如果str2不是str1的子串,则返回NULL。

④ 代码示例:

char str[]="1234xyz";
char *str1=strstr(str,"34");
cout << str1 << endl;

 

ESP8266处于透传模式的全部示例程序

main.c

#include "esp8266.h"
#include "tim7.h"
#include "usart3.h"
#include "usart.h"
#include "delay.h"
#include "malloc.h"

void NVIC_Config()
{
    NVIC_InitTypeDef NVIC_InitStructure;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_Init(&NVIC_InitStructure);
}

const u8* wifista_ssid = "CMCC_Vbkq";
const u8* wifista_password = "crshw6jr";
const u8* wifista_ip = "192.168.1.1";
const u8* wifista_port = "8086";

int main()
{   
    delay_init();
    uart_init(115200); // ESP8266默认回传数据的速度是115200bps
    NVIC_Config();
    USART3_Config(115200); // ESP8266默认回传数据的速度是115200bps
    my_mem_init(SRAMIN);		//初始化内部内存池
    
    esp8266_start_trans(); // 开启透传模式
    esp8266_send_data("12",50); // 发送数据(Data:12),最大轮询次数50次
    esp8266_quit_trans(); // 退出透传模式
        
    while(1);
}

esp8266.h

#ifndef _ESP8266_H
#define _ESP8266_H

#include "sys.h"

u8 esp8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime);
u8* esp8266_send_data(u8 *cmd,u16 waittime);
u8* esp8266_check_cmd(u8 *str);
void esp8266_start_trans(void);
u8 esp8266_quit_trans(void);

#endif

esp8266.c

#include "esp8266.h"
#include "usart3.h"
#include "delay.h"
#include "sys.h"
#include "usart.h" // 该头文件中重载了fputc
#include "string.h"
#include "stdio.h"
#include "malloc.h"

extern vu16 USART3_RX_STA;
extern u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN字节

//ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果;其他,期待应答结果的位置(str的位置)
u8* esp8266_check_cmd(u8 *str)
{
	char *strx=0;
	if(USART3_RX_STA&0X8000)		//接收到一次数据了
	{ 
		USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
		strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}

//cmd:发送的命令字符串;ack:期待的应答结果,如果为空,则表示不需要等待应答;waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果);1,发送失败
u8 esp8266_send_cmd(u8* cmd,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",cmd);	//发送命令
	if(ack&&waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000) // 如果接收完成
			{
				if(esp8266_check_cmd(ack)) // 根据回传字符,检查是否是我们想要的结果
				{
					printf("ack:%s\r\n",(u8*)ack);
					break;//得到有效数据 
				}
				USART3_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;
} 

//向ESP8266发送数据
//cmd:发送的命令字符串;waittime:等待时间(单位:10ms)
//返回值:发送数据后,服务器的返回验证码
u8* esp8266_send_data(u8 *cmd,u16 waittime)
{
	char temp[5];
	char *ack=temp;
	USART3_RX_STA=0;
	u3_printf("%s",cmd);	//发送命令
	if(waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
				ack=(char*)USART3_RX_BUF;
				printf("ack:%s\r\n",(u8*)ack);
				USART3_RX_STA=0;
				break;
			} 
		}
	}
	return (u8*)ack;
}

extern const u8* wifista_ssid;
extern const u8* wifista_password;
extern const u8* wifista_ip;
extern const u8* wifista_port;
//ESP8266模块和PC进入透传模式
void esp8266_start_trans(void)
{
    char pbuffer[100];

	esp8266_send_cmd("AT","OK",50);
	
	//让Wifi模块恢复出厂设置的命令
	esp8266_send_cmd("AT+RESTORE","OK",20);
	
    //延时3S等待重启成功
	delay_ms(1000);         
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
    
    //设置工作模式 1:station模式   2:AP模式  3:兼容 AP+station模式
	esp8266_send_cmd("AT+CWMODE=1","OK",50); 
	
	//让模块连接上自己的路由
    sprintf((char*)pbuffer,"AT+CWJAP_CUR=\"%s\",\"%s\"",wifista_ssid,wifista_password);//设置无线参数:ssid,密码
	while(esp8266_send_cmd(pbuffer,"WIFI GOT IP",600));
	
	//0:单路连接模式;\;1:多路连接模式
	while(esp8266_send_cmd("AT+CIPMUX=0","OK",20));
	
    //是否开启透传模式  0:表示关闭 1:表示开启透传
	esp8266_send_cmd("AT+CIPMODE=1","OK",200);
    
	//建立TCP连接  这四项分别代表了 要连接的ID号0~4   连接类型  远程服务器IP地址   远程服务器端口号
    sprintf((char*)pbuffer,"AT+CIPSTART=\"TCP\",\"%s\",%s",wifista_ip,wifista_port);
	while(esp8266_send_cmd(pbuffer,"CONNECT",200));
	
	//透传模式下 开始发送数据的指令 这个指令之后就可以直接发数据了
	esp8266_send_cmd("AT+CIPSEND","OK",50);
}

//ESP8266退出透传模式   返回值:0,退出成功;1,退出失败
//配置wifi模块,通过想wifi模块连续发送3个+(每个+号之间 超过10ms,这样认为是连续三次发送+)
u8 esp8266_quit_trans(void)
{
	u8 result = 1;
	u3_printf("+++");
	delay_ms(1000);					//等待500ms太少 要1000ms才可以退出
	result = esp8266_send_cmd("AT","OK",20);//退出透传判断.
	if(result)
		printf("quit_trans failed!");
	else
		printf("quit_trans success!");
	return result;
}

tim7.h

#ifndef _TIM7_H
#define _TIM7_H

void TIM7_Config();

#endif

tim7.c

#include "tim7.h"
#include "stm32f10x.h"

// 从使能TIM7起10ms触发一次更新中断
void TIM7_Config()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);//TIM7时钟使能    
	
	//定时器TIM7初始化
	TIM_TimeBaseStructure.TIM_Period = 1000-1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler = 7200-1; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE ); //使能指定的TIM7中断,允许更新中断
	
	TIM_Cmd(TIM7,ENABLE);//开启定时器7
}

// 包含传输状态信息和传输信息的长度
extern vu16 USART3_RX_STA;
void TIM7_IRQHandler()
{
    if(TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)
    {
        USART3_RX_STA|=1<<15; // 10ms是ESP8266数据回传的最大时延
        TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
        TIM_Cmd(TIM7, DISABLE);  //关闭TIM7
    }
}

usart3.h

#ifndef _USART3_H
#define _USART3_H

#include "sys.h"
#include "stdarg.h"

#define USART3_MAX_RECV_LEN		600					//最大接收缓存字节数
#define USART3_MAX_SEND_LEN		600					//最大发送缓存字节数

// [14:0]:接收数据的个数;[15]:发送缓冲区的状态
extern vu16 USART3_RX_STA;   						//接收数据状态

// 接收缓冲区和发送缓冲区
extern u8  USART3_RX_BUF[USART3_MAX_RECV_LEN]; 		//接收缓冲,最大USART3_MAX_RECV_LEN字节
extern u8  USART3_TX_BUF[USART3_MAX_SEND_LEN]; 		//发送缓冲,最大USART3_MAX_SEND_LEN字节

void u3_printf(char* format,...);
void USART3_Config(u32 BaudRate);
void NVIC_Config();

#endif

usart3.c

#include "usart3.h"
#include "stm32f10x.h"
#include "sys.h"
#include "TIM7.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"

void USART3_Config(u32 BaudRate)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    
    USART_DeInit(USART3);
    
    USART_InitStructure.USART_BaudRate = BaudRate;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART3,&USART_InitStructure);
    
    USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
    
    USART_Cmd(USART3,ENABLE);
    
    USART3_RX_STA = 0;
    
    TIM7_Config();
    TIM_Cmd(TIM7,DISABLE);
}

//串口3,printf 函数
//确保一次发送数据不超过USART3_MAX_SEND_LEN字节
void u3_printf(char* format,...)  
{  
	u16 i,j; 
	va_list ap; 
	va_start(ap,format);
    memset(USART3_TX_BUF,0,USART3_MAX_SEND_LEN);
	vsprintf((char*)USART3_TX_BUF,format,ap); // 将数据发送至发送缓冲区内
	va_end(ap);
	i=strlen((const char*)USART3_TX_BUF); // 此次发送数据的长度
	for(j=0;j<i;j++) // 循环发送发送缓冲区内的全部字节数据
	{
        while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); // 等待上一帧数据发送完毕   
        USART_SendData(USART3,USART3_TX_BUF[j]); 
	} 
}

//串口接收缓存区 	
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; 				//接收缓冲,最大USART3_MAX_RECV_LEN个字节.
u8  USART3_TX_BUF[USART3_MAX_SEND_LEN]; 			//发送缓冲,最大USART3_MAX_SEND_LEN字节
//[15]:0:没有接收到数据;1:接受完一批数据.
//[14:0]:接收到的数据长度
vu16 USART3_RX_STA=0;

// 按8位数据长度接收数据——执行一次中断服务函数接受一个字符
void USART3_IRQHandler()
{
    u8 Result = 0;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
    {
        Result=USART_ReceiveData(USART3);
        if((USART3_RX_STA&(1<<15)) == 0) // 判断接收缓冲区是否已满或者之前接受的数据还未处理
        {
            if(USART3_RX_STA < USART3_MAX_RECV_LEN) // 如果接收长度未超过MAX
            {
                TIM_SetCounter(TIM7,0); // 计数器清空
                if(USART3_RX_STA == 0)
                {
                    memset(USART3_RX_BUF,0,USART3_MAX_RECV_LEN);
                    TIM_Cmd(TIM7,ENABLE); // 使能定时器7——从此刻开始计时,数据接收的最大时长为10ms
                }
                USART3_RX_BUF[USART3_RX_STA++] = Result; // 记录接收到的字符
            }
            else
            {
                USART3_RX_STA |= 1<<15; // 数据接收缓冲区已满停止接收数据,此次接收数据完毕
            }
        }
        USART_ClearITPendingBit(USART3, USART_IT_RXNE);
    }
}

AP+STA双模式全部示例代码

这里只展示与”透传模式“不同的代码。这里AP做TCP服务器,STA做TCP客户端

main.c

#include "esp8266.h"
#include "tim7.h"
#include "usart3.h"
#include "usart.h"
#include "delay.h"
#include "malloc.h"

void NVIC_Config()
{
    NVIC_InitTypeDef NVIC_InitStructure;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_Init(&NVIC_InitStructure);
}

// STA:将路由作为AP与其他STA相连接
const u8* wifi_ssid = "CMCC-Vbkq";
const u8* wifi_password = "crshw6jr";
// STA:将STA接到此IP和端口号的的AP中
const u8* exapip = "172.31.122.223";;
const u8* extcpport = "8086";
// AP:将自身当作路由使得周围STA可接入该AP
const u8* esp8266_ssid = "ESP8266";
const u8* esp8266_password = "12345678";
// 配置AP作为TCP Server端口
const u8* aptcpport = "8080";
// TCP Client的参数
const u8 ID0 = 0;
const u8 ID1 = 1;
const u8 DataNumber = 25;

int main()
{   
    u8 pBuffer[100]={0};
    
    delay_init();
    uart_init(115200); // ESP8266默认回传数据的速度是115200bps
    NVIC_Config();
    USART3_Config(115200); // ESP8266默认回传数据的速度是115200bps
    my_mem_init(SRAMIN);		//初始化内部内存池
    
    esp8266_start(); // 开启AP和STA模式 
    
    esp8266_send_data("12",50);
    
    while(1)
    {
        if(esp8266_check_cmd("KEY"))
        {
            esp8266_quit_trans(); // 退出多连接模式
        }
    }        
}

esp8266.h

#ifndef _ESP8266_H
#define _ESP8266_H

#include "sys.h"

u8 esp8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime);
u8* esp8266_send_data(u8 *cmd,u16 waittime);
u8* esp8266_check_cmd(u8 *str);
void esp8266_start(void);
u8 esp8266_quit_trans(void);

#endif

esp8266.c

#include "esp8266.h"
#include "usart3.h"
#include "delay.h"
#include "sys.h"
#include "usart.h" // 该头文件中重载了fputc
#include "string.h"
#include "stdio.h"
#include "malloc.h"

extern vu16 USART3_RX_STA;
extern u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN字节

//ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果;其他,期待应答结果的位置(str的位置)
u8* esp8266_check_cmd(u8 *str)
{
	char *strx=0;
	if(USART3_RX_STA&0X8000)		//接收到一次数据了
	{ 
		USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
		strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}

//cmd:发送的命令字符串;ack:期待的应答结果,如果为空,则表示不需要等待应答;waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果);1,发送失败
u8 esp8266_send_cmd(u8* cmd,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",cmd);	//发送命令
	if(ack&&waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000) // 如果接收完成
			{
				if(esp8266_check_cmd(ack)) // 根据回传字符,检查是否是我们想要的结果
				{
					printf("ack:%s\r\n",(u8*)ack);
					break;//得到有效数据 
				}
				USART3_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;
} 

//向ESP8266发送数据
//cmd:发送的命令字符串;waittime:等待时间(单位:10ms)
//返回值:发送数据后,服务器的返回验证码
u8* esp8266_send_data(u8 *cmd,u16 waittime)
{
	char temp[5];
	char *ack=temp;
	USART3_RX_STA=0;
	u3_printf("%s",cmd);	//发送命令
	if(waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
				ack=(char*)USART3_RX_BUF;
				printf("ack:%s\r\n",(u8*)ack);
				USART3_RX_STA=0;
				break;
			} 
		}
	}
	return (u8*)ack;
}

// STA:将路由作为AP与其他STA相连接
extern const u8* wifi_ssid;
extern const u8* wifi_password;
// STA:将STA接到此IP和端口的AP中
extern const u8* exapip;
extern const u8* extcpport;
// AP:将自身当作路由使得周围STA可接入该AP
extern const u8* esp8266_ssid;
extern const u8* esp8266_password;
// 配置AP作为TCP Server端口
extern const u8* aptcpport;
// TCP Client的参数
extern const u8 ID0,ID1;
extern const u8 DataNumber;

// AP-TCP Server; STA-TCP Client
void esp8266_start(void)
{
    char pBuffer[100]={0};
    
    while(esp8266_send_cmd("AT","OK",20));
    
    while(esp8266_send_cmd("AT+RESTORE","OK",50));
    delay_ms(1000);delay_ms(1000);delay_ms(1000);
    
    while(esp8266_send_cmd("AT+CWMODE=3","OK",50)); // 配置为STA+AP模式
    
    while(esp8266_send_cmd("AT+CWLAP","OK",50)); // 查询当前的所有可用AP
    printf("%s\n\r",USART3_RX_BUF);
    
    sprintf((char*)pBuffer,"AT+CWSAP=\"%s\",\"%s\",11,3",esp8266_ssid,esp8266_password);
    while(esp8266_send_cmd(pBuffer,"OK",50)); // 将自己作为路由
    printf("%s\n\r",USART3_RX_BUF);
    
    sprintf((char*)pBuffer,"AT+CWJAP=\"%s\",\"%s\"",wifi_ssid,wifi_password);
    while(esp8266_send_cmd(pBuffer,"OK",50)); // 连接路由WIFI
    printf("%s\n\r",USART3_RX_BUF);
    
    while(esp8266_send_cmd("AT+CIFSR","STAIP",20));   //检测是否获得STA IP
    printf("%s\n\r",USART3_RX_BUF);
	while(esp8266_send_cmd("AT+CIFSR","APIP",20));   //检测是否获得AP IP
    printf("%s\n\r",USART3_RX_BUF);
    
    while(esp8266_send_cmd("AT+CIPMUX=1","OK",50)); // 设置为多连接模式
    
    sprintf((char*)pBuffer,"AT+CIPSERVER=1,%s",aptcpport);
    while(esp8266_send_cmd(pBuffer,"OK",50)); // 将AP设置为TCP Server
    
    esp8266_send_cmd("AT+CIPSTO=1200","OK",50); //设置服务器超时时间
    
    sprintf((char*)pBuffer,"AT+CIPSTART=0,\"TCP\",\"%s\",%s",exapip,extcpport);
    while(esp8266_send_cmd(pBuffer,"OK",50)); // 将STA连接外部TCP Server端口
    
    sprintf((char*)pBuffer,"AT+CIPSEND=%d,%d",ID1,DataNumber);
    while(esp8266_send_cmd(pBuffer,"OK",200)); // (AP)ESP8266连接外部TCP客户端
    printf("%s\n\r",USART3_RX_BUF);
}

//ESP8266退出多连接模式   返回值:0,退出成功;1,退出失败
//配置wifi模块,通过想wifi模块连续发送3个+(每个+号之间 超过10ms,这样认为是连续三次发送+)
u8 esp8266_quit_trans(void)
{
	u8 result = 1;
	u3_printf("+++");
	delay_ms(1000);					//等待500ms太少 要1000ms才可以退出
	result = esp8266_send_cmd("AT","OK",20);//退出透传判断.
	if(result)
		printf("quit_trans failed!");
	else
		printf("quit_trans success!");
	return result;
}

”STA作为TCP服务器模式“的全部示例代码

这里只展示和之前代码不一样的代码片段。

main.c

#include "esp8266.h"
#include "tim7.h"
#include "usart3.h"
#include "usart.h"
#include "delay.h"
#include "malloc.h"

void NVIC_Config()
{
    NVIC_InitTypeDef NVIC_InitStructure;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_Init(&NVIC_InitStructure);
}

const u8* wifista_ssid = "CMCC_Vbkq";
const u8* wifista_password = "crshw6jr";
const u8* wifista_ip = "192.168.1.1";
const u8* wifista_port = "5678";
const u8 transdatanum = 2;
const u8 id = 0;

int main()
{   
    delay_init();
    uart_init(115200); // ESP8266默认回传数据的速度是115200bps
    NVIC_Config();
    USART3_Config(115200); // ESP8266默认回传数据的速度是115200bps
    my_mem_init(SRAMIN);		//初始化内部内存池
    
    esp8266_start_MultiLink(); // 开启多连接模式    
        
    while(1)
    {
        if(esp8266_check_cmd("KEY1"))
        {
            printf("正在退出多连接模式...");
            esp8266_quit_trans(); // 退出多连接模式
        }
    }
}

esp8266.h

#ifndef _ESP8266_H
#define _ESP8266_H

#include "sys.h"

u8 esp8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime);
u8* esp8266_send_data(u8 *cmd,u16 waittime);
u8* esp8266_check_cmd(u8 *str);
void esp8266_start_MultiLink(void);
u8 esp8266_quit_trans(void);

#endif

esp8266.c

#include "esp8266.h"
#include "usart3.h"
#include "delay.h"
#include "sys.h"
#include "usart.h" // 该头文件中重载了fputc
#include "string.h"
#include "stdio.h"
#include "malloc.h"

extern vu16 USART3_RX_STA;
extern u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN字节

//ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果;其他,期待应答结果的位置(str的位置)
u8* esp8266_check_cmd(u8 *str)
{
	char *strx=0;
	if(USART3_RX_STA&0X8000)		//接收到一次数据了
	{ 
		USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
		strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}

//cmd:发送的命令字符串;ack:期待的应答结果,如果为空,则表示不需要等待应答;waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果);1,发送失败
u8 esp8266_send_cmd(u8* cmd,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",cmd);	//发送命令
	if(ack&&waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000) // 如果接收完成
			{
				if(esp8266_check_cmd(ack)) // 根据回传字符,检查是否是我们想要的结果
				{
					printf("ack:%s\r\n",(u8*)ack);
					break;//得到有效数据 
				}
				USART3_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;
} 

//向ESP8266发送数据
//cmd:发送的命令字符串;waittime:等待时间(单位:10ms)
//返回值:发送数据后,服务器的返回验证码
u8* esp8266_send_data(u8 *cmd,u16 waittime)
{
	char temp[5];
	char *ack=temp;
	USART3_RX_STA=0;
	u3_printf("%s",cmd);	//发送命令
	if(waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
				ack=(char*)USART3_RX_BUF;
				printf("ack:%s\r\n",(u8*)ack);
				USART3_RX_STA=0;
				break;
			} 
		}
	}
	return (u8*)ack;
}

extern const u8* wifista_ssid;
extern const u8* wifista_password;
extern const u8* wifista_ip;
extern const u8* wifista_port;
extern const u8 transdatanum;
extern const u8 id;
//ESP8266模块和PC进入多连接模式
void esp8266_start_MultiLink(void)
{
    char pbuffer[100];

	esp8266_send_cmd("AT","OK",50);
    
	
	//让Wifi模块恢复出厂设置的命令
	esp8266_send_cmd("AT+RESTORE","OK",20);
	
    //延时3S等待重启成功
	delay_ms(1000);         
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
    
    //设置工作模式 1:station模式   2:AP模式  3:兼容 AP+station模式
	while(esp8266_send_cmd("AT+CWMODE=1","OK",50)); 
	
    // 扫描当前可用AP
    while(esp8266_send_cmd("AT+CWLAP","OK",50));
    printf("%s\n\r",USART3_RX_BUF);
    
	//让模块连接上自己的路由
    sprintf((char*)pbuffer,"AT+CWJAP_CUR=\"%s\",\"%s\"",wifista_ssid,wifista_password);//设置无线参数:ssid,密码
	while(esp8266_send_cmd(pbuffer,"WIFI GOT IP",600));
	
    // 查询本地IP地址
    while(esp8266_send_cmd("AT+CIFSR","OK",50));
    printf("%s\n\r",USART3_RX_BUF);    
    
	//0:单路连接模式;\;1:多路连接模式
	while(esp8266_send_cmd("AT+CIPMUX=1","OK",20));
	
    sprintf((char*)pbuffer,"AT+CIPSERVER=1,%s",wifista_port);
    while(esp8266_send_cmd(pbuffer,"OK",20));
	
	//多连接模式下,开始发送数据的指令这个指令之后就可以直接发数据了
    sprintf((char*)pbuffer,"AT+CIPSEND=%d,%d",id,transdatanum);
	esp8266_send_cmd(pbuffer,"OK",50);
}

//ESP8266退出多连接模式   返回值:0,退出成功;1,退出失败
//配置wifi模块,通过想wifi模块连续发送3个+(每个+号之间 超过10ms,这样认为是连续三次发送+)
u8 esp8266_quit_trans(void)
{
	u8 result = 1;
	u3_printf("+++");
	delay_ms(1000);					//等待500ms太少 要1000ms才可以退出
	result = esp8266_send_cmd("AT","OK",20);//退出透传判断.
	if(result)
		printf("quit_trans failed!");
	else
		printf("quit_trans success!");
	return result;
}

 

版权声明:本文为CSDN博主「肥肥胖胖是太阳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45590473/article/details/117899180

生成海报
点赞 0

肥肥胖胖是太阳

我还没有学会写个人说明!

暂无评论

相关推荐

STM32读取编码器数据(STM32-1)

编码器(encoder)是将信号或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。按照外形可以分为实心轴和空心轴;按照工作原理编码器可分为增量式和绝对式两类。增量式编码器是将位移转换成周期性

STM32智能家居项目设计

前言 该项目是嵌入式课程学习最后的一个项目设计,做的不是很好(大佬勿喷…),特别是STM32数据处理部分,因为刚学的STM32,并且对C语言的指针等的使用也有