shuichan_new/STM32F103_App1/HardWare/WIFI/esp8266.c

478 lines
16 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* esp8266.c - ESP-01S (AT固件) 驱动
*
* 移植自参考工程《4路继电器控制带WIFI和4G V3.4 WIFI无回弹》HARDWARE/WIFI/wifi.c
* 由标准库改为 HAL 库,串口由 USART2 改为 UART4(PC10/PC11),复位脚改为 PA5。
*
* 接收缓冲:与 4G 后端共享 U2_CopyBuffWiFi 后端激活时由 UART4 IDLE 中断追加,
* 见 stm32f1xx_it.c 的 UART4_IRQHandler两通道互斥由 network.c 切换时
* StopRx/StartRx 保证)。
*/
#include "esp8266.h"
#include "24c02.h"
#include "air780e.h" /* U2_CopyBuff / bg_delay */
#include "network.h"
#include "uart.h"
#include "log.h"
#include "led.h"
#include "system.h"
#include "stdio.h"
#include "string.h"
#include <stdarg.h>
#include <stdlib.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) {
g_net_ui_countdown = sec; /* OLED 显示用 */
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 复位...");
/* 先 ESP_EN 整体断电再上电:系统异常复位(如心跳超时重启)后模块可能
* 卡在透传/半死状态,仅 RST 不一定救得回,断电重启最彻底 */
NET_esp_power(0);
bg_delay(500);
NET_esp_power(1);
bg_delay(1000);
/* 关闭中断/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+CIPSTATUSSTATUS: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;
}
/* 加载固定热点凭据EEPROM 有效(蓝牙 SETWIFI 配置)优先,否则用编译默认 */
static void wifi_creds_load(char *ssid, char *pass)
{
uint8_t magic = 0;
eepromReadData(WIFI_CFG_MAGIC_ADDR, &magic, 1);
if (magic == WIFI_CFG_MAGIC) {
eepromReadData(WIFI_CFG_SSID_ADDR, ssid, WIFI_CFG_STR_LEN);
eepromReadData(WIFI_CFG_PASS_ADDR, pass, WIFI_CFG_STR_LEN);
ssid[WIFI_CFG_STR_LEN - 1] = 0;
pass[WIFI_CFG_STR_LEN - 1] = 0;
}
if (magic != WIFI_CFG_MAGIC || ssid[0] == 0) {
strcpy(ssid, WIFI_SSID);
strcpy(pass, WIFI_PASS);
}
}
/*==== 打印当前热点信号强度AT+CWJAP? 应答末字段为 RSSI(dBm) ====*/
void esp_log_rssi(void)
{
char *p = NULL;
/* 刚连上热点时模块可能还在收尾上一条 CWJAP回 busy p...),带延时重试 */
for (int attempt = 0; attempt < 3 && p == NULL; attempt++) {
if (attempt > 0) bg_delay(800);
ESP_printf("AT+CWJAP?");
esp_rx_clear();
if (esp_wait("+CWJAP:", 2000) == 0) {
p = (char *)esp_memfind(U2_CopyBuff, U2_CopyIndex, "+CWJAP:");
}
}
if (p == NULL) {
esp_dump_rx("CWJAP?"); /* 看模块实际回了什么,定位用 */
log_warn("> WiFi: 信号强度查询失败");
return;
}
/* 格式 +CWJAP:"ssid","bssid",ch,rssi取最后一个逗号后的整数 */
char *last = NULL;
{
for (char *q = p; *q && q < (char *)U2_CopyBuff + U2_CopyIndex; q++) {
if (*q == ',') last = q;
}
}
if (last) {
/* dBm 转百分比:-50dBm=100%(极好),-100dBm=0%(极限),线性映射 */
int rssi = atoi(last + 1);
int pct = 2 * (rssi + 100);
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
log_info("> WiFi: 信号强度 %d%% (%ddBm)", pct, rssi);
} else {
log_warn("> WiFi: 信号强度解析失败");
esp_dump_rx("CWJAP?");
}
}
/*==== 连接固定 WiFiEEPROM 或编译默认的 SSID/PASS超时见 WIFI_JOIN_TIMEOUT_S ====*/
int esp_join_fixed_ap(void)
{
char cmd[96];
char ssid[WIFI_CFG_STR_LEN], pass[WIFI_CFG_STR_LEN];
g_net_ui_phase = NET_UI_WIFI_FIXED;
wifi_creds_load(ssid, pass);
log_info("> WiFi: 连接固定热点 '%s' (%ds)", ssid, WIFI_JOIN_TIMEOUT_S);
/* 保持 CWAUTOCONN=1默认连接成功的AP会被模块保存
* 之后重连可优先等自动连接(见 esp_wait_got_ip
* 模块冷启动后首次 CWJAP 常因扫描不到 AP 报 +CWJAP:3失败自动重试一次 */
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt > 0) {
log_warn("> WiFi: 首次未连上2s 后重试");
bg_delay(2000);
}
snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\"", ssid, pass);
ESP_printf("%s", cmd);
esp_rx_clear();
if (esp_wait_countdown("WIFI GOT IP", WIFI_JOIN_TIMEOUT_S) == 0) {
log_info("> WiFi: 固定热点已连接");
return 0;
}
/* 提前结束 = 模块回了 FAIL/ERROR密码错误等数满倒计时 = 真超时(热点不在) */
esp_dump_rx("CWJAP");
}
log_warn("> WiFi: 固定热点连接失败或超时");
return -1;
}
/*==== 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_ui_phase = NET_UI_WIFI_SMART;
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====
* 注意 1RESTORE 后模块自动重启且波特率回到出厂 115200
* 这里发送后主动把 UART4 切回 115200 探测确认。
* 注意 2WiFi 已连上时模块处于 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;
}