7518|4

1379

帖子

2

TA的资源

五彩晶圆(初级)

楼主
 

【STM32WB55 测评】BLE协议栈与双核通信 [复制链接]

本帖最后由 cruelfox 于 2019-5-25 17:12 编辑

  我上一篇帖子的分析内容已经暗示了,IPCC 硬件IRQ与 BLE 通信有关系。IPCC 是 STM32WB55 片内两个 CPU 的通信渠道之一,那么它和 BLE 协议栈有着什么样的关系?本篇再深入一些调查。

  上篇所述,在 p2p_Server demo 程序里面,当 SW1 按钮按下之后,最终会引起“任务”的执行,也就是 P2PS_Send_Notification() 这个函数被调用。其中起到发送通知效果的是这一条语句:
    P2PS_STM_App_Update_Char(P2P_NOTIFY_CHAR_UUID, (uint8_t *)&P2P_Server_App_Context.ButtonControl);
这是调用了 WPAN 库 p2p_stm.c 中的函数
tBleStatus P2PS_STM_App_Update_Char(uint16_t UUID, uint8_t *pPayload)
{
  tBleStatus result = BLE_STATUS_INVALID_PARAMS;
  switch(UUID)
  {
    case P2P_NOTIFY_CHAR_UUID:
     result = aci_gatt_update_char_value(aPeerToPeerContext.PeerToPeerSvcHdle,
                             aPeerToPeerContext.P2PNotifyServerToClientCharHdle,
                              0, /* charValOffset */
                             2, /* charValueLen */
                             (uint8_t *)  pPayload);
      break;
    default:
      break;
  }
  return result;
}
其中再调用 BLE 协议栈函数 aci_gatt_update_char_value().

  在 ble_gatt_aci.c 中,不难找到这个函数的实现:
tBleStatus aci_gatt_update_char_value(uint16_t Service_Handle,
                                      uint16_t Char_Handle,
                                      uint8_t Val_Offset,
                                      uint8_t Char_Value_Length,
                                      uint8_t Char_Value[])
{
  struct hci_request rq;
  uint8_t cmd_buffer[BLE_CMD_MAX_PARAM_LEN];
  aci_gatt_update_char_value_cp0 *cp0 = (aci_gatt_update_char_value_cp0*)(cmd_buffer);
  tBleStatus status = 0;
  int index_input = 0;
  cp0->Service_Handle = htob(Service_Handle, 2);
  index_input += 2;
  cp0->Char_Handle = htob(Char_Handle, 2);
  index_input += 2;
  cp0->Val_Offset = htob(Val_Offset, 1);
  index_input += 1;
  cp0->Char_Value_Length = htob(Char_Value_Length, 1);
  index_input += 1;
  Osal_MemCpy((void *) &cp0->Char_Value, (const void *) Char_Value, Char_Value_Length);
  index_input += Char_Value_Length;
  Osal_MemSet(&rq, 0, sizeof(rq));
  rq.ogf = 0x3f;
  rq.ocf = 0x106;
  rq.cparam = cmd_buffer;
  rq.clen = index_input;
  rq.rparam = &status;
  rq.rlen = 1;
  if (hci_send_req(&rq, FALSE) < 0)
    return BLE_STATUS_TIMEOUT;
  if (status)
  {
    return status;
  }
  return BLE_STATUS_SUCCESS;
}
  这里所做的,是按照参数填写了一个 aci_gatt_update_char_value_cp0 复合类型的数据(使用cmd_buffer的空间存放),再填写了一个 hci_request 结构变量 rq,将它的地址传给 hci_send_req() 函数调用。
  在 ble_types.h 文件定义了上面的结构
typedef PACKED(struct)  
{
  uint16_t Service_Handle;
  uint16_t Char_Handle;
  uint8_t Val_Offset;
  uint8_t Char_Value_Length;
  uint8_t Char_Value[(BLE_CMD_MAX_PARAM_LEN - 6)/sizeof(uint8_t)];
} aci_gatt_update_char_value_cp0;
在 ST 文档 AN5270 中对这个结构有解释:

  在 ble_const.h 中定义了
struct hci_request
{
  uint16_t ogf;
  uint16_t ocf;
  int      event;
  void*    cparam;
  int      clen;
  void*    rparam;
  int      rlen;
};
这是提供给 hci_send_req() 函数的,告诉它命令的类型(ogf, ocf指定),命令参数和返回参数的地址、长度。

  hci_send_req() 的实现在 hci_tl.c 中,这里就不贴了,其中包括命令发送和等待返回的部分。命令发送的代码是:
  opcode = ((p_cmd->ocf) & 0x03ff) | ((p_cmd->ogf) << 10);
  SendCmd(opcode, p_cmd->clen, p_cmd->cparam);
ocf 和 ogf 被合并成一个 16-bit 数据,和命令参数一起提供给 SendCmd() 函数
static void SendCmd(uint16_t opcode, uint8_t plen, void *param)
{
  pCmdBuffer->cmdserial.cmd.cmdcode = opcode;
  pCmdBuffer->cmdserial.cmd.plen = plen;
  memcpy( pCmdBuffer->cmdserial.cmd.payload, param, plen );
  hciContext.io.Send(0,0);
  return;
}
命令代码和参数被复制到 cmdserial.cmd 这个结构里面,然后 hciCotext.io.Send() 被调用。这个 Send 是一个函数指针,在 hci_register_io_bus() 中被初始化为 TL_BLE_SendCmd,也就是:
int32_t TL_BLE_SendCmd( uint8_t* buffer, uint16_t size )
{
  ((TL_CmdPacket_t*)(TL_RefTable.p_ble_table->pcmd_buffer))->cmdserial.type = TL_BLECMD_PKT_TYPE;

  HW_IPCC_BLE_SendCmd();
  return 0;
}
到这里就越来越明显了,IPCC 被用来向 CPU2 传递 BLE 命令。

  具体如何发送?
void HW_IPCC_BLE_SendCmd( void )
{
    LL_C1_IPCC_SetFlag_CHx( IPCC, HW_IPCC_BLE_CMD_CHANNEL );
    return;
}
用到 HAL 库定义的
__STATIC_INLINE void LL_C1_IPCC_ClearFlag_CHx(IPCC_TypeDef *IPCCx, uint32_t Channel)
{
  WRITE_REG(IPCCx->C1SCR, Channel);
}
也就是只写了一个寄存器标志位,那么,疑问来了:数据是怎么传递的?

  因为数据是写到 pCmdBuffer 这个全局指针表示的地址的,这个指针在 TlInit() 中被初始化为传入参数,故又当 shci_init() 函数调用 TlInit() 时初始化, 最上层其实是在 appe_Tl_Init() 函数中作的初始化:
  SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&SystemCmdBuffer;
也就是 SystemCmdBuffer 这块数据用作向 CPU2 传递命令的共享内存。只要 CPU2 知道这个地址(比如事先约定好了,软件link时固定地址),一切就好解释了:通过 IPCC 设置标志,让 CPU1, CPU2 相互通过中断来触发事件处理;而通信的具体数据,就从固定内存位置去取。

  于是 CPU1 处理 BLE 事务,只是将命令打包传递给 CPU2,并等待 CPU2 回应的数据。无线硬件方面自然是 CPU2 管理的,且不只是硬件,协议栈的管理也是 CPU2 完成。前面分析的这个 aci_gatt_update_char_value() 是软件层面的命令,而非控制器的命令。下图是 BLE 协议栈的结构,和硬件打交道的是 HCI (Host-Controller Interface), 不过和 STM32WB55 代码里面的 hci 好象不是一回事。

  HCI 有一套命令,是蓝牙规范里面定义的。linux 下还可以用 hcitool 工具从命令行直接与控制器进行命令交互,HCI 命令也可以通过串行接口(SPI, UART等)来跑。HCI 有固定的数据格式,比如命令是由命令包组成,控制器向主机通知用事件包。


  STM32WB55 BLE 支持的命令在 AN5270 文档中也列出了。看一个 hci_disconnect() 的实现:
tBleStatus hci_disconnect(uint16_t Connection_Handle,
                          uint8_t Reason)
{
  struct hci_request rq;
  uint8_t cmd_buffer[BLE_CMD_MAX_PARAM_LEN];
  hci_disconnect_cp0 *cp0 = (hci_disconnect_cp0*)(cmd_buffer);
  tBleStatus status = 0;
  int index_input = 0;
  cp0->Connection_Handle = htob(Connection_Handle, 2);
  index_input += 2;
  cp0->Reason = htob(Reason, 1);
  index_input += 1;
  Osal_MemSet(&rq, 0, sizeof(rq));
  rq.ogf = 0x01;
  rq.ocf = 0x006;
  rq.event = 0x0F;
  rq.cparam = cmd_buffer;
  rq.clen = index_input;
  rq.rparam = &status;
  rq.rlen = 1;
  if (hci_send_req(&rq, FALSE) < 0)
    return BLE_STATUS_TIMEOUT;
  if (status)
  {
    return status;
  }
  return BLE_STATUS_SUCCESS;
}
从 ogf, ocf 的值推测,这个命令被 CPU2 收到后,会交由控制器处理。

  再看看接收方向,从 CPU2 发来的通知如何收到的。上一篇帖子里提到了 IPCC RX 中断,在处理过程中间有这个函数:
void HW_IPCC_BLE_RxEvtNot(void)
{
  TL_EvtPacket_t *phcievt;


  while(LST_is_empty(&EvtQueue) == FALSE)
  {
    LST_remove_head (&EvtQueue, (tListNode **)&phcievt);
    BLE_IoBusEvtCallBackFunction(phcievt);
  }
  return;
}
注意到这里用到一个全局变量(结构体)EvtQueue. 它的定义是
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static tListNode  EvtQueue;
前面的修饰含义是什么?其实 SystemCmdBuffer 的定义也有同样的修饰。有什么特别之处?

  我用 arm-none-eabi-nm 程序查看一下编译生成 ELF 文件的符号表:
......
20001e84 B errno
20001e88 B __bss_end__
20001e88 B _ebss
20001e88 B end
20030000 A _estack
20030000 b TL_RefTable
2003001c b BleCmdBuffer
20030128 b TL_DeviceInfoTable
20030148 b TL_BleTable
20030158 b TL_ThreadTable
20030164 b TL_SysTable
2003016c b TL_MemManagerTable
20030188 b TL_TracesTable
2003018c b TL_Mac_802_15_4_Table
20030198 b FreeBufQueue
200301a0 b TracesEvtQueue
200301a8 d SystemCmdBuffer
200302b4 d BleSpareEvtBuffer
200303c0 d SystemSpareEvtBuffer
200304cc d EvtPool
20030a08 d EvtQueue
20030a10 d CsBuffer
20030a20 d SystemEvtQueue
  发现玄机了:在地址 0x20030000 以后的地方,也就是 SRAM2 里面,还有一些变量(哪怕BLE程序不会用到)。这些变量虽然从 C 语言角度看和一般的全局变量没什么差别,但它们并没有和其它全局变量放在一起——放在 .data 和 .bss 段中。我大胆猜测:这些变量就是用于 CPU1 和 CPU2 交换数据的,因为 IPCC 只提供硬件中断机制,并无数据传递功能
  在 mbox_def.h 中对前面的几个 TL_xxxTable 相应的结构型作了定义,比如
  typedef struct
  {
    MB_DeviceInfoTable_t    *p_device_info_table;
    MB_BleTable_t           *p_ble_table;
    MB_ThreadTable_t        *p_thread_table;
    MB_SysTable_t           *p_sys_table;
    MB_MemManagerTable_t   *p_mem_manager_table;
    MB_TracesTable_t        *p_traces_table;
    MB_Mac_802_15_4_t       *p_mac_802_15_4_table;
  } MB_RefTable_t;
存放其它几个表的地址,又比如
typedef struct
  {
    uint8_t     *pcmd_buffer;
    uint8_t     *pcs_buffer;
    uint8_t     *pevt_queue;
    uint8_t     *phci_acl_data_buffer;
  } MB_BleTable_t;
存放了 BLE 的几个关键交互数据的地址。
  这样的设计,只要 TL_RefTable 保证在 SRAM2 的最低地址,那么 CPU2 就可以定位它需要的其它数据结构的位置,CPU1 和 CPU2 通过内存中变量的共享,达到了命令、数据等传递目的。这样就完整回答了我前面的“数据怎么传递”的疑问。


此内容由EEWORLD论坛网友cruelfox原创,如需转载或用于商业用途需征得作者同意并注明出处
此帖出自无线连接论坛

最新回复

佩服!   详情 回复 发表于 2019-11-8 20:56
点赞 关注(1)
 

回复
举报

7628

帖子

2

TA的资源

五彩晶圆(高级)

沙发
 
还要靠逆向才能理解,看来这不管文档还是芯片都有点,,,,那什么,,,,
此帖出自无线连接论坛
 
 

回复

32

帖子

0

TA的资源

一粒金砂(中级)

板凳
 
本帖最后由 卡森 于 2019-5-26 14:44 编辑

给我们开发者做了一个明灯,多谢!只是看完了蛋蛋一抽一抽的
此帖出自无线连接论坛
 
 
 

回复

12

帖子

0

TA的资源

一粒金砂(初级)

4
 
楼主专家级,鉴定完毕。
此帖出自无线连接论坛
个人签名
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

5
 

佩服!

此帖出自无线连接论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表