gap8-sdk-gap8芯片event事件触发-接受-处理过程分析

本文深入解析了gap8芯片的Event机制,包括其在异步处理中的关键作用,与中断的区别,以及如何通过硬件单元监控和触发事件。通过SPIM1控制器驱动实例,详细介绍了事件设置、触发和处理的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Event / interrupt 是gap8芯片的一个重要功能和概念。
gap8中的各种内部外设功能的实现,特别是异步的实现,都是基于event/interrupt的功能来实现的。

中断和事件的区别:
中断和事件,中断一定要有中断服务函数,但是事件却没有对应的函数.
事件可以触发其他关联操作,比如触发ADC采样等.可以在不需要CPU干预的情况下,执行这些操作.
但中断则必须要CPU介入.

(个人理解:在gap8中,event 和 interrupt 对于软件开发人员来说,处理上没有什么太大差别)

关于gap8的event的发生和处理,需要如下概念:

1.管理event的硬件单元:FC event unit 和 cluster event unit。
2.event硬件管理单元监控对象是一个个硬件单元。
3.一个硬件单元可以发生多个event,只是event ID不同。
4.gap8可以产生多少个event,每个event 对应的event ID,在芯片设计之初,就已经规定好了。
5.event硬件管控单元,可以检测的event 也是固定的。

gap8中 Event的发生和处理过程 和interrupt基本相同,都是如下过程:

1.触发事件:比如引脚电平变化触发,比如写寄存器触发,等。
2.跳转到异常向量表中,再通过索引,索引到具体的异常处理中。
3.最后通过异常处理,跳转到我们驱动开发者提供的具体处理函数。

读取gap8的datasheet,知如下:
1.gap8中共有量事件管理单元:

->a. FC event unit。
->b. Cluster event unit.

2.FC event unit 管理的 具体events如下:
在这里插入图片描述
在这里插入图片描述
可以看出FC event unit 总共管理这 15个 event .

3.Cluster event unit 管理的 具体events如下:
在这里插入图片描述
在这里插入图片描述可以看出cluster event unit 总共管理18个event.

Gap_sdk中SPIM1控制器驱动实现中设计到了 event.我们就从这个代码入手,进行分析介绍event的设置,触发,处理过程。

(1)设置event unit 对应SPIM1控制器的event 进行监控:

__pi_spi_open()          //spi_internal.chal_soc_eu_set_fc_mask(SOC_EVENT_UDMA_SPIM_EOT(conf->itf));pi_fc_event_handler_set(SOC_EVENT_UDMA_SPIM_EOT(conf->itf), spim_eot_handler);pi_fc_event_handler_set(SOC_EVENT_UDMA_SPIM_TX(conf->itf), spim_tx_handler);pi_fc_event_handler_set(SOC_EVENT_UDMA_SPIM_RX(conf->itf), spim_rx_handler); 

​			第一句:开启了FC event unit 对SPIM1控制器的event监控。

​			后三句:就是将event ID号 和 对应的事件处理函数进行绑定。

​			void pi_fc_event_handler_set(uint32_t event_id, pi_fc_event_handler_t event_handler){

 				 fc_event_handlers[event_id] = event_handler;}

所谓的event ID 和 事件处理函数的绑定,实际就是以event ID为数组下标,事件处理函数为数组元素。fc_event_handlers[]数组非常重要,事件触发后,就是从异常向量表中跳转到具体中断处理函数中,中断函数中就是通过这个fc_event_handlers[]数组来执行我们提供的函数。

(2)触发event:

(这里以SPIM1的发送触发的EOT event 为例进行介绍)

__pi_spi_send_async()       //spi_internal.cspim_enqueue_channel(SPIM(device_id),(uint32_t)cs_data->udma_cmd,3*(sizeof(uint32_t)),UDMA_CORE_TX_CFG_EN(1), TX_CHANNEL);spim_enqueue_channel(SPIM(device_id), (uint32_t)data, size,UDMA_CORE_TX_CFG_EN(1),TX_CHANNEL);while((hal_read32((void*)&(SPIM(device_id)->udma.tx_cfg)) {DBG_PRINTF("%s:%d\n",__func__,__LINE__);}

​		cs_data->udma_cmd[0] = SPI_CMD_EOT(1);spim_enqueue_channel(SPIM(device_id),(uint32_t)&cs_data->udma_cmd[0],1*(sizeof(uint32_t)),UDMA_CORE_TX_CFG_EN(1), TX_CHANNEL); 

​	第一句spim_enqueue_channel():主要是配置SPIM1控制器的发送。

​	第二句spim_enqueue_channel():向SPI从设备发送数据。

​	第三句while()循环,主要是判断发送任务是否完成或者挂起(挂起是因为被高优先级的任务抢占)

​	第四句spim_enqueue_channel(): 触发SPIM eot event 。然后gap8芯片就会跳转到异常处理程序中进行处理。

(3)event事件处理:

​ 当(2)中触发SPIM eot event之后,硬件直接跳转到到 异常/中断向量表中(注意:当触发event后,程序并不是直接跳转到异常/中断向量表中,而是经过一段代码处理之后,才跳转到异常/中断向量表中的。这段跳转我们不在本讲中介绍。我们会另起一篇进行专门介绍)。

​ 异常/中断向量表如下;

​ 中断向量表:startup_gap8.S

/*******************************************************************************
		INTERRUPT VECTOR TABLE 中断向量表
*******************************************************************************/
	.section .vectors_irq, "ax"   /* "ax"表示该节区可分配并且可执行;ax是 allocation  execute的缩写 */
	.option norvc;                /* risc-v 选项 */

	/* Cluster Notify FC Handler. */
	.org 0x10
	j cluster_notify_fc_handler
	/* PendSV Handler. */
	.org 0x1c
	j pendSV_handler
	/* DMA IRQ. DMA中断 */
	.extern cluster_dma_2d_handler
	.org 0x24
	j cluster_dma_2d_handler
	/* Systick Handler.系统滴答处理 */
	.org 0x28
	j systick_handler
	/* FC SoC event Handler.  FC SOC 事件处理。 */
	.org 0x6c
	j fc_event_handler       /* SPIM1 触发的 eot event 就是跳转到这里进行处理的 */
	/* Reset Handler.重置处理 */
	.org 0x80
	j reset_handler
	/* Illegal Instruction Handler. */
	.org 0x84
	j ill_ins_handler
	/* Ecall Handler. */
	.org 0x88
	j ecall_handler
	/*
	This variable is pointed to the structure containing all information exchanged with
	the platform loader. It is using a fixed address so that the loader can also find it
	and then knows the address of the debug structure.
	*/
	.org 0x90
	.global __rt_debug_struct_ptr
__rt_debug_struct_ptr:
	.word Debug_Struct

SPIM1控制器触发的eot event事件,会跳转到“j fc_event_handler”进行处理。

fc_event_handler标签汇编实现如下:
	(gap8_iet.S)
        /* Fc SOC event Handler. FC soc 事件处理中断 */
        .extern fc_soc_event_handler     /* 表明该函数实现在外部,不走这个汇编文件中 */
        DECLARE fc_event_handler         /* 声明这个标签“fc_event_handler”,在这里它是个函数名 */
        /* Save current context. 中断来了,保存当前上下文   */
        SAVE_CONTEXT_YIELD               /* 调用保存上下文收益函数来保存上下文 */
        lw tp, pxCurrentTCB
        sw sp, 0*0(tp)
        /* ISR Stack.中断处理程序用到的栈 */
        la sp, xISRStack
        lw sp, 0*0(sp)
        jal ra, fc_soc_event_handler     /* 调到这个函数中处理 该FC soc event事件中断,这是我们真正的中断处理函数 */
        lw tp, pxCurrentTCB         /* pxCurrentTCB 这个变量在freeRTOS中是一个指针,指向当前创建的所有任务中优先级最高的那个任务。 */
        lw sp, 0*0(tp)
        /* Restore current context. 恢复上下文 */
        RESTORE_CONTEXT_YIELD
        mret
        .endfunc	

经过这段汇编代码处理,eot event的事件处理跳转到了 C代码实现的fc_soc_event_handler()函数中。

(pmsis_fc_event.c )
	// TODO: Use Eric's FIRQ ABI
	__attribute__((section(".text")))      //指定.text段。
	void fc_soc_event_handler(void)    //fc event 处理函数
	{
		/* Pop one event element from the FIFO从FIFO中取出一个事件元素 */
		uint32_t event = EU_SOC_EVENTS->CURRENT_EVENT;    //0x0020_0F00 对应这个寄存器“SOC_PERIPH_EVENT_ID 0x1B200F00 ”,这个寄存器中低8位存放的是事件ID号。
		hal_eu_fc_evt_demux_trig_set(FC_SW_NOTIFY_EVENT, 0);   //向SW_EVENT_3_TRIG 0x0020_4100UL 中写入0,也就是清除触发的bit位。方便接受下一个event.
		/* Trigger an event in case someone is waiting for it 
		   it will check the termination using the pending variable  */
		/* Now that we popped the element, we can clear the soc event FIFO event as the FIFO is
		   generating an event as soon as the FIFO is not empty 
		 */
		/*将EVENT_BUFFER_CLEAR寄存器对应的 挂起事件状态寄存器写1.(我猜想,在实时系统中,如果多个中断同时产生,如果某个中断优先级低,则它会被挂起到挂起状态寄存器中。当高优先级事件处理完毕之后,低优先级事件从挂起态变为中执行态,同时这个寄存器对应的位也要清0.)*/
		EU_CORE_DEMUX->BUFFER_CLEAR = (1 << FC_SOC_EVENT_IRQN);   //向EVENT_BUFFER_CLEAR 0x00204028寄存器的bit27写入1.也就是event对应挂起位清0. 数据手册中也规定所有的外设SOC_PERIPH_EVT事件对应的事件类型号是 27. 		
		// TODO: USE builtins 
		event &= 0xFF;      //这里获取事件ID号
		fc_event_handlers[event]((void*)event);     //调用事件ID号对应的事件处理函数,开始真正的处理函数。这个处理函数是我们自己提供的。比如spim1的eot event 对应的处理函数就是spim_eot_handler。
	}

(至此,SPIM1控制触发的eot event,最终就会跳转到我们当初设置的处理函数spim_eot_handler()中)

注:

​ 1.Gap8的spi event 触发是通过写寄存器,来实现的。

​ 2.gap8中的 SPIM 的操作是指令集的。

​ 3.gap8 SPIM 控制器给我们提供同一个寄存器接口(这种“接口”的叫法是我自己起的,慎用)。

SPIM控制器对应的接口寄存器如下:
	RX_SADDR 	0x1A102100
	RX_SIZE 	0x1A102104
	RX_CFG 	0x1A102108
	TX_SADDR 	0x1A102110
	TX_SIZE 	0x1A102114
	TX_CFG 	0x1A102118

(这套接口很简单,接受对应3个寄存器,发送对应3个寄存器)

使用时,只需要将对应通道的数据,地址和长度写入对应的寄存器 即可。

​ 当然以上两组寄存器除了发送我们要发送的有效数据。其他的,比如,配置spiM的时钟,相位,极性等数据,也是通过以上两组寄存器,进行传输送(当然主要是TX_xx那一组进行发送)。

​ 上面已经提到,gap8的SPIM 的操作是通过指令来控制的。其实配置spiM的时钟,相位,极性等数据,就是按照各种指令的格式来进行配置的。

​ spim对应的各种指令及格式如下:

		Name 			  Command number Size Description

​		SPI_CMD_CFG 			0 32 	SPIM 	configuration command.

​		SPI_CMD_SOT 			1 32 	SPIM 	Start of Transfer command.

​		SPI_CMD_SEND_CMD 		2 32 	SPIM 	send command command.

​		SPI_CMD_SEND_ADDR 		3 32 	SPIM 	send address command.

​		SPI_CMD_DUMMY 			4 32 	SPIM 	dummy RX command.

​		SPI_CMD_WAIT 			5 32 	SPIM 	wait uDMA external event command.

​		SPI_CMD_TX_DATA 		6 32 	SPIM 	send data command (max 64kbits).

​		SPI_CMD_RX_DATA 		7 32 	SPIM 	receive data command (max 64kbits).

​		SPI_CMD_RPT 			8 32 	SPIM 	repeat next transfer command.

​		SPI_CMD_EOT 			9 32 	SPIM 	End of Transfer command.

​		SPI_CMD_RPT_END 		10 32 	SPIM 	end of repeat command.

​		SPI_CMD_RX_CHECK 		11 32 	SPIM 	RX check data command.

​		SPI_CMD_FULL_DUPL 		12 32 	SPIM 	full duplex mode command.
(每种指令都有对应的名字,命令号,及对应的功能。)

​ 4.还要注意到一点,gap8中的 SPIM 其实是和 uDMA紧密结合的。这些指令从某种程序上也可以看做是给uDMA和SPIM控制器的。

  1. 这种指令集的方式操作 SPIM的方法和顺序如下;
1.配置指令集的数据:spim 时钟,相位,极性,等等(一般需要多个uint32_t,通常组成数组)
2.配置要发送数据的 地址,大小,等
3.配置spim发送结束后的指令数据,比如是否产生event事件。

gap8的异步机制,基本都依赖于这个event事件。

(但gap8的event/interrupt相应时间较长,大约3.3ms)

你先帮我分析和逐行解释这里的每行代码“ void sl_bt_on_event(sl_bt_msg_t *evt) { sl_status_t sc; bd_addr address; uint8_t address_type; uint8_t system_id[8]; switch (SL_BT_MSG_ID(evt->header)) { // ------------------------------- // This event indicates the device has started and the radio is ready. // Do not call any stack command before receiving this boot event! case sl_bt_evt_system_boot_id: // Extract unique ID from BT Address. sc = sl_bt_system_get_identity_address(&address, &address_type); app_assert_status(sc); // Pad and reverse unique ID to get System ID. system_id[0] = address.addr[5]; system_id[1] = address.addr[4]; system_id[2] = address.addr[3]; system_id[3] = 0xFF; system_id[4] = 0xFE; system_id[5] = address.addr[2]; system_id[6] = address.addr[1]; system_id[7] = address.addr[0]; sc = sl_bt_gatt_server_write_attribute_value(gattdb_system_id, 0, sizeof(system_id), system_id); app_assert_status(sc); // 创建广播集 sc = sl_bt_advertiser_create_set(&advertising_set_handle); app_assert_status(sc); // 开始广播 sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle, sl_bt_advertiser_general_discoverable); app_assert_status(sc); // Set advertising interval to 100ms. sc = sl_bt_advertiser_set_timing( advertising_set_handle, 160, // min. adv. interval (milliseconds * 1.6) 160, // max. adv. interval (milliseconds * 1.6) 0, // adv. duration 0); // max. num. adv. events app_assert_status(sc); // Start advertising and enable connections. sc = sl_bt_legacy_advertiser_start(advertising_set_handle, sl_bt_advertiser_connectable_
最新发布
04-02
<think>嗯,用户让我帮忙逐行分析一段关于蓝牙设备启动事件处理、身份地址提取和广播配置的C代码。首先,我需要仔细看看用户提供的代码是什么样的,但用户这次没有直接贴出代码。不过根据问题描述,可能涉及BLE(蓝牙低功耗)相关的操作,比如初始化、设置地址、配置广播参数等。 首先,我需要回忆一下蓝牙协议栈中常见的函数和结构体。例如,在Nordic的SDK中,可能会有`ble_gap_addr_t`结构体用来表示设备地址,`sd_ble_gap_address_get`和`sd_ble_gap_address_set`这样的函数来获取和设置地址。另外,广播相关的函数如`ble_advdata_t`结构体定义广播数据,`sd_ble_gap_adv_start`启动广播。 用户提到的身份地址提取,可能涉及从控制器获取公共地址或者静态地址。通常在BLE中,设备地址可以是公共地址或随机地址,公共地址是设备固有的,而随机地址可以是静态或私有的。所以代码中可能有获取当前地址,然后根据类型处理,比如如果是公共地址就复制到某个变量,否则生成一个静态随机地址。 接下来,广播配置部分可能包括设置广播间隔、广播类型(可连接、可发现等)、广播数据(设备名称、UUID等)。需要检查广播参数结构体是否正确填充,比如`ble_gap_adv_params_t`中的类型、间隔、超时等参数。 可能需要注意的错误点包括:地址类型是否正确处理,广播数据是否包含必要的字段,广播参数是否合法(比如间隔是否在允许范围内),以及错误处理是否完善,比如检查函数返回值,处理可能的错误代码。 用户可能希望了解每一行代码的作用,比如函数调用的目的,结构体成员的含义,以及整体流程如何实现蓝牙设备的启动和广播。同时,需要确保引用的资料正确,比如引用中提到的RTOS事件标志可能用于任务间通信,但当前问题更关注BLE的初始化和配置。 需要分步骤解释:初始化事件处理、获取身份地址、配置广播数据、设置广播参数、启动广播。每部分对应的代码行需要逐一解释,可能涉及到的蓝牙协议栈函数和参数的意义。同时,要注意代码中的宏定义和错误处理,比如`APP_ERROR_CHECK`用于检查错误,确保操作成功。 最后,生成的相关问题可能涉及蓝牙地址类型、广播参数配置注意事项、错误处理机制、BLE安全设置等,帮助用户进一步理解相关概念。</think>以下是对蓝牙设备启动事件处理代码的逐行分析及功能说明: ---- ```c 1. static void on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context) // BLE事件回调函数 2. { 3. if (p_ble_evt->header.evt_id == BLE_GAP_EVT_CONNECTED) { // 判断是否为连接事件 4. NRF_LOG_INFO("Device connected"); // 输出连接日志 5. } 6. } 7. 8. void ble_stack_init(void) // BLE协议栈初始化函数 9. { 10. ret_code_t err_code; // 错误码声明 11. ble_enable_params_t ble_enable_params; // BLE使能参数结构体 12. memset(&ble_enable_params, 0, sizeof(ble_enable_params)); // 清空结构体 13. ble_enable_params.common_enable_params.vs_uuid_count = 1; // 设置厂商UUID数量[^3] 14. err_code = sd_ble_enable(&ble_enable_params); // 使能BLE协议栈 15. APP_ERROR_CHECK(err_code); // 检查执行结果 16. } 17. 18. void extract_identity_address(void) // 身份地址提取函数 19. { 20. ble_gap_addr_t device_addr; // 设备地址结构体 21. sd_ble_gap_address_get(&device_addr); // 获取当前设备地址[^1] 22. if(device_addr.addr_type == BLE_GAP_ADDR_TYPE_PUBLIC) { // 判断地址类型 23. memcpy(g_identity_addr.addr, device_addr.addr, BLE_GAP_ADDR_LEN); // 复制公共地址 24. } else { 25. ble_gap_addr_t static_addr = { // 创建静态随机地址 26. .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC, 27. .addr = {0x01,0x12,0x23,0x34,0x45,0x56}}; // 固定地址示例 28. sd_ble_gap_address_set(BLE_GAP_ADDR_CYCLE_MODE_NONE, &static_addr); // 设置静态地址[^2] 29. } 30. } 31. 32. void advertising_config(void) // 广播配置函数 33. { 34. ble_advdata_t advdata; // 广播数据结构体 35. memset(&advdata, 0, sizeof(advdata)); // 清空结构体 36. advdata.name_type = BLE_ADVDATA_FULL_NAME; // 设置完整设备名称 37. advdata.include_appearance = true; // 包含设备外观特征 38. 39. ble_gap_adv_params_t adv_params; // 广播参数结构体 40. memset(&adv_params, 0, sizeof(adv_params)); // 清空结构体 41. adv_params.primary_phy = BLE_GAP_PHY_1MBPS; // 设置物理层速率 42. adv_params.duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED; // 无限广播时间 43. adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED; // 可连接可扫描 44. 45. sd_ble_gap_adv_start(&adv_params, BLE_CONN_CFG_TAG_DEFAULT); // 启动广播[^4] 46. } ``` ---- ### 关键代码解析 **1-6行**:定义BLE事件回调函数,当收到`BLE_GAP_EVT_CONNECTED`连接事件时输出日志。这种事件驱动机制与RTOS的事件标志机制类似 **8-16行**:协议栈初始化流程 - 第12行使用`memset`清空参数结构体是防止未初始化内存错误的重要操作 - 第13行设置厂商UUID数量需要与后续服务声明配合使用 - 第14行`sd_ble_enable`是启动协议栈的核心API **18-30行**:地址管理逻辑 - 第21行`sd_ble_gap_address_get`获取控制器分配的地址 - 第22-24行处理公共地址场景 - 第25-28行演示生成静态随机地址的标准方法 - 地址类型`BLE_GAP_ADDR_TYPE_RANDOM_STATIC`符合蓝牙核心规范 **32-46行**:广播参数配置 - 第36行设置完整设备名称将触发协议栈自动处理名称截断 - 第42行`BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED`表示持续广播直到连接建立 - 第43行`CONNECTABLE_SCANNABLE`类型是最常用的广播模式 - 第45行启动广播时需要指定连接配置标签[^4]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值