前面的文章里面讲了很多蓝牙基础的协议和硬件方面的知识,现在进入实践篇
今天我介绍一下,我使用磐启微的PAN1070来实现一个室内自行车的应用,最后把源码和硬件放在gitee上面,大家可以回家改造一下自己的傻瓜运动器械。
先介绍一下PAN1070,这是一个蓝牙SOC,主打低功耗来替换Nordic的蓝牙SOC,众所周知哈,国产替代势不可挡。
这个芯片的接收功耗还是比较低的,并且有512KB的FLASH,可以实现非常多的功能,最主要是的磐启微自己提供了很多的实际的案例,在开发的时候,我们可以直接参照案例修改一下就可以应用起来。
同时,PAN1070的最新的SDK是基于开源的NimBLE协议栈开发,和火热的ESP32的蓝牙协议栈一样,这样上手起来也会比较轻松。
首先,我们先把官方的SDK下载到本地,然后在我们的Gitee上创建一个仓库,并将SDK上传上去,作为一个基础版本。
https://gitee.com/geek-pi/pan1070.git
官方的SDK从这里下载:https://wiki.panchip.com/download/2799/
官方文档地址:https://docs.panchip.com
也可以从上面网址进入文档介绍后,从最左下角选择对应的版本的SDK,对了官方叫NDK,是指的基于NimBLE协议栈的版本,后续还有ZDK,这个是基于Zephyr的,所以别迷糊了。
整个SDK包括几个内容
SDK包含了我们编程序是需要的源代码和示例代码,HDK更多的是硬件相关的,比如各种开发板等,我们可以先不理会。MCU是一些控制器相关的使用例程,我们可以在用到某些单片机外设的时候再去了解,DOC和Tools都是一些文档和工具。遇到问题时在去看吧。
我们先来看原厂提供的例程有哪些?
在目录SDK→nimble→samples→solutions中有很多现成的解决方案,我们做新的应用的时候,可以在这里找相似的去做修改,有一些也可以直接用,比如透传模块,直接使用appuart,还有多主机多从机连接的案例,可以使用blehiduartmult_roles这个案例。
因为我本期介绍的是实现一个室内自行车的方案,所以我选择了一个比较简单的案例来修改,也就是blevehicleskey 。
这个方案本来功能是实现接近解锁的,内部通过RSSI来判断是否要对电动车或者电动门之类的设备进行解锁。这个有空可以了解下,今天主要看如何在这个例程上修改为室内自行车。
先说一下我的开发习惯
在基础的仓库上,我会新建一个分支,并以实现的功能作为命名,然后直接在官方的例程上做修改,这样基本上不需要自己搭建环境,同时还可以在vs code中看到所有例程的代码,方便来回copy。
每个例程中的代码其实很简单,一个文件件存放工程配置信息,一个文件件存放源代码,用户自己添加的代码都可以放到src中。
我们使用keil打开工程
虽然这里引用了很多文件,大部分都是蓝牙协议栈和freeRTOS相关的底层代码,我们可以先不关心他们,主要先要了解的是上图中的2部分,第一个是SDK的配置文件,他里面是一些配置选型,利用的keil的头文件注释界面来配置各种参数。第二部分就是调用nimble相关的API来实现蓝牙的功能,基本上就是配置广播帧广播,然后配置服务和服务对应的读写接口。
先来看一下配置界面
由于配置信息很多,这里只介绍我用到的,就是这里的log打印系统,这里要根据我们手里的板子的链接来选择一下,我的板子使用的串口log输出引脚是P05,波特率我选择的115200 。
之后,我们编译这个项目,下载运行后,就可以看到串口的logo信息了。
下载不了?是不是烧录算法没有找到,我们需要把SDK目录下的烧录算法拷贝到keil中,keil就可以找到了,SDK烧录算法的路径为:SDK3MCUmcumisc 。
下图是串口打印信息
现在,我们的例程已经跑起来了,我们的DIY才刚刚开始。
一、创建新的分支来开发室内自行车,命名为ftms
在基础工程上新建分支来开发新功能,可以保证各个项目公用一个基础,不必新建立很多工程,直接利用SDK现有的工程即可。通过git管理,还可以保证他们之间互不影响。
ftms是运动健康协议的缩写,这里使用它来作为分支名。
二、修改广播信息,让顽鹿等APP认识我们的蓝牙设备
广播信息的修改在main函数中,函数名称为:bleprph_advertise(),所有的广播参数都是在这里写入到一个缓存中,基本上就是按照数据+长度这样的结构堆积进去的。
这里我们需要把广播帧设置为下图这样:
具体含义可以参考这个文章
//首先增加UUID,也就是FTM的标准UUID,这个是一个宏定义。
fields.uuids16 = (bleuuid16t[]) {
BLEUUID16INIT(BTUUIDFTM)
};
fields.num_uuids16 = 1;
// Service Data AD Type: 0x16 (1 byte)
uint8_t service_data[] = {
0x26, 0x18, // Fitness Machine Service UUID (0x2618, 小端存储)
0x01, // Flags (0x01, bit0 set to indicate Fitness Machine Available)
0x30, 0x00 // Fitness Machine Type (0x1000, 表示划船机类型,2字节小端)
};
fields.svc_data_uuid16 = service_data;
fields.svc_data_uuid16_len = sizeof(service_data);
这样广播以后,顽鹿APP中就可以看到我们的设备了。
三、添加一些对应的服务和特性
让上位机APP发现我们的设备只是第一步,第二部是让我们的设备可以和APP通信。那么,我们就需要设定一个接口协议来和上位机通信,这就是蓝牙协议中的标准,我们要配置多个服务,以及服务内部包含的特征。这些特征有的是可读写的,有的是只读的,也有的是可以Notify的,可以理解为蓝牙设备主动给APP发的。
我们下面在代码中看看如何增加这样的属性和特性进去。这个修改要在gatt_svr.c中进行,它是定义了一个数据类型,可以说是结构体套着结构体的一个结构,这样写主要是为了直观,因此我们改代码也就直观了。
static const struct blegattsvcdef gattsvrsvcs[] = {
//服务
{
/* Service: 显示属性的DIS/
.type = BLEGATTSVCTYPEPRIMARY,
.uuid = BLEUUID16DECLARE(BTUUIDDIS),
.characteristics = (struct blegattchrdef[]) {
{
.uuid = BLEUUID16DECLARE(BTUUIDDISMODEL),
.accesscb = gattsvrchraccessftminfo, //只读的只需要回调函数
.flags = BLEGATTCHRFREAD, //只读
},
{
.uuid = BLEUUID16DECLARE(BTUUIDDISMNS),
.accesscb = gattsvrchraccessftminfo, //只读的只需要回调函数
.flags = BLEGATTCHRFREAD, //只读
},
{
0, / No more characteristics in this service /
}, }
},
//新的服务
{
/ Service: vendor ble uart /
.type = BLEGATTSVCTYPEPRIMARY,
.uuid = BLEUUID16DECLARE(BTUUIDFTM),
.characteristics = (struct blegattchrdef[]) {
{
/ Characteristic: Heart-rate measurement /
.uuid = BLEUUID16DECLARE(BTUUIDFTMINDOORBIKE),
.accesscb = gattsvrchraccessftms,
.valhandle = &hrsftmshandle, //Notify 对应的handle句柄
.flags = BLEGATTCHRFNOTIFY,
},
// {
// / Characteristic: Heart-rate measurement /
// .uuid = BLEUUID16DECLARE(BTUUIDFTMSTATUS),
// .accesscb = gattsvrchraccessftms,
// .valhandle = &hrsftmshandle,
// .flags = BLEGATTCHRFNOTIFY | BLEGATTCHRFREAD,
// },
{
/ Characteristic: Heart-rate measurement /
.uuid = BLEUUID16DECLARE(BTUUIDFTMCTRL),
.accesscb = gattsvrchraccessftms,
.valhandle = &ftmctrlhandle,//Notify 对应的handle句柄
.flags = BLEGATTCHRFWRITE | BLEGATTCHRF_INDICATE,
},
{
0, / No more characteristics in this service /
},
}
},
//其他服务
{
0, / No more services */
},
};
第一个属性是FTMS要求的服务,APP会从这个服务中读取各种属性,比如厂家,序列号,型号以及产品类型等等,他们基本上都是只读的,因此,可以从程序中看到,他的flags属性的设置。
对于只读的特性,我们只需要设置一个回调函数就可以了,比如:.accesscb = gattsvrchraccessftminfo, //只读的只需要回调函数。
在回调函数中,我们来处理上位机的读取请求,就是把我们本地定义好的字符串返回给协议栈。