Nordic SoftDevice蓝牙主机操作流程
之前学习nordic的nus client 主机例程时做了些笔记,现在有空重新整理了一下发出来。
NRF_SDH_BLE_OBSERVER 宏介绍
- 这个宏可以设置多个BLE事件的回调函数,并按设置的优先级依次执行。这么一来,就可以将扫描,广播,连接,服务发现,读写等蓝牙事件的处理分到多个函数中去处理。
- 比如,在mian.c里会调用NRF_BLE_SCAN_DEF(m_scan)来使能扫描功能,实际上用的是NRF_SDH_BLE_OBSERVER(_name ## _ble_obs, NRF_BLE_SCAN_OBSERVER_PRIO,nrf_ble_scan_on_ble_evt, &_name),也就是之后所有的ble事件都会来到nrf_ble_scan_on_ble_evt函数,这个函数里面只针对扫描相关的事件进行处理,然后再调用用户在scan_init时注册的回调函数来做所需要的逻辑。
- 也就是说,ble事件的发生其实会调用很多回调:
1.ble事件 —> 系统定义好的扫描事件回调 —> 针对特定ble事件,调用用户在mian.c编写的回调实现逻辑
2.ble事件 —> 系统定义好的扫描服务发现回调 —> 针对特定ble事件,调用用户在mian.c编写的回调实现逻辑
3.ble事件 —> nus服务里定义好的回调 —> 针对特定ble事件,转为nus事件,再调用用户在mian.c编写的回调实现逻辑
4.ble事件 —> 由于main.c的stack_init函数里也有一句NRF_SDH_BLE_OBSERVER,所以ble事件也会来到main.c的ble_evt_handler - 在nus的例程中,有4个具体的实例,都在mian.c开头都进行了DEF的宏操作,也就是注册了一个默认的回调,ble事件是先到默认回调,再到用户在各种init函数中设置的回调函数的。
- 使用这种叫做观测者模式的方法,可以将众多ble事件分发到具体的回调函数中去处理,而且回调函数可以分散写在多个文件当中,减少了耦合程度。
nus服务实例
- nus实例:m_ble_nus_c
- nus初始化:void nus_c_init(void),里面设置了nus服务的用户回调函数(写在mian.c的那个)。实际上,在nus的服务文件里面调用了NRF_SDH_BLE_OBSERVER宏进行观察者注册,发生ble事件时,首先由观察者分发到nus服务回调,里面将ble事件转换了成nus事件,再调用mian.c的用户服务回调。通过这种方式,可以很方便的复用nus这个模块,并且用户只需要对转换后的nus事件进行处理即可,不再需要对ble的读和写和通知事件进行区分。
- nus服务的用户回调函数:ble_nus_c_evt_handler(ble_nus_c_t * p_ble_nus_c, ble_nus_c_evt_t const * p_ble_nus_evt);
scan实例
- 扫描实例:m_scan
- 扫描初始化,scan_init(void),设置扫描参数,使能滤波器,找到就连接,这里还设置了扫描的用户回调函数
- 开始扫描,scan_start(void);
- 扫描的用户回调函数:scan_evt_handler(scan_evt_t const * p_scan_evt);
NRF_BLE_SCAN_EVT_CONNECTING_ERROR:报错
NRF_BLE_SCAN_EVT_CONNECTED:打印连接MAC地址
NRF_BLE_SCAN_EVT_SCAN_TIMEOUT:重新扫描
gatt实例
- gatt实例:m_gatt
- gatt队列:m_ble_gatt_queue
- gatt初始化:设置MTU,设置gatt层的用户回调:gatt_init(void)
- gatt层的用户回调:gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
NRF_BLE_GATT_EVT_ATT_MTU_UPDATED:调整MTU
服务搜索实例
- 搜索实例:m_db_disc
- db_discovery_init(void),这个函数设置了服务发现的用户回调函数,当连接成功后会调用ble_db_discovery_start进行服务发现,服务发现完成后调用用户回调函数
- 服务发现用户回调函数:db_disc_handler(ble_db_discovery_evt_t * p_evt);这里面就的调用了ble_nus_c_on_db_disc_evt(&m_ble_nus_c, p_evt),这个是具体服务的函数,里面会去判断当前设备有无这个服务,然后调用用户的服务回调。
协议栈初始化
- 协议栈初始化,ble_stack_init(void); 主要是时钟配置、使能协议栈。这个函数会打印出使用的RAM空间,需要在keil进行相应的配置。最后,调用了NRF_SDH_BLE_OBSERVER宏进行注册观察者,有ble事件发生时调用相应的回调函数。
- 蓝牙事件回调函数,各种事件的处理:ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
BLE_GAP_EVT_CONNECTED:连接事件,调用ble_db_discovery_start函数,开始搜索服务
BLE_GAP_EVT_DISCONNECTED:断开连接事件,打印LOG
BLE_GAP_EVT_TIMEOUT:打印超时LOG
BLE_GAP_EVT_SEC_PARAMS_REQUEST:
BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:连接参数更新应答
BLE_GAP_EVT_PHY_UPDATE_REQUEST:PHY更新应答
BLE_GATTC_EVT_TIMEOUT:客户端超时,主动断开连接
BLE_GATTS_EVT_TIMEOUT:服务端超时,主动断开连接
串口打印
- ble_nus_chars_received_uart_print(uint8_t * p_data, uint16_t data_len);
- uart_event_handle(app_uart_evt_t * p_event);
- void uart_init(void);
错误处理
- assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name);
- nus_error_handler(uint32_t nrf_error);
关闭电源
- shutdown_handler(nrf_pwr_mgmt_evt_t event);
板子相关函数,主要是指示灯
- 按键的中断函数:bsp_event_handler(bsp_event_t event)
- 按键和指示灯初始化:buttons_leds_init(void)
以下函数略
- 定时器初始化:timer_init(void)
- 调试器打印初始化:log_init(void);
- 能量管理器初始化:power_management_init(;void);
- 这个不知道是啥:idle_state_handle(void);
流程整理,整体是事件驱动流转的
- 初始化,开始扫描
- 发现要连接的目标,触发BLE_GAP_EVT_ADV_REPORT事件,调用on_adv_report进行分析,如果是要连接的设备则进行连接。也可以通过设置相关的filter,一旦发现需要的目标则自动连接。
- 如果连接成功,触发BLE_GAP_EVT_CONNECTED,调用ble_db_discovery_start寻找gatt层的服务。
- 等待服务发现完成触发回调,进入到具体服务(比如nus)的处理过程,主要是判断有无相应的svc和char,然后转换成具体服务的一个事件,调用mian.c文件中写的具体服务的回调。
- main.c中的用户服务回调进行订阅或者读写
- 触发蓝牙读写事件,进入到具体服务的回调函数中进行初步处理,然后转为具体服务的事件,再调用mian.c文件中写的用户服务回调,如此往复。