400 lines
14 KiB
C
400 lines
14 KiB
C
/* esp8266.c - ESP-01S (AT固件) 驱动
|
||
*
|
||
* 移植自参考工程《4路继电器控制带WIFI和4G V3.4 WIFI无回弹》HARDWARE/WIFI/wifi.c,
|
||
* 由标准库改为 HAL 库,串口由 USART2 改为 UART4(PC10/PC11),复位脚改为 PA5。
|
||
*
|
||
* 接收缓冲:与 4G 后端共享 U2_CopyBuff(WiFi 后端激活时由 UART4 IDLE 中断追加,
|
||
* 见 stm32f1xx_it.c 的 UART4_IRQHandler,两通道互斥由 network.c 切换时
|
||
* StopRx/StartRx 保证)。
|
||
*/
|
||
|
||
#include "esp8266.h"
|
||
#include "air780e.h" /* U2_CopyBuff / bg_delay */
|
||
#include "uart.h"
|
||
#include "log.h"
|
||
#include "led.h"
|
||
#include "system.h"
|
||
#include "stdio.h"
|
||
#include "string.h"
|
||
#include <stdarg.h>
|
||
|
||
/*==== 接收缓冲操作 ====*/
|
||
void esp_rx_clear(void)
|
||
{
|
||
U2_CopyIndex = 0;
|
||
U2_CopyBuff[0] = 0;
|
||
U2_CopyFlag = 0;
|
||
}
|
||
|
||
/* 二进制安全的子串查找(strstr 遇 0x00 会失明) */
|
||
static uint8_t *esp_memfind(uint8_t *hay, uint16_t hay_len, const char *needle)
|
||
{
|
||
uint16_t nlen = strlen(needle);
|
||
if (hay_len < nlen) return NULL;
|
||
for (uint16_t i = 0; i <= hay_len - nlen; i++) {
|
||
if (memcmp(hay + i, needle, nlen) == 0) return hay + i;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* 等待接收缓冲中出现关键字,期间切片喂狗/跑后台任务。
|
||
* 二进制安全匹配;出现 "ERROR" 立即失败返回,不等超时。返回 0=等到 */
|
||
static int esp_wait(const char *expect, int timeout_ms)
|
||
{
|
||
int t = timeout_ms / 100;
|
||
while (t-- > 0) {
|
||
bg_delay(100);
|
||
if (U2_CopyFlag) {
|
||
if (esp_memfind(U2_CopyBuff, U2_CopyIndex, expect)) return 0;
|
||
if (expect[0] != 'E' && esp_memfind(U2_CopyBuff, U2_CopyIndex, "ERROR")) return -1;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* 带倒计时日志的长等待:每秒打印一次剩余秒数,便于观察进度。
|
||
* 出现 "ERROR"/"FAIL" 立即失败返回。返回 0=等到 */
|
||
static int esp_wait_countdown(const char *expect, int timeout_s)
|
||
{
|
||
int remain = timeout_s * 10; /* 100ms 计数 */
|
||
int sec = timeout_s;
|
||
while (remain-- > 0) {
|
||
bg_delay(100);
|
||
if (U2_CopyFlag) {
|
||
if (esp_memfind(U2_CopyBuff, U2_CopyIndex, expect)) {
|
||
log_info("> WiFi: 等待完成 (剩 %ds)", sec);
|
||
return 0;
|
||
}
|
||
if (esp_memfind(U2_CopyBuff, U2_CopyIndex, "ERROR") ||
|
||
esp_memfind(U2_CopyBuff, U2_CopyIndex, "FAIL")) {
|
||
return -1;
|
||
}
|
||
}
|
||
if (remain % 10 == 0 && sec > 0) {
|
||
log_info("> WiFi: 倒计时 %ds", sec--);
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/*==== 透传/AT 数据发送:UART4,末尾自动加\r\n ====*/
|
||
static void ESP_printf(const char *fmt, ...)
|
||
{
|
||
static uint32_t last_tx_tick = 0;
|
||
char buf[256];
|
||
va_list ap;
|
||
|
||
/* 任意两条 AT 指令之间至少间隔 30ms,与 4G 侧一致 */
|
||
while (HAL_GetTick() - last_tx_tick < 30) {
|
||
HAL_Delay(1);
|
||
}
|
||
|
||
va_start(ap, fmt);
|
||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||
va_end(ap);
|
||
|
||
HAL_UART_Transmit(&huart4, (uint8_t *)buf, strlen(buf), 500);
|
||
HAL_UART_Transmit(&huart4, (uint8_t *)"\r\n", 2, 500);
|
||
last_tx_tick = HAL_GetTick();
|
||
}
|
||
|
||
/* 透传模式原始数据发送(MQTT 二进制报文,不加\r\n) */
|
||
void esp_send(const uint8_t *data, uint16_t len)
|
||
{
|
||
HAL_UART_Transmit(&huart4, (uint8_t *)data, len, 1000);
|
||
}
|
||
|
||
/*==== GPIO ====*/
|
||
void esp_gpio_init(void)
|
||
{
|
||
GPIO_InitTypeDef g = {0};
|
||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||
ESP_RST_HIGH(); /* 默认拉高,模块正常运行 */
|
||
g.Pin = ESP_RST_PIN;
|
||
g.Mode = GPIO_MODE_OUTPUT_PP;
|
||
g.Pull = GPIO_NOPULL;
|
||
g.Speed = GPIO_SPEED_FREQ_LOW;
|
||
HAL_GPIO_Init(ESP_RST_PORT, &g);
|
||
ESP_RST_HIGH();
|
||
}
|
||
|
||
/*==== 硬件复位 + 波特率同步 ====
|
||
* 拉低复位脚 500ms 后,先按 115200 探测(出厂/RESTORE 后的模块),
|
||
* 通则用 AT+UART_DEF 固定为 WIFI_BAUD_TARGET(掉电保存)并切换;
|
||
* 不通说明模块已是目标波特率,直接切换使用。 */
|
||
int esp_reset(void)
|
||
{
|
||
log_info("> WiFi: ESP 复位...");
|
||
|
||
/* 关闭中断/DMA,防止模块复位期间乱码触发中断风暴(同 4G cat_reset 思路) */
|
||
HAL_NVIC_DisableIRQ(UART4_IRQn);
|
||
HAL_NVIC_DisableIRQ(DMA2_Channel3_IRQn);
|
||
HAL_UART_DMAStop(&huart4);
|
||
DMA2_Channel3->CCR &= ~DMA_CCR_EN;
|
||
while (DMA2_Channel3->CCR & DMA_CCR_EN);
|
||
DMA2->IFCR = DMA_IFCR_CGIF3;
|
||
__HAL_UART_CLEAR_IDLEFLAG(&huart4);
|
||
__HAL_UART_CLEAR_OREFLAG(&huart4);
|
||
__HAL_UART_CLEAR_PEFLAG(&huart4);
|
||
__HAL_UART_CLEAR_FEFLAG(&huart4);
|
||
__HAL_UART_CLEAR_NEFLAG(&huart4);
|
||
esp_rx_clear();
|
||
|
||
ESP_RST_LOW();
|
||
bg_delay(500);
|
||
ESP_RST_HIGH();
|
||
bg_delay(1500); /* 等模块完成启动(boot log 输出完毕) */
|
||
|
||
/* 复位完成后重新启动 DMA+IDLE 接收 */
|
||
esp_rx_clear();
|
||
UART4_SetBaudRate(WIFI_BAUD_DEFAULT);
|
||
HAL_NVIC_EnableIRQ(DMA2_Channel3_IRQn);
|
||
HAL_NVIC_EnableIRQ(UART4_IRQn);
|
||
|
||
/* 先按 115200 探测 */
|
||
if (esp_send_cmd("AT", "OK", 1500) == 0) {
|
||
/* 通了:固定为目标波特率(AT+UART_DEF 掉电保存) */
|
||
if (esp_send_cmd("AT+UART_DEF=921600,8,1,0,0", "OK", 3000) == 0) {
|
||
UART4_SetBaudRate(WIFI_BAUD_TARGET);
|
||
bg_delay(200);
|
||
if (esp_send_cmd("AT", "OK", 1500) == 0) {
|
||
log_info("> WiFi: 波特率 -> 921600");
|
||
return 0;
|
||
}
|
||
/* 目标波特率通信失败:回滚为 115200,维持可用 */
|
||
log_warn("> WiFi: 921600 不可用, 回退到 115200");
|
||
UART4_SetBaudRate(WIFI_BAUD_DEFAULT);
|
||
bg_delay(100);
|
||
esp_send_cmd("AT+UART_DEF=115200,8,1,0,0", "OK", 2000);
|
||
return 0;
|
||
}
|
||
/* 固件不支持 UART_DEF 等:维持 115200 也能用 */
|
||
log_warn("> WiFi: UART_DEF 失败, 保持 115200");
|
||
return 0;
|
||
}
|
||
|
||
/* 115200 不通:模块可能已是目标波特率 */
|
||
UART4_SetBaudRate(WIFI_BAUD_TARGET);
|
||
if (esp_send_cmd("AT", "OK", 1500) == 0) {
|
||
log_info("> WiFi: 波特率已是 921600");
|
||
return 0;
|
||
}
|
||
|
||
log_warn("> WiFi: 所有波特率均无 AT 应答");
|
||
UART4_SetBaudRate(WIFI_BAUD_DEFAULT);
|
||
return -1;
|
||
}
|
||
|
||
/*==== 通用 AT 指令:发送→清缓冲→等关键字 ====*/
|
||
|
||
/* 接收缓冲十六进制转储(诊断用):strstr 遇 0x00 会失明,
|
||
* 字符串打印看不出缓冲里实际收到了什么 */
|
||
/* 接收缓冲内容转储(诊断用):按文本打印,不可打印字节显示为 '.' */
|
||
static void esp_dump_rx(const char *tag)
|
||
{
|
||
char text[65];
|
||
uint16_t len = U2_CopyIndex < 64 ? U2_CopyIndex : 64;
|
||
for (uint16_t i = 0; i < len; i++) {
|
||
uint8_t c = U2_CopyBuff[i];
|
||
text[i] = (c >= 32 && c <= 126) ? (char)c : '.';
|
||
}
|
||
text[len] = '\0';
|
||
log_warn("> WiFi 接收[%s] idx=%u: %s", tag, U2_CopyIndex, len ? text : "(empty)");
|
||
}
|
||
|
||
/*==== 通用 AT 指令:发送→清缓冲→等关键字 ====
|
||
* 注意清缓冲在发送【之后】:清掉上一条应答的迟到尾巴(如 CWJAP 的
|
||
* "WIFI GOT IP" 之后姗姗来迟的 OK)和命令回显,防止 esp_wait 匹配到
|
||
* 陈旧内容造成"假成功"(曾导致 AT+CIPMODE=1 实际失败却被判定 OK)。
|
||
* DMA+IDLE 架构下这样做是安全的:发送完成的瞬间模块应答还在路上,
|
||
* 在途字节留在 Rx_Buf 里,IDLE 后才会追加进 U2_CopyBuff。 */
|
||
int esp_send_cmd(const char *cmd, const char *expect, int timeout_ms)
|
||
{
|
||
ESP_printf("%s", cmd);
|
||
esp_rx_clear();
|
||
if (esp_wait(expect, timeout_ms) != 0) {
|
||
log_warn("> WiFi AT 超时: %s", cmd);
|
||
esp_dump_rx(cmd);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*==== 查询模块是否已自动连上保存的热点(SmartConfig 配过的 AP)====
|
||
* 主动查 AT+CIPSTATUS(STATUS:2=已拿到 IP),无被动等待的时序竞争。
|
||
* 注:模块保存的 AP 上电 2~3s 即连上,其 "WIFI GOT IP" 打印会被波特率
|
||
* 同步阶段的清缓冲擦掉,所以不能靠等打印判断。 */
|
||
int esp_saved_ap_connected(int timeout_s)
|
||
{
|
||
log_info("> WiFi: 检查已保存热点的自动连接 (%ds)", timeout_s);
|
||
for (int i = 0; i < timeout_s; i++) {
|
||
ESP_printf("AT+CIPSTATUS");
|
||
esp_rx_clear();
|
||
if (esp_wait("STATUS:2", 1000) == 0) {
|
||
log_info("> WiFi: 已连接保存的热点");
|
||
return 0;
|
||
}
|
||
}
|
||
log_warn("> WiFi: 无已保存的热点");
|
||
return -1;
|
||
}
|
||
|
||
/*==== 连接固定 WiFi(代码里保存的 SSID/PASS),超时见 WIFI_JOIN_TIMEOUT_S ====*/
|
||
int esp_join_fixed_ap(void)
|
||
{
|
||
char cmd[96];
|
||
|
||
log_info("> WiFi: 连接固定热点 '%s' (%ds)", WIFI_SSID, WIFI_JOIN_TIMEOUT_S);
|
||
|
||
/* 保持 CWAUTOCONN=1(默认):连接成功的AP会被模块保存,
|
||
* 之后重连可优先等自动连接(见 esp_wait_got_ip) */
|
||
snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\"", WIFI_SSID, WIFI_PASS);
|
||
ESP_printf("%s", cmd);
|
||
esp_rx_clear();
|
||
if (esp_wait_countdown("WIFI GOT IP", WIFI_JOIN_TIMEOUT_S) != 0) {
|
||
/* 提前结束 = 模块回了 FAIL/ERROR(密码错误等);数满倒计时 = 真超时(热点不在) */
|
||
log_warn("> WiFi: 固定热点连接失败或超时");
|
||
esp_dump_rx("CWJAP");
|
||
return -1;
|
||
}
|
||
log_info("> WiFi: 固定热点已连接");
|
||
return 0;
|
||
}
|
||
|
||
/*==== Smart Config (ESP-Touch) 智能配网,60s 倒计时 ====
|
||
* 手机 APP(ESP-Touch) 广播 WiFi 密码;成功后 AP 信息由模块自动保存,
|
||
* 之后重连可直接 AT+CWJAP 用已存热点。 */
|
||
int esp_smartconfig(void)
|
||
{
|
||
log_info("> WiFi: SmartConfig 开始 (%ds), 请用 ESP-Touch APP", WIFI_SMART_TIMEOUT_S);
|
||
g_net_led_smartconfig = 1; /* NET_LED 100ms 快闪提示配网模式 */
|
||
|
||
if (esp_send_cmd("AT+CWSTARTSMART", "OK", 5000) != 0) { g_net_led_smartconfig = 0; return -1; }
|
||
|
||
esp_rx_clear();
|
||
if (esp_wait_countdown("connected", WIFI_SMART_TIMEOUT_S) != 0) {
|
||
log_warn("> WiFi: SmartConfig 超时");
|
||
esp_send_cmd("AT+CWSTOPSMART", "OK", 2000);
|
||
g_net_led_smartconfig = 0;
|
||
return -1;
|
||
}
|
||
log_info("> WiFi: SmartConfig 已获取热点");
|
||
esp_send_cmd("AT+CWSTOPSMART", "OK", 2000);
|
||
g_net_led_smartconfig = 0;
|
||
|
||
/* 配网成功后模块自动连接热点。主动查 CIPSTATUS(STATUS:2=已拿到IP),
|
||
* 不能等 "WIFI GOT IP" 打印——它可能在 CWSTOPSMART 交互期间被清缓冲擦掉 */
|
||
if (esp_saved_ap_connected(10) != 0) {
|
||
log_warn("> WiFi: SmartConfig 未获取到 IP");
|
||
return -1;
|
||
}
|
||
log_info("> WiFi: SmartConfig 已连接热点");
|
||
return 0;
|
||
}
|
||
|
||
/*==== 退出透传模式(OTA 换连接前用)====
|
||
* "+++" 前后各需 1s 静默 guard time,且不能带 \r\n;
|
||
* 回命令态后顺手关掉当前 TCP 连接(失败忽略,后续 esp_reset 会兜底) */
|
||
int esp_exit_transparent(void)
|
||
{
|
||
bg_delay(1100);
|
||
esp_send((uint8_t *)"+++", 3);
|
||
bg_delay(1100);
|
||
if (esp_send_cmd("AT", "OK", 2000) != 0) {
|
||
log_warn("> WiFi: 退出透传无 AT 应答");
|
||
return -1;
|
||
}
|
||
esp_send_cmd("AT+CIPCLOSE", "OK", 2000);
|
||
log_info("> WiFi: 退出透传成功");
|
||
return 0;
|
||
}
|
||
|
||
/*==== 恢复出厂设置:AT+RESTORE,模块忘掉已保存的热点(用于测试 SmartConfig)====
|
||
* 注意 1:RESTORE 后模块自动重启且波特率回到出厂 115200,
|
||
* 这里发送后主动把 UART4 切回 115200 探测确认。
|
||
* 注意 2:WiFi 已连上时模块处于 TCP 透传态,直接发 AT 会被当作 TCP 数据
|
||
* 发往服务器(模块永远收不到),必须先 "+++" 退透传回命令态! */
|
||
int esp_factory_restore(void)
|
||
{
|
||
log_info("> WiFi: AT+RESTORE (忘记已保存热点)");
|
||
esp_exit_transparent(); /* 透传中先退回命令态;非透传时调用无害 */
|
||
esp_rx_clear();
|
||
ESP_printf("AT+RESTORE");
|
||
bg_delay(2500); /* 等模块执行 RESTORE 并完成重启 */
|
||
UART4_SetBaudRate(WIFI_BAUD_DEFAULT);
|
||
if (esp_send_cmd("AT", "OK", 2000) != 0) {
|
||
log_warn("> WiFi: 波特率复位后 RESTORE 无应答");
|
||
return -1;
|
||
}
|
||
log_info("> WiFi: RESTORE 完成, 已保存热点已清除");
|
||
return 0;
|
||
}
|
||
|
||
/*==== 连接 TCP 服务器并进入透传模式 ====*/
|
||
int esp_enter_transparent(const char *ip, uint16_t port)
|
||
{
|
||
char cmd[96];
|
||
|
||
log_info("> WiFi: 进入透传 %s:%u", ip, (unsigned)port);
|
||
|
||
/* 整体可重试一次:CIPSEND 收到 ERROR 多为 CIPMODE 未生效或连接状态残留 */
|
||
for (int attempt = 0; attempt < 2; attempt++) {
|
||
if (attempt > 0) {
|
||
log_warn("> WiFi: 透传重试");
|
||
bg_delay(1000);
|
||
}
|
||
|
||
/* 先清掉可能残留的旧 TCP 连接(上一轮失败没关的连接会让
|
||
* CIPMODE/CIPMUX 设置报 "link is builded" ERROR);
|
||
* 无连接时 CIPCLOSE 回 ERROR,忽略结果 */
|
||
esp_send_cmd("AT+CIPCLOSE", "OK", 1500);
|
||
|
||
/* CIPMUX=0 必须在 CIPMODE=1 之前:透传模式只在单连接下可设置 */
|
||
if (esp_send_cmd("AT+CIPMUX=0", "OK", 2000) != 0) continue;
|
||
if (esp_send_cmd("AT+CIPMODE=1", "OK", 2000) != 0) continue;
|
||
|
||
snprintf(cmd, sizeof(cmd), "AT+CIPSTART=\"TCP\",\"%s\",%u", ip, (unsigned)port);
|
||
ESP_printf("%s", cmd);
|
||
esp_rx_clear();
|
||
if (esp_wait("CONNECT", WIFI_TCP_TIMEOUT_MS) != 0) {
|
||
log_warn("> WiFi: TCP 连接失败");
|
||
esp_dump_rx("CIPSTART");
|
||
continue;
|
||
}
|
||
log_info("> WiFi: TCP 已连接, 接收: %.100s", U2_CopyBuff);
|
||
|
||
/* 等 CIPSTART 的 OK 收尾(先匹配到 CONNECT 时 OK 可能还没回来),
|
||
* 避免模块 busy 导致 CIPSEND 无响应 */
|
||
esp_wait("OK", 2000);
|
||
bg_delay(300);
|
||
|
||
/* 进透传:等 ">" */
|
||
ESP_printf("AT+CIPSEND");
|
||
esp_rx_clear();
|
||
if (esp_wait(">", 10000) == 0) {
|
||
esp_rx_clear();
|
||
log_info("> WiFi: 透传模式成功");
|
||
return 0;
|
||
}
|
||
|
||
log_warn("> WiFi: CIPSEND 无 '>'");
|
||
esp_dump_rx("CIPSEND");
|
||
/* 发 AT 探测:有 OK=仍在命令态;无应答=固件已自动进透传 */
|
||
ESP_printf("AT");
|
||
esp_rx_clear();
|
||
if (esp_wait("OK", 2000) != 0) {
|
||
log_warn("> WiFi: 无 AT 应答, 假定已进入透传");
|
||
esp_rx_clear();
|
||
return 0;
|
||
}
|
||
/* 仍在命令态:查 CIPMODE 实际值做诊断 */
|
||
if (esp_send_cmd("AT+CIPMODE?", "+CIPMODE:1", 2000) == 0) {
|
||
log_warn("> WiFi: CIPMODE 仍为 1, 连接可能被服务器关闭");
|
||
} else {
|
||
log_warn("> WiFi: CIPMODE 已还原或从未设置成功");
|
||
}
|
||
}
|
||
log_warn("> WiFi: 进入透传失败");
|
||
return -1;
|
||
}
|