基于STM32串口通信的ESP8266WiFi模块使用
掌握esp8266的使用可以实现真正的万物物联。esp8266wifi通信对于MCU而言归结到底还是串口或spi通信。因此,掌握RS232通信协议、SPI通信协议以及esp8266的配置就可以基本搞定WiFi模块的使用。
参考文章:https://blog.csdn.net/Utotao/article/details/88942864?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170528914016800226516517%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=170528914016800226516517&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-88942864-null-null.142^v99^pc_search_result_base4&utm_term=esp8266&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_44852376/article/details/127890213?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_utm_term~default-0-127890213-blog-127808117.235^v40^pc_relevant_anti_t3&spm=1001.2101.3001.4242.1&utm_relevant_index=1
1、ESP8266设置步骤
ESP8266是一款超低功耗的UART-WiFi 透传模块,拥有业内极富竞争力的封装尺寸和超低能耗技术,专为移动设备和物联网应用设计,可将用户的物理设备连接到Wi-Fi 无线网络上,进行互联网或局域网通信,实现联网功能。
ESP8266封装方式多样,天线可支持板载PCB天线,IPEX接口和邮票孔接口三种形式。ESP8266可广泛应用于智能电网、智能交通、智能家具、手持设备、工业控制等领域。
1.支持STA/AP/STA+AP 三种工作模式
2.内置TCP/IP协议栈,支持多路TCP Client连接
3.支持UART/GPIO数据通信接口
4.支持Smart Link 智能联网功能
5.内置32位MCU,可兼作应用处理器
6.3.3V 单电源供电
1.2 工作模式
ESP8266模块支持STA、AP、STA+AP 三种工作模式。
1.2.1 工作模式一:STA 模式
STA 模式: ESP8266模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。
1.2.2 工作模式二:AP 模式
AP 模式: ESP8266模块作为热点,实现手机或电脑直接与模块通信,实现局域网无线控制。
1.2.2 工作模式三:STA+AP 模式
STA+AP 模式: 两种模式的共存模式,即可以通过互联网控制可实现无缝切换,方便操作。
1.3 STA 模式配置方式:利用阿里云平台
设置模组为STA模式: AT+CWMODE=1 复位: AT+RST 连接wifi: AT+CWJAP="LAPTOP5","159357258" 用户信息配置: AT+MQTTUSERCFG=0,1,"k0qw9jwFM83.0101|securemode=2\,signmethod=hmacsha256\,timestamp=1704953806994|","0101&k0qw9jwFM83","cbec60c0cf274d4833215c0b6262a81f519c9be330c952e08ff84d553362dc0a",0,0,"" 连接服务器: AT+MQTTCONN=0,"iot-06z00iyrvaufnvr.mqtt.iothub.aliyuncs.com",1883,1 订阅消息:(儒要更改devicename,来源:产品->查看->Topic列表->物理模型通信一>属性上报->订阅) AT+MQTTSUB=0,"/sys/k0qw9jwFM83/0101/thing/event/property/post_reply",1 发布消息: AT+MQTTPUB=0,"/sys/k0qw9jwFM83/0101/thing/event/property/post","{\"method\":\"thing.event.property.post\"\,\"params\":{\"PowerSwitch_1\":1\,\"Humidity\":15\,\"temperature\":50}}",0,0
串口配置:
1.代码结构图:
2.串口初始化:在Hardware中添加文件夹Serial.c用来配置串口发送数据函数
首先是配置GPIO时钟以及USART时钟
void Serial_Init(void)
{ /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入 /*USART初始化*/ USART_InitTypeDef USART_InitStructure; //定义结构体变量 USART_InitStructure.USART_BaudRate = 115200; //波特率 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择 USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位 USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1 /*中断输出配置*/ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断 /*NVIC中断分组*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2 /*NVIC配置*/ NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1 NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设 /*USART使能*/ USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行 }
3.发送数据
/** * 函 数:串口发送一个字节 * 参 数:Byte 要发送的一个字节 * 返 回 值:无 */ void Serial_SendByte(uint8_t Byte) { USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成 /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/ } /** * 函 数:串口发送一个数组 * 参 数:Array 要发送数组的首地址 * 参 数:Length 要发送数组的长度 * 返 回 值:无 */ void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; for (i = 0; i < Length; i ++) //遍历数组 { Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据 } } /** * 函 数:串口发送一个字符串 * 参 数:String 要发送字符串的首地址 * 返 回 值:无 */ void Serial_SendString(char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止 { Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据 } } /** * 函 数:次方函数(内部使用) * 返 回 值:返回值等于X的Y次方 */ uint32_t Serial_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; //设置结果初值为1 while (Y --) //执行Y次 { Result *= X; //将X累乘到结果 } return Result; } /** * 函 数:串口发送数字 * 参 数:Number 要发送的数字,范围:0~4294967295 * 参 数:Length 要发送数字的长度,范围:0~10 * 返 回 值:无 */ void Serial_SendNumber(uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位 { Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字 } }
4.使用int fputc(int ch, FILE *f)重定向printf发送数据,需要添加include<stdio.h>。目的:使得printf发送数据到串口,而不是屏幕(printf默认是发送到电脑屏幕上)。
使用void Serial_Printf(char *format, ...)封装sprinf函数,要添加include<stdarg.h>。目的:使得串口1,串口2.....都可以使用printf发送数据。
/** * 函 数:使用printf需要重定向的底层函数 * 参 数:保持原始格式即可,无需变动 * 返 回 值:保持原始格式即可,无需变动 */ int fputc(int ch, FILE *f) { Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数 return ch; } /** * 函 数:自己封装的prinf函数 * 参 数:format 格式化字符串 * 参 数:... 可变的参数列表 * 返 回 值:无 */ void Serial_Printf(char *format, ...) //Serial2_Printf { char String[100]; //定义字符数组 va_list arg; //定义可变参数列表数据类型的变量arg va_start(arg, format); //从format开始,接收参数列表到arg变量 vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中 va_end(arg); //结束变量arg Serial_SendString(String); //使用串口1发送字符数组(字符串) 也可以在串口2中使用该函数,变成串口2定义的发送字符的函数,例如:Serial2_SendString(String); }
5.串口中断函数:
/** * 函 数:USART1中断函数 * 参 数:无 * 返 回 值:无 * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 * 函数名为预留的指定名称,可以从启动文件复制 * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 */ void USART1_IRQHandler(void) { static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断 { // i++; uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量 if(Serial_RxFlag==0) pRxPacket = 0; Serial_RxFlag=1; Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置 pRxPacket++; //数据包的位置自增 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位 } }
总体代码serial.c

#include "stm32f10x.h" // Device header #include <stdio.h> #include <stdarg.h> #include <string.h> #include "OLED.h" #define Serial_RxPacket_MAXLENGTH 200 char Serial_RxPacket[Serial_RxPacket_MAXLENGTH]; //定义接收数据包数组 uint8_t Serial_RxFlag; //定义接收数据包标志位 char* LED_ONCommand = "\"PowerSwitch_1\":1"; char* LED_OFFCommand = "\"PowerSwitch_1\":0"; uint32_t i=0; //测试用 /** * 函 数:串口初始化 * 参 数:无 * 返 回 值:无 */ void Serial_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入 /*USART初始化*/ USART_InitTypeDef USART_InitStructure; //定义结构体变量 USART_InitStructure.USART_BaudRate = 115200; //波特率 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择 USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位 USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1 /*中断输出配置*/ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断 /*NVIC中断分组*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2 /*NVIC配置*/ NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1 NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设 /*USART使能*/ USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行 } /** * 函 数:串口发送一个字节 * 参 数:Byte 要发送的一个字节 * 返 回 值:无 */ void Serial_SendByte(uint8_t Byte) { USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成 /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/ } /** * 函 数:串口发送一个数组 * 参 数:Array 要发送数组的首地址 * 参 数:Length 要发送数组的长度 * 返 回 值:无 */ void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; for (i = 0; i < Length; i ++) //遍历数组 { Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据 } } /** * 函 数:串口发送一个字符串 * 参 数:String 要发送字符串的首地址 * 返 回 值:无 */ void Serial_SendString(char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止 { Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据 } } /** * 函 数:次方函数(内部使用) * 返 回 值:返回值等于X的Y次方 */ uint32_t Serial_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; //设置结果初值为1 while (Y --) //执行Y次 { Result *= X; //将X累乘到结果 } return Result; } /** * 函 数:串口发送数字 * 参 数:Number 要发送的数字,范围:0~4294967295 * 参 数:Length 要发送数字的长度,范围:0~10 * 返 回 值:无 */ void Serial_SendNumber(uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位 { Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字 } } /** * 函 数:使用printf需要重定向的底层函数 * 参 数:保持原始格式即可,无需变动 * 返 回 值:保持原始格式即可,无需变动 */ int fputc(int ch, FILE *f) { Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数 return ch; } /** * 函 数:自己封装的prinf函数 * 参 数:format 格式化字符串 * 参 数:... 可变的参数列表 * 返 回 值:无 */ void Serial_Printf(char *format, ...) { char String[100]; //定义字符数组 va_list arg; //定义可变参数列表数据类型的变量arg va_start(arg, format); //从format开始,接收参数列表到arg变量 vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中 va_end(arg); //结束变量arg Serial_SendString(String); //串口发送字符数组(字符串) } uint8_t CheckCommand(void) { //判断接收到的指令是否包含"PowerSwitch_1":0" if (strstr(Serial_RxPacket,LED_OFFCommand)!=NULL) { //包含"PowerSwitch_1":0"指令,返回3 //清除数组内容 memset(Serial_RxPacket,0,sizeof(Serial_RxPacket)); return 3; } else if(strstr(Serial_RxPacket,LED_ONCommand)!=NULL) { //包含"PowerSwitch_1":1"指令,返回1 //清除数组内容 memset(Serial_RxPacket,0,sizeof(Serial_RxPacket)); return 1; } else { memset(Serial_RxPacket,0,sizeof(Serial_RxPacket)); return 2; } } /** * 函 数:USART1中断函数 * 参 数:无 * 返 回 值:无 * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 * 函数名为预留的指定名称,可以从启动文件复制 * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 */ void USART1_IRQHandler(void) { static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断 { // i++; uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量 if(Serial_RxFlag==0) pRxPacket = 0; Serial_RxFlag=1; Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置 pRxPacket++; //数据包的位置自增 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位 } }
总体代码serial.h

#ifndef __SERIAL_H #define __SERIAL_H #include <stdio.h> extern char Serial_RxPacket[]; extern uint8_t Serial_RxFlag; extern uint32_t i; void Serial_Init(void); void Serial_SendByte(uint8_t Byte); void Serial_SendArray(uint8_t *Array, uint16_t Length); void Serial_SendString(char *String); void Serial_SendNumber(uint32_t Number, uint8_t Length); void Serial_Printf(char *format, ...); uint8_t CheckCommand(void); #endif
总体代码main.h

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "LED.h" #include "string.h" #include "Key.h" //#include "Serial_2.h" uint8_t CheckCommand_Value; int main(void) { /*模块初始化*/ OLED_Init(); //OLED初始化 LED_Init(); //LED初始化 Serial_Init(); //串口1初始化 Key_Init(); //按键初始化 uint8_t KeyValue; Delay_s(4); //连接wifi: printf("AT+CWJAP=\"FL321\",\"123456789\"\r\n"); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "1/4:AT+CWJAP"); Delay_s(3); //用户信息配置: printf("AT+MQTTUSERCFG=0,1,\"k0qw9jwFM83.0101|securemode=2\\,signmethod=hmacsha256\\,timestamp=1704953806994|\",\"0101&k0qw9jwFM83\",\"cbec60c0cf274d4833215c0b6262a81f519c9be330c952e08ff84d553362dc0a\",0,0,\"\"\r\n"); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "2/4"); Delay_s(3); //连接服务器: printf("AT+MQTTCONN=0,\"iot-06z00iyrvaufnvr.mqtt.iothub.aliyuncs.com\",1883,1\r\n"); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "3/4"); Delay_s(3); //订阅消息: printf("AT+MQTTSUB=0,\"/sys/k0qw9jwFM83/0101/thing/event/property/post_reply\",1\r\n"); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "4/4"); Delay_s(3); while (1) { KeyValue=Key_GetNum();//获取按键值 if(KeyValue==2) { LED1_ON(); //发布消息: printf("AT+MQTTPUB=0,\"/sys/k0qw9jwFM83/0101/thing/event/property/post\",\"{\\\"method\\\":\\\"thing.event.property.post\\\"\\,\\\"params\\\":{\\\"PowerSwitch_1\\\":1\\,\\\"Humidity\\\":99\\,\\\"temperature\\\":36}}\",0,0\r\n"); Delay_s(3); LED1_OFF(); } if(KeyValue==1) { LED1_ON(); //发布消息: printf("AT+MQTTPUB=0,\"/sys/k0qw9jwFM83/0101/thing/event/property/post\",\"{\\\"method\\\":\\\"thing.event.property.post\\\"\\,\\\"params\\\":{\\\"PowerSwitch_1\\\":0\\,\\\"Humidity\\\":12\\,\\\"temperature\\\":40}}\",0,0\r\n"); Delay_s(3); LED1_OFF(); } if (Serial_RxFlag == 1) //如果接收到数据包 { Delay_ms(20); //等待串口接受完数据 // printf("%s\n",Serial_RxPacket); //CH340连接在A9A10 // OLED_ShowNum(3,1,i,5); //测试串口接收数据大小和内容 // i=0; CheckCommand_Value=CheckCommand(); if(CheckCommand_Value==1) { LED2_ON(); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "LED2_ON==OK"); } else if(CheckCommand_Value==3) { LED2_OFF(); OLED_ShowString(1, 1, " "); OLED_ShowString(1, 1, "LED2_OFF==OK"); } else if(CheckCommand_Value==2) { OLED_ShowString(2, 1," "); OLED_ShowString(2, 1,"error"); Delay_ms(500); OLED_ShowString(2, 1," "); } Serial_RxFlag = 0; //处理完成后,需要将接收数据包标志位清零,否则将无法接收后续数据包 CheckCommand_Value=0; } } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步