shuichan_old/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c

365 lines
13 KiB
C
Raw 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 "air780e.h" /* U2_CopyBuff / bg_delay */
#include "uart.h"
#include "log.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: 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/历史CWJAP保存的AP返回 0=拿到IP ====*/
int esp_wait_got_ip(int timeout_s)
{
log_info("> WiFi: wait saved AP auto-connect (%ds)", timeout_s);
if (esp_wait_countdown("WIFI GOT IP", timeout_s) != 0) {
log_warn("> WiFi: no saved AP auto-connect");
return -1;
}
log_info("> WiFi: saved AP joined");
return 0;
}
/*==== 连接固定 WiFi代码里保存的 SSID/PASS60s 超时 ====*/
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);
/* 配网成功后模块自动连接热点,等拿到 IP */
if (esp_wait("WIFI GOT IP", 10000) != 0) {
log_warn("> WiFi: SmartConfig no IP");
return -1;
}
log_info("> WiFi: SmartConfig joined AP");
return 0;
}
/*==== 恢复出厂设置AT+RESTORE模块忘掉已保存的热点用于测试 SmartConfig====
* 注意RESTORE 后模块自动重启且波特率回到出厂 115200
* 这里发送后主动把 UART4 切回 115200 探测确认 */
int esp_factory_restore(void)
{
log_info("> WiFi: AT+RESTORE (forget saved AP)");
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;
}