【串口】蓝牙模块与简易数据包解析

我们已经学习了串口的DMA模式与如何收发不定长数据,本次我们将来学习蓝牙模块。

蓝牙是一种常见的无线通信协议,例如大家常用的蓝牙耳机,运动手环都是通过蓝牙与手机进行连接,蓝牙又分为经典蓝牙与低功耗蓝牙(BLE)两种。像蓝牙耳机这种,持续传输数据的设备,使用的就是经典蓝牙。而像运动手环这种间歇性同步数据的设备,则常用低功耗蓝牙,低功耗蓝牙一般也称为BLE。

蓝牙通信分为主机与从机,通常是由我们的单片机设备作为从机,手机等终端设备作为主机,在建立通信连接前,从机要向外广播自己的信息,然后主机扫描到附近正在广播的蓝牙设备(子机)后就发起连接。连接建立后,双方就可以通过约定好的协议进行通信了。

蓝牙的通信协议有些复杂,例如低功耗蓝牙中就包含GAP广播协议、GATT协议、Service、Characteristic等众多知识点。

在我们的开发板中,有一枚BT24蓝牙串口透传模块,这类模块的作用就是将复杂的蓝牙协议简化为串口透传。所谓透传就是将STM32串口发送给其的数据,原封不动地转发给与其连接的主机设备,并且将接收到的来自主机的数据通过串口转发给STM32。开发板上专门为蓝牙模块引出了USART3的TX与RX,以及为蓝牙模块供电的5V与GND。

找到4Pin的杜邦线,一段接在开发板,另一端接在蓝牙模块上。务必要注意线序正确,5V连5V、GND连GND、TX与RX交叉连接。

接下来建立一个工程

工程名称不妨叫做ble(低功耗蓝牙)。

首先我们将与蓝牙模块连接的USART3开启为异步模式,蓝牙模块的默认波特率为9600,我们修改一下配置,然后为USART3开启中断,以及添加DMA通道

保存并生成代码,根据之前的学习,首先我们在PV注释对中创建一个receiveData数组,不妨把数组设大一点,然后在程序刚开始时,使用HAL_UARTEx_ReceiveToIdle_DMA开启串口不定长数据接收,参数与之前一样,哪个串口接收,接收到哪个变量。以及最多接收多少数据,另外别忘了关闭DMA接收过半中断,或者不用DMA直接使用中断模式进行接收也是可以的,

我们去复制HAL_UARTEx_RxEventCallback的定义,回到main.c 在USER Code 0注释对中进行重新定义。首先还是好习惯,判断一下是不是huart3,然后将接收到的数据发送回蓝牙模块,要发的数据是receiveData,而发送的长度则是RxEventCallback的入参Size,另外别忘了再次开启接收,以及关闭DMA接受过半中断

我们来编译下载看看效果,为了与蓝牙模块进行通信,我们需要一个可以连接蓝牙的设备与软件,通常我们会使用手机上的某些BLE调试APP进行调试。例如比较专业的有nRF Connect功能强大而专业,但不建议刚入门,不了解蓝牙协议的去使用,简单的可以使用蓝牙透传模块生产商提供的简易版APP。大家可以在资料包中找到,安装到安卓手机就可以使用了。

不过,为了更方便地在编程时进行蓝牙调试,已经在串口助手上添加了蓝牙调试功能。电脑带有蓝牙功能的,首先要确保电脑的蓝牙功能已经开启,然后就可以打开串口助手,点击此图标,便可以从普通串口模式切换为蓝牙串口模式。

点击选择蓝牙,便开始扫描附近的蓝牙设备,对于我们使用的蓝牙模块而言,其默认名称为BT24。稍等一会就可以扫描到,如果有时等待很久也扫描不到,也可以按一下蓝牙模块上的复位键,将模块复位试试,选择BT24并点击配对,就可以将电脑作为主机与蓝牙模块建立蓝牙连接。然后我们就可以像平常使用串口助手一样,进行蓝牙模块的调试了。

例如我们随便发送一段数据,可以看到数据原封不动地返回来了,在发送一段更长的数据,也反回来了。

我们成功实现了通过蓝牙进行无线通信,接下来我们不妨做个稍微复杂的任务,假设我们有这样一个需求,要实现通过蓝牙向STM32发送指令,来控制来控制三色小灯的亮灭。指令数据包按照这样的16进制格式规定,

0x01 0x02 0x03分别代表红 绿 蓝三色小灯

0xFF代表亮 0x00代表灭

例如0x01 0x00代表红色小灯熄灭

可以同时对多组小灯控制,例如0x01 0x00 0x03 0xFF 代表红色小灯熄灭,蓝色小灯亮起

对于类似格式的指令,往往会有一个包头,用于指示一帧数据的开始,此处我们不妨就规定包头为0xAA,包头后往往有一位数据包长度,指示此数据包一共多长,然后在是指令或者数据的内容,最后还会有一个数据和,用来校验数据传递过程中是否出错。校验和是一种常见数据校验方式,其计算方式是将前面的数据依次相加,然后取最后1字节数据。

当我们接收到数据时,自行计算一遍收到数据的校验和,与数据包中自带的校验和进行比较,如果相等,则证明数据在传输过程中极大概率没有出错,如果不相等,那一定是在传输过程中某个数据传错了,那就按照实际的情况,忽略此数据包,或者要求发送方重新发送数据包

需求已定,我们开始编程。

首先我们回到Cube MX,与往常一样,将三个小灯的GPIO都是设置为推挽输出,并且设置用户标签

保存并生成代码,然后来到RxEventCallback,按照我们刚刚规定的数据包协议规则,首先我们要验证数据包的第0位,是不是我们规定的包头0xAA,然后还要验证数据包的第1位也就是数据包长度。是不是与接收到的数据包等长,如果这些都是正确的,我们就进行校验和的计算,然后判断一下校验和是否与数据包最后一位相等,来确认没有数据传输错误。所有的都校验完成,确认数据包信息无误,我们就可以对指令进行分析了。

写一个for循环,从指令内容的开头,也就是数据包第2位开始,到数据包最后截至,每次跨度设置为2。我们不妨把之前的代码复制过来进行改造,如果第i+1位数据是0x00,那就是熄灭小灯,否则就保持点亮。如果第i位数据是0x01,那就是红色小灯,绿色小灯是0x02,蓝色是0x03,改造完成。

复制代码
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if(huart == &huart3)
    {
        HAL_UART_Transmit_DMA(&huart3, receiveData, Size);
        if(receiveData[0] == 0xAA)
        {
            if(receiveData[1] == Size)
            {
                uin8_t sum = 0;
                for(int i = 0; i < Size - 1; i++)
                {
                    sum += receiveData[i];
                }
                if(sum == receiveData[Size - 1])
                {
                    for(int i = 2; i < Size - 1; i += 2)
                    {
                        GPIO_PinState state = GPIO_PIN_SET;
                        if(receiveData[i + 1] == 0x00)
                        {
                            state = GPIO_PIN_RESET;
                        }
                        if(receiveData[i] == 0x01)
                        {
                            HAL_GPIO_WritePin(LED_RED_GPIO_Port,LED_RED_Pin,state);
                        }
                        else if(receiveData[i] == 0x02)
                        {
                            HAL_GPIO_WritePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin,state);
                        }
                        else if(receiveData[i] == 0x03)
                        {
                            HAL_GPIO_WritePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin,state);
                        }
                    }
                }
            }
        }
        HAL_UARTEx_ReceiveToIdle_DMA(&huart3, receiveData, sizeof(receiveData));
        __HAL_DMA_DISABLE_IT(&hdma_usart3_rx,DMA_IT_HT);
    }
}

我们编译一下,来看一下效果。

我们先来试试让红灯亮起的指令,首先是数据包长度,这次应该是5个(0x05),然后是让红灯(0x01)亮起(0xFF),最后是校验和,我们可以打开电脑上的计算机切换到程序员模式进行计算,得到结果取最后两位即可(0xAF),发送后可以看到红色小灯成功亮起。再来试试让红色小灯熄灭,绿色小灯亮起。这次数据包长度是7个(0x07),然后红灯(0x01)熄灭(0x00)绿灯(0x02)亮起(0xFF),在用计算器算一下校验和()。每次修改了数据还要记得重新计算一次校验和,属实过于麻烦,在串口助手中添加了自动计算校验功能。我们只需要点击这里选择校验算法,例如我们用的校验和,就可以看到串口助手自动计算出了检验和。

我们发送数据,可以看到红色小灯成功熄灭,绿色小灯成功亮起,并且可以注意到发送的数据包中自动添加了校验和。

我们再来试试使用手机上的蓝牙调试助手与STM32进行蓝牙通信,首先要在电脑上断开蓝牙,或者按下蓝牙模块重启按钮,手机要开启蓝牙功能,并且授予APP精准定位权限(因为蓝牙可以被用于精准定位)。

选择透传,点击搜索BLE,选择扫描到的BT24模块。

然后输入数据,发送,可以看到实现了我们想要的效果。

回到代码,需要补充的是,我们本次实现的是在中断里进行的比较简易的数据包解析,如果数据包发送比较快,发送间隔比较小等情况,或者在比较复杂严苛的工程中,我们还是需要建立数据缓冲区,并且将解析数据的步骤搬出中断,在主循环中进行处理。最新的资料包中的UART_CIMMAND例程展示了如何通过循环缓冲区缓存数据,并在主循环中进行处理,考虑了数据粘包、数据丢失等情况,并且有详细的注释

相关推荐
国科安芯2 小时前
抗辐照DCDC电源模块在商业卫星通信载荷中的应用
网络·人工智能·单片机·嵌入式硬件
m0_377108142 小时前
物理day4-22
单片机
nuoxin1142 小时前
CYUSB4024-FCAXI 是一款USB 20Gbps 控制器-富利威
网络·人工智能·嵌入式硬件·fpga开发·dsp开发
charlie1145141912 小时前
嵌入式C++开发第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明
开发语言·c++·stm32·学习·c++23
wwddgod2 小时前
STM32L071 串口唤醒stop低功耗模式笔记
笔记·stm32·单片机·低功耗·串口唤醒
liuluyang5302 小时前
DW I2C寄存器与使用简介
stm32·单片机·嵌入式硬件·dw i2c
Mr..Jackey2 小时前
RA6809 的 HMI(人机交互) 开发:菜单逻辑架构设计与实现详解(4)
单片机·51单片机·人机交互·交互
笨笨饿3 小时前
#65_反激电源
stm32·单片机·嵌入式硬件·算法·硬件工程·个人开发
汽车芯猿3 小时前
嵌入式固件内存占用分析利器:Python实现S19/HEX地址空间可视化工具
python·单片机·嵌入式硬件