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.c
hal_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.c
spim_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:%dn",__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控制器的。
这种指令集的方式操作 SPIM的方法和顺序如下;
1.配置指令集的数据:spim 时钟,相位,极性,等等(一般需要多个uint32_t,通常组成数组)
2.配置要发送数据的 地址,大小,等
3.配置spim发送结束后的指令数据,比如是否产生event事件。
gap8的异步机制,基本都依赖于这个event事件。
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.c
hal_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.c
spim_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:%dn",__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控制器的。
这种指令集的方式操作 SPIM的方法和顺序如下;
1.配置指令集的数据:spim 时钟,相位,极性,等等(一般需要多个uint32_t,通常组成数组)
2.配置要发送数据的 地址,大小,等
3.配置spim发送结束后的指令数据,比如是否产生event事件。
gap8的异步机制,基本都依赖于这个event事件。
举报