3.17 基于立创·梁山派GD32F470的安信可Ai-WB2-01S蓝牙WiFi模块驱动移植与手机控制LED实战
手把手教你用立创·梁山派驱动安信可Ai-WB2-01S蓝牙WiFi模块实现手机控制LED最近在做一个物联网小项目需要给GD32开发板加上无线通信功能选来选去安信可的Ai-WB2-01S模块挺合适它集成了蓝牙和WiFi价格也实惠。但网上的资料比较零散自己摸索移植驱动花了不少时间。今天我就把整个驱动移植和手机控制LED的实战过程整理出来希望能帮你少走弯路。这篇教程适合正在学习GD32或STM32想给单片机加上无线通信功能的同学。我会从硬件接线开始一步步带你完成串口驱动编写、AT指令解析最后实现用手机蓝牙APP控制开发板上的LED灯。整个过程都是基于立创·梁山派GD32F470开发板代码可以直接用。1. 准备工作认识模块与硬件连接在写代码之前咱们得先把硬件搞清楚、连对线。1.1 认识安信可Ai-WB2-01S模块Ai-WB2-01S是一个小巧的双模无线模块既能用蓝牙也能用WiFi。它通过最常用的串口UART和我们的主控单片机通信我们用发送AT指令的方式来控制它。几个关键参数你得知道工作电压3.3V。千万注意别接5V会烧坏工作电流峰值大于500mA。这意味着供电要足最好用开发板上的3.3V电源引脚别用一些电流输出能力弱的LDO。通信接口UART串口。引脚数8个引脚但实际常用的就4个。它的官方资料和AT指令集都在安信可的文档中心链接我放在这里有需要可以深入查看模块资料主页https://docs.ai-thinker.com/wb2AT指令集指南https://aithinker-combo-guide.readthedocs.io/en/latest/index.html1.2 连接梁山派开发板接线非常简单模块和开发板之间只需要连接4根线电源、地、发送TX、接收RX。这里有个关键点单片机的TX要接模块的RX单片机的RX要接模块的TX可别接反了。根据原文的移植方案具体连接关系如下Ai-WB2-01S模块引脚立创·梁山派引脚说明TXPB11模块发送接单片机接收RXPB10模块接收接单片机发送3V33V3电源正极3.3VGNDGND电源地注意模块上可能还有其他引脚如IO8、RST但根据官方指导这些引脚默认不可用除非特殊申请。我们做基础通信用上面这四根线就够了。接好线之后硬件部分就准备好了。接下来咱们在软件上让它们“对上话”。2. 软件驱动移植让单片机与模块通信我们的目标是让GD32F470通过串口2USART2与WB2模块通信同时用串口0USART0打印调试信息到电脑方便我们观察。2.1 基础工程与串口驱动修改我直接使用了立创官方提供的“005 串口打印信息”例程作为工程模板这样就有了串口0打印的基础。我们需要在此基础上增加串口2的驱动。首先打开bsp_usart.h文件添加串口2相关的宏定义和函数声明。#ifndef _BSP_USART_H #define _BSP_USART_H #include gd32f4xx.h #include systick.h // 串口0的配置用于调试打印原有 #define BSP_USART_TX_RCU RCU_GPIOA #define BSP_USART_RX_RCU RCU_GPIOA #define BSP_USART_RCU RCU_USART0 #define BSP_USART_TX_PORT GPIOA #define BSP_USART_RX_PORT GPIOA #define BSP_USART_AF GPIO_AF_7 #define BSP_USART_TX_PIN GPIO_PIN_9 #define BSP_USART_RX_PIN GPIO_PIN_10 #define BSP_USART USART0 #define USART_RECEIVE_LENGTH 256 // 接收缓冲区大小 extern uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区 extern uint8_t g_recv_length; // 接收数据长度 extern uint8_t g_recv_complete_flag; // 接收完成标志位 void usart0_gpio_config(uint32_t band_rate); void usart0_send_data(uint8_t ucch); void usart0_send_string(uint8_t *ucstr); // 新增串口2的配置用于连接WB2模块 void usart2_gpio_config(uint32_t band_rate); void usart2_send_data(uint8_t ucch); void usart2_send_string(uint8_t *ucstr); #endif /* BSP_USART_H */接下来是重头戏在bsp_usart.c中实现串口2的初始化、发送和中断接收函数。中断接收是这里的关键它能让我们及时、不丢数据地收到模块返回的AT指令响应。// ... 前面是串口0的代码保持不变 ... /************************************************ 函数名称 usart2_gpio_config 功 能 串口2配置GPIO和参数 参 数 band_rate:波特率 返 回 值 无 作 者 kwin *************************************************/ void usart2_gpio_config(uint32_t band_rate) { /* 1. 开启时钟 */ rcu_periph_clock_enable(RCU_USART2); // 开启USART2时钟 rcu_periph_clock_enable(RCU_GPIOB); // 开启GPIOB时钟 /* 2. 配置GPIO复用功能 */ // PB10 复用为 USART2_TX, PB11 复用为 USART2_RX gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_10); gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_11); /* 3. 配置GPIO模式 */ // TX引脚配置为复用功能、上拉模式 gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10); // RX引脚配置为复用功能、上拉模式 gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_11); /* 4. 配置GPIO输出类型和速度 */ gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_11); /* 5. 配置串口参数 */ usart_deinit(USART2); // 复位串口 usart_baudrate_set(USART2, band_rate); // 设置波特率 usart_parity_config(USART2, USART_PM_NONE); // 无校验位 usart_word_length_set(USART2, USART_WL_8BIT); // 8位数据位 usart_stop_bit_set(USART2, USART_STB_1BIT); // 1位停止位 /* 6. 使能串口功能 */ usart_enable(USART2); usart_transmit_config(USART2, USART_TRANSMIT_ENABLE); // 使能发送 usart_receive_config(USART2, USART_RECEIVE_ENABLE); // 使能接收 /* 7. 配置中断关键 */ nvic_irq_enable(USART2_IRQn, 2, 2); // 使能USART2中断设置优先级 usart_interrupt_enable(USART2, USART_INT_RBNE); // 使能接收缓冲区非空中断 usart_interrupt_enable(USART2, USART_INT_IDLE); // 使能总线空闲中断 }提示波特率我设置的是115300和原文保持一致。有些模块默认可能是115200如果通信不上可以检查一下这个参数。空闲中断IDLE很好用它能帮我们判断一帧数据是否接收完毕。串口2的发送函数和串口0类似就是换了个外设void usart2_send_data(uint8_t ucch) { usart_data_transmit(USART2, (uint8_t)ucch); while(RESET usart_flag_get(USART2, USART_FLAG_TBE)); // 等待发送完成 } void usart2_send_string(uint8_t *ucstr) { while(ucstr *ucstr) { usart2_send_data(*ucstr); } }最后也是最核心的中断服务函数。它负责在收到数据时把数据存到缓冲区并在检测到数据帧结束时空闲中断设置完成标志。/************************************************ 函数名称 USART2_IRQHandler 功 能 串口2中断服务函数 参 数 无 返 回 值 无 作 者 kwin *************************************************/ void USART2_IRQHandler(void) { // 处理接收缓冲区非空中断有数据来了 if(usart_interrupt_flag_get(USART2, USART_INT_FLAG_RBNE) SET) { // 将收到的数据存入缓冲区并增加长度计数 g_recv_buff[g_recv_length] usart_data_receive(USART2); } // 处理总线空闲中断一帧数据发完了 if(usart_interrupt_flag_get(USART2, USART_INT_FLAG_IDLE) SET) { usart_data_receive(USART2); // 读一下IDLE标志位来清除它读出的数据丢弃 g_recv_buff[g_recv_length] \0; // 给字符串加上结束符 g_recv_complete_flag 1; // 设置接收完成标志通知主循环处理 } }这个中断函数的设计很经典RBNE中断来一个字节存一个字节IDLE中断来判断一包数据收齐了。主循环只需要检查g_recv_complete_flag是否为1就知道有没有新数据要处理了。2.2 编写WB2模块的AT指令驱动串口通了接下来就是和模块“对话”了。对话的语言就是AT指令。我们创建一个专门的文件bsp_Ai_WB2_01S.c/.h来封装这些操作。头文件bsp_Ai_WB2_01S.h先声明我们要用到的函数#ifndef _bsp_Ai_WB2_01S_H #define _bsp_Ai_WB2_01S_H #include gd32f4xx.h #include systick.h uint8_t IsOK(void); // 测试AT指令是否返回OK uint8_t Set_WIFI(uint8_t *SSID, uint8_t *PWD); // 设置并连接WiFi void Get_Ver(void); // 获取模块版本号 void Get_Addr(void); // 获取模块蓝牙MAC地址 void Get_Name(void); // 获取模块蓝牙名称 void Get_Pin(void); // 获取模块Pin码 void Init_BLE(void); // 初始化蓝牙模块设置名称、UUID、模式等 #endif在源文件里我们实现最基础的几个函数。所有函数模式都类似发送AT指令 - 等待接收完成标志 - 处理或打印结果。这里以IsOK()和Init_BLE()为例#include bsp_usart.h #include bsp_Ai_WB2_01S.h /************************************************ 函数名称 IsOK 功 能 发送AT测试命令检查模块是否正常 参 数 无 返 回 值 1收到OK模块正常0超时或失败 作 者 kwin *************************************************/ uint8_t IsOK(void) { uint8_t i 0; usart2_send_string(AT\r\n); // 发送最基本的AT指令 // 等待接收完成标志超时则返回0 while(g_recv_complete_flag 0) { delay_1ms(5); i; if(i 200) return 0; // 大约等待1秒 } g_recv_complete_flag 0; // 清除标志准备下次接收 g_recv_length 0; // 清空接收长度 // 简单判断接收缓冲区里是否有OK字符串 if((g_recv_buff[2] O) (g_recv_buff[3] K)) return 1; else return 0; } /************************************************ 函数名称 Init_BLE 功 能 初始化蓝牙模块设置名称、UUID和工作模式 参 数 无 返 回 值 无 作 者 kwin *************************************************/ void Init_BLE(void) { uint8_t i 0; // 设置蓝牙设备名称手机搜索时会看到这个名字 usart2_send_string(ATBLENAMEKwin_Test\r\n); while(g_recv_complete_flag 0){ delay_1ms(1); i; if(i200) return; } g_recv_complete_flag 0; g_recv_length 0; i0; // 设置蓝牙透传服务的UUID可以理解为服务的唯一身份证号 usart2_send_string(ATBLESERUUID55535343fe7d4ae58fa99fafd2054321\r\n); while(g_recv_complete_flag 0){ delay_1ms(1); i; if(i200) return; } g_recv_complete_flag 0; g_recv_length 0; i0; // 设置发送(TX)特征的UUID usart2_send_string(ATBLETXUUID49535343884143f4a8d4ecbe34728888\r\n); while(g_recv_complete_flag 0){ delay_1ms(1); i; if(i200) return; } g_recv_complete_flag 0; g_recv_length 0; i0; // 设置接收(RX)特征的UUID usart2_send_string(ATBLERXUUID495353431e4d4bd9ba6123c647246666\r\n); while(g_recv_complete_flag 0){ delay_1ms(1); i; if(i200) return; } g_recv_complete_flag 0; g_recv_length 0; i0; // 设置蓝牙为从机模式0:从机1:主机 usart2_send_string(ATBLEMODE0\r\n); while(g_recv_complete_flag 0){ delay_1ms(1); i; if(i200) return; } g_recv_complete_flag 0; g_recv_length 0; }注意这里的UUID是示例值用于蓝牙透传服务。在实际产品中你可能需要生成或使用特定的UUID。ATBLEMODE0表示模块作为蓝牙从设备外设等待手机中心设备来连接。其他函数如Get_Ver获取版本、Get_Addr获取MAC地址等逻辑都差不多就是发送不同的AT指令如ATGMRATBLEMAC并等待回复。你可以参考这个模式根据官方AT指令集扩展更多功能。3. 主程序逻辑串联一切实现控制驱动都准备好了最后就是在main.c里把它们组织起来实现我们“手机控制LED”的最终目标。#include gd32f4xx.h #include systick.h #include stdio.h #include bsp_led.h #include bsp_usart.h #include bsp_Ai_WB2_01S.h int main(void) { uint8_t i 0; systick_config(); // 初始化系统滴答定时器用于延时 led_gpio_config(); // 初始化LED的GPIO控制LED2 usart0_gpio_config(115200); // 初始化串口0用于调试打印 usart2_gpio_config(115300); // 初始化串口2连接WB2模块 LED2OFF; // 初始关闭LED2 g_recv_complete_flag 0; g_recv_length 0; printf(AT test begin \r\n); // 第一步检测模块是否存在并通信正常 while(0 IsOK()) { LED2TOGGLE; // 如果模块无响应LED2闪烁提示 printf(waiting for Ai_WB2_01s\r\n); delay_1ms(500); // 延时约500ms实现1Hz闪烁 } printf(Module is OK!\r\n); // 第二步获取模块信息并初始化蓝牙 Get_Ver(); // 打印版本号到串口0 Init_BLE(); // 配置蓝牙名称、UUID、模式 Get_Addr(); // 获取并打印蓝牙MAC地址 Get_Pin(); // 获取并打印PIN码配对码 Get_Name(); // 获取并打印蓝牙名称 printf(BLE Init Done. Waiting for connection...\r\n); // 第三步主循环等待并处理手机发来的数据 while(1) { delay_1ms(100); // 简单延时降低CPU占用 // 如果串口2收到了一帧完整数据 if(g_recv_complete_flag) { // 简单协议判断前三个字符是否为LED if(g_recv_length 4) { if((g_recv_buff[0]L) (g_recv_buff[1]E) (g_recv_buff[2]D)) { // 第四个字符是k开灯 if(g_recv_buff[3] k) { LED2ON; printf(\r\nLED2 ON\r\n); // 串口0打印状态 usart2_send_string(LED2 is opened\r\n); // 回复给手机APP } // 第四个字符是g关灯 else if(g_recv_buff[3] g) { LED2OFF; printf(\r\nLED2 OFF\r\n); usart2_send_string(LED2 is closed\r\n); } } } else { // 如果不是控制指令直接把收到的内容打印出来看看 printf(REC:%s, g_recv_buff); } // 处理完数据清空标志和长度准备接收下一帧 g_recv_complete_flag 0; g_recv_length 0; } } }主程序的逻辑很清晰初始化配置系统时钟、LED、两个串口。模块握手发送AT指令等待模块回复OK确保硬件连接和通信正常。如果收不到OKLED2会闪烁报警。蓝牙配置读取模块信息并对其进行初始化设置好蓝牙名称、服务UUID等使其进入可被手机发现和连接的状态。循环监听在主循环中不断检查是否收到来自手机蓝牙的数据。协议解析与控制如果收到数据判断是否为预设的指令例如LEDk开灯LEDg关灯然后执行相应的LED控制操作并通过串口2给手机一个回复同时用串口0打印日志到电脑。4. 上电验证与手机端操作将代码编译下载到梁山派开发板给模块和开发板上电。打开串口调试助手连接开发板的USART0波特率115200你应该能看到类似以下的启动信息AT test begin Module is OK! ATGMR version: x.x.x (根据模块固件版本显示) ATBLEMAC xx:xx:xx:xx:xx:xx (模块的蓝牙MAC地址) BLE Init Done. Waiting for connection...现在拿出你的手机。你需要一个能连接蓝牙串口模块BLE Serial的APP比如“蓝牙串口”或“Serial Bluetooth Terminal”。在APP里搜索蓝牙设备应该能看到一个名叫“Kwin_Test”的设备点击连接。连接成功后在APP的发送框里输入LEDk并发送开发板上的LED2应该会点亮同时串口调试助手会打印LED2 ON手机APP也会收到回复LED2 is opened。发送LEDgLED2会熄灭。到这里整个从硬件连接到软件驱动再到手机控制的完整流程就走通了。你可以基于这个框架去实现更复杂的控制比如控制多个LED、PWM调光、读取传感器数据并通过蓝牙上报等等。希望这篇教程能帮你顺利迈出物联网开发的第一步。