21.1 WIFI模组和AT指令介绍
开发板上的ESP8264具有串口透传转换为MQTT协议的功能:
注意RXD0和TXD0是烧录ESP8264固件的调试口,与单片机连接的应该用IO6(RXD)、IO7(TXD):
21.2 基于非阻塞方法,实现AT指令的驱动
乐鑫官方说明:
AT 命令集 - ESP32-C2 - — ESP-AT 用户指南 latest 文档
可得:
我们就把USART2设置为115200的波特率,并且BufferSize写死为256。
AT命令涉及到的特殊字符需要转义处理:
AT透传推荐串口使用空闲中断+DMA模式,USART2的RX通道和DMA0的CH2相连:
DMA需要偏移4个字节访问:
驱动层
下面先实现驱动层代码,串口需要用空闲中断+DMA模式,封装成结构体有:
#include <stdint.h> #include <stdio.h> #include <stdbool.h> #include <string.h> #include "gd32f30x.h" #include "delay.h" #define USART2_DATA_ADDR (USART2 + 0x04) //串口2的数据寄存器十六进制地址值 #define MAX_BUF_SIZE 256 #define WIFI_DISABLE gpio_bit_reset(GPIOG, GPIO_PIN_7) #define WIFI_ENABLE gpio_bit_set(GPIOG, GPIO_PIN_7) static uint8_t g_recvDataBuf[MAX_BUF_SIZE]; static uint16_t g_recvDataLen; typedef struct { uint32_t uartNo; rcu_periph_enum rcuUart; rcu_periph_enum rcuGpio; uint32_t gpio; uint32_t txPin; uint32_t rxPin; uint8_t irq; uint32_t dmaNo; rcu_periph_enum rcuDma; dma_channel_enum dmaCh; } UartHwInfo_t; // static UartHwInfo_t g_uartHwInfo = {USART2, RCU_USART2, RCU_GPIOB, GPIOB, GPIO_PIN_10, // GPIO_PIN_11, USART2_IRQn, DMA0, RCU_DMA0, DMA_CH2}; static UartHwInfo_t g_uartHwInfo = { .uartNo = USART2, .rcuUart = RCU_USART2, .rcuGpio = RCU_GPIOB, .gpio = GPIOB, .txPin = GPIO_PIN_10, .rxPin = GPIO_PIN_11, .irq = USART2_IRQn, .dmaNo = DMA0, .rcuDma = RCU_DMA0, .dmaCh = DMA_CH2, };IO口初始化,G7配置为推挽输出控制模组使能,TX复用推挽,RX上拉输入:
static void WifiGpioInit(void) { rcu_periph_clock_enable(RCU_GPIOG); gpio_init(GPIOG, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_7); rcu_periph_clock_enable(g_uartHwInfo.rcuGpio); gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, g_uartHwInfo.txPin); gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, g_uartHwInfo.rxPin); }串口透传WIFI指令分为下面几步:
①唤醒外设时钟;
②复位USART;
③设置波特率,这里传递115200;
③使能TEN\REN寄存器;
④使能串口接收空闲中断;
⑤置位UEN位使能USART。
static void WifiUartInit(uint32_t baudRate) { /* 使能UART时钟;*/ rcu_periph_clock_enable(g_uartHwInfo.rcuUart); /* 复位UART;*/ usart_deinit (g_uartHwInfo.uartNo); /* 通过USART_CTL0寄存器的WL设置字长;*/ //usart_word_length_set(g_uartHwInfo.uartNo, USART_WL_8BIT); /* 通过USART_CTL0寄存器的PCEN设置校验位;*/ //usart_parity_config(g_uartHwInfo.uartNo, USART_PM_NONE); /* 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;*/ //usart_stop_bit_set(g_uartHwInfo.uartNo, USART_STB_1BIT); /* 在USART_BAUD寄存器中设置波特率;*/ usart_baudrate_set(g_uartHwInfo.uartNo, baudRate); /* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/ usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE); /* 在USART_CTL0寄存器中设置REN位,使能接收功能;*/ usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE); /* 使能串口接收空闲中断;*/ usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_IDLE); /* 使能串口中断;*/ nvic_irq_enable(g_uartHwInfo.irq, 0, 0); /* 在USART_CTL0寄存器中置位UEN位,使能UART;*/ usart_enable(g_uartHwInfo.uartNo); }复习DMA配置流程,注意方向是外设到CPU,将数据从串口的数据寄存器(偏移0x04)搬运到全局数组之中即可:
static void WifiDmaInit(void) { /* 使能DMA时钟;*/ rcu_periph_clock_enable(g_uartHwInfo.rcuDma); /* 复位DMA通道;*/ dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh); dma_parameter_struct dmaStruct; /* 配置传输方向;*/ dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY; /* 配置数据源地址;*/ dmaStruct.periph_addr = USART2_DATA_ADDR; /* 配置源地址是固定的还是增长的;*/ dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; /* 配置源数据传输位宽;*/ dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; /* 配置数据目的地址;*/ dmaStruct.memory_addr = (uint32_t)g_recvDataBuf; /* 配置目的地址是固定的还是增长的;*/ dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; /* 配置目的数据传输位宽;*/ dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT; /* 配置数据传输最大次数;*/ dmaStruct.number = MAX_BUF_SIZE; /* 配置DMA通道优先级;*/ dmaStruct.priority = DMA_PRIORITY_HIGH; dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, &dmaStruct); /* 使能UART接收数据使用DMA;*/ usart_dma_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_DMA_ENABLE); /* 使能DMA通道;*/ dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh); }IDLE空闲中断的核心就是实现下面的中断服务函数:
void USART2_IRQHandler(void) { if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE) != RESET) { usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE); //第一步,读取stat0寄存器,清除IDLE标志位 usart_data_receive(g_uartHwInfo.uartNo); //第二步,读取数据寄存器 g_recvDataLen = MAX_BUF_SIZE - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh); memset(&g_recvDataBuf[g_recvDataLen], 0, MAX_BUF_SIZE - g_recvDataLen); printf("uart recv str:%s\n", g_recvDataBuf); //反面教材,中断里面用了printf dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh); dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, MAX_BUF_SIZE); dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh); } }串口每收到一个字节,由 DMA 自动搬进 g_recvDataBuf;当一帧数据发送完毕、串口检测到"总线空闲"(超过 1 字节时间没新数据)时触发 USART2_IRQHandler,在中断里计算出收到了多少字节。分行解读步骤如下:
①先确认触发源是 IDLE 中断(USART2 中断还可能来自其他源,先判断标志)。
②清除 IDLE 标志。手册规定 IDLE 位必须用"先读状态寄存器、再读数据寄存器"的顺序才能清掉,所以这里 clear + data_receive 两步配合使用。
③算接收长度。DMA 初始化时把传输次数设为 MAX_BUF_SIZE(256),每搬一个字节该计数减 1,所以 剩余计数 = dma_transfer_number_get(...),已收长度 = 总数 − 剩余。
④把缓冲区这次没用到的尾部清零,保证 g_recvDataBuf 能当 C 字符串安全使用(清掉上一帧残留)。
⑤打印结果。注释已说明——中断里调 printf 是反面教材(printf 走串口发送、会阻塞、有重入风险、可能丢数据)。
⑥重新初始化 DMA,为下一帧接收做准备。因为这里是普通(非循环)模式,需要关通道→重设传输次数(256)→开通道,相当于"复位接收窗口"。
应用层
用状态机避免死等:
static WifiCommState_t AtCmdHandle(char *cmd, char *rsp, uint32_t timeoutMs) { static WifiCommState_t s_commState = WIFI_COMM_OK; static uint64_t s_sendCmdTime; char *recvStrBuf; if (s_commState != WIFI_COMM_WAIT) { if (cmd != NULL) { SendWifiModuleStr(cmd); } s_commState = WIFI_COMM_WAIT; s_sendCmdTime = getSysRunTime(); } else { if ((getSysRunTime() - s_sendCmdTime) < timeoutMs) { recvStrBuf = RecvWifiModuleStr(); if (strstr(recvStrBuf, rsp) != NULL) { s_commState = WIFI_COMM_OK; } } else { s_commState = WIFI_COMM_FAIL; } } return s_commState; }21.3 基于状态机搭建程序框架
每10ms调用一次下面的函数:
①模组初始化由"单条查询"改为"两步配置 + 状态机"
旧逻辑:只发 AT+CWMODE? 查询。
新逻辑:ATE0(关闭回显)→ AT+CWMODE=1(设 Station 模式)。
CheckWifiModuleWork() 用 AtCheckModuleCmdType 枚举 + cmdType 静态变量驱动逐步执行,返回 OK/FAIL/WAIT 三态,而非之前的同步阻塞。
②新增 CheckWifiConnect():硬编码连接指定 2.4G Wifi
SSID/PWD 写死为 HUAWEI-R1CMF4_2.4G / ThreadxZWJ1024ARM。
用 sprintf 拼 AT+CWJAP="%s","%s",等待 GOT IP,超时 15s,最多重试 3 次。
③新增顶层状态机 WifiWorkState_t 驱动 WifiNetworkTask()
CHECK_WIFI_MODULE -> CHECK_WIFI_CONNECT -> CONNECT_MQTT_SERVER -> COMM_MQTT_SERVER | (FAIL) (预留) v HWRESET_WIFI_MODULE(最多1次) -> WIWI_MODULE_ERROR(放弃)wifi_app.c:
#include <stdint.h> #include <stdio.h> #include <stdbool.h> #include <string.h> #include "systick.h" #include "wifi_drv.h" #include "sensor_drv.h" #include "delay.h" #include "wifi_app.h" typedef enum { WIFI_COMM_WAIT, WIFI_COMM_OK, WIFI_COMM_FAIL, } WifiCommState_t; static WifiCommState_t AtCmdHandle(char *cmd, char *rsp, uint32_t timeoutMs) { static WifiCommState_t s_commState = WIFI_COMM_OK; static uint64_t s_sendCmdTime; char *recvStrBuf; if (s_commState != WIFI_COMM_WAIT) { if (cmd != NULL) { SendWifiModuleStr(cmd); } s_commState = WIFI_COMM_WAIT; s_sendCmdTime = getSysRunTime(); } else { if ((getSysRunTime() - s_sendCmdTime) < timeoutMs) { recvStrBuf = RecvWifiModuleStr(); if (strstr(recvStrBuf, rsp) != NULL) { s_commState = WIFI_COMM_OK; } } else { s_commState = WIFI_COMM_FAIL; } } return s_commState; } typedef struct { /* 要发送的AT命令 */ char *cmd; /* 期望的应答数据,默认处理匹配到该字符串认为命令执行成功 */ char *rsp; /* 得到应答的超时时间,达到超时时间为执行失败,单位ms*/ uint32_t timeoutMs; } AtCmdInfo_t; /*模组初始化命令集*/ static AtCmdInfo_t g_checkModuleCmdTable[] = { { .cmd = "ATE0\r\n", // 关闭回显 .rsp = "OK", .timeoutMs = 1000, }, { .cmd = "AT+CWMODE=1\r\n", // 设置为Station模式 .rsp = "OK", .timeoutMs = 1000, }, }; typedef enum { AT_E0 = 0, AT_CWMODE_1, } AtCheckModuleCmdType; WifiCommState_t CheckWifiModuleWork(void) { WifiCommState_t commState; static uint8_t retryCount = 0; static AtCheckModuleCmdType cmdType = AT_E0; switch (cmdType) { case AT_E0: commState = AtCmdHandle(g_checkModuleCmdTable[AT_E0].cmd, g_checkModuleCmdTable[AT_E0].rsp, g_checkModuleCmdTable[AT_E0].timeoutMs); if (commState == WIFI_COMM_OK) { retryCount = 0; cmdType = AT_CWMODE_1; } else if (commState == WIFI_COMM_FAIL) { retryCount++; if (retryCount == 3) { retryCount = 0; return WIFI_COMM_FAIL; } } break; case AT_CWMODE_1: commState = AtCmdHandle(g_checkModuleCmdTable[AT_CWMODE_1].cmd, g_checkModuleCmdTable[AT_CWMODE_1].rsp, g_checkModuleCmdTable[AT_CWMODE_1].timeoutMs); if (commState == WIFI_COMM_OK) { cmdType = AT_E0; return WIFI_COMM_OK; } else if (commState == WIFI_COMM_FAIL) { return WIFI_COMM_FAIL; } break; } return WIFI_COMM_WAIT; } static AtCmdInfo_t g_ConnectApCmdTable[] = { { .cmd = "AT+CWJAP=\"%s\",\"%s\"\r\n", // 这里的\是给编译器用的 //.cmd = "AT+CWJAP=\"HIKE_5F\\,2.4G\",\"hike666666\"\r\n", .rsp = "GOT IP", .timeoutMs = 15000, }, }; typedef enum { AT_CWJAP_SSID_PWD, } AtConnectApCmdType; //硬编码自己能用的2.4GWifi static char g_apSsid[20] = "HUAWEI-R1CMF4_2.4G"; static char g_apPwd[20] = "ThreadxZWJ1024ARM"; WifiCommState_t CheckWifiConnect(void) { WifiCommState_t commState; static AtConnectApCmdType cmdType = AT_CWJAP_SSID_PWD; static uint8_t retryCount = 0; char cmdStrBuf[256]; switch (cmdType) { case AT_CWJAP_SSID_PWD: sprintf(cmdStrBuf, g_ConnectApCmdTable[AT_CWJAP_SSID_PWD].cmd, g_apSsid, g_apPwd); commState = AtCmdHandle(cmdStrBuf, g_ConnectApCmdTable[AT_CWJAP_SSID_PWD].rsp, g_ConnectApCmdTable[AT_CWJAP_SSID_PWD].timeoutMs); if (commState == WIFI_COMM_OK) { retryCount = 0; return WIFI_COMM_OK; } else if (commState == WIFI_COMM_FAIL) { retryCount++; if (retryCount == 3) { retryCount = 0; return WIFI_COMM_FAIL; } } break; } return WIFI_COMM_WAIT; } typedef enum { CHECK_WIFI_MODULE, CHECK_WIFI_CONNECT, CONNECT_MQTT_SERVER, COMM_MQTT_SERVER, HWRESET_WIFI_MODULE, WIWI_MODULE_ERROR, } WifiWorkState_t; void WifiNetworkTask(void) { WifiCommState_t commState; static WifiWorkState_t workState = CHECK_WIFI_MODULE; static uint8_t hwresetCnt = 0; switch (workState) { case CHECK_WIFI_MODULE: commState = CheckWifiModuleWork(); if (commState == WIFI_COMM_OK) { workState = CHECK_WIFI_CONNECT; } else if (commState == WIFI_COMM_FAIL) { workState = HWRESET_WIFI_MODULE; } break; case CHECK_WIFI_CONNECT: commState = CheckWifiConnect(); if (commState == WIFI_COMM_OK) { workState = CONNECT_MQTT_SERVER; } else if (commState == WIFI_COMM_FAIL) { workState = CHECK_WIFI_MODULE; } break; case CONNECT_MQTT_SERVER: break; case COMM_MQTT_SERVER: break; case HWRESET_WIFI_MODULE: if (hwresetCnt < 1) // 如果AT命令不通,硬件复位1次 { HwresetWifiModule(); DelayNms(1000); workState = CHECK_WIFI_MODULE; hwresetCnt++; } else { printf("wifi module error!\n"); workState = WIWI_MODULE_ERROR; // 如果硬件复位1次,AT命令还是不通,就不再执行WIFI任务的业务逻辑,直接退出,避免影响其他任务 } break; default: break; } }