☰
STM32F103C8T6 驱动 ESP8266 Station 模式接入手机热点实战
2026/10/15 15:06:10 网站建设 项目流程

简介:本资源面向嵌入式开发初学者与物联网爱好者,提供STM32F103C8T6驱动ESP8266模块、在Station模式下连接手机热点并发送数据的完整工程。项目以STM32作为控制核心,通过UART串口与ESP8266交互,涵盖GPIO与串口初始化、AT指令复位与配置、连接指定热点SSID及密码、数据收发等关键环节,帮助读者理解WiFi Station模式的工作原理与微控制器控制无线通信的方法。压缩包共89个文件,约1MB,以34个h头文件与33个c源文件为主,另含8个汇编启动文件、5张调试效果图及少量工程配置与说明文档,目录按User、Libraries、Project、Doc等模块划分,结构清晰便于检索。目前已有4082人学习下载。读者可参考其中的串口驱动、ESP8266驱动、LED指示与测试代码,快速搭建实验环境并掌握物联网通信开发思路。

1. STM32F103C8T6 配 ESP8266 走 Station 模式:把数据送到手机热点,到底难在哪

很多人第一次做 STM32F103C8T6 加 ESP8266 的联网实验,卡住的地方不是写代码,而是 ESP8266 的 Station 模式到底怎么配、AT 指令回什么算成功、手机热点为什么连不上。这个标题讲的就是一条最小可用链路:STM32F103C8T6 通过串口控制 ESP8266,让 ESP8266 作为 Station 接入手机开的热点,再把传感器数据发出去。它适合已经能点灯、能跑串口、想迈出无线通信第一步的嵌入式从业者,也适合做课程设计或原型验证的人。核心难点集中在三处:串口时序与电平、AT 指令的状态机、手机热点的频段与加密方式。把这三处理顺,整条链路就能稳定复现。

2. 先理清 Station 模式与硬件连接:为什么你的 ESP8266 一直返回 ERROR

2.1 Station 模式在链路里扮演什么角色

ESP8266 常见工作模式有三种:Station、AP、AP+Station。Station 模式的意思是模块作为客户端去连接一个已有热点,也就是手机开的热点。它拿到的是热点分配的 IP,数据通过这个 IP 往外走。AP 模式是模块自己当热点,让别人连它。两者方向相反,配置指令也不同。做数据上报,选 Station 模式,因为手机热点通常已经有网络出口,模块只要接入就能把数据送到手机上的 TCP 服务端或云平台。

STM32F103C8T6 在这里不直接处理网络协议,它只做两件事:通过 USART 发 AT 指令,解析模块返回的字符串。所以整条链路的稳定性,一半取决于 STM32 的串口收发是否可靠,一半取决于 AT 指令的顺序和等待时间。很多人把 ESP8266 当普通串口外设,发完指令立刻读,结果读到的是上一条的回显,状态机就乱了。

2.2 硬件接线与电平匹配的四个关键点

ESP8266 模块常见的是 ESP-01S 或 ESP-12F 系列,工作电压 3.3V,峰值电流能到 300mA 以上。STM32F103C8T6 的 USART 引脚是 3.3V 电平,理论上可以直接对接,但供电必须单独处理。

连接项STM32F103C8T6ESP8266注意
串口发送PA9 (USART1_TX)RX交叉连接
串口接收PA10 (USART1_RX)TX交叉连接
供电3.3V 稳压输出VCC电流要够,别用板载 LDO 小电流口
地GNDGND必须共地
使能普通 GPIOCH_PD / EN拉高才能工作

供电是血泪经验最多的地方。用 ST-Link 的 3.3V 给 ESP8266 供电,模块启动瞬间电流拉高,电压跌到 2.8V 以下,模块反复重启,串口打印一堆乱码。正确做法是单独一路 3.3V 稳压,输出能力至少 500mA,并在模块 VCC 和 GND 之间并一个 100uF 电解加 0.1uF 瓷片。CH_PD 引脚不能悬空,必须拉高,否则模块不启动。

2.3 串口初始化:波特率和中断接收怎么设

ESP8266 出厂默认波特率通常是 115200,但部分旧模块是 9600。STM32F103C8T6 的 USART1 挂在 APB2 上,72MHz 主频下配 115200 没问题。建议用中断接收加空闲中断的方式收数据,因为 AT 返回是变长字符串,轮询容易丢字节。

// USART1 初始化,PA9 复用推挽,PA10 浮空输入 void USART1_Init(uint32_t baud) { GPIO_InitTypeDef gpio; USART_InitTypeDef usart; NVIC_InitTypeDef nvic; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin = GPIO_Pin_9; gpio.GPIO_Mode = GPIO_Mode_AF_PP; // TX 复用推挽 gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); gpio.GPIO_Pin = GPIO_Pin_10; gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; // RX 浮空输入 GPIO_Init(GPIOA, &gpio); usart.USART_BaudRate = baud; usart.USART_WordLength = USART_WordLength_8b; usart.USART_StopBits = USART_StopBits_1; usart.USART_Parity = USART_Parity_No; usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &usart); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收非空中断 USART_Cmd(USART1, ENABLE); nvic.NVIC_IRQChannel = USART1_IRQn; nvic.NVIC_IRQChannelPreemptionPriority = 1; nvic.NVIC_IRQChannelSubPriority = 1; nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic); }

这段代码把 TX 配成复用推挽、RX 配成浮空输入,是 STM32 接串口外设的标准做法。波特率参数由调用方传入,方便在 9600 和 115200 之间切换排查。中断优先级不要设太高,否则容易打断其他时序。接收中断里只做一件事:把字节塞进环形缓冲区,主循环再去解析,避免在中断里做字符串比较。

3. AT 指令状态机:从复位到连上手机热点的完整顺序

3.1 一条可靠的 AT 指令发送函数

AT 指令的本质是「发一行字符串,等一个预期回显」。难点在于等待时间和回显匹配。ESP8266 在不同固件版本下返回的字符串略有差异,比如OK、ready、WIFI CONNECTED。写一个通用发送函数,把预期关键字和超时都作为参数传进去。

// 发送 AT 指令并等待预期关键字,返回 0 成功,-1 超时 int ESP_SendCmd(const char *cmd, const char *expect, uint32_t timeout_ms) { uint8_t rx_buf[256]; uint32_t start = GetTickCount(); // 毫秒级滴答 USART_ClearFlag(USART1, USART_FLAG_TC); while (*cmd) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, *cmd++); } // 补发回车换行,AT 指令必须以 \r\n 结尾 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, '\r'); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, '\n'); while (GetTickCount() - start < timeout_ms) { if (RingBuf_ReadLine(rx_buf, sizeof(rx_buf)) > 0) { if (strstr((char *)rx_buf, expect) != NULL) { return 0; // 命中预期关键字 } if (strstr((char *)rx_buf, "ERROR") != NULL) { return -2; // 模块明确报错 } } } return -1; // 超时 }

关键参数是timeout_ms。AT测试指令给 500ms 足够,AT+CWJAP连接热点要给 15000ms 以上,因为模块要扫描、认证、拿 IP。expect参数决定匹配逻辑,连热点时匹配WIFI GOT IP比匹配OK更可靠,因为OK可能在认证阶段就返回,实际还没拿到 IP。环形缓冲区按行读取,避免把多条返回粘在一起。

3.2 从复位到连上热点的指令序列

下面这条序列是经过多次验证的最小集合。每一步都要判断返回值,失败就重试或复位模块,不要一路发到底。

int ESP_ConnectHotspot(const char *ssid, const char *pwd) { if (ESP_SendCmd("AT\r\n", "OK", 500) != 0) return -1; if (ESP_SendCmd("AT+CWMODE=1\r\n", "OK", 500) != 0) return -2; // 1 = Station if (ESP_SendCmd("AT+RST\r\n", "ready", 3000) != 0) return -3; // 复位等 ready Delay_ms(1000); if (ESP_SendCmd("AT+CWDHCP=1,1\r\n", "OK", 1000) != 0) return -4; // 开 DHCP // 连接热点,SSID 和密码用引号包住 char cmd[128]; sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd); if (ESP_SendCmd(cmd, "WIFI GOT IP", 20000) != 0) return -5; return 0; }

AT+CWMODE=1把模块设成 Station。AT+RST复位后要等ready,这个字符串出现说明固件启动完成。AT+CWDHCP=1,1开启 Station 侧的 DHCP,让模块自动获取 IP,不写这条有些固件会拿不到地址。AT+CWJAP是连接热点的核心指令,SSID 和密码必须用英文双引号包住,密码里如果有特殊字符要转义。等待WIFI GOT IP而不是OK,是因为只有拿到 IP 才算真正接入。

3.3 手机热点侧的三个设置项

手机热点不是开了就能连。常见做法是进热点设置里确认三件事:频段选 2.4GHz、加密方式选 WPA2、最大连接数至少为 1。ESP8266 只支持 2.4GHz,手机热点如果默认开 5GHz,模块扫描不到,AT+CWJAP会一直返回FAIL。加密方式如果是 WPA3,部分旧固件不支持,也会失败。把热点名设成纯英文加数字,避免中文 SSID 带来的编码问题。

提示:调试阶段先把手机热点密码设简单一点,排除密码字符转义问题,链路通了再换复杂密码。

4. 数据发送与接收验证:TCP 连接怎么建、数据怎么发

4.1 建立 TCP 连接并发送一帧数据

连上热点后,模块有了 IP,接下来要连一个服务端。手机端可以装一个 TCP 调试工具,监听某个端口,模块作为客户端去连。指令是AT+CIPSTART,发送数据前要先AT+CIPSEND申请长度。

int ESP_SendData(const char *server_ip, uint16_t port, const char *data) { char cmd[128]; // 建立 TCP 连接 sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", server_ip, port); if (ESP_SendCmd(cmd, "CONNECT", 10000) != 0) return -1; // 申请发送长度 sprintf(cmd, "AT+CIPSEND=%d\r\n", strlen(data)); if (ESP_SendCmd(cmd, ">", 2000) != 0) return -2; // 收到 '>' 后发送实际数据 USART_SendString(USART1, data); if (ESP_SendCmd("", "SEND OK", 5000) != 0) return -3; return 0; }

AT+CIPSTART的返回关键字用CONNECT,表示 TCP 三次握手完成。AT+CIPSEND后模块返回>提示符,这时才能发数据,发完等SEND OK。数据长度必须和实际发送字节数一致,多一个少一个都会导致模块等待或报错。如果服务端在手机热点所在的手机上,IP 填手机在热点网段里的地址,通常是 192.168.x.1 这类网关地址,具体看手机热点设置里显示的网关。

4.2 用单连接模式简化状态管理

ESP8266 支持多连接和单连接。做数据上报,建议用单连接模式AT+CIPMUX=0,这样同一时刻只有一个 TCP 链路,状态机简单,不容易出现连接 ID 混乱。多连接模式要维护 0 到 4 的连接号,发送时要带连接号,调试成本高。单连接模式下AT+CIPSEND不需要带连接号,出错概率低。

// 初始化时设单连接 ESP_SendCmd("AT+CIPMUX=0\r\n", "OK", 1000);

如果一次发送的数据超过模块单包限制,常见做法是分片,每片不超过 2048 字节,逐片AT+CIPSEND。实际传感器数据一般几十字节,不用考虑分片。

4.3 接收服务端下发的数据

如果只是上报,可以不处理接收。但要做双向通信,就要开透传或解析+IPD。+IPD,<长度>:<数据>是模块收到网络数据的格式。在环形缓冲区解析时,先找+IPD,再读长度,再取对应字节数。透传模式用AT+CIPMODE=1加AT+CIPSEND进入,进入后串口数据直接走网络,退出要发+++,且前后要有至少 1 秒静默。透传省事但不好做协议解析,建议先用非透传把链路跑通。

5. 避坑与排查:连不上热点时先看这五条

5.1 模块反复打印乱码或 ready

现象是串口一直输出乱码,偶尔夹着ready。原因是供电不足导致模块欠压复位。解决是换独立 3.3V 稳压,输出能力 500mA 以上,VCC 并 100uF 加 0.1uF,CH_PD 确认拉高。用万用表量模块 VCC 对 GND,启动瞬间不应低于 3.0V。

5.2 AT 返回 OK 但 CWJAP 一直 FAIL

现象是AT和AT+CWMODE都正常,AT+CWJAP返回FAIL。原因通常是热点频段是 5GHz,或加密方式是 WPA3,或 SSID 含中文。解决是把手机热点切到 2.4GHz、WPA2、纯英文 SSID,再试。也可以用AT+CWLAP扫描,看目标热点是否出现在列表里,不出现就是频段或距离问题。

5.3 拿到 IP 但 CIPSTART 连不上

现象是WIFI GOT IP出现,AT+CIPSTART返回ERROR或超时。原因是服务端 IP 或端口写错,或手机上的 TCP 工具没启动监听。解决是确认服务端监听地址是热点网段内的地址,端口没被占用,手机防火墙没拦。先用手机 ping 一下模块 IP,确认二层可达。

5.4 发送数据后收不到 SEND OK

现象是AT+CIPSEND返回>,数据发出去后卡住。原因是声明长度和实际发送字节数不一致,或数据里含\r\n被模块当成结束符。解决是发送前用strlen精确算长度,数据里避免裸回车换行,必要时做转义或改用十六进制发送。

5.5 运行一段时间后掉线

现象是连续跑几小时后模块无响应。原因是模块发热、电源纹波、或热点侧踢掉空闲连接。解决是加看门狗,定期发AT心跳,检测失败就重新走一遍连接流程。热点侧如果有省电策略,把热点保持常开,别让手机自动关热点。

6. 把链路做稳的进阶习惯:状态机加超时重连

把上面跑通只是第一步,真正要投入项目,得让它在无人值守下自己恢复。我一般会写一个连接状态机,用枚举表示IDLE、CHECK_MODULE、JOIN_AP、CONNECT_TCP、SEND_DATA、RECONNECT几个状态,每个状态带一个超时计数。主循环每 100ms 跑一次状态机,超时就降级到RECONNECT,重新从AT开始。这样即使模块被干扰复位,也能自己爬回来。

typedef enum { ST_IDLE, ST_CHECK, ST_JOIN, ST_TCP, ST_SEND, ST_RECONN } EspState; EspState g_state = ST_IDLE; uint32_t g_state_tick = 0; void Esp_StateMachine(void) { switch (g_state) { case ST_CHECK: if (ESP_SendCmd("AT\r\n", "OK", 500) == 0) { g_state = ST_JOIN; g_state_tick = GetTickCount(); } else if (GetTickCount() - g_state_tick > 3000) { g_state = ST_RECONN; // 模块无响应,走复位 } break; case ST_JOIN: if (ESP_ConnectHotspot(SSID, PWD) == 0) { g_state = ST_TCP; } else if (GetTickCount() - g_state_tick > 30000) { g_state = ST_RECONN; } break; case ST_TCP: if (ESP_SendData(SERVER_IP, SERVER_PORT, "hello") == 0) { g_state = ST_SEND; } else { g_state = ST_RECONN; } break; case ST_SEND: // 周期性上报,间隔到了再发 if (GetTickCount() - g_state_tick > 5000) { g_state = ST_TCP; g_state_tick = GetTickCount(); } break; case ST_RECONN: ESP_SendCmd("AT+RST\r\n", "ready", 3000); Delay_ms(2000); g_state = ST_CHECK; g_state_tick = GetTickCount(); break; default: g_state = ST_CHECK; g_state_tick = GetTickCount(); break; } }

这个状态机的核心是每个状态都有超时兜底,不会卡死在某一步。ST_RECONN里先复位模块再回到检查,比直接重发指令更彻底。实际部署时把SEND_DATA的间隔和传感器采样周期对齐,避免频繁建连。验证方法很简单:把手机热点关掉再打开,看模块能不能在 30 秒内自己恢复上报。我踩过的坑是只做了发送没做心跳,模块假死后主循环还在跑,数据早就断了,后来加了AT心跳检测才暴露出来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询