NXP

做无线连接MCU开发的嵌友,这篇要看的!

2019-07-12 12:12发布

640?wx_fmt=jpeg前言无线连接MCU的应用开发与通用MCU相比有比较大的差异,工程师除了掌握芯片外设的使用,还需要对原厂提供的SDK有比较全面的了解,包括理解系统调度机制,熟悉API和编程框架等。这些代码往往比较庞大,或者以闭源形式提供,学习曲线较为陡峭。就算是具备一定MCU经验的工程师,没有好的指引来帮助梳理思路,也需要较长的时间熟悉,才能编写正确的业务代码。BLE低功耗蓝牙就是这类应用的一个典型,为了帮助工程师快速上手NXP的BLE SoC开发,恩智浦MCU加油站将陆续推送系列文章《深入NXP低功耗蓝牙SDK开发》来讲解BLE SDK开发的方方面面,想要学习BLE的小伙伴们千万不要错过哦!这里假设读者已经具备了基本的BLE概念,如Profile、GAP、GATT、HCI、LL等各个层次的职责,广播与连接的行为,基于GATT数据传输的方式等。如果对这个概念还不熟悉,可以阅读“Getting Started with Bluetooth Low Energy”一书的第1~3章共75页内容,利用业余时间在2-3天内可以轻松完成。恩智浦的MCUXpresso SDK
MCUXpresso SDK是NXP面向MCU市场,推出的一套完整的软件开发套件,支持Kinetis、LPC、i.MX RT等通用微控制器,以及QN、JN系列和KW系列的无线连接MCU。SDK包含了芯片硬件抽象层、外设驱动库、多个中间件库(根据芯片功能的不同包含不同的中间件,比如带Ethernet外设的MCU则会有lwip网络协议栈中间件),以及丰富的示例代码。
目前在NXP面向物联网和可穿戴设备数据传输应用的BLE SoC芯片中,除QN902x使用原有QN SDK外,其他都采用了MCUXpresso SDK和NXP自研的Bluetooth协议栈,呈现给用户的编程接口完全一致。无论是开发超低功耗的QN9080,支持Zigbee/Thread/BLE多模的KW41,还是面向汽车应用的KW36,对固件工程师来说,掌握了一款芯片的开发,拿下其他的也就是轻而易举之事。对于这些低功耗蓝牙MCU,BLE协议栈是作为MCUXpresso SDK的一个中间件而存在的。用户解压缩SDK包后,在./middleware/wireless/bluetooth_x.y.z下可以找到协议栈的代码和库文件。低功耗蓝牙应用将包含这个目录下的文件,以使用协议栈提供的API和服务。所有低功耗蓝牙的示例代码可以在./boards//wireless_examples/bluetooth目录下找到。另外,低功耗蓝牙应用与通用MCU SDK还有一点差异,它需要一些额外的通用组件作为支撑,如OS环境抽象、非易失存储、队列管理、低功耗管理等,因此多引入了一个connectivity framework的中间件层。在./middleware/wireless/framework_x.y.z下可以找到framework包含的所有功能模块源代码。下图展示了SDK的文件结构中与低功耗蓝牙相关的目录。640?wx_fmt=pngMCUXpresso SDK文件目录组织
如果基于MCUXpresso IDE开发,通常会导出某个示例工程到workspace目录下,被导出的工程与解压缩后的SDK的layout稍有不同,它仅包含了该示例代码所需的驱动和中间件,并以一个平铺的方式组织文件夹,但文件夹下的内容并无区别。

NXP BLE SDK系统架构
NXP提供了一套完整的BLE协议栈和编程框架,并已通过了Bluetooth Core Spec 5.0的认证。用户第一次开发自己的应用时,应当先大致了解整个系统的组成,知道每个部分的职责,分别有哪些文件。有了一些基础,再通过查询API文档,很快便能掌握BLE应用开发。下图为BLE SDK总体的框架图,图中使用了几种不同的颜 {MOD}来区分各个部分:
  • 应用(灰)
  • 低功耗蓝牙服务框架(黄)
  • Connectivtiy Framework(绿)
  • BLE协议栈(蓝)
  • 通用SDK(白)
640?wx_fmt=pngNXP BLE SDK系统架构OSA
OSA属于Connectivity Framework,单独介绍它是因为OSA服务于整个SDK和协议栈,并且也是系统复位后的入口。整个蓝牙协议栈和SDK的设计,使用了RTOS下才有的多任务、消息和事件等服务。为了减少代码大小和栈的内存,适配一些资源紧张的SoC,在设计时才引入了OSA。
OSA提供一套接口封装,底层可以使用FreeRTOS或者Bare Metal(无OS的裸机框架)前后台两种实现。如果底层为FreeRTOS,OSA只是简单的将FreeRTOS的API转换为OSA API。如果底层为Bare Metal系统,OSA则内部实现了一套多任务、信号量、事件等RTOS API的模拟实现,当用户调用OSA_Start启动多任务调度环境时,实际上是陷入了一个while(1)的循环,在这个循环中会不断地检查任务队列的状态,并选择优先级最高的任务来运行,这些任务之间无法互相抢占,因此每个任务的设计需要额外小心,占用CPU的时间最好不要超过2ms,以确保处理BLE事件的任务能及时得到调度。在编写OSA的任务代码时,有几个不同于一般RTOS的地方需要注意。
  • 任务体while(1)循环的最后需要加上一个条件判断,如果是gUseRtos_c==0,则直接跳出循环,相当于每次任务只执行一次。
  • 任务体while(1)循环之前的初始化工作需要特殊化处理,定义一个静态或者全局变量initailized,并对它进行测试,确保在当OSA运行于Bare Metal配置时,这些初始化代码仅会运行一次。
  • OSA_EventWait, OSA_SemphoreWait, OSA_MutexLock等函数在OSA的FreeRTOS实现时会阻塞任务,而在Bare Metal实现中,调用这些API不会阻塞,而是通过内部一个状态机来检测资源,如果资源目前是不可获得状态,则直接返回。
  • OSA_TimeDelay函数在Bare Metal实现时,是不会切换到其他任务运行的,而是在原地等待直到超时。
OSA的源码可以在目录./middleware/wireless/framework_x.y.z/OSAbstraction下找到,如感兴趣可以深入研读。
SDK提供的每一份BLE示例代码中都提供了freertos和Bare Metal(bm)两个工程。

Connectivity Framework
Connectivity Framework包含了众多的子模块,服务于应用和BLE协议栈。本系列文章将会抽取其中几个比较重要的几个子模块,作为单独的文章来分析。本文希望先给读者一个总体概念,下面的表格列出了各个模块的职责和必要性。标注为“必要”的模块如果缺失,最后整个BLE工程构建将会失败,说明该模块已被协议栈或者其他的代码引用了。标注为“非必要”的模块是为应用层服务,如果用户的应用不需要这个功能,则可以移除。子模块
职责
必要性
说明
FunctionLib
memcpy等标准函数的封装
必要
通过头文件配置选择私有实现或直接使用C库
子模块
职责
必要性
说明
List
通用双向链表实现
必要
List是MemManager和Messaging的基础
子模块
职责
必要性
说明
MemManager
基于块的内存分配
必要
协议栈所需的动态内存都从这里分配
子模块
职责
必要性
说明
Messaging
消息队列服务
必要
不具备任务同步功能,需配合OSA Event使用
子模块
职责
必要性
说明
OSAbstraction
提供操作系统服务抽象
必要
整个系统调度都依赖于OSA
子模块
职责
必要性
说明
TimersManager
提供软定时服务
必要
协议栈超时机制需要使用到
子模块
职责
必要性
说明
SecLib
封装SMP加密操作
必要
部分芯片可以选择使用硬件加密加速引擎
子模块
职责
必要性
说明
LowPower
实现低功耗管理
非必要
仅当系统需要使用低功耗时用到
子模块
职责
必要性
说明
OtaSupport
提供OTA服务
非必要
仅包含OTA的应用需要用到
子模块
职责
必要性
说明
SerialsManager
提供不同串行通讯链路层的封装
非必要
当配置协议栈为DTM模式或Host Only模式时需要
子模块
职责
必要性
说明
Flash
对不同芯片内部和外部Flash操作的封装
非必要
当包括NVM模块时为必要
子模块
职责
必要性
说明
NVM
在Flash上提供一套非易失存储机制
非必要
仅当系统需要用到Bonding时才为必要
子模块
职责
必要性
说明
MWS
多协议共存协议
非必要
当需要与外部WLAN芯片配
子模块
职责
必要性
说明
Shell
提供控制台交互和格式化打印功能
非必要
服务于应用程序
子模块
职责
必要性
说明
GPIO/LED/Keyboard
提供简单输入输出能力
非必要
服务于应用程序
子模块
职责
必要性
说明
Reset/Panic/RNG
对系统各个功能的简单封装
非必要
服务于应用程序

BLE协议栈
BLE协议栈以闭源库的形式提供,分为Host和Controller两部分。下图描述了组成Stack的主要文件和相互之间的依赖关系。640?wx_fmt=jpegController Stack负责与硬件和时序相关的蓝牙链路层处理,它通过HCI接口与Host Stack进行协同工作完成BLE数据交互。由于使用BLE SDK时几乎不需要直接与Controller Stack打交道,我们知道它的存在即可。Host Stack是一个与硬件无关的C库,它通过HCI接口向Controller Stack发送命令和接收消息,负责完成Bluetooth Core Spec中的L2CAP、ATT、SM、GATT、GAP等多个层次职责。Host Stack通过一组头文件暴露出协议栈各个层的API和回调事件接口,理解Host Stack提供的服务和数据结构对于掌握BLE SDK开发至关重要。本系列专题将以独立文章分享各个部分服务的具体应用,这里先将协议栈的两类主要接口介绍给大家。
  1. API
    API即应用程序编程接口,是外部应用主动向协议栈发起请求的入口。协议栈所提供的绝大部分API都是异步的,利用Messaging消息队列向协议栈的Host任务发送一条后便返回。实际的处理是在Host任务中完成,再通过某个注册的callback回调函数来反馈执行的结果。
  2. Callback注册回调函数
    Callback注册回调函数是由应用程序提供的一个钩子函数,在协议栈内部处理中如果有需要通知到用户层事件发生或者数据变化时,将调用注册的钩子函数通知应用程序做出响应动作。
下面一段示例代码演示了Gap_Connect()和Gap_Disconnect()两个异步API的行为和他们所触发的callback回调函数后对应事件的响应。通过这段代码可以大致了解应用代码如何与协议栈进行交互。
// 1. initiate API to request and connection and register a callback
bleResult_t rt;
rt = Gap_Connect(&gConnReqParams, Gap_ConnectionCallback);
if (rt != gBleSuccess_c) {
    
printf("Gap_Connect API fail, reason = %d ", rt);
}

// when PC(R15) arrives here, the connection may NOT be setup
....

// 2. initiate API to request a disconnection action.
rt = Gap_Disconnect(peerDeviceId);
if (rt != gBleSuccess_c) {
   
printf("Gap_Disconnect fail, reason = %d ", rt);
}

// when PC(R15) arrives here, the connection may NOT be disconnected
....

// 3. Event will be triggered when connection and disconnection happens
void Gap_ConnectionCallback(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnection Event)
{
     
switch (pConnectionEvent-eventType)
     {
           
case gConnEvtConnected_c:
                 
// async event to notify that link is connected
                 
printf("Connected to device (%d) ", peerDeviceId);
                 
break;
                 
           
case gConnEvtDisconnected_c:
                 
// async event to notify that link is disconnected
                 
printf("Disconnected to device (%d) ", peerDeviceId);
                 
break;
                 ....

低功耗蓝牙服务框架
低功耗蓝牙服务框架这个名称,是笔者为SDK中非协议栈部分的蓝牙代码取的一个名字,涉及内容主要包括有HCI、FSCI、Profiles、Connection Manager, Service Discovery、App Thread、BLE Initialization、Stack Runtime Environment等几个部分,顾名思义这些代码都是为实现BLE应用而服务的,目的是简化用户开发的难度。
  • Stack Runtime Environment
上一小节介绍的Host和Controller协议栈库,提供的是一组可链接的符号,需要外部创建任务以提供运行时环境,和创建必要的队列和事件资源,这就是Stack Runtime Environment的职责。协议栈里的了两个任务处理函数Host_TaskHandler()和Controller_TaskHandler(),分别在创建的Host_task和Controller_task里调用。另外任务处理函数还需要用到队列和事件资源,用户任务与Host_task交互、以及Host_task与Controller_task的交互都依赖于他们。下表说明了所创建资源的用途。资源
类别
用途
gApp2Host_TaskQueue
消息队列
用户任务调用Host Stack API时,向这个队列插入一个请求
资源
类别
用途
gHci2Host_TaskQueue
消息队列
Controller Taskdan产生HCI事件时,向这个队列插入一个请求
资源
类别
用途
gHost_TaskEvent
OSA事件
用于通知Host Stack队列中有新的消息插入
资源
类别
用途
mControllerTaskEvent
OSA事件
Host Task向Controller Task发送HCI命令时或中断向Controller Task请求服务
  • App Thread
App Thread是在系统启动后由OSA创建的第一个任务,它在完成BLE系统初始化后创建了一个应用后台,主要目的是服务Host stack触发的各类callback回调事件。通过Host_stack提供的Registeration API注册的回调函数,被调用的上下文都在前面介绍的Host task里,如果在这些callback做一些耗时较长的用户动作(比如上面的printf打印输出),Host task其他部分会得不到响应,从而影响BLE协议交互。因此很有必要将这些callback回调事件的处理,放到一个相对低优先级任务中,这个任务就是App Thread。首先Applmain.c中对Host stack提供的回调函数接口都做了实现,这些实现并不作实际处理,而是转发一个消息到App Thread。App Thread一直处于等待事件的状态,收到事件后再分发到各个自定义的callback中作相应处理。App Thread同时还可以通过等待另外一个队列,来接收其他任务发送的Application消息并处理,以方便用户实现类似于协议栈的异步处理机制。下表列出了App Thread的队列和时间资源以及他们的用途。资源
类别
用途
mHostAppInputQueue
消息队列
Host Stack产生的callback里将向这个队列插入一个请求
资源
类别
用途
mAppCbInputQueue
消息队列
App Thread本身或者其他用户任务都可以向这个队列插入一个请求
资源
类别
用途
mAppEvent
OSA事件
用于通知App Thread有队列里有新的消息插入
下面代码段以App_Connect为例来展示了代码是如何通过App Thread来处理Host stack回调函数的。
// User call App_Connect, instead of Gap_Connect
bleResult_t App_Connect(gapConnectionRequestParameters_t*   pParameters,                                          gapConnectionCallback_t             connCallback) {    pfConnCallback = connCallback;    return Gap_Connect(pParameters, App_ConnectionCallback); }

// Simplified App_ConnectionCallback, the function insert a message and notify App Thread
void App_ConnectionCallback (deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent) {    appMsgFromHost_t *pMsgIn = NULL;    uint8_t msgLen = GetRelAddr(appMsgFromHost_t, msgData) + sizeof(connectionMsg_t);

   pMsgIn = MSG_Alloc(msgLen);
   // use Applmain defined message type    pMsgIn->msgType = gAppGapConnectionMsg_c;    pMsgIn->msgData.connMsg.deviceId = peerDeviceId;    FLib_MemCpy(&pMsgIn->msgData.connMsg.connEvent, pConnectionEvent, sizeof(gapConnectionEvent_t));    MSG_Queue(&mHostAppInputQueue, pMsgIn);    OSA_EventSet(mAppEvent, gAppEvtMsgFromHostStack_c); }

// Pseduo code of App_Thread
void App_Thread (uint32_t param)
{    while(1) {         OSA_EventWait(mAppEvent, osaEventFlagsAll_c, FALSE, osaWaitForever_c , &event);         if (event & gAppEvtMsgFromHostStack_c) {              while (MSG_Pending(&mHostAppInputQueue))  {                   pMsgIn = MSG_DeQueue(&mHostAppInputQueue);                   // check msgType and dispatch callback handler                   if (pMsg->msgType == gAppGapConnectionMsg_c) {                        pfConnCallback(pMsg->msgData.connMsg.deviceId, &pMsg->msgData.connMsg.connEvent);                   } else if (pMsg->msgType == ...) {


通过下图可以全面的了解到App Thread, Host task和Controller task三者之间是如何完成交互的。掌握了这张图的消息交互流程,开发基于NXP BLE SDK的应用也就变的非常容易了。
640?wx_fmt=png
  • HCI
    HCI是低功耗蓝牙Controller协议栈和Host协议栈之间的沟通桥梁,对于SoC,两个协议栈都运行在同一芯片上,这部分功能实际上是简单的函数调用。当BLE SDK被配置为只作为controller Only模式(用于DTM),或者Host Only模式(controller使用另外一颗芯片)时,HCI的就需要与通过SerialManager来完成与外界通讯。
  • FSCI
    FSCI是Framework Serial Communication Interface的缩写,该模块定义了一套统一的接口将无线通讯微控制器(BLE,Thread,Zigbee)所提供的服务提供给另外一颗主处理器或者PC系统。对于BLE SoC而言,片上运行了完整的BLE Host和Controller协议栈,此时处于Network Processor网络处理器模式。外部处理器在只需要遵循FSCI协议便能控制SoC发起广播、建立连接和进行数据交换。
  • Profiles
    在GATT之上,Bluetooth SIG定义了多个标准的GATT based Profile帮助应用层互联互通,同时每个厂家也会定义一些简单的私有Profile来实现raw数据传输、OTA等功能。由于所有的Callback回调事件处理都是在应用中完成的,Profile在NXP BLE SDK中职责比较简单,主要负责完成Profile数据到GATT数据库的操作转换。
  • Conneciton Manager 与 Service Discovery
    连接建立和服务发现是每个BLE应用都需要经历的过程,BLE SDK将这部分通用的代码从应用层分离了出来,形成两对独立的.c/.h文件ble_conn_manager.c/h和ble_service_discovery.c/h。这样减少了应用层重复的代码,提高代码可维护性。这两个部分服务的功能在后面介绍蓝牙SDK具体编程的文章中都会有所涉及。
  • GATT Database
    通过一套宏定义的方法,完成静态或者动态GATT数据库的建立。第一次看到gatt_db.h会很难以理解里面的特殊格式,但要是“照猫画虎”的增加自己的service或者characeteristic还是比较简单的,这就是它的神奇之处。写一篇文章专门讲解gatt_db模块到底是如何通过一系列的宏定义来成这个工作的。


应用
BLE SDK的每个应用示例都包含了十分类似的应用层文件,下表简单描述了各个文件的职责。其中.c/h是整个应用的核心。文件名职责app_config.cBLE广播参数、扫描参数、安全性相关的配置app_preinclude.h全局参数配置文件(其他文件不需要include此文件,在IDE里已配置)gatt_db.hGATT database描述文件gatt_uuid128.h私有128位UUID描述文件.c/h应用示例代码,如heart_rate_sensor.c,广播发起、扫描,以及各种BLE协议栈事件的处理
小结
本文从零开始介绍NXP BLE SDK系统架构,逐步分析了各个子模块的功能。对于BLE协议栈和服务框两部分,作了较为细致的讲解,以帮助大家了解自己编写的应用是如何与框架进行交互的。(转自恩智浦MCU加油站)640?1.有些道理需要写好多年代码才能悟出来!2.官宣:你爱的STM32又出新子系列了!3.Python正向硬件圈杀来!工程师们,别再问需不需要学Python了!
4.在PC上对MCU程序中的数据进行可视化,用过FreeMASTER吗?
5.希望这个教训,做单片机开发的朋友都不要再犯!6.我为何要弃 Java、JavaScript、Ruby 于不顾,而去寻找新的编程语言?640?wx_fmt=gif免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。