跳转至

STM32之WIFI通信(ESP8266 ESP-01S)

前言

本文:使用的是 STM32F103C8T6 + ESP-01S模块 通过采用串口的通信方式 来实现 WIFI功能

ESP8266的介绍

ESP8266指的是乐鑫推出的ESP8266EX芯片,用ESP8266EX芯片和FLASH芯片等封装在PCB上、金属屏蔽罩下便得到ESP8266模块。

ESP8266EX 由乐鑫公司开发,提供了⼀套⾼度集成的 Wi-Fi SoC 解决⽅案,其低功耗、 紧凑设计和⾼稳定性可以满⾜⽤户的需求。

ESP8266EX 拥有完整的且⾃成体系的 Wi-Fi 网络功能,既能够独立应用,也可以作为从机 搭载于其他主机 MCU 运⾏。当 ESP8266EX 独⽴应⽤时,能够直接从外接 Flash 中启动。 内置的⾼速缓冲存储器有利于提⾼系统性能,并且优化存储系统。此外 ESP8266EX 只需通过 SPI/SDIO 接⼝或 I2C/UART ⼝即可作为 Wi-Fi 适配器,应⽤到基于任何微控制器的 设计中。 ESP8266EX 集成了天线开关、射频 balun、功耗放⼤器、低噪放⼤器、过滤器和电源管理 模块。这样紧凑的设计仅需极少的外部电路并且将 PCB 的尺⼨降到最⼩

ESP-01S的介绍

ESP-01S 是由安信可科技开发的Wi-Fi模块,该模块核心处理器 ESP8266 在较小尺寸封装中集成了业界领先的 Tensilica L106 超低功耗 32 位微型 MCU,带有 16 位精简模式,主频支持 80 MHz 和 160 MHz,支持 RTOS,集成 Wi-Fi MAC/ BB/RF/PA/LN

ESP-01S Wi-Fi 模块支持标准的 IEEE802.11 b/g/n 协议,完整的 TCP/IP 协议栈。用户可以使用该模块为现有的设备添加联网功能,也可以构建独立的网络控制器。

ESP-01S

脚序 名称 功能说明
1 GND 接地
2 IO2 GPIO2/UART1_TXD
3 IO0 GPIO0;下载模式:外部拉低;运行模式:悬空或者外部拉高
4 RXD UART0_RXD/GPIO3
5 TXD UART0_TXD/GPIO1
6 EN 芯片使能端,高电平有效
7 RST 复位
8 VCC 3.3V 供电(VDD);外部供电电源输出电流建议在500mA以上

官方说明: EN 脚和 RST 脚必须上拉到 VCC

通过我的实践经验:

  • 遇到最头疼问题(当时),就是感觉模块时好时不好,偶然发现 拔掉重新插就好的现象,后来再到将 ESP-01S的VCC重新插拔就好了,知道了: 官方 推荐 3.3V 电压,峰值 500mA 以上电 说明我们的板子供电可能不足 所以建议使用外部电源
  • 有个中断优先级的问题 就是最开始给USART1的优先级低于USART2的优先级 导致程序不能正常执行 可能是因为WIFI初始化时候有乱码发送 这时候串口1发送指令 而导致错乱 可能是因为这个
  • 我们STM32单片机 和 ESP-01S模块通过串口通信时连接可以只用使用 VCC GND TXD RXD 这四个引脚
  • 但是我自己感觉会有点问题的存在: 就是 在复位时 并不会 出现上电时候 那些乱码 ESP8266_Start
  • 所以我想到了 是不是因为 我当时RST复位引脚 在测试时没有上拉到VCC也可以正常使用 那我板子复位了 但WIFI模块不复位 重新跑我的程序 我觉得也是可以的 但是呢 这时发现 EN 使能位 出现了问题 不插可以 插就出现问题
    • 我加上EN使能位时,单片机重新复位程序时 容易卡死 而不使用EN使能位时 程序非常快的执行了一遍 但是不会出现上电时的那些乱码了 这个问题还需加深考虑
  • ==最终:==我还是将 VCC GND TXD RXD EN RST 引脚,都接上了 并且 在STM32复位时 将 RST复位引脚/VCC引脚 插拔一下 会更好的 运行 感觉是硬件问题 因为我使用的是最小开发板+模块

STM32与ESP8266通信

好了 上面是关于 如何对ESP-01S的模块硬件上的连接 以及一些AT指令 那么我接下来讲一下如何使STM32和ESP-01S进行通信 最后WIFI连接到我们的电脑上

STM32_ESP_01S

在单片机内部

  • 串口助手发送给USART1的数据 USART1通过USART2再传给 ESP8266
  • ESP8266发送给USART2的数据 USART2通过USART1再传给串口助手

可以看出以上就是我们STM32在串口助手和ESP01S之间的通信方式了,所以只要学会串口通信,在学习一下发送给ESP8266的指令就可以使用WIFI连接到我们的网路了,下面介绍一下 AT指令

AT指令

AT指令,其实就是一种通信协议,你可以理解为 串口是马路,而AT指令就是交通规则,只有正确的驾驶才可以通过,即我们通过串口向ESP8266发送数据帧 它会检测 你发送的是不是AT指令,从而做出相应的动作 指令集主要分为:基础 AT 指令、Wi-Fi 功能 AT 指令、TCP/IP 相关的 AT 指令

AT指令集非常多,我在这里只列举几个最常用的AT指令:

指令 含义 响应
AT 测试 AT 启动 OK
AT+RST 重启模块 OK
AT+CWMODE=1/2/3 设置 Wi-Fi 模式 (Station/SoftAP/Station+SoftAP) OK
AT+CWJAP="SSID","PWD" 设置 ESP8266 Station 需连接的 AP -- 连接WIFI OK
AT+CIPSTART="TCP","122.114.122.174",37590 建⽴ TCP 连接,UDP 传输或 SSL 连接 "安可信网址""端口号" OK
AT+CIPMODE=1 设置传输模式(0:普通传输模式 ‣ 1:透传模式,仅⽀持 TCP 单连接和 UDP 固定通信对端的情况) 透传模式传输时,如果连接断开,ESP8266 会不停尝试重连,此时单独输⼊ +++ 退出透传,则停⽌重 连;普通传输模式则不会重连,提示连接断开。 OK
AT+CIPSEND 发送数据 收到此命令后先换⾏返回 > ,注意:在不退出透传时AT指令将不起作用 >
+++ 退出透传模式(不能加换行) OK
AT+CIPCLOSE 关闭 TCP/UDP/SSL 传输 OK
AT+CIPMUX=1 设置多连接 OK
AT+CWSAP="ESP8266","1234567890",5,3 配置 ESP8266 SoftAP 参数 -- ESP8266为WIFI源 OK
AT+CIPSERVER=1,5050 建立TCP服务器( 0:关闭服务器 ‣ 1:建⽴服务器),端口号 OK
AT+CIFSR 查询本地 IP 地址 OK

AT指令的使用时,需要自动加上换行符号

想要练习AT指令 可以使用 安信可透传云 V1.0进行测试 使用多条发送的数据并不会打印到串口上,我们平常发送AT会有数据回显是因为厂商设置的我们也可以是使用ATE0指令来关闭回显

ESP8266_ANXINKE

STM32实战演习

通过以上学习,我们就可以与ESP8266进行通信 并连接自己的热点 注意:WIFI热点要开2.4GHZ的

本次实现连接淘宝API然后获取当前时间,获取时间的格式为 JSON数据 我们可以对JSON数据格式进行解析 然后把时间发送给RTC这样我们就可以永久保持这个时间了 对了当长时间不与TCP进行连接时服务器会主动断开与我们的连接

STM32_WIFIGetTime

USART1.h

#ifndef  __USART_H__//如果没有定义了则参加以下编译
#define  __USART_H__//一旦定义就有了定义 所以 其目的就是防止模块重复编译

#include "stm32f10x.h"
#include "stdio.h"
#include <stdarg.h>

void Usart_Init(void);
void Usart1_SendByte(uint8_t Byte);

extern char RxPacket[];
extern uint8_t RxFlag;
#endif  //结束编译

USART1.c

#include "usart.h"

uint8_t RxFlag;
/*
 *PA9  --  TX
 *PA10 --  RX
*/
void Usart_Init(void)
{
  //使能时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);//使能时钟A和USART1

  //为初始化函数做准备
  GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//设置PA9引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;//设置输出模式为复用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
  //初始化函数PIN9↓
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//设置PA10引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;//设置输出模式为上拉输入
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
  //初始化函数PIN10↓
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化

  USART_InitTypeDef USART_InitStructure; //定义串口结构体
  USART_InitStructure.USART_BaudRate = 115200; //波特率
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数字帧长度
  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_Tx|USART_Mode_Rx; //模式为发送+接收
  //初始化串口1
  USART_Init(USART1, &USART_InitStructure); 

  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

  NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //根据上面的我们所选取的USART1
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//这里选择的是抢占 1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //这里选择的是响应1
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能指定的中断通道
  //初始化函数↓
  NVIC_Init(&NVIC_InitStructure);
  //使能串口1
  USART_Cmd(USART1, ENABLE);
}
/**
  * @brief  串口1发送字节 -- 发送的最基本的函数 -->其它发送函数都是基于它
  * @param  
  * @retval 
  */
void Usart1_SendByte(uint8_t Byte)
{
  USART_SendData(USART1,Byte);
  while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );
}

/**
  * @brief  串口中断函数
            中断函数里面可以放你想要实现的功能函数
  * @param  
  * @retval 
  */
void USART1_IRQHandler(void)
{
  uint8_t data = 0;
  if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
  {
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    data = USART_ReceiveData(USART1);//获取串口助手发送给单片机的数据
    USART_SendData(USART2,data);//通过串口2发送 --> ESP8266
  }
}
/**
  * @brief  函数重定义--封装printf函数
  * @param  
  * @retval 
  */
int fputc(int ch, FILE *f)
{
  Usart1_SendByte(ch);
  return ch;
}

ESP8266.h

#ifndef  __ESP8266_H__//如果没有定义了则参加以下编译
#define  __ESP8266_H__//一旦定义就有了定义 所以 其目的就是防止模块重复编译

#include "stm32f10x.h"
#include "delay.h"
#include "stdlib.h"
#include "string.h"

void ESP8266_Init(void);
void Usart2_SendByte(uint8_t Byte);
void Usart2_SendString(uint8_t *string);
void Esp8266_SendString(uint8_t *string,uint16_t len);//串口2 发送字符串
uint8_t EspSendCmdAndCheckRecvData(uint8_t *cmd,uint8_t *Rcmd,uint16_t outtime);
void WIFI_ConnectTaoBao(void);

typedef  struct{
    uint8_t rxbuff[1024];
    uint16_t rxcount;
    uint8_t rxover;

    uint8_t txbuff[1024];
    uint16_t txcount;
}WIFI;

extern WIFI wifi;

#endif  //结束编译

ESP8266.c

#include "ESP8266.h"
/**
  * @brief  ESP8266初始化 
            PA2--TX
            PA3--RX
            PA5--ESP8266使能引脚
  */
WIFI wifi = {0};
void ESP8266_Init(void)
{
  //使能时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能时钟A
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能时钟USART2
  //为初始化函数做准备
  GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//设置PA2引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;//设置输出模式为复用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
  //初始化函数PIN2
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//设置PA3引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;//设置输出模式为上拉输入
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
  //初始化函数PIN3↓
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//设置PA5引脚 -- //ESP8266使能引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;//设置输出模式为推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
  //初始化函数PIN5↓
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化

  USART_InitTypeDef USART_InitStructure; //定义串口结构体
  USART_InitStructure.USART_BaudRate = 115200; //波特率
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数字帧长度
  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_Tx|USART_Mode_Rx; //模式为发送+接收
  //初始化串口2
  USART_Init(USART2, &USART_InitStructure); 

  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//使能接收中断
  USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//使能空闲中断

  NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //根据上面的我们所选取的USART2
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//这里选择的是抢占 1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //这里选择的是响应2
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能指定的中断通道
  //初始化函数↓
  NVIC_Init(&NVIC_InitStructure);
  //使能串口2
  USART_Cmd(USART2, ENABLE);

  GPIO_SetBits(GPIOA,GPIO_Pin_5);//GPIOA 高电平有效 -- 使能ESP8266
  Delay_ms(2000);//延时  等待WiFi模块稳定
}

/**
  * @brief  串口2发送字节 -- 发送的最基本的函数 -->其它发送函数都是基于它
  * @param  
  * @retval 
  */
void Usart2_SendByte(uint8_t Byte)
{
  USART_SendData(USART2,Byte);
  while(USART_GetFlagStatus(USART2,USART_FLAG_TXE) == RESET );
}
/**
  * @brief  串口中断函数
            中断函数里面可以放你想要实现的功能函数
  * @param  
  * @retval 
  */
void USART2_IRQHandler(void)
{
  uint8_t data = 0;
  if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
  {
    USART_ClearITPendingBit(USART2, USART_IT_RXNE);

    data = USART_ReceiveData(USART2);//ESP8266 发送给 STM32的数据

    wifi.rxbuff[wifi.rxcount++] = data;//将 ESP8266 发送给单片机的数据 转存到rxbuff里面

    USART_SendData(USART1,data); //可通过 串口助手在电脑屏幕上 显示
  }
  if(USART_GetITStatus(USART2, USART_IT_IDLE) == 1) //串口空闲中断
  {
      data = USART2->SR;

      data = USART2->DR;

      wifi.rxover = 1;
  }
}
/**
  * @brief  发送字符串
  * @param  string 字符串
  * @retval 
  */
void Usart2_SendString(uint8_t *string)
{
  for(uint8_t i = 0;string[i] != '\0';i++)
  {
    Usart2_SendByte(string[i]);
  }
}
/**
  * @brief  发送字符串 + 带长度
  * @param  
  * @retval 
  */
void Esp8266_SendString(uint8_t *string,uint16_t len)//串口3 发送字符串
{
  for(uint16_t i=0;i<len;i++)
  Usart2_SendByte(string[i]);
}

/**
  * @brief  发送AT指令,检查AT指令的返回,判断AT指令是否发送成功
  * @param  cmd:发送的AT指令
  * @param  Rcmd:AT指令对应的返回值
  * @param  outtime:ESP8266超时回复判断
  * @retval 
  */
uint8_t EspSendCmdAndCheckRecvData(uint8_t *cmd,uint8_t *Rcmd,uint16_t outtime)
{
  uint8_t data = 0;

  //发送AT指令之前要清空

  memset(wifi.rxbuff,0,1024);

  wifi.rxover = 0;

  wifi.rxcount = 0;

  Usart2_SendString(cmd);//发送AT指令 -- > 才会进入串口2中断服务

  while(outtime) 
  {
    if(wifi.rxover == 1)
    {
      if(strstr((char *)wifi.rxbuff,(char *)Rcmd) != NULL)
      {
        data = 1;
        break;
      }
    }
    outtime--;
    Delay_ms(1);
  }
  return data;
}
/**
  * @brief  连接淘宝API 获取当前时间
  * @param  
  * @retval 
  */
void WIFI_ConnectTaoBao(void)
{

    if(EspSendCmdAndCheckRecvData("AT\r\n","OK",1000) == 1)
  {
    if(EspSendCmdAndCheckRecvData("AT+CWMODE=1\r\n","OK",1000) == 1)
    {
      if(EspSendCmdAndCheckRecvData("AT+CWJAP=\"你的WIFI\",\"你的WIFI密码\"\r\n","OK",100000) == 1)
      {
        if(EspSendCmdAndCheckRecvData("AT+CIPSTART=\"TCP\",\"api.m.taobao.com\",80\r\n","OK",10000)==1)
        {
          EspSendCmdAndCheckRecvData("AT+CIPMODE=1\r\n","OK",1000);//开启透传
          EspSendCmdAndCheckRecvData("AT+CIPSEND\r\n",">",1000);//发送数据

          //清空回收的buff
          memset(wifi.rxbuff,0,1024);

          wifi.rxover = 0;

          wifi.rxcount = 0;

          Delay_ms(1000);
          Usart2_SendString("GET http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp\r\n");
          Delay_ms(1000);

          Usart2_SendString("+++");//退出透传模式
        }
      }
    }
  }
}

/**
  * @brief  解析JSON数据
  * @param  
  * @retval 
  */
void GetTime_RecvData(void)
{
  char Val[20] = {0};
  char tep[20] = {0};
  uint8_t i = 0;
  uint32_t timeget = 0;
//  long long timeget = 0;
  time_t time = 0;
  if(wifi.rxover==1)
  {
    wifi.rxover = 0;
    if(wifi.rxcount>10)//判断是否是 下发指令的回传的指令 而不是上传时系统默认下发的 //防止有影响
    {
    char *addr =  strstr((char *)(wifi.rxbuff+6),"\"t\"");
    addr+=5;
    while(*(addr + i) != '"')
    {
      Val[i] = *( addr+i);
      i++;
    }
    printf("%s\r\n",Val);
//    sscanf(Val, "%lld", &timeget);  // 存储类型为long long int
//    printf("%lld\r\n",timeget);

    for(i = 0;i<10;i++) //这种要去掉3位 才能使用atoi转换
    {
      tep[i] = Val[i];
    }
     printf("%s\r\n",tep);
    timeget = atoi(tep);
    memset(wifi.rxbuff,0,1024);

    wifi.rxcount = 0;

    RTC_Init(timeget);

    }
  }
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "usart.h"
#include "ESP8266.h"
//#include "RTC.h"
int main(void)
{
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  OLED_Init();
  Usart_Init();
  ESP8266_Init();
  WIFI_ConnectTaoBao();
//  GetTime_RecvData();
  while(1)
  {

  }
}

最后更新: November 4, 2023
创建日期: October 7, 2023

评论