STM32的蓝牙通讯(HAL库)

蓝牙基础知识(了解即可):

1.是一种利用低功率无线电,支持设备短距离通信的无线电技术,能在包括移动电话、PDAQ、无线耳机、笔记本电脑、相关外设等众多设备之间进行无线信息交换,蓝牙工作在全球通用的2.4 GHz(2.4 至 2.485 GH)ISM(即工业、科学、医学)频段(和WI-FI频段一样就能结合起来用),使用IEEE802.11协议。

第一~三蓝牙重心都往高速传输速率发展,但是由于WI-FI已经发展的很好了,所以没必要将发展理念和WI-FI重合到来第4代就将重心向低功耗靠拢

****第四代蓝牙:****主推Low Energy低功耗,BLE(Bluetooth Low Energy)低功耗功能。

第五代蓝牙 :开启物联网时代大门,在低功耗模式下具备更快更远的传输能力,为我们提供两种选择:低功耗远距离传输/高速近距离传输。(物联网广泛应用)

第六代蓝牙:新增加了信道功能(就是可以定位连接了蓝牙的设备,提高到1cm的精度)

蓝牙技术类型:

蓝牙协议包括两种技术:BR(Basic Rate经典蓝牙)和LE(Low Energy)都是使用2.4G无线电频率因此双模设备可以共享同一个天线。

这两种技术都包括搜索(discovery)管理、连接(connection)管理等机制,但它们是相互独立的,不能互通的技术!

厂商如果只实现了一种,那么只能与同样实现该技术的设备互通。如果厂商要确保能和所有的蓝牙设备互通,那么就只能同时实现两种技术,而不去管是否真的需要。

2.市场上常见蓝牙架构:

SOC蓝牙单芯片方案

此类芯片一般可以直接做为MCU用,这类产品一般用于消费类电子,集成度很高,调一调参数可以直接使用,常见的有蓝牙耳机,音响,蓝牙心率等产品。

SOC蓝牙+MCU方案

接外部主机,WI-FI就是这种架构蓝牙芯片内置射频模块,电路和物理层的各种支持,但是自己不烧写应用逻辑,那么我们就可以通过调用它给定的规则来调用它的功能符合我们的项目设想(蓝牙手表上网):

在集成好的蓝牙芯片基础上,通过特定的接口(UART居多),发送自定义的command来达到想要的功能,比如发送0x01代表搜索周围设备。

外设一个单芯片方案,其自定义指令包含蓝牙芯片(BT Controller)、微控制单元(MCU)以及蓝牙协议栈(BT Host)。

此部分的应用,将蓝牙作为一个外设使用,用于远程通信,例如网上卖的一些蓝牙串口。

比如我们后面应用就是采用的这种方案。 STM32作为MCU使用,ESP32作为蓝牙芯片使用。

蓝牙host+controller分开方案

单独将协议给功能更加强大的MCU处理目的就是为了实现更加强大的功能:

这种应用算是蓝牙最复杂的应用,客户需要使用蓝牙的场景有很多,涉及的蓝牙协议也有很多,需要将Host与Controller分开,集成更多的蓝牙协议,比如蓝牙电话(HFP)、蓝牙音频(A2DP)、蓝牙音乐控制(AVRCP)、蓝牙电话本(PBAP)、蓝牙短信(MAP),手机蓝牙等。

此部分应用,将定制蓝牙的各种服务,实现蓝牙多功能需求。

3.蓝牙协议栈:很复杂,厂家规定且打包好了,我们会用它的函数或者指令就行(一般都有用户指南,可以根据提示自己内测)

蓝牙芯片架构:

蓝牙的核心系统,由一个Host和一个或多个Controller组成。

(1)BT Host:逻辑实体(控制软件架构),在HCI(Host Controller Interface(host和Controller的接口层))的上层。

(2)BT Controller:逻辑实体(控制偏底层和硬件),在HCI(Host Controller Interface)的下层。

根据Host与Controller的组成关系,常见的蓝牙芯片也分为以下几种:

(1)单模蓝牙芯片:单一传统蓝牙的芯片,单一低功耗蓝牙的芯片。即1个Host结合1个Controller。

(2)双模蓝牙芯片:同时支持传统蓝牙和低功耗蓝牙的芯片。即1个Host结合多个Controller

对于BT Host而言虽然下层有不同的BT Controller但是我们都可以用软件来分开控制,写代码而已,所以就一个BT Host就行。

蓝牙协议是通信协议的一种,一般而言,我们把某个协议的实现代码称为协议栈(protocol stack),BLE协议栈就是实现低功耗蓝牙协议的代码。

4.BLE低功耗蓝牙协议栈框架

要实现一个BLE应用,首先需要一个支持BLE射频的芯片 ,然后还需要提供一个与此芯片配套的BLE协议栈 ,最后在协议栈上开发自己的应用

可以看出BLE协议栈是连接芯片和应用的桥梁,是实现整个BLE应用的关键。

简单来说,因为是由上到下的层层协议,所以BLE协议栈 主要用来对你的应用数据进行层层封包(类似与CAN协议的控制段,应答段等),以生成一个满足BLE协议的空中数据包,也就是说,把应用数据包裹在一系列的帧头(header)和帧尾(tail)中。

蓝牙协议规定了两个层次的协议,分别为蓝牙核心协议 (Bluetooth Core)和蓝牙应用层协议(Bluetooth Application)。

蓝牙核心协议关注对蓝牙核心技术的描述和规范,它只提供基础的机制,并不关心如何使用这些机制;

蓝牙应用层协议,是在蓝牙核心协议的基础上,根据具体的应用需求,百花齐放,定义出各种各样的策略,如FTP、文件传输、局域网等等。

ESP32-C3中的蓝牙功能:

ESP32-C3支持Bluetooth 5(LE)。下载好固件之后(我们前面下载的固件已经支持Wi-Fi和蓝牙了),STM32仍然可以通过AT指令操作蓝牙。

1.BLE角色划分

在 Bluetooth LE 协议栈中不同的层级有不同的角色划分。这些角色划分互不影响。

****LL:****设备可以划分为主机和从机,从机广播,主机可以发起连接。

GAP:定义了4种特定角色:广播者、观察者、外围设备和中心设备。(我们在编程不会涉及它)

****GATT:****设备可以分为服务端和客户端。定义了数据的组织结构和服务器端提供哪些服务,客户端就可以调用哪些服务。如果其为主机广播信号发起连接,本质底层是联网操作和上层是否是服务器或客户端无关,如果对方连接了主机,对方就是服务器,主机就是客户端,就好比你用手机监听到蓝牙,连上蓝牙不就是将手机数据发到蓝牙上,我们就可以戴蓝牙耳机听音乐了

2.BLE的地址(主机和从机建立连接后就知晓对方地址)

分为公共地址和随机地址。**公共地址:**BLE的公共地址,就类似于我们日常的身份证号码,是全球唯一的且不可改变的。为了保证BLE公共地址的全球唯一性,其需要向IEEE购买,然后IEEE组织就会对应地分配公共地址给买家。BLE的公共地址是全球唯一的,且在BLE设备的整个生命周期都不会改变。总长度为6个字节,共48位。

随机地址除了公共地址类型之外,还有一个随机地址类型,提供额外的隐私保护,其又分为静态地址(Static Address)和私有地址(Private Address),它们之间主要通过最高的2位有效位来区分。

(1)静态地址。总长度也是48位,但是最高位的2位必须是0b11。随机部分至少有一个位是0和1。也就是说不能全部是0或全部是1。设备重启之前不会改变。

(2)私有地址。总长度也是48位。最高位是00/01。随机部分至少有一个位是0和1。也就是说不能全部是0或全部是1。定时更新改变。

3.蓝牙扫描和通讯,蓝牙结点要广播信息通知别人进行连接

咱们蓝牙就作为LL的从机角色和GATT的服务端角色每各一段时间就发送广播信号,告诉别的设备咱们的存在。蓝牙可以使用40多个信道,但是广播信号只允许在37,38,39三个信道发出去,所以我们就能配是按顺序依次发出去,还是单独选一信道给发出去

通过扫描,主机能收到从机的广播包和回应数据包,然后给这些从机设备发连接请求,连接上后就能通信了。 这里还能配蓝牙能不能扫到,扫到能不能连,只能谁来连.......都能设,可以看用户指南

通讯可以配的:一个从机设备包括一个或者多个服务;一个服务中又可以包括一条或者多条特征值,每个特征值都有自己的属性 Property,属性的取值有:可读 Read、可写 Write、通知 Notify、指示Indicate。每个服务和特征值 都有自己的唯一标识 UUID,标准UUID为128位,蓝牙协议栈中一般采用16位,也就是两个字节的UUID格式。

蓝牙通讯案例:透传模式下收发数据(选的芯片ESP32-C3)

用户指南翻到Bluetooth LE AT实例就有教怎么配的,我们选这个例子

.设置蓝牙透传模式:

透传模式:设备 A 发送一串字节流,通过蓝牙透传模式,设备 B 接收到的就是完全相同的字节流,就如同这两个设备直接用一根线连接起来进行数据传输一样,ESP32无需先知道数据有多长也不用做一堆命令解析的操作了,避免了普通数据传输模式的很多麻烦。

针对蓝牙的操作:

1.在手机端下载Bluetooth LE(随便在你手机的商城那搜索蓝牙助手,随便下个就行都能用)

2.初始化蓝牙

cpp 复制代码
    printf("设置模块角色为:服务器...\n");
    uint8_t *cmd = "AT+BLEINIT=2\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

3.创建蓝牙服务

cpp 复制代码
    // 3. 服务端创建服务
    printf("服务端创建服务...\n");
    cmd = "AT+BLEGATTSSRVCRE\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

4.开启蓝牙服务

cpp 复制代码
    printf("服务端开启服务...\n");
    cmd = "AT+BLEGATTSSRVSTART\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

5.获取/设置蓝牙自己的MAC地址,厂家就给设备买了唯一的MAC地址,我们直接查询就行,避免我们蓝牙和别的蓝牙名称一致分不出来

cpp 复制代码
    printf("获取服务端MAC地址...\n");
    cmd = "AT+BLEADDR?\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

6.设置广播参数,这内容太长了,大家去翻用户指南看会更清晰

cpp 复制代码
    printf("设置蓝牙广播参数...\n");
    cmd = "AT+BLEADVPARAM=50,50,0,0,7,0\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

7.设置广播数据

cpp 复制代码
    printf("设置广播数据...\n");
    cmd = "AT+BLEADVDATAEX=\"mydevice\",\"A123\",\"0102030405\",1\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

8.开启蓝牙广播

cpp 复制代码
    // 8. 开始广播
    printf("开始广播...\n");
    cmd = "AT+BLEADVSTART\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

9.查询蓝牙给连接的设备提供了什么服务,可写也可以不写

10.配置蓝牙 SPP

cpp 复制代码
    printf("配置 BLE SPP 参数...\n");
    cmd = "AT+BLESPPCFG=1,1,7,1,5\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

11.使能蓝牙 SPP(反馈OK,就说明能给手机发数据和收数据了)和手机连上了就开启,断开了就关闭

11.1使能SPP,和手机连上了就开启,断开了就关闭,要接收变化状态就给SYSMSG配成100就是4呗

cpp 复制代码
    printf("设置系统提示信息...\n");
    cmd = "AT+SYSMSG=4\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));
cpp 复制代码
static uint8_t BLE_IsConnChanged(uint8_t msg[])
{
    // 1. 如果是BLE建立连接,就进入SPP透传模式
    if (strstr((char *)msg, "+BLECONN:") != NULL)
    {
        printf("有BLE客户端连接,马上进入SPP模式...\n");
        uint8_t * cmd = "AT+BLESPP\r\n";
        ESP32_SendCmd(cmd, strlen((char *)cmd));

        return 1;
    }

    // 2. 如果是BLE断开连接,就退出透传模式
    else if (strstr((char *)msg, "+BLEDISCONN:") != NULL)
    {
        printf("BLE客户端断开连接,即将退出SPP模式...\n");
        
        // 直接串口发送 +++ 退出透传模式
        HAL_UART_Transmit(&huart2, "+++", 3, 1000);

        // 延时等待
        HAL_Delay(2000);

        return 1;
    }

    // 3. 如果是其它类型的连接变化(Wi-Fi),不做任何处理,返回1
    else if(strstr((char *)msg, "WIFI CONNECTED") != NULL
        || strstr((char *)msg, "WIFI GOT IP") != NULL
        || strstr((char *)msg, "+DIST_STA_IP:") != NULL)
    {
        return 1;
    }
    
    // 4. 其它情况默认是正常数据
    return 0;
}

11.2STM32通过串口给ESP32发什么,蓝牙就会通过电磁波向外发送一样的数据

cpp 复制代码
// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen)
{
    // 直接通过串口发送数据
    HAL_UART_Transmit(&huart2, txBuff, txLen, 1000);
}

11.3接收数据,没有进入SPP模式时接收到的都是蓝牙的内部数据,不是手机发来的,所以要判断连没连上手机

cpp 复制代码
// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen)
{
    // 从串口接收数据或状态改变信息
    HAL_UARTEx_ReceiveToIdle(&huart2, rxBuff, 1024, rxLen, 1000);

    // 如果是空数据,rxLen = 0,就直接返回
    if (*rxLen == 0)
    {
        return;
    }

    // 如果是连接变化信息,执行相应的SPP操作,并清零len,表示不是正常数据
    if ( BLE_IsConnChanged(rxBuff) )
    {
        *rxLen = 0;
    }
}

针对手机的操作:

1.蓝牙开启广播后,手机就能在蓝牙助手那连蓝牙了

2.连上后,要勾选需要的服务(点单),勾上蓝牙就能上菜了

3.通过串口助手给蓝牙发数据

ble.h

cpp 复制代码
#ifndef __BLE_H
#define __BLE_H

#include "esp32.h"

// 初始化
void BLE_Init(void);

// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen);

// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen);

#endif

ble.c

cpp 复制代码
#include "ble.h"

// 初始化
void BLE_Init(void)
{
    // 1. 初始化ESP32
    ESP32_Init();

    // 2. 设置蓝牙模块角色:2-server
    printf("设置模块角色为:服务器...\n");
    uint8_t *cmd = "AT+BLEINIT=2\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 3. 服务端创建服务
    printf("服务端创建服务...\n");
    cmd = "AT+BLEGATTSSRVCRE\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 4. 服务端开启服务
    printf("服务端开启服务...\n");
    cmd = "AT+BLEGATTSSRVSTART\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 5. 获取服务端MAC地址
    printf("获取服务端MAC地址...\n");
    cmd = "AT+BLEADDR?\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 6. 设置蓝牙广播参数
    printf("设置蓝牙广播参数...\n");
    cmd = "AT+BLEADVPARAM=50,50,0,0,7,0\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 7. 设置广播数据
    printf("设置广播数据...\n");
    cmd = "AT+BLEADVDATAEX=\"mydevice\",\"A123\",\"0102030405\",1\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 8. 开始广播
    printf("开始广播...\n");
    cmd = "AT+BLEADVSTART\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 9. 配置 BLE SPP参数
    printf("配置 BLE SPP 参数...\n");
    cmd = "AT+BLESPPCFG=1,1,7,1,5\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));

    // 10. 配置在透传模式下,连接发生变化时会传输一个提示信息
    printf("设置系统提示信息...\n");
    cmd = "AT+SYSMSG=4\r\n";
    ESP32_SendCmd(cmd, strlen((char *)cmd));
}

// 发送数据
void BLE_SendData(uint8_t txBuff[], uint16_t txLen)
{
    // 直接通过串口发送数据
    HAL_UART_Transmit(&huart2, txBuff, txLen, 1000);
}

// 自定义函数,判断是否有连接变化信息;如果有则返回1,没有返回0
static uint8_t BLE_IsConnChanged(uint8_t msg[]);

// 接收(读取)数据
void BLE_ReadData(uint8_t rxBuff[], uint16_t *rxLen)
{
    // 从串口接收数据或状态改变信息
    HAL_UARTEx_ReceiveToIdle(&huart2, rxBuff, 1024, rxLen, 1000);

    // 如果是空数据,rxLen = 0,就直接返回
    if (*rxLen == 0)
    {
        return;
    }

    // 如果是连接变化信息,执行相应的SPP操作,并清零len,表示不是正常数据
    if ( BLE_IsConnChanged(rxBuff) )
    {
        *rxLen = 0;
    }
}

static uint8_t BLE_IsConnChanged(uint8_t msg[])
{
    // 1. 如果是BLE建立连接,就进入SPP透传模式
    if (strstr((char *)msg, "+BLECONN:") != NULL)
    {
        printf("有BLE客户端连接,马上进入SPP模式...\n");
        uint8_t * cmd = "AT+BLESPP\r\n";
        ESP32_SendCmd(cmd, strlen((char *)cmd));

        return 1;
    }

    // 2. 如果是BLE断开连接,就退出透传模式
    else if (strstr((char *)msg, "+BLEDISCONN:") != NULL)
    {
        printf("BLE客户端断开连接,即将退出SPP模式...\n");
        
        // 直接串口发送 +++ 退出透传模式
        HAL_UART_Transmit(&huart2, "+++", 3, 1000);

        // 延时等待
        HAL_Delay(2000);

        return 1;
    }

    // 3. 如果是其它类型的连接变化(Wi-Fi),不做任何处理,返回1
    else if(strstr((char *)msg, "WIFI CONNECTED") != NULL
        || strstr((char *)msg, "WIFI GOT IP") != NULL
        || strstr((char *)msg, "+DIST_STA_IP:") != NULL)
    {
        return 1;
    }
    
    // 4. 其它情况默认是正常数据
    return 0;
}

main.c

cpp 复制代码
// 全局变量,接收数据缓冲区、长度
uint8_t rxBuff[1024];
uint16_t rxLen;

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  // MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  // 蓝牙初始化
  BLE_Init();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // 读取数据
    BLE_ReadData(rxBuff, &rxLen);

    // 如果接收到数据,就原样发回去
    if (rxLen > 0)
    {
      printf("接收到数据!数据长度 = %d, 内容 = %.*s\n", rxLen, rxLen, rxBuff);

      BLE_SendData(rxBuff, rxLen);

      // 将长度清0
      rxLen = 0;
    }
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
相关推荐
国科安芯9 小时前
抗辐照芯片在低轨卫星星座CAN总线通讯及供电系统的应用探讨
运维·网络·人工智能·单片机·自动化
weixin_4526006910 小时前
GC8872刷式直流电机驱动器详解:3.6A驱动能力与PWM控制
stm32·单片机·嵌入式硬件·智能家居·音响·电动工具
Despacito0o13 小时前
STM32 I2C通信完整教程:从协议原理到硬件实现
stm32·单片机·嵌入式硬件
你好,奋斗者!13 小时前
小电流驱动大电流:原理、实现方式与应用前景
stm32·单片机·嵌入式硬件·电路设计
XINVRY-FPGA16 小时前
XCZU4EV-1FBVB900E Xilinx FPGA AMD Zynq UltraScale+ MPSoC EV(Embedded Vision)
arm开发·嵌入式硬件·计算机视觉·fpga开发·硬件架构·硬件工程·fpga
猫猫的小茶馆17 小时前
【STM32】FreeRTOS 任务的删除(三)
java·linux·stm32·单片机·嵌入式硬件·mcu·51单片机
学不动CV了17 小时前
单片机ADC采集机理层面详细分析(二)
c语言·arm开发·stm32·单片机·嵌入式硬件·开源·51单片机
学不动CV了17 小时前
51核和ARM核单片机OTA实战解析(二)
c语言·arm开发·stm32·单片机·嵌入式硬件·51单片机
Yuroo zhou18 小时前
IMU的精度对无人机姿态控制意味着什么?
单片机·嵌入式硬件·算法·无人机·嵌入式实时数据库