德州CC2640R2f蓝牙芯片学习笔记(二)代码框架

目录

  • 前言:
  • 资料:
  • 一、main函数:
  • 二、app任务初始化:
  • 三、app 任务中的事件处理:
    • 3.1、事件
    • 3.2、任务处理
    • 3.3、任务间的消息
    • 3.4、发送到消息队列
    • 3.5、任务内部事件
    • 3.6、回调函数:
  • 四、蓝牙:
    • 4.1、发送蓝牙数据:
      • 4.1.1、主机向从机发送数据:
      • 4.1.2、从机向主机发送数据:
    • 4.2、接收蓝牙数据
      • 4.2.1、从机接受
      • 4.2.2、主机接受
    • 4.3、蓝牙profile:
      • 4.3.1、simpleprofile:
        • 1、定义特征属性表,在属性表中,设定特征和属性相关内容。
        • 2、定义特征值设置和读取函数:
        • 3、定义特征和属性的 读写过程
        • 4、设置回调函数
    • 4.4、ICALL BLE5模块:
  • 五、用户数据存储:
  • 六、CCFG
  • 七、动态内存:

本系列文章由江山(csdn名:补不补布)(github:jianggogogo)自己写成,当中用到引用时都已经标记出来,如果出现版权问题,请直接联系我修改。当然,技术在于分享,欢迎大家转载,不过请注明出处。最后,如果出现有错误的地方欢迎大家指正。

前言:

本篇文章,基于谷雨的开发手册,主要是在原来的基础上加入自己的理解:
看一看cc2640R2f的代码框架,从大体上来看函数到底是怎么样子一个结构。

资料:

1、蓝牙的笔记

一、main函数:

作为入口函数,首先找到位置,位于startup下面的main.c:
在这里插入图片描述

/*******************************************************************************

int main()
{
  /*注册断言回调函数*/
  RegisterAssertCback(AssertHandler);

  /*初始化硬件gpio口*/
  PIN_init(BoardGpioInitTable);

  /* Initialize ICall module */
  /*初始化软件模块,用于app和stack之间的通信*/
  ICall_init();

  /*创建任务*/
  ICall_createRemoteTasks();

  /*GAPRole管理蓝牙设备角色等事务,*/
  GAPRole_createTask();

  /*最后创建的任务是app ,也就是 SimpleBLEPeripheral Task*/
  SimpleBLEPeripheral_createTask();

  /* enable interrupts and start SYS/BIOS */
  BIOS_start();
  
  return 0;
}

二、app任务初始化:

/*********************************************************************
static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{
  //app任务初始化函数,为任务分配服务
  SimpleBLEPeripheral_init();

  // Application main loop
  for (;;)
  {
  //一个用于任务的循环
}
}

App 任务初始化函数一般需要做下列几件事情:
第 65 页 / 共 177 页
 ICall_registerApp注册,必须首先调用
 设置 GAP 层 ,例如广播间隔等参数
 设置 GAPRole ,例如是否开启广播等、连接参数等
 设置绑定管理器,例如是否开启绑定,以及绑定模式。
 添加 Profile ,并注册 Profile 回调函数
 启动设备
 其他硬件配置

三、app 任务中的事件处理:

3.1、事件

uint32_t events; 
// 阻塞在这里,等待事件返回 
events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS, ICALL_TIMEOUT_FOREVER); 
//不断去处理到达的事件
if (events) { }

3.2、任务处理

当蓝牙协议栈通过
触发一个 RTOS 任务 ,开始任务处理 过程 。任务处理过程一般分为三个
部分。
 协议栈消息,例如发送 AttRsp 确认消息 ,协议栈内部消息 GATT_MSG_EVENT 等
 RTOS 消息队列,通过消息队列缓存的延期待执行的数据,例如缓存的 Profile 特征值
数据等。
 自定义 EVENT ,例如 app 自定义的周期性时间,每个 5s 发送一次 notify 等。

3.3、任务间的消息

蓝牙协议栈通过 ICall 将消息传递给应用程序任务,例如上一小节的 GATT_MSG_EVENT 消除处理函数 SimpleBLEPeripheral_processStackMsg 。

3.4、发送到消息队列

这些消息使用
SimpleBLEPeripheral_enqueueMsg 函数入队 。消息被添加到队列中,并且
按照添加的顺序进行消息处理。 发送消息和 处理消息的代码片段如下 。

  • 当然我们最后还要使用TI 再次封装的实用工具: Util_enqueueMsg(appMsgQueue, syncEvent, (uint8*)pMsg);,将数据添加到消息队列中去。 注意第二个参数是 syncEvent,当前任务的同步事件句柄。
    Util_enqueueMsg函数如下:
uint8_t Util_enqueueMsg(Queue_Handle msgQueue, Event_Handle event, uint8_t *pMsg)
 { ... 
 pRec->pData = pMsg; // 队列是原子操作 
 Queue_put(msgQueue, &pRec->_elem); // Wake up the application thread event handler. 
 if (event) {
  Event_post(event, UTIL_QUEUE_EVENT_ID); 
  } 
  ... 
  return FALSE; }
 

3.5、任务内部事件

在app 任务内部,可以创建 16 个自定义事件,可以通过定时 或其他方式 来异步执行事件

3.6、回调函数:

例如、蓝牙状态回调:

static void SimpleBLEPeripheral_processStateChangeEvt(gaprole_States_t newState) { switch ( newState ) {
 case GAPROLE_STARTED: 
 {
  //协议栈启动运行 
  GAPRole_GetParameter(GAPROLE_BD_ADDR, ownAddress); 
  } 
  break;
 case GAPROLE_ADVERTISING: 
   //设备正在广播
    break;
     case GAPROLE_CONNECTED: 
     { 
     //设备已连接 
     } 
     break;

注意,所有的回调函数的代码处理主体应该在任务上下文中进行,也就是通过
RTOS 消息
队列的方式,先缓存起来,然后待任务空闲时处理,例如刚才的函数,是在回调函数中 push
到消息队列中。

四、蓝牙:

4.1、发送蓝牙数据:

蓝牙数据的发送就是分为两种情况:一种是主机向从机发送数据,另一种是从机向主机发送数据:

4.1.1、主机向从机发送数据:

主机向从机发送数据
GATT_WriteCharValue ()(),该函数的代码片段如下

//申请用于发送蓝牙数据的内存 
req.pValue = GATT_bm_alloc(connHandle, ATT_WRITE_REQ, 1, NULL); if ( req.pValue != NULL )
 { //从机特征值uuid对应的handle,主机发起连接时获得。 
 req.handle = charHdl; req.len = 1; //待发送的数据 
 req.pValue[0] = charVal; 
 req.sig = 0;
 req.cmd = 0; 
 //开始发送
  status = GATT_WriteCharValue(connHandle, &req, selfEntity); 
  if ( status != SUCCESS ) 
  { 
  GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ); 
  } 
 }

4.1.2、从机向主机发送数据:

GATT_Notification ()()

//申请用于发送蓝牙数据的内存 
noti.pValue = GATT_bm_alloc(connHandle, ATT_HANDLE_VALUE_NOTI, 2, NULL); if (noti.pValue != NULL)
 { //通知特征值的句柄,可以通过GATT属性表得到 
 noti.handle = pAttr->handle; noti.len = 2; 
 //待发送的数据 
 noti.pValue[0] = LO_UINT16(blkNum); noti.pValue[1] = HI_UINT16(blkNum); 
 //发送数据 
 if (GATT_Notification(connHandle, &noti, FALSE) != SUCCESS) { 
 GATT_bm_free((gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI);
  } 
 }

4.2、接收蓝牙数据

和发送一样,蓝牙接受数据也是区分主机和从机两个方向。

4.2.1、从机接受

对应GATT_WriteCharValue函数。

第一步,注册

//Profile 回调函数,用来接收特征值事件。
// Register callback with SimpleGATTprofile 
SimpleProfile_RegisterAppCBs(&SimpleBLEPeripheral_simpleProfileCBs);

第二步,在回调函数中调用
SimpleProfile_GetParameter读取 数据

static void SimpleBLEPeripheral_processCharValueChangeEvt(uint8_t paramID) { 
switch(paramID) 
{ case SIMPLEPROFILE_CHAR1: //读取数据 
SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR1, &newValue);
 //打印到显示屏上
  Display_print1(dispHandle, 4, 0, "Char 1: %d", (uint16_t)newValue); break; ... }

4.2.2、主机接受

第一步,注册接收
notify 消息
// 注册接收Indications/Notifications消息
GATT_RegisterForInd(selfEntity);
第二步,
在 GATT_MSG_EVENT 消息处理函数 SimpleBLECentral_processGATTMsg增
加 ATT_HANDLE_VALUE_NOTI 处理代码 。

static void SimpleBLECentral_processGATTMsg(gattMsgEvent_t *pMsg) { if (state == BLE_STATE_CONNECTED) {
 // See if GATT server was unable to transmit an ATT response 
 if (pMsg->hdr.status == blePending) 
 else if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT) ... 
 //handle notifications after initialization
  else if (pMsg->method == ATT_HANDLE_VALUE_NOTI)
   { 
  //数据内容为:pMsg->msg.handleValueNoti.pValue //数据长度为:pMsg->msg.handleValueNoti.len 
  UartWrite(pMsg->msg.handleValueNoti.pValue,
pMsg->msg.handleValueNoti.len);
 } ... }

4.3、蓝牙profile:

蓝牙Profile 可以理解为主从双方通信过程中的格式化数据,并存储在蓝牙从机中,作为服务端,而主机作为客户端,客户端可以来获取服务端的数据或者属性,这个数据就称之为特征值。
德州CC2640R2f蓝牙芯片学习笔记(二)代码框架_第1张图片
Simple_peripheral 从机中的 Profile 为 SimpleProfile ,位于 src profiles simple_profile cc26xx
目录中。 重要的代码片段如下。

4.3.1、simpleprofile:

这个profile中采用的profile为:SimpleProfile。

1、定义特征属性表,在属性表中,设定特征和属性相关内容。

//特征属性表
static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
 {
  {
   {
    ATT_BT_UUID_SIZE, primaryServiceUUID 
   }
 , /* type */
  GATT_PERMIT_READ, /* permissions */
   0, /* handle */
  (uint8 *)&simpleProfileService /* pValue */ 
  },
 // Characteristic 1 Declaration 
 {
  { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar1Props }, 
  // Characteristic Value 1
   { 
   {
    ATT_BT_UUID_SIZE, simpleProfilechar1UUID 
   },
    GATT_PERMIT_READ | GATT_PERMIT_WRITE,
     0, 
     &simpleProfileChar1 },
      // Characteristic 1 User Description
       { 
       		{ 
       		ATT_BT_UUID_SIZE, charUserDescUUID 
     	  	}, 
       GATT_PERMIT_READ,
        0, 
        simpleProfileChar1UserDesp 
        }, 
        ... };

UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,这里设置为0xFFF1。

CONST uint8 simpleProfilechar1UUID[ATT_BT_UUID_SIZE] = { 
LO_UINT16(SIMPLEPROFILE_CHAR1_UUID), HI_UINT16(SIMPLEPROFILE_CHAR1_UUID) 
};

用户可读的描述::

// Simple Profile Characteristic 1 User Description
 static uint8 simpleProfileChar1UserDesp[17]
  = "Characteristic 1";

特征值为一个字节的数据:

// Characteristic 1 Value 
static uint8 simpleProfileChar1 = 0;

2、定义特征值设置和读取函数:

  • 特征值设置:
bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value ) 
{ 
switch 
( param )
//特征值1设置
 case SIMPLEPROFILE_CHAR1: 
 if ( len == sizeof ( uint8 ) ) 
 { 
 simpleProfileChar1 = *((uint8*)value);
  }
   else 
   { 
   ret = bleInvalidRange;
    } 
    break; //特征值2设置
     case SIMPLEPROFILE_CHAR2: 
     ... 
     break; }
  • 读取函数
bStatus_t SimpleProfile_GetParameter( uint8 param, void *value ) 
{ s
witch ( param )
 { 
 case SIMPLEPROFILE_CHAR1: *((uint8*)value) = simpleProfileChar1; break;
  case SIMPLEPROFILE_CHAR2:
   break; 
   ...
    }

3、定义特征和属性的 读写过程

主机读取特征值

static bStatus_t simpleProfile_ReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method) 
{ 
if ( pAttr->type.len == ATT_BT_UUID_SIZE )
 { // 16-bit UUID
 uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]); switch ( uuid ) 
 { 
 //主机读特征值策略
  case SIMPLEPROFILE_CHAR1_UUID: *pLen = 1; 
	pValue[0] = *pAttr->pValue;
   break; 
   
   case SIMPLEPROFILE_CHAR5_UUID:
    *pLen = SIMPLEPROFILE_CHAR5_LEN; 
    VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN ); 
    break;

主机写入特征值

static bStatus_t simpleProfile_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method)
 { if ( pAttr->type.len == ATT_BT_UUID_SIZE )
  { // 16-bit UUID 
  uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
   switch ( uuid ) 
  { //主机写入数据 
  case SIMPLEPROFILE_CHAR1_UUID: 
  ... 
  uint8 *pCurValue = (uint8 *)pAttr->pValue; 
  *pCurValue = pValue[0]; 
  notifyApp = SIMPLEPROFILE_CHAR1; // 调用app注册的Profile回调函数 
  ... 
  simpleProfile_AppCBs->pfnSimpleProfileChange( notifyApp );
   ...

4、设置回调函数

bStatus_t SimpleProfile_RegisterAppCBs( simpleProfileCBs_t *appCallbacks )
 {
if ( appCallbacks ) { //保存回调函数 simpleProfile_AppCBs = appCallbacks; ...

4.4、ICALL BLE5模块:

ICall是一种软件模块,可为应用程序与协议栈提供通信服务, app 中调用的协议栈 API 函数,大多来自 ICall 模块,另外 ICall 还提供 RTOS 的一些线程同步、动态内存等服务。 ICall 使得 app 和 stack 在统一的 RTOS 环境中高效运行,共享资源。
德州CC2640R2f蓝牙芯片学习笔记(二)代码框架_第2张图片
德州CC2640R2f蓝牙芯片学习笔记(二)代码框架_第3张图片

五、用户数据存储:

ccdc2640R2提供的存储管理叫做SNV,snv主要用于协议栈的绑定管理器存储。

开发者无需初始化SNV ,直接使用 SNV 提供的 Read/Writ e 函数即可(初始化函数已在 stack中调用,无需开发者干预)。在使用 SNV 服务之前,需要设置 stack 子工程的预处理宏定义:OSAL_SNV=1或 OSAL_SNV=2 1 或 2 表示使用的 Flash Page 4K )数量。 若 OSAL_SNV=0 ,则表示禁用 SNV 存储:

1、保存数据:
uint8 osal_snv_write( osalSnvId_t id, osalSnvLen_t len, void *pBuf)
2、读取数据:
uint8 osal_snv_read( osalSnvId_t id, osalSnvLen_t len, void *pBuf)
3、由于SNV 被多个模块共享,例如协议栈的 GapBondMgr 绑定管理器等,因此要小心的定义 snv item ,在 bcomdef.h 中查看 设置了系统占用的 item ,以及开发者可以使用的 item 范围
如下代码:

// Customer NV Items - Range 0x80 - 0x8F - This must match the number of Bonding entries
 #define BLE_NVID_CUST_START 0x80 //!< Start of the Customer's NV IDs    
 #define BLE_NVID_CUST_END 0x8F //!< End of the Customer's NV IDs

六、CCFG

CCA Customer Configuration Area 是客户配置取,占用闪存的最后一页。是客户配置取,占用闪存的最后一页,而 CCFG 占用CCA 扇区最后的 86 个字节。默认情况下,连接器会将 CCA 页中未使用的 Flash 空间分配给 app使用。

七、动态内存:

内存可以分为两个部分,堆和栈。函数中申请的局部变量,以及函数嵌套,中断都是使用栈空间。而静态变量和全局变量则是使用的堆空间。动态内存使用的内存也位于堆内存。
由于栈空间分配的数量十分有限,所以不要在函数中使用太大的数组。尽量使用动态内存。

sdk中一般使用 ICall_malloc 申请内存, ICall_free 释放内存 ,代码片段如下:

// 申请长度为len的动态内存
 uint8 *newValue = (uint8*)ICall_malloc(len); 
 ...
// 释放内存
 ICall_free(newValue);

除了ICall_malloc 中还有另外集中申请动态内存的函数,分别是:
ICall_allocMsg ,对应 free 函数为 ICall_ free Ms g
GATT_bm_alloc ,对应 free 函数为: GATT_bm_free

三者的主要区别是,
ICall_malloc 用于开发者的一般性内存申请,而 ICall_allocMsg 主要用于 RTOS 消息队列的内存申请
GATT_bm_alloc 用于待发送的蓝牙数据内存申请。

你可能感兴趣的:(#,蓝牙,嵌入式通讯)