你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32WB55 测评BLE demo程序执行过程浅析

[复制链接]
STMCU小助手 发布时间:2022-10-23 19:16

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

从 main.c 入手,主函数 main() 就写了一些函数调用而已:
& `6 [0 D7 d) X* ^int main(void)
" d: S+ h  C. c1 u3 P9 Y{
: T3 g3 l# R9 ?! [) a+ cHAL_Init();
+ @$ m( k- R4 k4 c8 z1 \- NReset_Device();
, l1 Z! H5 j# SConfig_HSE();
" Q. m0 e) x, V3 r$ `SystemClock_Config();, {1 v2 \2 }$ u; J0 }$ a
PeriphClock_Config();
  ?; X4 J% _& H& P4 M; ^" bInit_Exti();
8 t) M( E3 A3 p, E' `* V* SMX_GPIO_Init();( q4 P* M8 A3 R1 A' s, r
MX_DMA_Init();+ _4 f# X, p2 J. Y
MX_RF_Init();
% h( d* I0 f* F4 |MX_RTC_Init();
% Q0 l: k# ]% e. A* v) jAPPE_Init();
$ }. }: S5 \; h. u3 D+ zwhile(1)' f) M: u$ ^; R, f- e$ E
{
+ m; ^5 Z6 N1 K% z$ tSCH_Run(~0);' [! E  D9 Z3 G( x- O0 C7 @
}1 ?7 m4 k: U! @( n; `
}

首先是 HAL_Init() 这个库函数, 起初始化 HAL 库的作用,其中又调用了 stm32wbxx_hal_msp.c 中写的用户初始化代码 HAL_MspInit(), 执行工程相关的初始化。本工程执行了一个 __HAL_RCC_HSEM_CLK_ENABLE();) r; H% ^  \  P. j  O( v
  然后是 Reset_Device(), 这个函数就在 main.c 中,里面执行的是 Reset_BackupDomain() 和 Reset_IPCC() ——可能是这部分硬件并不是和CPU一起复位的,所以需要单独处理下。
! i! E5 X' J0 n0 Y) C; T- b" y+ e5 @  接下来的 Config_HSE(), SystemClock_Config() 和 PeriphClock_Config() 是配置系统各种时钟的,根据程序运行的需要设置。6 y( M. `# c9 I/ ]- ?( E2 A2 r: c4 r/ Q
  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 的两个特殊硬件,双核通信要用到。% _% s( C( e3 L( v$ G
  然后,就到了 APPE_Init() 函数了,它在 app_entry.c 中定义。函数名的意思是初始化应用程序(前面的初始化只是硬件),它里面的函数会跟 WPAN 库打交道。

void APPE_Init( void )
) z2 d0 }& [( Y/ ?% e{! [# k* j$ M  H$ C* A( J: u
SystemPower_Config();
7 F' J6 f. y' H9 @+ f1 ?* n+ [HW_TS_Init(hw_ts_InitMode_Full, &hrtc);5 d% n4 a4 K+ v; F$ K
Init_Debug();
; }$ Z& d- j3 U1 p/ l1 A8 cLPM_SetOffMode(1 << CFG_LPM_APP, LPM_OffMode_Dis);
: M# z0 v6 {5 l1 Q. z4 W7 F& MLed_Init();
4 t# I8 ?: |- U" f8 J% DButton_Init();2 o. e" @- w6 W1 k$ y
appe_Tl_Init();# H( D9 K9 t* e" g
}

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

APPE_Init() 执行完毕之后,回到 main() 当中,开始一个死循环:重复调用 SCH_Run(). 这个函数定义在 scheduler.c 中,也就是执行调度器的意思。但是这里的调度器和我熟悉的 FreeRTOS 的调度器不同,后者是不会返回的,因此不需要放在循环里面。为了确认这一点,我在 SCH_Run() 函数入口设置断点,然后恢复执行,CPU仍然会再次遇到断点。在 SCH_Run() 函数体中,执行每个任务是通过一条语句:
  P$ N& X3 e% l' Y+ f* r( |, R/** Execute the task */
- i' a( a" J0 q  ~- O8 ^  ^TaskCb31 - bit_nbr;3 p/ d& g  u' w0 f. k* I
这里有一个数组 TaskCb 存放任务对应的函数,不妨看看里面有什么:
3 z6 q  }1 l3 G" K& Z, w9 C(gdb) print TaskCb
, L; n) E, g0 I8 ^$1 = {0x8001eb9 , 0x8002371 ,
8 t8 u0 u) l/ f+ `0x80058ed , 0x8005f5d }
. ]9 b; I6 }* X) G* r2 W3 ?* O里面有四个“任务”,不过从我对 scheduler.c 的观察看来,这不是真正的 RTOS ——每个任务没有独立的堆栈,不能嵌套执行。

这四个任务分别在什么时候注册的呢?我在 SCH_RegTask() 函数入口设断点,再来跟踪一次。
6 y' y- X: W# Y! t8 e9 H% @1 L3 U  第一次调用:appe_Il_Init() 里面, task_id=3. g/ e, V$ Y" @0 J5 l# ^6 n
SCH_RegTask( CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, shci_user_evt_proc );
: |5 C! _. m* }4 f% y3 }4 o  第二次调用:APP_BLE_Init() 里(app_ble.c),task_id=2
: t* Q) J; {& _$ j: l9 @) LSCH_RegTask(CFG_TASK_HCI_ASYNCH_EVT_ID, hci_user_evt_proc);# D- F7 J5 |3 B6 v# W
  第三次调用:仍然在 APP_BLE_Init() 里,task_id=0
+ K* G3 N( [# \; gSCH_RegTask(CFG_TASK_ADV_CANCEL_ID, Adv_Cancel);
  P. A- v$ Z( P, M5 h' `" K0 [8 K  第四次调用:在 p2p_server_app.c 的 P2PS_APP_Init() 函数里,task_id=1
# U" K, Z% E  n; T( E! q0 }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发送通知的,即按键触发的事件处理。# f* }/ f) a+ X, r; s9 @
  到这里,大概了解到 STM32WB55 的 demo 应用程序是“事件驱动”组织的。在初始化过程中建立起调度器,将处理不同事件的“任务”交由调度器管理。调度器在有任务需要处理的时候调用相应任务的函数。这背后当然还得有硬件中断(IRQ)来驱动。

不妨就跟踪一下按键引发的中断:EXTI的 IRQ 处理过程。按下SW1时,EXTI中断服务程序被硬件执行void PUSH_BUTTON_SW1_EXTI_IRQHandler(void)
& q1 A: `  ], a$ n" g/ A4 U  e{9 ^9 R, ?, s2 Y, {' H
HAL_GPIO_EXTI_IRQHandler(BUTTON_SW1_PIN);) c, W& F% W3 }/ n
}( R1 e/ L9 U1 {8 T/ ^' \( h
  就直接调用 HAL 库的中断处理函数
4 I* W% G! X4 ~# A0 }void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
% K6 \' Y7 B3 L8 I; ~3 y5 \{, c9 e2 }% y9 F/ S
/* EXTI line interrupt detected */& ~& q' R, U4 }2 `
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u); l" }; t0 h+ `9 f
{
2 o! |( A) I$ m% ?. C2 m__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);; _$ s  o1 n5 X$ v( d
HAL_GPIO_EXTI_Callback(GPIO_Pin);
6 s" g/ C. T3 V; X& _}5 \0 O, [5 T  c  w4 h6 {/ p% C6 C
}2 g0 c# T1 Q1 S5 K) K% ?  n
  它调用用户编写的中断处理函数
- O7 h2 |0 ?$ Cvoid HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin )0 n7 b' [- m' V) y+ h4 Q
{+ A9 t4 I( w0 o4 _( Z
switch (GPIO_Pin)8 T2 P- v0 j0 c! w  `$ y# l5 l
{) C; d+ t4 _' M0 ?
case BUTTON_SW1_PIN:
3 L$ r- b, L, u. o9 y1 SAPP_BLE_Key_Button1_Action();: t7 X. p" r4 L& F+ B/ W) w# R0 X) K
break;
% a0 S2 C. ^$ u5 }5 ccase BUTTON_SW2_PIN:" Y/ F$ f6 _) V3 m4 |
APP_BLE_Key_Button2_Action();
4 N5 ^8 B6 ~% y8 N: \/ g# D) H6 Fbreak;
0 ?. |1 E1 t/ C. e9 W9 \; ncase BUTTON_SW3_PIN:
" f# I  y. x( F  ~5 oAPP_BLE_Key_Button3_Action();, |% u) E$ X& P: W& M% j2 N# J
break;% ~7 e* ?8 m/ ?5 B
default:+ {4 i- n$ [; N  Y
break;
) ~- `& x" j2 k" k}* _1 `; F9 m$ F
return;
! D% r, M" Z  I  X, o' L& F( y6 M}# K# i3 u7 L6 C& w
  结果就是选择调用了  @; X& z' D8 i% Z) ]* `; _( O% I
void APP_BLE_Key_Button1_Action(void)
) F3 k; \" u" ^3 n3 N! ^/ j{
* c! b$ Q3 _( G  }+ \P2PS_APP_SW1_Button_Action();* |3 z' d4 t4 `7 ~
}( X5 u) g6 W7 g5 _* ]
  最终是到这里
6 h" l# \/ P9 o8 nvoid P2PS_APP_SW1_Button_Action(void)% I3 z1 u1 Z' J
{
; J' U8 y, I. g4 @" |SCH_SetTask( 1<# C. O& i; N, f' O0 ?7 d6 Z' C
return;# }: w( Q9 z% R; B. T7 S
}

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

再看另外一个,我跟踪了一次 IPCC接收中断(和CPU2通信使用)的响应过程:
% B$ j, x! u  W' Ivoid IPCC_C1_RX_IRQHandler(void)
$ Q1 c, X* Y& Y! F0 V{
$ b4 {  c7 R2 yHW_IPCC_Rx_Handler();5 |+ f) q! }) F
return;+ Y' s! V: W6 m5 M" C1 o
}
. {4 G. H0 j+ |# U1 a, [* B% e0 a  调用的是 hw_ipcc.c 中的 HW_IPCC_Rx_Handler 来处理4 t% U, I  s3 [. U% ]# c% B
void HW_IPCC_Rx_Handler( void ): s$ ~4 ?) A) Z: u& d; f
{; B' m4 h5 ?9 E' m$ n" Z3 ^/ P" J$ A" q
if (HW_IPCC_RX_PENDING( HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL ))8 Z, c& Z6 ~) c
{$ [# V* z) q3 p
HW_IPCC_THREAD_NotEvtHandler();+ ?: A" P( u# U! w8 t" b1 v, L4 T
}
/ i+ A, V: o. T7 E9 S- W5 ~& Zelse if (HW_IPCC_RX_PENDING( HW_IPCC_BLE_EVENT_CHANNEL ))
0 |5 A6 o9 v* A4 z1 J" d! K4 ^1 ^{% k' S* v$ t  W2 n9 }' \! J# p" I
HW_IPCC_BLE_EvtHandler();# Y( d6 y6 E- i/ `1 K% y
}
0 ~2 ~4 V/ ^! R/ F" E- ?else if (HW_IPCC_RX_PENDING( HW_IPCC_SYSTEM_EVENT_CHANNEL ))9 R  Z+ ?+ [, Q+ \' N
{
4 ], x. c; x& T8 gHW_IPCC_SYS_EvtHandler();
, b' G% V: ]1 M# y  D8 f; |- G9 @}8 l. ~3 K6 [8 E/ P
else if (HW_IPCC_RX_PENDING( HW_IPCC_TRACES_CHANNEL ))8 s1 D0 m  s0 `1 A: Y3 j) M2 `% N
{
5 y, E1 ?, U! x1 d% b. iHW_IPCC_TRACES_EvtHandler();
: J' i! e$ K3 @7 ^4 T) a}; c: B" j. Y# b7 N# s/ o, H  ~
else if (HW_IPCC_RX_PENDING( HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL ))' n& Q0 X8 e9 P4 I" o( @4 p2 K3 ~
{" |. i6 Y2 ^0 E: Z( f
HW_IPCC_THREAD_CliNotEvtHandler();4 j( K) v% c- ?" u
}5 ]( j: g& K( t
return;
' W6 V5 d3 A- `}& k9 [  e  t' r. q: {1 a3 r
  根据硬件寄存器标志位,选择了调用 HW_IPCC_BLE_EvtHandler 函数4 j( |5 F" l6 F/ K
static void HW_IPCC_BLE_EvtHandler( void )/ {3 q# s, ^2 L$ ]
{' c/ J( \/ W) O3 @( D
HW_IPCC_BLE_RxEvtNot();
' @3 z% b4 o4 B0 _LL_C1_IPCC_ClearFlag_CHx( IPCC, HW_IPCC_BLE_EVENT_CHANNEL );) K7 p& \# u7 i; P6 y' C+ h
return;, b6 W) _$ L* j' K$ d' s+ U4 }
}
9 O6 ~' m. {8 [! E) d  在清除 IPCC 寄存器相关标志位之前,调用 tl_mbox.c 中的 HW_IPCC_BLE_RxEvtNot 函数
. f. F5 T: X# R* W. |' H" }void HW_IPCC_BLE_RxEvtNot(void)
" x+ ~) S( [; |. a) O{
  }: i: ]7 K& j) ^+ Y3 tTL_EvtPacket_t *phcievt;

while(LST_is_empty(&EvtQueue) == FALSE)
( \' {& D+ Q1 K9 E$ f4 t{9 T: ^  e+ z# q6 |' ~6 {# K5 ]4 ^
LST_remove_head (&EvtQueue, (tListNode )&phcievt);
. |  N1 c4 j$ `* O- P9 H3 KBLE_IoBusEvtCallBackFunction(phcievt);, q6 B. N8 y9 i) ^1 Z6 s
}1 u) j# V- m' o0 n1 L: b
return;. |  r( h1 C5 i% \4 Z1 R
}, R: }4 m* D& L: {: k
  上面这个函数要处理一个列表,对每个 HCI 事件,调用回调函数 BLE_IoBusEvtCallBackFunction, 但这是一个函数指针
' \6 Z/ `, ]0 Ostatic void ( BLE_IoBusEvtCallBackFunction) (TL_EvtPacket_t *phcievt);
0 P( h) k7 l0 R( m还需要找到它指向的函数。不难找到,在 TL_BLE_Init() 函数里面有这一行:2 K9 ?$ @" u. S
BLE_IoBusEvtCallBackFunction = pInitHciConf->IoBusEvtCallBack;
/ _7 ]+ {% _* N也就是 TL_BLE_Init() 传入参数——一个 TL_BLE_InitConf_t 类型的指针所指定的函数。TL_BLE_Init() 又是被间接调用的,在 TlInit() 函数中有如下代码:; b/ {! w" D) o, z2 ^
Conf.IoBusEvtCallBack = TlEvtReceived;; C- ^$ K" O( o
hciContext.io.Init(&Conf);0 u8 a' C% E, i- o
  所以,BLE_IoBusEvtCallBackFunction 实际上是 TlEvtReceived 的入口地址" A/ l" L' L, F- m% k) r- a9 Q
static void TlEvtReceived(TL_EvtPacket_t *hcievt)
( V- E# s0 Z7 p% w' \6 G{/ L- w  D+ Y* A% @7 [# ?6 N6 ~
if ( ((hcievt->evtserial.evt.evtcode) == TL_BLEEVT_CS_OPCODE) || ((hcievt->evtserial.evt.evtcode) == TL_BLEEVT_CC_OPCODE ) )
# m' n) ]3 G- E9 Y; K2 @{% a2 T; P8 h0 j/ v7 D
LST_insert_tail(&HciCmdEventQueue, (tListNode *)hcievt);3 r2 }6 J  |! k. \- S! T
hci_cmd_resp_release(0); /< Notify the application a full Cmd Event has been received */
) q. |8 \) z% v- o}
( q/ n: H5 p- q+ m/ F$ r9 a- xelse
; g& n! H% g9 w7 m{
$ a# F7 h: M8 D2 zLST_insert_tail(&HciAsynchEventQueue, (tListNode )hcievt);
/ o! \7 Y: j' d8 g, W: P3 Qhci_notify_asynch_evt((void) &HciAsynchEventQueue); /*< Notify the application a full HCI event has been received /. H& f8 F8 v# h1 j7 c
}4 L+ L1 g$ @% J8 I1 o9 d
return;
8 r- V* X( f/ ?. Z: w. X}. a4 m6 F1 P- Y8 L$ ]# b7 G  L
  它里面的两个函数与调度器有关:' g! c+ L5 K1 z+ O  X& U
void hci_notify_asynch_evt(void pdata)" C3 H5 e4 B  l7 W6 z; M; B1 M$ ~
{
; |- H* }# _4 ?; ?0 K4 r' q% K' CSCH_SetTask(1 << CFG_TASK_HCI_ASYNCH_EVT_ID, CFG_SCH_PRIO_0);
8 _6 k$ c9 m  W  W! Q# j1 Kreturn;
3 G. S" ~6 _2 y7 x$ A. a8 h, {+ u}

void hci_cmd_resp_release(uint32_t flag)
! p7 @. O" l  h! J5 s{  m8 k4 n/ z7 E
SCH_SetEvt(1 << CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID);
+ s+ p! k; u" \return;
$ p4 n9 r6 z% r" C- R" U}' l7 |9 q4 [$ c
  所以,这个中断也触发了事件,后面的处理工作还是由调度器管理的。

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

+ H1 E7 [$ E8 ~1 q$ U- _1 Q
收藏 评论0 发布时间:2022-10-23 19:16

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版