/* 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 "system.h" #include "stdio.h" #include "string.h" #include /*==== 接收缓冲操作 ====*/ 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: wait done (%ds left)", 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: countdown %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 reset..."); /* 关闭中断/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: baud -> 921600"); return 0; } /* 目标波特率通信失败:回滚为 115200,维持可用 */ log_warn("> WiFi: 921600 unusable, rollback to 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 failed, stay 115200"); return 0; } /* 115200 不通:模块可能已是目标波特率 */ UART4_SetBaudRate(WIFI_BAUD_TARGET); if (esp_send_cmd("AT", "OK", 1500) == 0) { log_info("> WiFi: baud already 921600"); return 0; } log_warn("> WiFi: no AT reply at any baud"); UART4_SetBaudRate(WIFI_BAUD_DEFAULT); return -1; } /*==== 通用 AT 指令:发送→清缓冲→等关键字 ====*/ /* 接收缓冲十六进制转储(诊断用):strstr 遇 0x00 会失明, * 字符串打印看不出缓冲里实际收到了什么 */ static void esp_dump_rx(const char *tag) { char hex[3 * 40 + 1]; int n = 0; uint16_t len = U2_CopyIndex < 40 ? U2_CopyIndex : 40; for (uint16_t i = 0; i < len; i++) { n += snprintf(hex + n, sizeof(hex) - n, "%02X ", U2_CopyBuff[i]); } if (len == 0) n = snprintf(hex, sizeof(hex), "(empty)"); log_warn("> WiFi rx[%s] idx=%u: %s", tag, U2_CopyIndex, hex); } /*==== 通用 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 t/o: %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: check saved AP auto-connect (%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: saved AP joined"); return 0; } } log_warn("> WiFi: no saved AP"); return -1; } /*==== 连接固定 WiFi(代码里保存的 SSID/PASS),超时见 WIFI_JOIN_TIMEOUT_S ====*/ int esp_join_fixed_ap(void) { char cmd[96]; log_info("> WiFi: join fixed AP '%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) { log_warn("> WiFi: fixed AP join timeout"); return -1; } log_info("> WiFi: fixed AP joined"); return 0; } /*==== Smart Config (ESP-Touch) 智能配网,60s 倒计时 ==== * 手机 APP(ESP-Touch) 广播 WiFi 密码;成功后 AP 信息由模块自动保存, * 之后重连可直接 AT+CWJAP 用已存热点。 */ int esp_smartconfig(void) { log_info("> WiFi: SmartConfig start (%ds), use ESP-Touch APP", WIFI_SMART_TIMEOUT_S); if (esp_send_cmd("AT+CWSTARTSMART", "OK", 5000) != 0) return -1; esp_rx_clear(); if (esp_wait_countdown("connected", WIFI_SMART_TIMEOUT_S) != 0) { log_warn("> WiFi: SmartConfig timeout"); esp_send_cmd("AT+CWSTOPSMART", "OK", 2000); return -1; } log_info("> WiFi: SmartConfig got AP"); esp_send_cmd("AT+CWSTOPSMART", "OK", 2000); /* 配网成功后模块自动连接热点。主动查 CIPSTATUS(STATUS:2=已拿到IP), * 不能等 "WIFI GOT IP" 打印——它可能在 CWSTOPSMART 交互期间被清缓冲擦掉 */ if (esp_saved_ap_connected(10) != 0) { log_warn("> WiFi: SmartConfig no IP"); return -1; } log_info("> WiFi: SmartConfig joined AP"); 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: exit transparent no AT reply"); return -1; } esp_send_cmd("AT+CIPCLOSE", "OK", 2000); log_info("> WiFi: exit transparent OK"); 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 (forget saved AP)"); 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 no reply after baud reset"); return -1; } log_info("> WiFi: RESTORE done, saved AP forgotten"); return 0; } /*==== 连接 TCP 服务器并进入透传模式 ====*/ int esp_enter_transparent(const char *ip, uint16_t port) { char cmd[96]; log_info("> WiFi: enter transparent %s:%u", ip, (unsigned)port); /* 整体可重试一次:CIPSEND 收到 ERROR 多为 CIPMODE 未生效或连接状态残留 */ for (int attempt = 0; attempt < 2; attempt++) { if (attempt > 0) { log_warn("> WiFi: transparent retry"); 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 connect failed"); esp_dump_rx("CIPSTART"); continue; } log_info("> WiFi: TCP connected, rx: %.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: transparent mode OK"); return 0; } log_warn("> WiFi: CIPSEND no '>'"); esp_dump_rx("CIPSEND"); /* 发 AT 探测:有 OK=仍在命令态;无应答=固件已自动进透传 */ ESP_printf("AT"); esp_rx_clear(); if (esp_wait("OK", 2000) != 0) { log_warn("> WiFi: no AT reply, assume already transparent"); esp_rx_clear(); return 0; } /* 仍在命令态:查 CIPMODE 实际值做诊断 */ if (esp_send_cmd("AT+CIPMODE?", "+CIPMODE:1", 2000) == 0) { log_warn("> WiFi: CIPMODE still 1, link may be closed by server"); } else { log_warn("> WiFi: CIPMODE reverted or never set"); } } log_warn("> WiFi: enter transparent failed"); return -1; }