[NRF51822 learning tutorial] SDK framework analysis

This lecture is an introduction to the framework and will not involve too many code details. The official SDK of 51822 has no framework dependency. What is a framework, for example, there is an operating system abstraction layer (OSAL) in TI's BLE SDK, which is a round-robin scheduling. You need to create tasks and so on in his way.
The 51822 SDK essentially only provides various calling interfaces, such as opening the initialization protocol stack, initializing some hardware function modules, starting broadcasting, and initiating links. How to use these interfaces is entirely up to you. However, general firmware development is the initialization of various resources in a similar process, and 51822 is no exception. Therefore, the main function of the example as a slave in the SDK is similar to the following steps:

Take the official serial BLE as an example:

[cpp]  view plain   copy
  1. int main(void)  
  2.   {  
  3.   leds_init();  //not required, but used in this example  
  4.   timers_init();  //not necessary, but used in this example  
  5.   buttons_init();  //not required, but used in this example  
  6.   uart_init();  //Not necessary, but the serial port is used in this example  
  7.   ble_stack_init();  //Required  
  8.   gap_params_init();  //Required  
  9.   services_init();  //Related to services created by yourself, different services have different details but are generally established  
  10.   //The process is basically the same, usually just use the official example to modify some parameters  
  11.   advertising_init();  //Broadcast data initialization, must  
  12.   conn_params_init();  //It depends on the situation. If you don’t need to negotiate the connection parameters after connection, the initialization is also //not necessary  
  13.   sec_params_init();  //Security parameter initialization, if you don’t use pairing and binding, you don’t need to initialize it  
  14.   advertising_start();  //Enable broadcasting, must  
  15.   // Enter main loop  
  16.   for (;;)  
  17.   {  
  18.   power_manage(); // Go  to sleep  
  19.   }  
  20.   }  

  It can be seen that in fact, only these 5 functions are necessary at the core. You can remove all other codes, as long as these 5 function devices can be run as long as the mobile phone can search for the device and communicate with the device.
  This way of initialization can be said to be no different from our general MCU development.
  What about after initialization. In the previous development of bare-board single-chip microcomputers, we entered a while loop to perform some repetitive things. Later, in order to reduce power consumption, we started to add a sleep code in the while(1) loop to make the chip sleep when it was not working, and rely on interrupts to wake up. Deal with what comes.
  And the main function of 51822 above is also a for{} loop at the end, power_manage(); The internal code is actually a sleep instruction. The Main function is gone here, and in the end it is actually a cyclic sleep. There is no task, only sleep. Then it is conceivable that the 51822 protocol stack implementation should be based on "event wakeup", that is, sleep when nothing happens, wake up work when something happens, and then continue to sleep. So where are the codes for handling events?
  How does the protocol stack work? Where do I want to create a service to add? Where is the data sent by the phone? How do I send the data to the phone?
  The following explains these one by one Question:
  How does the protocol stack work?
  To understand how the protocol stack works, we must first understand that the 51822 protocol stack is 100% event-driven. This means that any data sent by the protocol stack to the app is event-based.
  For example, the device receives a link request from a mobile phone, or data from a mobile phone. The protocol stack first receives these data and does some processing, and then packs these data (such as link requests, or ordinary data, etc.) into a structure, and attaches the event ID, such as BLE_GAP_EVT_CONNECTED or BLE_GATTS_EVT_WRITE to tell the upper app about the event. The event represented by the structure.
  比如BLE_GAP_EVT_CONNECTED代表链接事件,那么这个事件结构体中包含的数据就是连接参数等数据。而BLE_GATTS_EVT_WRITE代表写事件,那么结构体中的数据就是对端设备(比如手机)写给板子的数据。


比如uart的demo中dispatch派发函数

[cpp]  view plain   copy
  1. static void ble_evt_dispatch(ble_evt_t * p_ble_evt)  
  2. {  
  3.     ble_conn_params_on_ble_evt(p_ble_evt);  
  4.     ble_nus_on_ble_evt(&m_nus, p_ble_evt);  
  5.     on_ble_evt(p_ble_evt);  
  6. }  
在任何与BLE相关的事件被协议栈上抛上来给app时,ble_evt_dispatch就会被调用。从而将事件抛给各个服务函数或处理模块,这里是将事件抛给了
  连接参数管理处理函数ble_conn_params_on_ble_evt
  Uart服务的事件处理函数ble_nus_on_ble_evt (nus为Nordicuart server)
  通用的事件处理函数on_ble_evt
  不同的事件在事件结构体ble_evt_t中通过id来区别。不同是事件处理函数通常也只是处理自己感情去的事件,我们来看看ble_nus_on_ble_evt事件处理函数的内部
[cpp]  view plain   copy
  1. voidble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)  
  2. {  
  3. if ((p_nus == NULL) || (p_ble_evt == NULL))  
  4. {  
  5. return;  
  6. }  
  7. switch (p_ble_evt->header.evt_id)  
  8. {  
  9. caseBLE_GAP_EVT_CONNECTED:  
  10. on_connect(p_nus, p_ble_evt);  
  11. break;  
  12. caseBLE_GAP_EVT_DISCONNECTED:  
  13. on_disconnect(p_nus, p_ble_evt);  
  14. break;  
  15. caseBLE_GATTS_EVT_WRITE:  
  16. on_write(p_nus, p_ble_evt);  
  17. break;  
  18. default:  
  19. // No implementation needed.  
  20. break;  
  21. }  
  22. }  

  可以看到,uart服务事件处理函数只关心三个事件,链接事件,断开链接事件以及写事件(对端设备发数据过来),不同的事件再针对做不同的,这个就由开发人员自己来实现了。比如对于连接事件通常应该记录下事件结构体中的连接句柄,因为后续的BLE操作基本都要基于连接句柄(可以看做是两个设备通信的信道ID,实际为链路层中的数据接入地址概念)。
  PS: 事件是交给dispatch来派发给各个服务以及模块的,对于更底层的事件又是如何交给dispatch函数的过程请参考群公告中的 51822教程-协议栈概述教程。
  解决了所谓的事件驱动再来解决:如果希望创建一个服务在哪里添加?
  在main函数的初始化过程中有一个services_init();这个函数的内部就是添加服务,添加特征值等代码。
  函数内部其实就是注册了一会回调函数nus_data_handler(该函数会在手机发数据给板子时将数据从电脑串口打印出来) 然后再执行真正的初始化函数ble_nus_init。
  该函数的内部又会调用sd_ble_gatts_service_add这个协议栈的api接口来添加服务。
  后面也会调用sd_ble_gatts_characteristic_add这个协议栈的api接口来添加特征值。
  层次关系如下:


也就是说完成一个完整的服务建立函数其实只要sd_ble_gatts_service_add()和sd_ble_gatts_characteristic_add ()这两个核心函数。
  通常建立服务并不需要自己去从头写过。而是直接赋值官方的这个services_init()函数,然后做一些小改动就可以。比如修改一下uuid, 修改一下读/写属性,多添加一个特征值等。要修改的其实很少。
  下面解决最后两个问题:手机发送来的数据在哪里?我怎么发送数据给手机?
  要搞清楚这两个问题,先来看一下群里常问的几个与上面相关的问题:
  问:手机发给51822设备的数据在哪个函数里出来的?
  答:
  没有函数
  协议栈会抛上来一个事件结构体
  收到的数据在结构体中
  问:蓝牙上传函数,与下发函数都是一样的吗?都是服务API函数?
  答:
  只有上传函数 是服务器用来将数据传给客户端的。
  下发数据 是蓝牙芯片收到数据后,协议栈会拋上来一个有数据的事件结 构体。具体参看示例代码中的 dispatch派发程序中各个事件处理函数对各 种事件的数据。
  问:sd_ble_gatts_hvx()这个函数是 蓝牙的发送函数,有知道蓝牙的接收函数 ?
  答:
  Bluetooth does not have a receiving function. Bluetooth data is received at the bottom layer. After receiving the data, it will return the event to the upper layer ble_evt_dispatch function, which distributes the event to various services or event processing functions. The service or processing function will capture whether there is a write event case BLE_GATTS_EVT_WRITE: if it exists, do the corresponding processing. The received data is in the returned event structure.
  In fact, after reading these three problems, the above problems have been solved almost. As a slave device, BLE has an API interface for sending data to the mobile phone, which is the sd_ble_gatts_hvx() asked above. It can be set by parameters to send it in a notification mode or an indication mode (notification does not require reply confirmation, indication is required). But the data sent by the mobile phone does not have a receiving function. Why? Because the protocol stack is event-driven! After receiving the data, the protocol stack will give the upper app a write event (instructing the peer device to write data), and write it over The data is in this event structure. We just need to extract it. So there is no receiving function API.

Guess you like

Origin blog.csdn.net/lilifang_2011/article/details/72875671