3564|0

1533

帖子

2

资源

五彩晶圆(初级)

【STM32WB55 测评】BLE demo程序执行过程浅析 [复制链接]

本帖最后由 cruelfox 于 2019-5-22 20:36 编辑

  编译了 BLE_p2pServer 这个工程以后,我就可以用GDB进行跟踪调试了。跟踪跟踪,姑且看看这个软件框架是什么样的,不作详细分析了(时间也不允许)。

  从 main.c 入手,主函数 main() 就写了一些函数调用而已:
int main(void)
{
  HAL_Init();
  Reset_Device();
  Config_HSE();
  SystemClock_Config();
  PeriphClock_Config();
  Init_Exti();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_RF_Init();
  MX_RTC_Init();
  APPE_Init();
  while(1)
  {
     SCH_Run(~0);
  }
}

  首先是 HAL_Init() 这个库函数, 起初始化 HAL 库的作用,其中又调用了 stm32wbxx_hal_msp.c 中写的用户初始化代码 HAL_MspInit(), 执行工程相关的初始化。本工程执行了一个 __HAL_RCC_HSEM_CLK_ENABLE();
  然后是 Reset_Device(), 这个函数就在 main.c 中,里面执行的是 Reset_BackupDomain() 和 Reset_IPCC() ——可能是这部分硬件并不是和CPU一起复位的,所以需要单独处理下。
  接下来的 Config_HSE(), SystemClock_Config() 和 PeriphClock_Config() 是配置系统各种时钟的,根据程序运行的需要设置。
  Init_Exti(), MX_GPIO_Init(), MX_DMA_Init(), MX_RF_Init(), MX_RTC_Init() 这几个函数配置 EXTI, GPIO, DMA, RTC 这几个基本的硬件。不过 MX_RF_Init() 函数里面是空的,硬件初始化并不在这里(估计是由另外那个CPU2做的)。以上这些函数都容易看懂,就是在使用无线功能以前做好其它片上设备的准备工作。HSEM 和 IPCC 是 STM32WB55 的两个特殊硬件,双核通信要用到。
  然后,就到了 APPE_Init() 函数了,它在 app_entry.c 中定义。函数名的意思是初始化应用程序(前面的初始化只是硬件),它里面的函数会跟 WPAN 库打交道。

void APPE_Init( void )
{
  SystemPower_Config();
  HW_TS_Init(hw_ts_InitMode_Full, &hrtc);
  Init_Debug();
  LPM_SetOffMode(1 << CFG_LPM_APP, LPM_OffMode_Dis);
  Led_Init();
  Button_Init();
  appe_Tl_Init();        
}

  这其中 HW_TS_Init() 在 hw_timerserver.c 中定义,顾名思义是初始化软件定时器(服务)。瞅了瞅代码,这是基于RTC实现的功能,至于软件定时器提供给谁用的?姑且发现在 app_ble.c 中调用了 HW_TS_Create.
  Init_Debug() 函数用了 dbg_trace.c 中的 DbgTraceInit(), 初始化了 USART1. 运行这个程序时通过串口输出的信息就是软件自带的 Debug 支持。
  最后的 appe_Tl_Init() 又是重头戏了, 函数名中 "Tl" 是 Transport layer 的缩写,这个函数在 appe_entry.c 中,不过调用的是 WPAN 库里面的函数。由于我没有找到这个库的手册,这里需要如何初始化不清楚,从代码只能略猜一二。

  APPE_Init() 执行完毕之后,回到 main() 当中,开始一个死循环:重复调用 SCH_Run(). 这个函数定义在 scheduler.c 中,也就是执行调度器的意思。但是这里的调度器和我熟悉的 FreeRTOS 的调度器不同,后者是不会返回的,因此不需要放在循环里面。为了确认这一点,我在 SCH_Run() 函数入口设置断点,然后恢复执行,CPU仍然会再次遇到断点。在 SCH_Run() 函数体中,执行每个任务是通过一条语句:
   /** Execute the task */
    TaskCb[31 - bit_nbr]();
这里有一个数组 TaskCb 存放任务对应的函数,不妨看看里面有什么:
(gdb) print TaskCb
$1 = {0x8001eb9 , 0x8002371 ,
  0x80058ed , 0x8005f5d }
里面有四个“任务”,不过从我对 scheduler.c 的观察看来,这不是真正的 RTOS ——每个任务没有独立的堆栈,不能嵌套执行。

  这四个任务分别在什么时候注册的呢?我在 SCH_RegTask() 函数入口设断点,再来跟踪一次。
  第一次调用:appe_Il_Init() 里面, task_id=3
  SCH_RegTask( CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, shci_user_evt_proc );
  第二次调用:APP_BLE_Init() 里(app_ble.c),task_id=2
  SCH_RegTask(CFG_TASK_HCI_ASYNCH_EVT_ID, hci_user_evt_proc);
  第三次调用:仍然在 APP_BLE_Init() 里,task_id=0
  SCH_RegTask(CFG_TASK_ADV_CANCEL_ID, Adv_Cancel);
  第四次调用:在 p2p_server_app.c 的 P2PS_APP_Init() 函数里,task_id=1
  SCH_RegTask( CFG_TASK_SW1_BUTTON_PUSHED_ID, P2PS_Send_Notification );

  这里 CFG_TASK...ID 的定义在 app_conf.h 中可以找到。p2p Server程序一共注册了如上这四个任务。与其说是任务,不如说是事件处理子程序吧。从名称上看,task_id 3,4这两个分别是 hci 和 shci 的用户事件处理程序(HCI是 Host Controller Interface的话,SHCI又是什么呢?),分别在 hci_tl.c 和 shci_tl.c 中定义,结构也相似。task_id为0的这个是停止广播。task_id为1的这个子程序是向BLE client发送通知的,即按键触发的事件处理。
  到这里,大概了解到 STM32WB55 的 demo 应用程序是“事件驱动”组织的。在初始化过程中建立起调度器,将处理不同事件的“任务”交由调度器管理。调度器在有任务需要处理的时候调用相应任务的函数。这背后当然还得有硬件中断(IRQ)来驱动。

  不妨就跟踪一下按键引发的中断:EXTI的 IRQ 处理过程。按下SW1时,EXTI中断服务程序被硬件执行void PUSH_BUTTON_SW1_EXTI_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(BUTTON_SW1_PIN);
}
  就直接调用 HAL 库的中断处理函数
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}
  它调用用户编写的中断处理函数
void HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin )
{
  switch (GPIO_Pin)
  {
    case BUTTON_SW1_PIN:
     APP_BLE_Key_Button1_Action();
      break;
    case BUTTON_SW2_PIN:
      APP_BLE_Key_Button2_Action();
      break;
    case BUTTON_SW3_PIN:
      APP_BLE_Key_Button3_Action();
      break;
    default:
      break;
  }
  return;
}
  结果就是选择调用了
void APP_BLE_Key_Button1_Action(void)
{
  P2PS_APP_SW1_Button_Action();
}
  最终是到这里
void P2PS_APP_SW1_Button_Action(void)
{
  SCH_SetTask( 1<
  return;
}

  层层嵌套调用很冗长吧,除去简单判断,就是做了个触发事件处理(任务)的操作。对按键的响应转移到了一个任务函数中,而不是在中断服务里面处理,这个特点又和RTOS一致。

  再看另外一个,我跟踪了一次 IPCC接收中断(和CPU2通信使用)的响应过程:
void IPCC_C1_RX_IRQHandler(void)
{
  HW_IPCC_Rx_Handler();
  return;
}
  调用的是 hw_ipcc.c 中的 HW_IPCC_Rx_Handler 来处理
void HW_IPCC_Rx_Handler( void )
{
  if (HW_IPCC_RX_PENDING( HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL ))
  {
        HW_IPCC_THREAD_NotEvtHandler();
  }
  else if (HW_IPCC_RX_PENDING( HW_IPCC_BLE_EVENT_CHANNEL ))
  {
        HW_IPCC_BLE_EvtHandler();
  }
  else if (HW_IPCC_RX_PENDING( HW_IPCC_SYSTEM_EVENT_CHANNEL ))
  {
        HW_IPCC_SYS_EvtHandler();
  }
  else if (HW_IPCC_RX_PENDING( HW_IPCC_TRACES_CHANNEL ))
  {
        HW_IPCC_TRACES_EvtHandler();
  }
  else if (HW_IPCC_RX_PENDING( HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL ))
  {
    HW_IPCC_THREAD_CliNotEvtHandler();
  }
  return;
}
  根据硬件寄存器标志位,选择了调用 HW_IPCC_BLE_EvtHandler 函数
static void HW_IPCC_BLE_EvtHandler( void )
{
   HW_IPCC_BLE_RxEvtNot();
   LL_C1_IPCC_ClearFlag_CHx( IPCC, HW_IPCC_BLE_EVENT_CHANNEL );
   return;
}
  在清除 IPCC 寄存器相关标志位之前,调用 tl_mbox.c 中的 HW_IPCC_BLE_RxEvtNot 函数
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;
}
  上面这个函数要处理一个列表,对每个 HCI 事件,调用回调函数 BLE_IoBusEvtCallBackFunction, 但这是一个函数指针
static void (* BLE_IoBusEvtCallBackFunction) (TL_EvtPacket_t *phcievt);
还需要找到它指向的函数。不难找到,在 TL_BLE_Init() 函数里面有这一行:
  BLE_IoBusEvtCallBackFunction = pInitHciConf->IoBusEvtCallBack;
也就是 TL_BLE_Init() 传入参数——一个 TL_BLE_InitConf_t 类型的指针所指定的函数。TL_BLE_Init() 又是被间接调用的,在 TlInit() 函数中有如下代码:
    Conf.IoBusEvtCallBack = TlEvtReceived;
    hciContext.io.Init(&Conf);
  所以,BLE_IoBusEvtCallBackFunction 实际上是 TlEvtReceived 的入口地址
static void TlEvtReceived(TL_EvtPacket_t *hcievt)
{
  if ( ((hcievt->evtserial.evt.evtcode) == TL_BLEEVT_CS_OPCODE) || ((hcievt->evtserial.evt.evtcode) == TL_BLEEVT_CC_OPCODE ) )
  {
    LST_insert_tail(&HciCmdEventQueue, (tListNode *)hcievt);
    hci_cmd_resp_release(0); /**< Notify the application a full Cmd Event has been received */
  }
  else
  {
    LST_insert_tail(&HciAsynchEventQueue, (tListNode *)hcievt);
    hci_notify_asynch_evt((void*) &HciAsynchEventQueue); /**< Notify the application a full HCI event has been received */
  }
  return;
}
  它里面的两个函数与调度器有关:
void hci_notify_asynch_evt(void* pdata)
{
  SCH_SetTask(1 << CFG_TASK_HCI_ASYNCH_EVT_ID, CFG_SCH_PRIO_0);
  return;
}

void hci_cmd_resp_release(uint32_t flag)
{
  SCH_SetEvt(1 << CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID);
  return;
}
  所以,这个中断也触发了事件,后面的处理工作还是由调度器管理的。

  分析到这里,对 demo 程序的软件结构大概有了个了解。这是事件驱动的写法,可是嵌套的调用太多,分析起来不直观。不知道作为这上面软件的开发者感觉又如何。


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

赞赏

1

查看全部赞赏


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

查找数据手册?

EEWorld Datasheet 技术支持

最新文章 更多>>
    关闭
    站长推荐上一条 1/9 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表