1. 硬件准备与环境搭建
STM32F103C8T6和ESP-01S这对组合在物联网开发中非常经典,前者是性价比极高的ARM Cortex-M3内核MCU,后者则是自带WiFi功能的通信模块。我刚开始接触这个组合时,最头疼的就是硬件连接问题。这里分享几个实测有效的接线技巧:
-
电源部分:ESP-01S需要3.3V供电,但峰值电流可能达到300mA。建议单独使用AMS1117稳压芯片供电,避免直接使用STM32的3.3V引脚导致电压不稳。我在项目中就遇到过因为供电不足导致WiFi频繁掉线的情况。
-
串口连接:STM32的PA9(TX)接ESP-01S的RX,PA10(RX)接ESP-01S的TX。注意一定要交叉连接,新手最容易犯的错误就是TX-TX直连导致通信失败。建议先用杜邦线测试,稳定后再考虑焊接。
-
复位电路:ESP-01S的EN引脚需要上拉10k电阻到3.3V,CH_PD引脚同样需要上拉。很多开发板省略了这个设计,导致模块无法正常启动。
开发环境推荐使用Keil MDK或者PlatformIO。我更喜欢后者,因为它的库管理更友好。安装时记得添加STM32CubeMX支持,方便生成初始化代码。第一次搭建环境可能会遇到驱动问题,特别是ST-Link的驱动,建议直接从ST官网下载最新版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ESP-01S固件烧录实战
ESP-01S出厂固件可能不支持MQTT协议,需要先烧录定制固件。安信可的1471号固件是我测试过最稳定的版本,支持AT指令集操作MQTT。烧录过程有几个关键点:
-
硬件准备:需要USB转TTL工具,连接方式如下:
- TTL工具的3.3V接ESP-01S的VCC
- GND对接GND
- TX接ESP-01S的RX
- RX接ESP-01S的TX
- GPIO0接地进入烧录模式
-
烧录软件配置:
bash复制
esptool.py --port COM3 write_flash 0x00000 firmware.bin波特率建议选择115200,太高可能导致失败。我第一次烧录时就因为选了460800导致校验失败。
-
常见问题处理:
- 如果报"Failed to connect",检查GPIO0是否可靠接地
- 烧录进度卡住可以尝试降低波特率
- 完成后记得断开GPIO0的接地,复位后进入正常运行模式
烧录成功后,发送"AT"指令应该能收到"OK"响应。如果没有反应,检查电源是否稳定,我遇到过因为USB口供电不足导致模块无响应的情况。
3. AT指令配置详解
ESP-01S通过AT指令与STM32交互,下面这些指令是我在项目中实际验证过的,附上详细参数说明:
3.1 基础配置指令
c复制// 关闭回显(避免重复接收自己发送的指令)
ATE0
// 设置STA模式(设备连接路由器)
AT+CWMODE=1
// 连接WiFi(替换你的SSID和密码)
AT+CWJAP="your_ssid","your_password"
这里有个坑:WiFi密码包含特殊字符时要用转义符。比如密码是"abc"123"的话,指令要写成:
c复制AT+CWJAP="home_wifi","abc\"123"
3.2 MQTT专用指令
配置MQTT参数时,Client ID必须唯一,我通常用设备MAC地址加时间戳:
c复制AT+MQTTUSERCFG=0,1,"client_123456","admin","public",0,0,""
连接MQTT服务器(以EMQX为例):
c复制AT+MQTTCONN=0,"broker.emqx.io",1883,1
最后的参数1表示自动重连,对于物联网设备强烈建议开启。
4. STM32端代码实现
STM32需要通过串口与ESP-01S通信,下面分享经过实战检验的代码框架:
4.1 串口初始化
c复制void USART1_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
// 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置TX(PA9)为推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置RX(PA10)为浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 串口参数配置
USART_InitStruct.USART_BaudRate = baudrate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);
// 启用串口
USART_Cmd(USART1, ENABLE);
}
4.2 AT指令发送函数
c复制void Send_AT_Command(char* cmd) {
USART_SendData(USART1, (uint8_t*)cmd, strlen(cmd));
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
delay_ms(100); // 等待模块响应
}
实际项目中需要添加超时检测和重试机制。我封装了一个带重试的函数:
c复制uint8_t Send_AT_With_Retry(char* cmd, char* expect, uint8_t retries) {
while(retries--) {
Clear_Buffer();
Send_AT_Command(cmd);
if(strstr(Receive_Buffer, expect) != NULL) {
return 1; // 成功
}
delay_ms(500);
}
return 0; // 失败
}
5. MQTT通信实战
5.1 主题订阅与发布
订阅主题的完整流程:
c复制// 订阅主题(QoS=1)
AT+MQTTSUB=0,"sensor/temperature",1
// 发布数据(JSON格式)
AT+MQTTPUB=0,"sensor/temperature","{\"value\":25.6}",1,0
这里有个细节:发布JSON数据时需要对引号转义。我在第一次尝试时就被这个坑过,发送的数据总是被截断。
5.2 消息接收处理
STM32通过串口中断接收ESP-01S返回的数据:
c复制void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char ch = USART_ReceiveData(USART1);
// 简单的环形缓冲区实现
rx_buffer[rx_index++] = ch;
if(rx_index >= BUFFER_SIZE) rx_index = 0;
// 检测到完整消息(以\n结尾)
if(ch == '\n') {
Process_Message(rx_buffer);
rx_index = 0;
memset(rx_buffer, 0, BUFFER_SIZE);
}
}
}
对于MQTT消息,通常格式如下:
code复制+MQTTSUBRECV:0,"sensor/temperature",15,{"value":25.6}
需要解析出主题和消息内容:
c复制void Process_Message(char* msg) {
if(strstr(msg, "+MQTTSUBRECV") != NULL) {
// 提取主题
char* topic_start = strchr(msg, '"') + 1;
char* topic_end = strchr(topic_start, '"');
*topic_end = '\0';
// 提取消息内容
char* payload_start = strchr(topic_end + 1, '"') + 1;
char* payload_end = strrchr(payload_start, '"');
*payload_end = '\0';
printf("Received [%s]: %s\n", topic_start, payload_start);
}
}
在实际项目中,建议使用状态机来解析AT指令响应,因为模块返回的数据可能分多次接收。我遇到过因为处理不完整导致系统死锁的情况,后来改用状态机就稳定多了。
