FireBLE

FireBLE 是一个面向于打造智能生活的开源平台,以BLE(Bluetooth Low Energy)技术为核心,拥有超低的功耗、不俗的处理能力和广泛的应用场合,专注于更智能、高效率的工作模式,让生活在科技中更安全、方便、快捷。也许您一个不经意的想法与FireBLE擦出的火花,会在这片原野上燎出火焰,甚至燃烧整个世界。

内核介绍

更新时间:2017-08-08 阅读:4234

前言

本章主要叙述QN902x的系统结构和BLE内核的基本介绍,主要是对一些基本概念的介绍和理解。

QN902x系统结构

System struct.png
QN902x有三个存储器,分别为:

  • 96K ROM:里面固化了运行BLE协议栈所需要的底层驱动、内核、基础协议栈,源码不可见,提供部分的API使用,以机器码的形式存储,使用的时候直接读取地址。
  • 64K SRAM:系统内存,分为八个块,可以选择开启和关闭任意块,系统上电后会将程序从FLASH中复制到SRAM中运行。
  • 128K FLASH:FLASH主要用于存储程序代码和掉电数据,也应用于OTA升级中。

从存储上划分,ROM存放的内核、底层驱动、基础协议栈被称为固件,SRAM中运行的app、profile、设备驱动被称为应用程序。
System software architecture.png

QN902x有三种工作模式:

  1. SOC(System On Chip)模式
  2. NP(Network Processor)模式
  3. HCI(Host Control interface)模式

SOC模式

SOC模式下,所有的程序都必须在QN902x上运行并完成,此模式比较适合一些简单的处理场合,例如蓝牙防丢器、蓝牙温度计、IBeacon基站、蓝牙手环等等,其特点的是对处理器要求不高,数据吞吐量比较低,并且对功耗要求比较高的场合,一般此时以从机形式存在。

NP模式

NP模式下,只有app在外部执行,而蓝牙协议栈和profile都在QN902x中运行,app与QN902x通过ACI(Application control interface)接口通信。此模式适合于需要处理比较复杂数据的场合,QN902x作为模组提供数据交互,例如USB Dongle、智能健康秤等等。

HCI模式

HCI模式下,只有内核和部分最底层协议栈是在QN902x中运行的,此时,外部通过HCI(Host controller interface)与QN902x通信,可实现较大的定制。

系统内核简介

昆天科的BLE协议栈分为LL、L2CAP、SMP、ATT、GATT、GAP、profile以及APP八个层,每一个层都是一个单独的状态机,有独立的任务描述器和状态形式。系统内核的主要任务就是实现内存分配、任务调度、定时器功能和层与层之间的消息通信和事件处理。

CPU上电后,首先启动的是ROM中的BootLoad,BootLoad会对Spi或者是Uart口进行检查是否有命令发出:

  • 有烧写命令发出,就会开始FLASH或者SRAM的烧录;
  • 如果200ms内没有收到命令,则将FLASH中存储的应用程序装入SRAM并且开始运行应用程序。

应用程序装入SRAM后,会进行一系列的环境初始化(startup.s),然后跳转main函数中,我们可以在main函数中看到整个系统初始化过程(如下图所示)。初始化完毕之后,程序进入while循环中,首先调用了ke_schedule函数。查询[ QN902x软件开发手册]得知,此函数就是系统内核的任务调度函数,由于函数固化在ROM,是不公开源代码的,所以,我们只需要遵循手册中的规则,去发出消息就可以了。如果系统中存在任务,调度器就会挑选出其中优先级别最高的任务执行。执行完毕后,系统开始判断系统任务中需要调用的模块,然后根据系统需求选择不同的睡眠模式,以降低系统功耗。

事件

系统在main函数的大循环中不断执行ke_schedule函数,一旦发现事件列表为非空,就会在事件列表中取出其优先级最高的事件执行处理。

系统中一共有32个事件,最高的8个事件已经被BLE协议栈使用了,所以用户只有24个协议栈可以自由使用。

定义事件

用户可以定义0~23共24个事件,数字越大等级越高。

 #define EVENT_BUTTON1_PRESS_ID	 0

事件回调函数设置

事件回调函数设置,如果设置不成功,则产生报警。(一些涉及到Assert的东西可以自行百度,这是一个断言报警,如果设置不成功将会在串口中给出一个警告,并且指出某一文件的某一行发出此警报,停止掉当前系统的运行。)

   if(KE_EVENT_OK != ke_evt_callback_set(EVENT_BUTTON1_PRESS_ID,
                                           app_event_button1_press_handler))
   {
       ASSERT_ERR(0);
   }  //usr_design.c----->usr_init  

事件发起

事件发起之后,系统会启用事件的回调函数,直到事件被清除。

 ke_evt_set(1UL << EVENT_BUTTON1_PRESS_ID);    //usr_design.c------>usr_button1_cb

事件清除

事件处理完成后,需要清除事件,否则事件列表非空,系统无法进入较深度的睡眠,将会大大的增加功耗。

 ke_evt_clear(1UL << EVENT_BUTTON1_PRESS_ID);

消息

消息的任务是实现协议层与层之间的信息交换,可以从某一层指定向某一层发送消息。

消息的结构:

  • hdr 消息列表的头部.
  • hci_type HCI数据类型 (HCI内容,用户无须填写).
  • hci_offset HCI数据的偏移地址 (HCI内容,用户无须填写).
  • hci_len HCI数据的传输长度 (HCI内容,用户无须填写).
  • id 消息标识.
  • dest_id 目标任务标志.
  • src_id 源任务标识.
  • param_len 参数长度.
  • param 参数数据.

消息建立后,存储于内核堆栈中消息列表内,在填入消息参数后,发送消息。

消息创建

ke_msg_alloc用于创建一个空参数的消息,如果需要传递参数,那么需要自己填写参数的类型、长度和内容。如下,创建一个消息标识为 APP_SYS_UART_DATA_IND 的消息,目标任务为 TASK_APP ,来源不在任务列表中,传递的参数长度为 sizeof(struct app_uart_data_req) + (app_uart_env.len - 1) 。

 struct app_uart_data_req *req = ke_msg_alloc(APP_SYS_UART_DATA_IND,
                                                     TASK_APP,
                                                     TASK_NONE,
                                                     sizeof(struct app_uart_data_req) + (app_uart_env.len - 1));

接下来还有填写参数内容,一般参数都是空数据类型,只有长度和内容,只在取出参数的时候强制转换为对应的数据类型。

 req->len = app_uart_env.len;
 memcpy(req->data, app_uart_env.buf_rx, app_uart_env.len);


消息发送

消息发送,太简单了,没啥好说。

 ke_msg_send(req);

消息释放

有创建,必然有释放回收。在消息发送后,目的任务接收到消息后会返回一个KE_MSG_CONSUMED的消息接收确认标识,消息调度器会自动释放消息,我们只需记得返回消息接收确认标识就行了。

任务

任务包含任务的类型定义和任务描述器。 任务定义

任务描述器

  • state_handler 分别描述每一个状态下处理器能够唯一接收和处理的消息
  • default_handler 描述缺省状态能接收和处理的信息,状态机在任何一个状态都能够接收和处理该处理器中描述的任务消息。
  • state 状态机的定义,每一个实例对于一个状态机。
  • state_max 任务中最多拥有的状态个数
  • idx_max 任务实例的最多拥有的个数。
state_handler

在proxr中,定义了三个状态,分别为PROXR_DISABLE、PROXR_IDLE、PROXR_CONNECTED,每一个状态都可以有该状态下唯一接收和处理的消息,也可以不设置(APP_TASK.c中只有缺省状态的任务消息描述)。

 /// Disabled State handler definition.
 const struct ke_msg_handler proxr_disabled[] =
 {
     {PROXR_CREATE_DB_REQ,   (ke_msg_func_t) proxr_create_db_req_handler },
 };
 
 /// Idle State handler definition.
 const struct ke_msg_handler proxr_idle[] =
 {
     {PROXR_ENABLE_REQ,      (ke_msg_func_t) proxr_enable_req_handler },
 };
 
 /// Connected State handler definition.
 const struct ke_msg_handler proxr_connected[] =
 {
     {GATT_WRITE_CMD_IND,    (ke_msg_func_t) gatt_write_cmd_ind_handler},
 };
 
 /// Specifies the message handler structure for every input state.
 const struct ke_state_handler proxr_state_handler[PROXR_STATE_MAX] =
 {
     [PROXR_DISABLED]    = KE_STATE_HANDLER(proxr_disabled),
     [PROXR_IDLE]        = KE_STATE_HANDLER(proxr_idle),
     [PROXR_CONNECTED]   = KE_STATE_HANDLER(proxr_connected),
 };


default_handler

在proxr中,default_handler描述了GAP_DISCON_CMP_EVT消息,因此proxr在任何状态下都可以直接发送GAP_DISCON_CMP_EVT消息以断开连接。

 /// Default State handlers definition
 const struct ke_msg_handler proxr_default_state[] =
 {
     {GAP_DISCON_CMP_EVT,    (ke_msg_func_t) gap_discon_cmp_evt_handler},
 };

/// Specifies the message handlers that are common to all states. const struct ke_state_handler proxr_default_handler = KE_STATE_HANDLER(proxr_default_state);

state
 /// Defines the place holder for the states of all the task instances.
 ke_state_t proxr_state[PROXR_IDX_MAX];
state_max

状态机拥有的最多的状态数。

 /// Possible states of the PROXR task
 enum
 {
     /// Disabled State
     PROXR_DISABLED,
     /// Idle state
     PROXR_IDLE,
     /// Connected state
     PROXR_CONNECTED,
 
     /// Number of defined states.
     PROXR_STATE_MAX
 };
idx_max

由于proxr是以从机服务器的角色出现的,所以只有一个任务实例。反之,主机客户端可以有多个,分别对应不同的从机。

 /// Maximum number of Proximity Reporter task instances
 #define PROXR_IDX_MAX                 (1)
PROXR task descriptor
 // Register PROXR task into kernel  
 void task_proxr_desc_register(void)
 {
     struct ke_task_desc task_proxr_desc;
     
     task_proxr_desc.state_handler = proxr_state_handler;
     task_proxr_desc.default_handler=&proxr_default_handler;
     task_proxr_desc.state = proxr_state;
     task_proxr_desc.state_max = PROXR_STATE_MAX;
     task_proxr_desc.idx_max = PROXR_IDX_MAX;
 
     task_desc_register(TASK_PROXR, task_proxr_desc);
 }

任务状态常用API

当前任务状态获取

在proxr中就用到APP的状态来执行开启与关闭广播的操作。(fireblue/Demo_BLE/prj_proxr/src/usr_design.c)

 case APP_SYS_BUTTON_1_TIMER:
           // make sure the button is pressed
           if(gpio_read_pin(BUTTON1_PIN) == GPIO_LOW)
           {
               if(APP_IDLE == ke_state_get(TASK_APP))
               {
                   struct app_proxr_env_tag *app_proxr_env = &app_env.proxr_ev;
                   if(!app_proxr_env->enabled)
                   {
                       // start adv
                       app_gap_adv_start_req(GAP_GEN_DISCOVERABLE|GAP_UND_CONNECTABLE,
                               app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE),
                               app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()),
                               GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);
 
 #if (QN_DEEP_SLEEP_EN)
                       // prevent entering into deep sleep mode
                       sleep_set_pm(PM_SLEEP);
 #endif
                   }
               }
               else if(APP_ADV == ke_state_get(TASK_APP))
               {
                   // stop adv
                   app_gap_adv_stop_req();
 
 #if (QN_DEEP_SLEEP_EN)
                   // allow entering into deep sleep mode
                   sleep_set_pm(PM_DEEP_SLEEP);
 #endif
               }
           }
           break;
当前状态设置

fireblue/Demo_BLE/src/profile/proxr_task.c

 // Go to Connected state
 ke_state_set(TASK_PROXR, PROXR_CONNECTED);

消息调度器

消息调度机制提供了一个发送命令到一个任务状态机的方法。消息有三种状态,consumed、free和save,调度器发现消息列表非空后,就会逐一的找到消息的处理函数去执行处理,在执行完处理函数之后一般处理函数会返回一个消息状态。如果在处理未完成的时候,系统又不得不去执行其他的任务,消息调度器就会把消息设置为save,在有空闲的时候再调出来执行,如果执行完毕,就直接把该消息free掉,释放掉其占用的资源。这部分属于闭源代码,而且也很少涉及,我们只需稍作了解。

定时器调度器

定时器调度器是一个非常频繁使用的一个调度器,它提供了一个延时一段时间后发出某个消息的操作,时间基数为10ms。使用定时器发送消息是不能够携带参数的,所以定时器虽然简单,也有一定的限制性。

定时器事件设置

参数一为需要发送的消息标识,参数二为目的任务状态机,参数三为延时的时间,以10ms为基数。

例:200ms后发送停止报警的消息到APP任务状态机。

 buzzer_on(BUZZ_VOL_LOW);
 ke_timer_set(APP_PROXR_ALERT_STOP_TIMER, TASK_APP, 500);    // 5 seconds

定时器事件与事件一样,处理完成后需要清除,否则会导致无法进入深层次的睡眠状态,大大增加功耗。

 buzzer_off();
 ke_timer_clear(APP_PROXR_ALERT_STOP_TIMER, TASK_APP);

包含头文件

要使用内核的服务,则需要包含以下的头文件

头文件 说明
ke_msg.h 消息调度以及消息的创建、发送和释放
ke_task.h 内核任务管理
ke_timer.h 定时器任务调度管理
ble.h 协议栈内核调度器
lib.h lib.h