21.1 WIFI模组和AT指令介绍开发板上的ESP8264具有串口透传转换为MQTT协议的功能注意RXD0和TXD0是烧录ESP8264固件的调试口与单片机连接的应该用IO6(RXD)、IO7TXD: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调用一次下面的函数①模组初始化由单条查询改为两步配置 状态机旧逻辑只发 ATCWMODE? 查询。新逻辑ATE0关闭回显→ ATCWMODE1设 Station 模式。CheckWifiModuleWork() 用 AtCheckModuleCmdType 枚举 cmdType 静态变量驱动逐步执行返回 OK/FAIL/WAIT 三态而非之前的同步阻塞。②新增 CheckWifiConnect()硬编码连接指定 2.4G WifiSSID/PWD 写死为 HUAWEI-R1CMF4_2.4G / ThreadxZWJ1024ARM。用 sprintf 拼 ATCWJAP%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 ATCWMODE1\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 ATCWJAP\%s\,\%s\\r\n, // 这里的\是给编译器用的 //.cmd ATCWJAP\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; } }
企业数字化 ERP 产品动态
相关推荐
编译报错指向编译器内部?先查Lombok和JDK的版本匹配 编译报错指向编译器内部?先查Lombok和JDK的版本匹配
项目编译报错,报错指向 Java 编译器内部的类,代码本身一点问题没有——这种情况,多半是 Lombok 和 JDK 的版本在打架。
先说原理:Lombok 是怎么工作的
Lombok 是一个… · 2026/11/10 12:05:47
绿色工厂合规数字化落地:台账型能碳管理平台的架构设计与实践方案 目录
背景:绿色工厂数字化的选型误区评审视角:绿色工厂对能碳数据体系的硬性约束两条技术路线对比:在线监测型 VS 台账核算型能源管理系统基于若依框架的轻量化能碳管理平台架构设计核心业务模块能力拆解落地实施路径:低侵入、分… · 2026/11/11 23:29:14
电工证报名甘肃兰州保姆级教程:别被中介坑了,全流程避坑指南 电工证报名甘肃兰州保姆级教程:别被中介坑了,全流程避坑指南 你是不是正在为【电工证报名甘肃兰州】发愁?看到各种中介广告、虚假信息、高价报名费,担心自己被坑,却又不知道从哪儿开始?别慌,这篇保姆级教程就是为你量身定制的,… · 2026/11/13 10:08:30
外省电工证怎么转过来?技能评价电工证电子证书怎么查 外省电工证怎么转过来?技能评价电工证电子证书怎么查 之前考的证在外省能不能转过来?这个问题,估计是很多在外地打工的电工朋友最头疼的事。尤其是像三门峡水利工程施工现场的电工,常常因为项目流动、跨省工作,导致证件管理变得复杂。现在国家推行… · 2026/11/13 10:08:30
初级电工证资格考试含金量高吗?怕考不过白交培训费别慌 初级电工证资格考试含金量高吗?怕考不过白交培训费别慌 怕考不过白交培训费?别急,先搞清楚初级电工证资格考试到底有啥用,含金量是不是真的高,再来考虑怎么备考。很多学员一上来就怕钱打水漂,其实只要方法对,考过不是问题。这篇文章就从安阳项目现场管… · 2026/11/13 10:08:30
横县电工证在哪里办官方回应来了,求职别再卡在证件上 横县电工证在哪里办官方回应来了,求职别再卡在证件上 你还在为 横县电工证在哪里办 发愁吗?手上没有特种作业操作证,连面试都进不去。尤其是像濮阳应届工程类毕业生,找工作处处碰壁, 官方回应… · 2026/11/13 10:08:30
工地太忙根本没时间复习考试?生产电子电工证全国通用怎么考更高效 工地太忙根本没时间复习考试?生产电子电工证全国通用怎么考更高效 工地太忙根本没时间复习考试,偏偏又得考生产电子电工证,不然连基本的上岗资格都没有。很多人问,这个证到底有没有用?能不能全国通用?今天咱们就从河南应届工程类毕业生的角度,说说这个… · 2026/11/13 10:08:30
三级技能电工证过期了不知道怎么复审?电子证书查询全攻略 三级技能电工证过期了不知道怎么复审?电子证书查询全攻略 证书过期了不知道怎么复审延期,是不是你最近遇到的烦心事?作为漯河房建工程的一线电工,你一定清楚三级技能电工证的重要性,它不仅是你从事电气作业的“通行证”,更是你在施工现场立足的底气。但… · 2026/11/13 10:07:27
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/12 14:09:01
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/13 5:40:45
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/12 23:27:43
维修电公用考电工证吗 在哪考最靠谱 维修电公用考电工证吗 在哪考最靠谱 工地太忙根本没时间复习考试,你是维修电工,天天在项目上跑,连抽个空背个题都难。那问题来了—— 维修电公用考电工证吗 ?答案是: 必须考 。尤其是在当前施工行业,没有电工证,连进场都可能被拦下。而且,… · 2026/11/13 0:00:08
电工证是不是可以登高?含金量和证书范围你必须知道的事 电工证是不是可以登高?含金量和证书范围你必须知道的事 证书过期了不知道怎么复审延期,这事儿很多人遇到过,尤其是像你这样在郑州金水区做劳务班组负责人,手里握着几十个工人的证书,稍微一疏忽,就可能影响工程进度和公司信誉。特别是电工证是不是可以登… · 2026/11/13 0:00:08
鸡西电工证哪家下证快官方报名入口怎么找 鸡西电工证哪家下证快官方报名入口怎么找 你是不是也担心 学历不高怕报不上名 ?别急,今天我就来跟你说说, 鸡西电工证哪家下证快 ,还有 官方报名入口 到底怎么找,别让那些“代办”“代考”耽误了你的机会。… · 2026/11/13 0:00:08
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/13 6:12:21
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/13 6:11:51
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/12 21:15:21