一、nrf51822芯片和协议栈特性

51822芯片特色:

  1. 各个模块的功能引脚可以随意选择,并不像传统的单片机通过IO复用实现,固定好了某个功能只能选用哪个IO口。传统单片机的IO功能复用通过寄存器使能其第二功能,而51822每个模块都能通过模块的寄存器选用各个引脚。
  2. PPI系统。相比中断更能降低处理器的占用,降低功耗。

协议栈特性:

  1. 协议堆栈和应用程序代码分离,没有链接时间依存性,分开编译和更新。没有专用的应用程序框架或RTOS依存性,TI的就有个轮询框架。
  2. 堆栈为百分百的异步并由事件驱动,并向应用程序层提供线程安全管理程序调用SVC的应用程序接口。
    协议栈向APP发送信息是通过事件发动的!
    APP向协议栈发送信息是通过API接口,而API接口通过SVC机制实现的。

协议栈如何发送数据给应用程序:

当有事件来临,比如连接请求或者对端设备发来了数据,协议栈内部会进行处理,处理完就会置位SWI2中断,中断内部就会调用事件分发,发送一个事件,给了事件分发任务(dispatch),这个任务会发给每个服务,每个服务会判断这个事件是什么,如果是自己感兴趣的就进行处理,比如连接请求是每个服务都感兴趣,服务可能就会保存连接句柄,因为连接句柄是后续处理都要用的。

(if部分一般是不通的,没有使能调度部分。)

        这个中断的触发在协议栈内部,我们看不到,比如协议栈收到发给自己的数据要进行处理,就会包装一个事件,然后触发这个SWI2中断函数!

intern_softdevice_events_execute函数中就会间接调用ble_evt_dispatch函数,把这个事件交给后者处理 ,后者就把事件发给各个服务。

(最开始的ble_stack_init协议栈初始化函数中通过函数softdevice_ble_evt_handler_set(ble_evt_dispatch),赋值给全局变量m_ble_evt_handler = ble_evt_dispatch;)

static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{dm_ble_evt_handler(p_ble_evt);ble_conn_params_on_ble_evt(p_ble_evt);bsp_btn_ble_on_ble_evt(p_ble_evt);on_ble_evt(p_ble_evt);ble_advertising_on_ble_evt(p_ble_evt);/*YOUR_JOB add calls to _on_ble_evt functions from each service your application is usingble_xxs_on_ble_evt(&m_xxs, p_ble_evt);ble_yys_on_ble_evt(&m_yys, p_ble_evt);*/
}

应用程序如何发数据给协议栈:
在服务中通过调用API即可。

比如调用数据发送函数,然后就是协议栈的事情了,我们也看不到代码实现了。大致过程如下:

number就相当于ID号一样。加入我们调用的数据发送功能函数的ID是3,就相当于调用了_SVC(3),然后就会“陷入”协议栈中的SVC中断函数。

上图中,下半部是协议栈的代码,上半部是我们自己写的程序代码,分别有一个中断向量表。但是实际上硬件只认一个,所以应用程序要紧挨着协议栈存储,协议栈即可找到应用程序中设定的“中断向量表”,需要的时候跳转就可。(协议栈中会有一个指针指向烧写协议栈后的最末端的地址。)

        实际上,当真正的外部一切中断到来时,都是到达协议栈内部的中断向量表中,找到对应的中断处理函数,此函数会找到APP中断向量表的偏移,最终定位到用户写的handler中断处理函数。

二、nrf51 sdk框架

必备的部分如下:

int main(void)
{ble_stack_init();gap_params_init();advertising_init();services_init();//跟自己创建的服务相关,不同的服务细节不同但大致建立过程一致,通常在官方的例子上修改一些参数即可。ble_advertising_start(BLE_ADV_MODE_FAST);// Enter main loop.for (;;){power_manage();//进入睡眠。
/*内部仅仅调用了sd_app_evt_wait(),内部其实就是调用一个arm m0的睡眠指令。*/}
}

由上可知,初始化之后就进入睡眠。那是怎么通讯的呢,可以推测,肯定是通过中断、事件这种机制。有事件产生时就在事件处理函数中处理的。

2.1 协议栈如何运作?

“100%基于事件”!协议栈向app发送的任何数据都是基于事件。
        开机运行初始化完后就进入低功耗睡眠了,直到底层有事件发生,都是协议栈内部先捕获事件并处理的!!

        比如设备收到手机发来的链接请求,或是手机发过来的数据等等。协议栈首先收到这些数据
后做一些处理,然后将这些数据(比如链接请求,或是普通数据等)打包成一个结构体,并附
上事件 ID,比如 BLE_GAP_EVT_CONNECTED 或 BLE_GATTS_EVT_WRITE 来分别告诉上层 app这个事件结构体代表的事件。
        比如 BLE_GAP_EVT_CONNECTED 代表链接事件,那么这个事件结构体中包含的数据就是连接参数等数据。
         而 BLE_GATTS_EVT_WRITE 代表写事件,那么结构体中的数据就是对端设备(比如手机)写给板子的数据。

ble_evt_dispatch函数是初始化过程中“自己”注册的。其代码是我们可以修改的。

协议栈内部处理完数据打包好就会交给ble_evt_dispatch函数。

比如 uart 的 demo 中 dispatch 派发函数 :
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
ble_conn_params_on_ble_evt(p_ble_evt);//连接管理相关的处理函数
ble_nus_on_ble_evt(&m_nus, p_ble_evt);//服务相关的,对手机读写数据感兴趣
on_ble_evt(p_ble_evt);
}
        在任何与 BLE 相关的事件被协议栈上抛上来给 app 时,该函数就会被调用。从而将事件抛给
各个服务函数或处理模块,这里是将事件抛给了连接参数管理处理函数 ble_conn_params_on_ble_evt;Uart 服务的事件处理函数 ble_nus_on_ble_evt (nus 为 Nordicuart server) ;通用的事件处理函数 on_ble_evt 。

这个函数把事件交给各个模块(实际就是各个事件处理函数,内部就是各种case事件)后,处理完后,最终还是会到这个函数这里,再次进入睡眠。形成一个循环的流程。

2.2 哪里添加服务?

        在 main 函数的初始化过程中有一个 services_init();这个函数的内部就是添加服务,添加特征
值等代码。
        以串口实验的demo为例,函数内部其实就是注册了一回调函数 nus_data_handler(该函数会在手机发数据给板子时将数据从电脑串口打印出来) 然后再执行真正的初始化函数 ble_nus_init。(这些都不是固定的,都是自己随便写的。参考template的demo,结构体什么的都是自己这个应用想怎么写就怎么写。51822sdk要求的是下面这两个函数。)
        该函数的内部又会调用 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, 修改一下读/写属性,多添加一个特征值等。要修改的其实很少。
        难点在于自己改sd_ble_gatts_characteristic_add ()函数时里面有很多参数需要自己设置。(其实很多绑定安全连接等参数我们初学使用中不改也行,需要的时候对照手册改)。

2.3 主机发送来的数据在哪里?如何发送数据给手机?

问:手机发给 51822 设备的数据,用哪个API函数取出来?(这体现了另一种协议栈设计方式)
答:没有函数。协议栈会抛上来一个事件结构体,收到的数据在事件结构体中。
依旧以SDK中自带的ble_app_uart这个demo为例:
ble_evt_dispatch(ble_evt_t * p_ble_evt)中有 ble_nus_on_ble_evt(&m_nus, p_ble_evt);
后者内部“捕获”三个事件:switch (p_ble_evt->header.evt_id)
case BLE_GAP_EVT_CONNECTED 、BLE_GAP_EVT_DISCONNECTED、BLE_GATTS_EVT_WRITE(写事件!)。
typedef struct
{ble_evt_hdr_t header;           /**< Event header. */union{ble_common_evt_t  common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ble_gap_evt_t     gap_evt;    /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ble_l2cap_evt_t   l2cap_evt;  /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ble_gattc_evt_t   gattc_evt;  /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ble_gatts_evt_t   gatts_evt;  /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */} evt;
} ble_evt_t;typedef struct
{uint16_t conn_handle;                                       /**< Connection Handle on which the event occurred. */union{ble_gatts_evt_write_t                 write;              /**主机写数据过来的结构体!!*/ble_gatts_evt_rw_authorize_request_t  authorize_request;  /**< Read or Write Authorize Request Parameters. */ble_gatts_evt_sys_attr_missing_t      sys_attr_missing;   /**< System attributes missing. */ble_gatts_evt_hvc_t                   hvc;                /**< Handle Value Confirmation Event Parameters. */ble_gatts_evt_timeout_t               timeout;            /**< Timeout Event. */} params;                                                   /**< Event Parameters. */
} ble_gatts_evt_t;typedef struct
{uint16_t                    handle;             /**< Attribute Handle.句柄!! */ble_uuid_t                  uuid;               /**< Attribute UUID. */uint8_t                     op;                 /**< Type of write operation, see @ref BLE_GATTS_OPS. */uint8_t                     auth_required;      /**< Writing operation deferred due to authorization requirement. Application may use @ref sd_ble_gatts_value_set to finalise the writing operation. */uint16_t                    offset;             /**< Offset for the write operation. */uint16_t                    len;                /**< Length of the received data. */uint8_t                     data[1];            /**< 数据!!Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for compilation.See @ref sd_ble_evt_get for more information on how to use event structures with variable length array members. */
} ble_gatts_evt_write_t;
      补充:  sd_ble_gatts_hvx()这个函数是蓝牙的发送函数。可以通过参数来设置是以通知方式发送还是指示方式发送(通知不需要回复确认,指示需要)。
        为啥又发送的函数,不弄个接受的函数呢?
        因为发送是“同步的”,我们代码知道什么时候发。可是不知道什么时候收数据,如果调用函数,岂不是一直等?这是异步的过程,底层实现只能通过异步事件通知的方式。

三、main函数中初始化详解

https://blog.csdn.net/chen3522061/article/details/118265541

四、main函数进入休眠之后呢?

        整个初始化完成后,设备便进入睡眠模式,每当广播间隔到期会发送一次广播。即工作状态为:睡眠+广播。直到有设备发来连接请求,当设备连接上手机后边继续处于睡眠状态等待”事件”的发生。

老版本的串口透传的例子为例

电脑串口->51822->手机

        main 函数的串口初始化程序 uart_init 的最后打开了串口的接收中断。那么这个方向的数据流的起点就是在串口中断中收到电脑上发来的数据为起点,通过无线发给手机。
(重点是API:sd_ble_gatts_hvx函数)
void UART0_IRQHandler(void)//就在主文件的main函数的上面
{static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];static uint8_t index = 0;uint32_t err_code;
uint8_t temp;
//取得电脑串口发过来的数据data_array[index] = simple_uart_get();index++;
//判断串口发送给来的数据是否达到 20 的字节,或者是不是发送了字母’q’。如果满足
//调用发送函数将收到的串口数据发送给手机。否则不发送等待知道满足条件。// (这里通常新手说手机收不到数据的原因,因为没输入达到 20 个字节)if ((data_array[index - 1] == 'q') || (index >= (BLE_NUS_MAX_DATA_LEN - 1))){err_code = ble_nus_send_string(&m_nus, data_array, index + 1);
//这个模样像API?然而是这个项目自己写的,封装了一下,其实内部起作用的是sd_ble_gatts_hvx!!//发送了数据后清零数组下标。以继续缓存后续的串口数据。index = 0;} }uint32_t ble_nus_send_string(ble_nus_t * p_nus, uint8_t * string, uint16_t length)
{ble_gatts_hvx_params_t hvx_params;//这里是检测参数是否正确。是否是已经连接上了手机 (只有连接后,conn_handle 才会// 被赋值为有效值),检查手机是否使能了开发板的通知,因为开发板作为服务端向手机
//发送数据时通过通知或指示两种方式,这两种方式都需要手机先使能开发板。if((p_nus->conn_handle==BLE_CONN_HANDLE_INVALID)||(!p_nus->is_notification_enabled)){return NRF_ERROR_INVALID_STATE;}
//一次发送的长度不能超过限定值,通常为 20if (length > BLE_NUS_MAX_DATA_LEN){return NRF_ERROR_INVALID_PARAM;}memset(&hvx_params, 0, sizeof(hvx_params));
//以为是通过 Rx 这个参数来发送数据给手机的,所以句柄要填 rx 的句柄
//这个句柄是在上面的服务初始化函数中的添加特征值函数调用完毕后或得的(最后一
// 个参数为返回的句柄)
//然后就是赋值要发送的数据,并且设置为 notify 方式hvx_params.handle = p_nus->rx_handles.value_handle;hvx_params.p_data = string;hvx_params.p_len = &length;hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
//发送函数return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);
}
        调用 sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);发送数据给手机的时候,第二
个参数是上面赋值的,那第一个参数这个连接句柄是怎么回事?在哪里设置过他?
        连接句柄你可以看做是信道标志一样(实际数据接入地址),每两个连接的设备都会具有这个
连接句柄。他们后续的通信都是通过这个连接句柄来进行(可以理解是信道标志,两个设
的通信标志必须一样,这代表他们是在同样的信道上通信才能正确进行通信)
        上面说过,板子整个初始化流程走完后就是睡眠和广播等待手机连接。那么这个conn_handle 就一定是手机发来连接,板子中协议栈处理完后上抛给 app 的连接事件中赋值的。从而记录下后续板子和手机通信的”信道”。 在 程序框架剖析 那一讲中介绍过,协议栈拋上来的事件结构体最终是由 dispatch 这个派发程序发给再发给各个服务的事件处理函数和模块的事件处理函数的。
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
//将事件交给连接管理模块的事件处理函数
ble_conn_params_on_ble_evt(p_ble_evt);
//将事件交给 uart 服务的事件处理函数
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
//处理一些一般的事件on_ble_evt(p_ble_evt);
}
再进入 uart 服务的事件处理函数中看下发生连接时是如何记下 后续通信所用的连接句柄
的:

疑问:那注册服务时的&p_nus->service_handle是什么呢?????????????

手机->51822手机电脑串口->51822->

sdk11版本的串口透传的例子为例

nrf51822杂乱笔记相关推荐

  1. NRF51822开发笔记-1.搭建NRF51822开发环境

    NRF51822开发笔记-1.搭建NRF51822开发环境 一.准备软件包 二.安装keil MDK并破解 三.安装NRF SDK 四.安装nRFgo studio 五.安装Jlink驱动 一.准备软 ...

  2. NRF51822开发笔记-5.nRF51822裸机实验GPIO输出驱动LED

    NRF51822开发笔记-5.nRF51822裸机实验GPIO输出驱动LED 一.例程分析 1.多个GPIO输出模式配置函数 2.单个GPI0输出模式配置函数 GPIO输出高电平函数 二.程序设计 一 ...

  3. NRF51822开发笔记-6.nRF51822裸机实验按键输入控制LED输出

    NRF51822开发笔记-6.nRF51822裸机实验按键输入控制LED输出 一.设计思路 二.程序代码 三.总结 单个GPIO输入配置函数 多个GPIO输入配置函数 一.设计思路 用一个按键控制LE ...

  4. NRF51822开发笔记-7.nRF51822芯片解读

    NRF51822开发笔记-7.nRF51822芯片解读 一.处理器 二.低功耗 三.硬件接口 学习了2个实验,能够基本上控制GPIO的输入输出模式,接下来要重点学习下这款芯片的规格,方便更好的去开发利 ...

  5. NRF51822开发笔记-2.Keil-MDK编译的第一个程序

    NRF51822开发笔记-2.Keil-MDK编译的第一个程序 1.进入安装路径,找到第一个实验Blinky_example,双击打开 2.编译 3.编译成功,无错误 4.生成了Hex文件 编译成功了

  6. 阅读记录:Learning multiple layers of representation(杂乱笔记)

    典型的浅层学习结构: 传统隐马尔可夫模型(HMM).条件随机场 (CRFs).最大熵模型(Maxent).支持向量机(SVM).核回归及仅含单隐层的多层感知器(MLP)等. 局部表示,分布式表示和稀疏 ...

  7. ActionScript 3.0 杂乱笔记3

    Event类: 1. 在ActionScript3.0的事件处理系统中时间对象主要有两个作用:一是将事件信息存储在一组属性中,来代表具体事件:二是包含一组方法,用于操作事件对象影响事件处理系统的行为. ...

  8. bootstrap 获取表格修改的结果_bootstrap-table前端修改后台传来的数据重新进行渲染...

    使用bootstrap-table显示数据,后台传回数据以后,可能需要对其做调整,如需要前端为数据添加单位 调整数据代码 $("#"+tableId).bootstrapTable ...

  9. c# 蓝牙虚拟串口_蓝牙模块——基础知识介绍

    1. 数据透传 蓝牙模块可以通过串口(SPI.IIC)和MCU控制设备进行数据传输. 蓝牙模块可以做为主机和从机.主机就是能够搜索别的蓝牙模块并主动建立连接,从机则不能主动建立连接,只能等别人连接自己 ...

最新文章

  1. Java微服务(一)【idea安装2020版本】
  2. Android的基本常用的短信操作
  3. 网络基础3(IP段格式,UDP数据报格式,TCP数据报格式)
  4. Jmeter日志输出和日志级别设置
  5. php 双向队列,PHP实现一个双向队列
  6. php %3ch1%3e字体,phpWebSite搜索模块跨站脚本执行漏洞
  7. linux 查找文件_LINUX常用命令全集
  8. 保姆级教程 CSS 知识点梳理大全,超详细!!!
  9. struts2框架概述
  10. 【仿真】Proteus8.9 下载与安装教程(超详细)
  11. OC 获取view相对位置_【黑苹果系列】小白教程之DSD补丁篇 | 7分钟教你优雅定制最关键的OC补丁(clover通用)...
  12. Apizza在线接口工具动态绑定API参数依赖
  13. 怎么去图片水印?教你三个方法解决图片怎么去水印
  14. 计算机组成原理常见英文缩写
  15. python中apply函数的使用
  16. 【论文简述及翻译】A Large Dataset to Train Convolutional Networks for Disparity, Optical Flow, and SceneFlow
  17. 软件项目管理 7.3.敏捷历时估算
  18. 欢迎各位小伙伴来领取免费的安卓教程
  19. 【代码实现】数学游戏:最后一个说30就输的数学游戏
  20. 合同法中的违约责任如何确定的 ?

热门文章

  1. os.environ[‘TF_CPP_MIN_LOG_LEVEL‘]=‘2‘是什么意思
  2. eclipse可以写前端吗_这是我写的情诗,情诗可以这样写吗?晒晒你写的情诗吧...
  3. 软件可扩展性:来自星巴克的经验
  4. 如何正确选择数控机床夹具?
  5. php七牛云获取管理凭证调用七牛云资源列举列表,获取七牛云文件列表
  6. 开发板FPGA电机控制源码(verilog+nios2架构)FPGA电机控制源码
  7. 芯片+步进电机档位控制实验
  8. 笔记本搜索不到wifi怎么办?
  9. 罗斯蒙特流量计调试需要考虑的两大方面
  10. C++标准库(第二版).pdf与STL源码剖析.pdf下载