广播流程源码分析1
在controller层有多种状态:广播、空闲、连接等等,这次分析的是广播这个状态或者叫
做角色。
在前面controller层循环的分析中,可以明确controller层也有event以供这层处理。在
分析的过程中,发现nimble的广播功能主要是由以下部分组成。
1. RTC0作为controller层的周期事件处理器(可能某个周期没有什么事件)
2. PPI作为事件发生连接器(一个事件发生触发另一个事件,比如时钟事件触发radio失能)
3. controller层任务大循环,不断接受处理各种事件。
static void
ble_phy_isr(void)
{
uint32_t irq_en;
//rt_kprintf("%d\r\n",NRF_RADIO->STATE);
os_trace_isr_enter();
/* Read irq register to determine which interrupts are enabled */
irq_en = NRF_RADIO->INTENCLR;
/*
* NOTE: order of checking is important! Possible, if things get delayed,
* we have both an ADDRESS and DISABLED interrupt in rx state. If we get
* an address, we disable the DISABLED interrupt.
*/
/* We get this if we have started to receive a frame */
if ((irq_en & RADIO_INTENCLR_ADDRESS_Msk) && NRF_RADIO->EVENTS_ADDRESS) {
/*
* wfr timer is calculated to expire at the exact time we should start
* receiving a packet (with 1 usec precision) so it is possible it will
* fire at the same time as EVENT_ADDRESS. If this happens, radio will
* be disabled while we are waiting for EVENT_BCCMATCH after 1st byte
* of payload is received and ble_phy_rx_start_isr() will fail. In this
* case we should not clear DISABLED irq mask so it will be handled as
* regular radio disabled event below. In other case radio was disabled
* on purpose and there's nothing more to handle so we can clear mask.
*/
if (ble_phy_rx_start_isr()) {
irq_en &= ~RADIO_INTENCLR_DISABLED_Msk;
}
}
/* Check for disabled event. This only happens for transmits now */
if ((irq_en & RADIO_INTENCLR_DISABLED_Msk) && NRF_RADIO->EVENTS_DISABLED) {
if (g_ble_phy_data.phy_state == BLE_PHY_STATE_RX) {
NRF_RADIO->EVENTS_DISABLED = 0;
ble_ll_wfr_timer_exp(NULL);
} else if (g_ble_phy_data.phy_state == BLE_PHY_STATE_IDLE) {
assert(0);
} else {
ble_phy_tx_end_isr();
}
}
/* Receive packet end (we dont enable this for transmit) */
if ((irq_en & RADIO_INTENCLR_END_Msk) && NRF_RADIO->EVENTS_END) {
ble_phy_rx_end_isr();
}
g_ble_phy_data.phy_transition_late = 0;
/* Ensures IRQ is cleared */
irq_en = NRF_RADIO->SHORTS;
/* Count # of interrupts */
STATS_INC(ble_phy_stats, phy_isrs);
os_trace_isr_exit();
}
打断点函数,按照执行顺序
1. ble_ll.c ble_ll_task()的 ble_npl_event_run(ev)处 1122行左右
2. ble_ll_adv.c ble_ll_adv_sm_start() 1678行左右
3. ble_ll_adv.c ble_ll_adv_sm_start()的ble_ll_sched_adv_new 1777行左右
4. nrf5x_isr.c RTC0_IRQHandler 28行左右
5. ble_ll_adv.c ble_ll_adv_tx_start_cb() 839行左右
注意,1断点须在4运行后再打,因为host与controller层还有其他event和本身一些event.
此时你会发现,在运行一段后,其都是以 1 4 5这个顺序运行。符合上面说的运行过程。至于2和3,
则是在host层发送adv初始化的时候才会跑的。
(将上面断点都删去)
因为仿真和中断的原因,rtc和radio中断断点会冲突。所以radio中断另分析
6.nrf5x_isr.c RTC0_IRQHandler 17行左右
7.ble_phy.c ble_phy_isr中ble_ll_wfr_timer_exp(NULL); 1263行左右
8.ble_phy.c ble_phy_isr中ble_phy_tx_end_isr() 1267行左右。
断点调试会发现,在执行顺序 6 8 6 7,通过代码分析,其就是两次radio中断来实现一次广播中的
发送和接受,第一次是tx发送完,第二次是超时(timer+PPI 嘿嘿)或者接受到请求。
static int
ble_ll_adv_sm_start(struct ble_ll_adv_sm *advsm)
{
uint8_t adv_chan;
uint8_t *addr;
uint8_t *evbuf;
/* only clear flags that are not set from HCI */
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_TX_ADD;
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_RX_ADD;
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD;
if (advsm->own_addr_type == BLE_HCI_ADV_OWN_ADDR_RANDOM) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!ble_ll_is_valid_random_addr(advsm->adv_random_addr)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
#else
if (!ble_ll_is_valid_random_addr(g_random_addr)) {
return BLE_ERR_CMD_DISALLOWED;
}
#endif
}
/*
* Get an event with which to send the connection complete event if
* this is connectable
*/
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
/* We expect this to be NULL but if not we wont allocate one... */
if (advsm->conn_comp_ev == NULL) {
evbuf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI);
if (!evbuf) {
return BLE_ERR_MEM_CAPACITY;
}
advsm->conn_comp_ev = evbuf;
}
}
/* Set advertising address */
if ((advsm->own_addr_type & 1) == 0) {
addr = g_dev_addr;
} else {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
addr = advsm->adv_random_addr;
#else
addr = g_random_addr;
#endif
advsm->flags |= BLE_LL_ADV_SM_FLAG_TX_ADD;
}
memcpy(advsm->adva, addr, BLE_DEV_ADDR_LEN);
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
memcpy(advsm->initiator_addr, advsm->peer_addr, BLE_DEV_ADDR_LEN);
if (advsm->peer_addr_type & 1) {
advsm->flags |= BLE_LL_ADV_SM_FLAG_RX_ADD;
}
}
#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1)
/* This will generate an RPA for both initiator addr and adva */
if (advsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
ble_ll_adv_rpa_update(advsm);
}
#endif
/* Set flag telling us that advertising is enabled */
advsm->adv_enabled = 1;
/* Determine the advertising interval we will use */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
/* Set it to max. allowed for high duty cycle advertising */
advsm->adv_itvl_usecs = BLE_LL_ADV_PDU_ITVL_HD_MS_MAX;
} else {
advsm->adv_itvl_usecs = (uint32_t)advsm->adv_itvl_max;
advsm->adv_itvl_usecs *= BLE_LL_ADV_ITVL;
}
/* Set first advertising channel */
adv_chan = ble_ll_adv_first_chan(advsm);
advsm->adv_chan = adv_chan;
/*
* XXX: while this may not be the most efficient, schedule the first
* advertising event some time in the future (5 msecs). This will give
* time to start up any clocks or anything and also avoid a bunch of code
* to check if we are currently doing anything. Just makes this simple.
*
* Might also want to align this on a slot in the future.
*
* NOTE: adv_event_start_time gets set by the sched_adv_new
*/
advsm->adv_pdu_start_time = os_cputime_get32() +
os_cputime_usecs_to_ticks(5000);
/*
* Schedule advertising. We set the initial schedule start and end
* times to the earliest possible start/end.
*/
ble_ll_adv_set_sched(advsm);
ble_ll_sched_adv_new(&advsm->adv_sch, ble_ll_adv_scheduled, NULL);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
ble_ll_adv_aux_schedule(advsm);
}
#endif
return BLE_ERR_SUCCESS;
}
void RTC0_IRQHandler(void)
{ //rt_kprintf("aaaaa\r\n");
rtc0_isr_addr();
}
void
ble_ll_task(void *arg)
{
struct ble_npl_event *ev;
/*
* XXX RIOT ties event queue to a thread which initialized it so we need to
* create event queue in LL task, not in general init function. This can
* lead to some races between host and LL so for now let us have it as a
* hack for RIOT where races can be avoided by proper initialization inside
* package.
*/
#ifdef RIOT_VERSION
ble_npl_eventq_init(&g_ble_ll_data.ll_evq);
#endif
/* Init ble phy */
ble_phy_init();
/* Set output power to 1mW (0 dBm) */
ble_phy_txpwr_set(MYNEWT_VAL(BLE_LL_TX_PWR_DBM));
/* Tell the host that we are ready to receive packets */
// ble_ll_hci_send_noop();
ble_ll_rand_start();
while (1) {
rt_kprintf("controller\r\n");
ev = ble_npl_eventq_get(&g_ble_ll_data.ll_evq, BLE_NPL_TIME_FOREVER);
assert(ev);
ble_npl_event_run(ev);
//if(ev->arg) rt_kprintf("%x %x\r\n",(*(int*)ev->arg),(*(int*)(ev->arg+4)));
}
}
void RADIO_IRQHandler(void)
{
radio_isr_addr();
}
广播功能大致流程(广播中等待接收超时的)
如果过程简单化是这样的
(1)host层发起adv的event,
(2)controller事件循环处理,开启controller时间事件循环(rtc0)
(2)时间时间循环处理对应事件,开启广播发送,剩下的就交给radio外设
(3)radio产生发送完成中断,然后设置接受超时操作
(4)如果超时了,触发radio的disable中断,说明本次广播事件完成,将本事件及处理发送到
controller事件队列中。执行2一直循环下去。如果未超时情况下则会产生address接受的radio
中断。
这个细节很复杂的。
大致详细说明,看着就很恶心:
1.来自host层开启controller功能,controller层事件队列处理,第一次启动将广播事件交于
RTC调度(至少现在从代码中看是这样),然后在事件中将任务交于controller层事件队列了。
ble_ll_adv.c中 1677行左右的ble_ll_adv_sm_start(),其中ble_ll_sched_adv_new(&advsm->adv_sch, ble_ll_adv_scheduled, NULL);
2. controller事件队列处理。
3. 然后RTC时间调度器执行调度中的事件,以完成发送报文等命令
打断点调试,其中一个很重要的函数(也是RTC的事件回调)ble_ll_adv_tx_start_cb,
其中会调用ble_phy_tx_set_start_time(),来设置在多久后使能txen(也就是发送使能,
其利用的就是PPI外设将timer一个事件以触发txen),
4. 然后就是RADIO中断操作了,主要是两个中断:tx完成与等待超时PPI触发的disable中断
怎么发现这的呢,都是慢慢断点debug,然后查看外设寄存器值和某些参考值。emm...呜呜呜
因为nrf52832的外设radio设置了Shortcut register,tx发送完成就会触发一个disable
中断,然后进入中断执行ble_phy_tx_end_isr(),里面有利用PPI将timer0与radio结合的操作。
当超时的时候,就触发radio中disable中断或radio接收到请求后触发timer0关闭。
5. 假如超时触发radio中断,执行ble_ll_wfr_timer_exp(NULL)来处理,然后就是从2开始执行了
里面有ble_ll_adv_tx_done(g_ble_ll_cur_adv_sm);这个就是将事件打入到controller事件队列里,
ble_npl_eventq_put(&g_ble_ll_data.ll_evq, &advsm->adv_txdone_ev);
总结
函数调用流程图后面再补吧。
大概就是这些啦,简单的来说就是
1.controller事件队列 ,处理各个事件
2.RTC0 事件调度,调度事件。比如传输开始
3. radio中断,用于传输和接受无线报文
4. PPI将radio、timer、rtc等事件联系起来。
整个过程大概就是这样。