本帖最后由 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原创,如需转载或用于商业用途需征得作者同意并注明出处