2021 年 3 月 9 日更新信息: Silicon Labs(芯科实验室)表示:“自2011年推出以来,Bluegiga Legacy蓝牙低能耗(BLE)模块在市场上广受欢迎,至今仍在大量生产中。然而,对于新的蓝牙5设计,我们建议使用基于EFR32BG SoCs的最新BGM13蓝牙模块和BGX13蓝牙模块。”
目标
本文说明了如何设置Silicon Labs BLE112蓝牙低能耗模块,从而通过UART与单片机进行通信。
快速链接
购买链接
- Silicon Labs(芯科实验室)BLE112蓝牙低能耗模块
- 德州仪器CC-Debugger——用于更新BLE112上的固件。
- Silicon Labs(芯科实验室)BLE112开发套件——可用于评估的目的;用户可以直接使用BLE112,而无需手动拆开引脚,其中还包含许多其他的板上器件。
参考文档
- BLE112文档和软件——提供了许多有用的资源,有助于使用BLE112模块,包括本教程中使用的SDK和BGLib API的参考指南。
- Silicon Lab(芯科实验室)的蓝牙智能BLE112的论坛搜索结果——包括很多与BLE112入门和使用相关的信息。
- BLE112规格书
下载
- BLE112分线板布局——压缩文件包含制作我们为BLE112创建的分线板所需的文件。
简介
(取自BlueGiga BLE112产品信息页)BLE112是一款适用于低能耗传感器和配件的蓝牙智能模块。它集成了蓝牙智能应用所需的全部功能,包括蓝牙无线电、软件堆栈和基于GATT的配置文件。BLE112蓝牙 智能模块还可以托管终端用户应用,这意味着在大小或价格受限的器件中不需要再使用外部单片机。此外,它具有灵活的硬件接口,可连接到不同的外设和传感器,并可以直接通过标准3V纽扣电池或一对AAA电池供电。在最低功耗睡眠模式下,其消耗量仅为500 nA,并可以在几百微秒内唤醒。
本教程将讨论如何使用BGAPI(已预装在BLE112上)和BGLib(由BlueGiga提供的C库,用于通过外部器件(如单片机)与BLE112连接)在单片机和BLE112模块之间建立通信。BGLib包含与BLE112连接交互所需的大部分代码;要将库与单片机配合使用,你只需编写一个发送函数和一个接收函数,告诉BGLib如何使用单片机上的UART或USB外设与BLE112进行通信。在本文中,我们将重点讨论如何使用单片机的UART外设与模块通信。为了更好地了解BLE112和BGLib的工作原理,或者如果你在阅读教程的过程中需要了解有关BLE112或BGLib的某些内容的详细说明,建议查看“参考文献”下方的链接。你需要在BlueGiga的网站上注册一个免费帐户才能访问其中的一些资料,这些资料非常有用,可以帮助你了解如何使用BLE112工作。
教程
下面我们将详细介绍首次启动并运行BLE112时应如何操作。
设置硬件接口
断开引脚
要开始与BLE112通信,我们首先需要分拆引脚,以便用导线和面包板将它们连接起来。为此,我们制作了一个用于焊接模块的载板。如果你没有制作载板的工具,则只需将电线焊接到连接头上即可(参见下面的接线图,了解我们将使用哪些引脚)。Bluegiga BLE112开发套件还支持访问BLE112模块上的引脚。如果你想自己构建分线板,可以参考规格书第17页上的焊盘图形;下图展示的是我们制作分线板时使用的相同引脚图:
你可以在上面的“快速链接”部分找到制作此板的相关文件。
更新固件
将引脚分拆好后,我们需要更新器件的固件,因为经过编程以通过UART进行通信的引脚取决于BLE112的固件。许多(但并非所有)BLE112模块都预装有BlueGiga的“UARTDemo”示例的固件。你可以点击此处,查看相关示例和BGLib的C代码(位于“Software → Bluetooth Low Energy Software and SDK”下)。为了在器件上更新固件,你可能需要使用TI的CC-Debugger;当与BlueGiga的BleUpdate程序配合使用时(该程序包含在上述SDK中),可以非常轻松地更新该器件的固件。对于本教程,我们将使用与“UARTDemo”示例相同的引脚设置,因为此硬件布局(包含在hardware.xml文件中)适用于大多数使用UART与BLE112进行通信的项目。以下是包含在“UARTDemo”示例中的hardware.xml文件,供你参考:
‘UARTDemo’ hardware.xml
<?xml version="1.0" encoding="UTF-8" ?>
<hardware>
<sleeposc enable="true" ppm="30" />
<usb enable="false" endpoint="none" />
<txpower power="15" bias="5" />
<usart channel="1" alternate="1" baud="57600" endpoint="api" />
<wakeup_pin enable="true" port="0" pin="0" />
<port index="0" tristatemask="0" pull="down" />
<pmux regulator_pin="7" />
</hardware>
正如上面的xml中所示,UART通道1已启用(具体来说是通道1、备用1)。在规格书中(截屏如下),
我们可以看到,P0_4被设置为Tx,而P0_5则设置为Rx。BLE112的默认通信模式已开启流控制,这就是要使用RT(RTS-准备发送)和CT(CTS-清除发送)引脚的地方。然而,许多UART外设都不具有流控制。幸运的是,BLE112包含了一种称为“数据包模式”的功能,它可以禁用流控制,并要求你指定要发送到BLE112模块的数据包的大小。本教程将使用数据包模式,因此我们需要修改hardware.xml文件,特别是以“usart channel”开头的行,如下所示:
hardware.xml 修改
<usart channel="1" alternate="1" baud="57600" endpoint="api" mode="packet" flow="false" />
此操作将关闭流控制,并将模式设置为数据包模式。现在,在定义发送数据的函数时,我们需要先发送数据包中包含的字节数(利用BGLib可以轻松实现;我们将在教程的软件部分中进行更加深入的介绍)。为了将hardware.xml的修改发送到BLE112,我们需要使用CC-debugger和BleUpdate。
给模块接线
固件更新后,我们可以将单片机的UART Rx引脚连接到BLE112的UART Tx引脚,并将MCU的Tx引脚连接到BLE112的Rx引脚。UART通信要求在空闲时保持数据线的高电平状态,因此我们还应在每条线上加入10K上拉电阻(如下所示)。根据规格书,我们可以发现重置引脚为低电平有效。如果你希望以此方式重置器件,可以使用单片机上的GPIO引脚来控制此引脚。由于BLE112也可以通过软件命令进行重置,因此在本教程中,我们只会将重置引脚连接到电源上。在hardware.xml文件中,我们还可以看到P0_0引脚被设置为唤醒引脚。为了避免BLE112在我们尝试与之通信时进入睡眠状态,我们还应将此引脚连接到电源上。你可能需要将某些引脚接地或连接3.3V电源以避免漏电流,但为了简单起见,我们未在本教程中这样做(如需了解更多信息,请参见规格书)。完整的引脚连接图如下所示:
设置软件接口
将BGLib连接到UART
要开始设置软件接口,首先需要在我们的项目代码中包含BGLib C库(可在SDK中找到)。其中有四个必要文件:“commands.c”、“cmd_def.c”、“cmd_def.h”和“apitypes.h”。它们几乎包含了与BLE112通信所需的所有内容(软件方面)。不过,由于这些库已设置为适用于任何带有UART或USB接口的单片机,我们需要告知库如何与特定的单片机的UART外设进行通信。我们需要使用两个函数:一个用于告知库如何通过UART发送数据,另一个用于告知库如何通过UART(或USB,但本教程将重点介绍UART通信)读取数据。
发送函数
对于发送/输出函数,需要一个指向你编写的发送函数的函数指针。它在‘cmd_def.c’中被定义为:
发送函数指针
void (*bglib_output)(uint8 len1,uint8* data1,uint16 len2,uint8* data2) = 0;
因此,我们需要将发送函数定义为具有相同的参数和返回类型。下面是该函数可能包含的一些基本伪代码:
发送函数伪代码
void output (uint8 len1, uint8* data1, uint16 len2, uint8* data2)
//since we are using packet mode, we need to begin every packet with the size of the packet
send character(len1 + len2)
//send bluetooth message header
for i = 0 to len1
send character(data1[i])
//send bluetooth message payload
for i = 0 to len2
send character(data2[i])
以下是针对TI Tiva TM4C123GH6PM单片机编写的发送函数示例,它支持通过其UART 5外设与蓝牙模块进行通信:
UART 发送函数示例
//this is the callback function which tells the BLE API how to
//send messages via the 123G's UART peripheral library. The messages
//are sent over UART 5
void SendBTMessage(uint8 len1,uint8* data1,uint16 len2,uint8* data2)
{
//this line assumes the BLE module is in packet mode, meaning the
//length of the packet must be specified immediately before sending
//the packet; this line does that
UARTCharPut(UART5_BASE, len1 + len2);
//this loop sends the header of the BLE message
for(int i = 0; i < len1; i++)
{
UARTCharPut(UART5_BASE, data1[i]);
}
//this loop sends the payload of the BLE message
for(int i = 0; i < len2; i++)
{
UARTCharPut(UART5_BASE, data2[i]);
}
//wait until UART is finished sending before continuing
while(UARTBusy(UART5_BASE));
}
输出函数编写完毕后,就需要在‘cmd_def.h’中将函数指针指向该函数。使用以上示例,C代码如下所示:
指向函数指针
bglib_output = &SendBTMessage;
现在,BGLib已经知道如何通过单片机的UART外设向蓝牙模组发送数据了。
读取函数
现在,我们需要告知它如何通过UART从BLE112读取数据。由于该函数会在UART上接收到数据时被调用,因此无需为此函数设置函数指针。相反,你很可能需要设置每当接收到数据时触发的中断,并使用接收/输入/读取函数作为中断处理程序。该函数比发送函数稍微复杂一些,因为我们希望使用由BGLib定义的结构来利用该函数具备的功能。以下是我们需要使用的结构(可在‘cmd_def.h’中找到):
BGLib 消息和头结构
typedef void (*ble_cmd_handler)(const void*);
struct ble_header
{
uint8 type_hilen;
uint8 lolen;
uint8 cls;
uint8 command;
};
struct ble_msg
{
struct ble_header hdr;
uint32 params;
ble_cmd_handler handler;
};
正如你所看到的,“ble_header”结构包含四个项:type_hilen,其中包含消息的类型(命令、响应或事件)(它还包含有效载荷长度的MSB,但由于在BLE消息中,有效载荷永远不会超过255字节,因此无需担心这个问题);lolen,其中包含消息的有效载荷的长度/大小(具体来说,是八位的LSB);cls,其中包含消息的类别代码(属性客户端、连接、系统等);以及command,其中包含消息的ID号。type_hilen、cls和command字节用于唯一标识可以由BLE112发送或接收的每条消息。lolen字节用于确定有效载荷的大小,以便我们在读取消息头之后读取这些字节。“ble_msg”结构包含ble_header结构、ble_cmd_handler(将BGLib指向适当的函数,以处理由ble_header指定的消息类型)和4个名为params的字节(供BGLib用于解析包含在ble_msg结构中的消息)。为了更好地了解使用这些结构存储的不同消息和消息头,建议你查看蓝牙智能软件API参考手册。
在编写接收函数之前,我们还需要熟悉一下“ble_get_msg_hdr(struct ble_header hdr)”函数。该函数可以简单地返回与我们从蓝牙模块接收到的头相对应的特定ble_msg(与其名称略有不同)。在“cmd_def.h”中,你可以看到,BGLib的ble_msg结构适用于每个单独的消息,因此ble_get_msg_hdr只是将通用的“ble_msg”交换为特定的消息结构。
由于我们需要在代码中使用这些专有结构,因此很难为接收函数编写通用的伪代码。相反,以下是使用TM4C123GH6PM的示例;有助于你轻松地将123G的UART接收函数替换为自己的MCU的UART函数:
接收函数示例
//this is the Bluetooth interrupt handler; it reads any messages
//sent by the bluetooth LE module over UART
void ReadBTMessage( void )
{
const struct ble_msg *BTMessage; //holds BLE message
struct ble_header BTHeader; //holds header of message
unsigned char data[256] = "\0";//holds payload of message
//clear the UART 5 interrupt flag
UARTIntClear(UART5_BASE, UART_INT_RX);
//read BLE message header
BTHeader.type_hilen = UARTCharGet(UART5_BASE);
BTHeader.lolen = UARTCharGet(UART5_BASE);
BTHeader.cls = UARTCharGet(UART5_BASE);
BTHeader.command = UARTCharGet(UART5_BASE);
//wait for UART to finish reading header to ensure data
//is valid before using it in the code below
while(UARTBusy(UART5_BASE));
//read the payload of the BLE message
for(uint8_t i = 0; i < BTHeader.lolen; i++)
data[i] = UARTCharGet(UART5_BASE);
//find the appropriate message based on the header, which allows
//the ble112 library to call the appropriate handler
BTMessage = ble_get_msg_hdr(BTHeader);
//print error if the header doesn't match any known message header
if(!BTMessage)
{
//handle error here
return;
}
//call the handler for the received message, passing in the received payload data
BTMessage->handler(data);
}
发送命令和接收响应与事件
当你设置了发送和接收函数,将bglib_output函数指针指向发送函数,并进行了中断设置以触发接收函数之后,你的代码就可以与BLE112通信了。如果一切运行正常,与BLE112的通信应该就像调用“cmd_def.h”底部附近的宏一样简单(这些行以“#define ble_cmd…”开头)。你应该能够调用这些函数,而BGLib将负责处理其余的发送流程。当你接收到数据时,应调用收到的具体消息的处理程序(在上述示例代码中,这就是“BTMessage-> handler(data)”行所处理的操作)。
要在调用处理程序时执行有意义的操作,你需要在“commands.c”中列出的处理程序函数中添加适当的代码。处理程序分为响应(以“ble_rsp…”开头)和事件(以“ble_evt…”开头)。响应来自BLE112,用于确认已接收到命令,其中还将包含错误编号,以告知你模块是否成功执行了命令。事件来自BLE112,用于告知你,它已执行了命令或从另一个蓝牙设备接收到了数据。此处的API参考指南列出了所有可能的事件和响应、BGLib C库中相应的处理程序函数,以及这些响应和事件中包含的信息。以下是使用BGLib处理程序函数的一些示例(BTFlags结构是我自己定义的,其中仅包含了用于跟踪BLE112的当前状态的各种标记):
事件处理程序示例
//set ready flag once BLE112 is ready for commands
//this event will occur after sending the ble_cmd_system_reset command
void ble_evt_system_boot(const struct ble_msg_system_boot_evt_t *msg)
{
BTFlags.ready = 1;
}
//set connected flag once a connection has been established to a remote device
void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg)
{
UARTprintf("\n\rconnection established");
BTFlags.connected = 1;
}
//this event occurs after the ble_cmd_attclient_find_information command is sent
//it assumes that information was obtained from a remote device
void ble_evt_attclient_find_information_found(const struct ble_msg_attclient_find_information_found_evt_t *msg)
{
UARTprintf("\n\rHandle: %02x\n\rUUID: ", msg->chrhandle);
uint8_t length = msg->uuid.len;
for(int i = 0; i < length; i++)
UARTprintf("%02x", msg->uuid.data[length - i - 1]);
}
接下来该怎么做?
现在,你已经可以与单个模块进行通信了,接下来,你可以尝试让两个模块之间进行通信。对于两个器件之间的通信,你需要将一个器件设置为从设备/服务器,将另一个器件设置为主设备/客户端。从设备通常会附加到某个外设或数据采集器件,并将从该外设中收集的数据存储在它的内存中。主设备可以在从设备中拉取这些数据,或者订阅某些数据。当主设备订阅了数据片段(也称为“特征”或“配置文件”)时,从设备会在每次更新时自动将该数据片段发送到主设备。要存储应用的特定数据,你需要创建自己的GATT配置文件并将其放置在从设备上(同样,BlueGiga文章对这些配置文件的定义和创建方式进行了说明)。