“It doesn’t matter where you are, you are nowhere compared to where you can go.”
经过上一篇博客FPGA+DSP SRIO通信(四)——中断系统(一)的学习,我们已经可以将FPGA端产生的doorbell中断变成INTDST,也就是系统中断,接下来要做的就是使系统中断和主机中断 相互关联起来,再将主机中断和CPU中断映射起来,最终编写程序以使CPU知道当某个主机中断发生时,应该做出什么操作。
DSP所做的行为只与CPU中断直接联系。
目录
上图所示为完整的FPGA到DSP的doorbell映射示意图,本文要实现的是上图右边红色箭头所指的蓝色框内的内容。
那么如何配置cpintc/CIC(CorePac Interrupt Controller)和Intc(Interrupt Controller),以生成主机中断呢?往下看。
##1、配置方法分类
笔者所接触的配置方法有两种,这两种方法的区别在于你的项目要使用sysbios程序还是裸机程序。
两种方法没有孰优孰劣,只是在代码实现方面,sysbios版本显得更加精简优雅。
在实现具体代码配置之前,我们先明确几个数字概念。
【系统事件号】
6678数据手册中的 Table7-38展示了6678的所有系统事件映射,也是一级中断映射,一级中断一共有128个,这128个中断可以被中断选择器选择,映射给12个CPU中断。笔者选择62号和63号系统事件,在表中的展示如下图:
【系统中断号】
##2、6678裸机中断配置
笔者所参照的6678裸机中断配置例程所在位置为C:\ti\pdk_C6678_1_1_2_6\packages\ti\csl\example\cpintc\cpintc_test.c,我会在文末将该例程共享出来供大家学习。该官方示例程序大家可以在读完本文后仔细阅读,会对6678的中断系统有更加详细的了解。
上图为一般中断系统的运行原理,DSP在执行指令时,如果遇到中断,会优先执行中断,而中断所要执行的内容就是ISR(中断服务函数)中的内容。如果同时接收到多个中断,会优先执行优先级高的中断。
根据以上说明,我们首先得编写一个中断服务函数,在DSP收到中断后,执行相应的功能,这里以打印出一串字符串为例。
CSL_CPINTC_Handle hnd;// 中断配置句柄
static void print_isr_handler (void* handle)
{
// 触发中断后编程者自定义的功能
printf("receive a interrupt from SRIO\n");
// 使主机中断失效
CSL_CPINTC_disableHostInterrupt (hnd, 2);
// 清除系统中断
CSL_CPINTC_clearSysInterrupt (hnd,116);
// 使能主机中断
CSL_CPINTC_enableHostInterrupt (hnd, 2);
// 获得正在等待的doorbell中断状态
CSL_SRIO_GetDoorbellPendingInterrupt (hSrio, 0, &doorbellStatus);
// 如果有doorbell中断正在等待,则清除正在等待的doorbell中断,以等待进入下一次中断
CSL_SRIO_ClearDoorbellPendingInterrupt (hSrio, 0, doorbellStatus);
}
下面一一解决上面这段代码中的疑问。
###2.1 CSL_CPINTC_disableHostInterrupt ()是从哪里来的?
这种类似函数的使用需要在程序中包含相关的头文件,具体需要在实现中断配置的.c文件中加入以下包含代码:
#include
#include
###2.2 CSL_CPINTC_disableHostInterrupt (hnd, 2)中的2是什么意思?
该函数中的2为主机中断号,一般使用4-15号主机中断。
###2.3 CSL_CPINTC_clearSysInterrupt (hnd,116)中的116是什么意思?
该函数中的116为CIC0(cpintc0)的116号输入,由于笔者配置的是SRIO doorbell中断,通过6678用户手册的表7-39可以看到,CIC0的116号输入是和INDST4系统中断相关联的,而INTDST通过FPGA+DSP SRIO通信(四)——中断系统(一)已经配置得到。
通过以上步骤我们已经了解了中断服务函数的组成,那么如何通过程序,编程使得下图右侧蓝色部分的关联功能实现呢?
下面这个函数体内实现了INTDST->cpintc->intc->CPU中断 的映射配置。具体说明见函数中的注释。
笔者这里配置了2个中断源,即FPGA端向DSP发送doorbell包,doorbell_info为4或5,则会触发相应的CPU中断。上文只展示了2个中断服务函数其中1个,另一个copy前一个中断服务函数,修改主机中断号和系统中断号即可。
先上干货:
/* Intc variable declarartion */
CSL_CPINTC_Handle hnd;
CSL_IntcContext intcContext;
CSL_IntcEventHandlerRecord EventHandler[30];
CSL_IntcObj intcObj_read;
CSL_IntcObj intcObj_write;
CSL_IntcHandle read_handle;
CSL_IntcHandle write_handle;
CSL_IntcGlobalEnableState state;
CSL_IntcEventHandlerRecord EventRecord;
CSL_IntcParam vectId_read;
CSL_IntcParam vectId_write;
int DoorbellIntr_Init (void)
{
Uint32 rawStatus;
Uint16 index;
/************************************************
*************** INTC Configuration *************
************************************************/
/* INTC module initialization */
intcContext.eventhandlerRecord = EventHandler;//save eventhandler
intcContext.numEvtEntries = 1;// 该变量和事件组合触发有关,如果是单事件触发,设置为1即可
if (CSL_intcInit(&intcContext) != CSL_SOK)
{
return -1;
}
/* Enable NMIs */
if (CSL_intcGlobalNmiEnable() != CSL_SOK)
{
return -1;
}
/* Enable global interrupts */
if (CSL_intcGlobalEnable(&state) != CSL_SOK)
{
return -1;
}
/* 为CPU中断4、5,事件ID 63、64打开INTC模块 */
/* CPU中断前面已经说过,INTC模块的事件ID在6678用户手册的table7-38中
* 本文的第一张图片显示了笔者要使用的事件ID */
vectId_read = CSL_INTC_VECTID_4;
vectId_write = CSL_INTC_VECTID_5;
read_handle = CSL_intcOpen (&intcObj_read, 62, &vectId_read , NULL);
write_handle = CSL_intcOpen (&intcObj_write, 63, &vectId_write , NULL);
/* 这里只检查了一个中断关联的成功性,如果不放心可以两个都检查 */
if (read_handle == NULL)
{
return -1;
}
if (write_handle == NULL)
{
return -1;
}
/* 这里将中断服务函数和中断源联系起来 */
EventHandler[0].handler = &read_isr_handler;
EventHandler[0].arg = 0;
EventHandler[1].handler = &write_isr_handler;
EventHandler[1].arg = 0;
if (CSL_intcPlugEventHandler(read_handle,&EventHandler[0]) != CSL_SOK)
{
return -1;
}
if (CSL_intcPlugEventHandler(write_handle,&EventHandler[1]) != CSL_SOK)
{
return -1;
}
/* 使能刚才配置的两种中断通路 */
/* Enabling the read events. */
if (CSL_intcHwControl(read_handle,CSL_INTC_CMD_EVTENABLE, NULL) != CSL_SOK)
{
return -1;
}
/* Enabling the write events. */
if (CSL_intcHwControl(write_handle,CSL_INTC_CMD_EVTENABLE, NULL) != CSL_SOK)
{
return -1;
}
/**************************************************
************* CPINTC-0 Configuration *************
**************************************************/
/*
* CPINTC(有的文档中称为CIC) ,这里说的CPINTC-0就是6678用户手册table7-39中描述的CIC0。
* 将 INTDST4(事件号116)和INTDST5(事件号117)映射到主机中断2和主机中断3上
*/
/* Open the handle to the CPINT Instance */
hnd = CSL_CPINTC_open(0);
if (hnd == 0)
{
return -1;
}
/* Disable all host interrupts. */
CSL_CPINTC_disableAllHostInterrupt(hnd);
/* Configure no nesting support in the CPINTC Module. */
CSL_CPINTC_setNestingMode (hnd, CPINTC_NO_NESTING);
/* clear system interrupt INTDST4(116) */
CSL_CPINTC_clearSysInterrupt (hnd,116);
CSL_CPINTC_clearSysInterrupt (hnd,117);
/* We now enable system interrupt INTDST4(116) */
CSL_CPINTC_enableSysInterrupt (hnd, 116);
CSL_CPINTC_enableSysInterrupt (hnd, 117);
/* We now map System Interrupt INTDST4 to channel 2 */
CSL_CPINTC_mapSystemIntrToChannel (hnd, 116 , 2);//test if this line not exist
CSL_CPINTC_mapSystemIntrToChannel (hnd, 117 , 3);
/* We enable host interrupts. */
CSL_CPINTC_enableHostInterrupt (hnd, 2);
CSL_CPINTC_enableHostInterrupt (hnd, 3);
/* Enable all host interrupts also. */
CSL_CPINTC_enableAllHostInterrupt(hnd);
return 0;
}
现在就可以愉快的测试了,在两个中断服务函数中添加断点,用FPGA向DSP通过SRIO发送doorbell包,doorbell_info写4或5,就可以看到DSP停到相应的中断服务函数中啦。
##3、6678 sysbios中断配置
sysbios的中断配置与逻辑中断配置相比,看起来更加简洁,但其实底层的步骤并没有一丝减少,只是TI减少了在配置每一步时所使用的C代码量而已。
千万不要以为sysbios减少了你这个工程师的工作量,因为你会认识到一个全新的知识架构,这将耗费你巨大的精力,而中断配置只涉及这个架构的冰山一角,我尽量简洁一点说明。
首先,FPGA+DSP SRIO通信(四)——中断系统(一)中做的工作你还是得做,因为外设中断源的配置和CPU的中断是独立开来的,不管你用得是sysbios中断配置方式还是裸机配置方式。接下来我们来看具体步骤。
###3.1 中断服务函数(ISR)
中断服务函数是中断配置中必不可少的一环,下面给出sysbios版的中断服务函数,和裸机(CSL)版的中断服务函数类似,照例,笔者也只给出一个:
/* 在该中断服务函数(ISR)中,笔者使用6号主机中断和CIC0的116号事件(系统事件) */
void interrupt_ISR()
{
printf("进入中断服务函数\n");
CpIntc_disableHostInt(0,6);
CpIntc_clearSysInt(0,116);
CpIntc_enableHostInt(0,6);
CSL_SRIO_GetDoorbellPendingInterrupt (hSrio, 0, &doorbellStatus);
CSL_SRIO_ClearDoorbellPendingInterrupt (hSrio, 0, doorbellStatus);
}
看的仔细的朋友会发现在裸机ISR中,笔者使用了CSL_CPINTC_disableHostInterrupt ();,而sysbios的ISR中,笔者使用的是CpIntc_disableHostInt(),两者底层实现是相同的,但除了函数形式的不同,还有其所在头文件的不同,如果不能正确包含头文件及其路径,会报找不到函数这种低级错误。
【DSP编译小窍门 选读部分】
如果编译之后报了类似于couldn’t find xxx.h 或 undefined sysmbols之类的错误,首先要检查你的工程属性->Build->C6000 Compiler->Include Options中有没有缺少xxx.h或未定义symbols的路径,如果有,就添加上,例如我的include options中就有以下路径:
“${CG_TOOL_ROOT}/include”
“C:\ti\pdk_C6678_1_1_2_6\packages\ti\platform”
“C:\ti\pdk_C6678_1_1_2_6\packages\ti\csl”
“C:\ti\pdk_C6678_1_1_2_6/packages/ti/drv/qmss”
“C:\ti\pdk_C6678_1_1_2_6/packages/ti/drv/cppi”
如果你使用的是官方封装好的lib,例如,如果你使用的是csl封好的库,其路径应该为C:\ti\pdk_C6678_1_1_2_6\packages\ti\csl\lib,其名称应该为ti.csl.ae66,那你还需要查看工程属性->Build->C6000 Linker->File Search Path中的上半部分是否添加了ti.csl.ae66,下半部分是否添加了C:\ti\pdk_C6678_1_1_2_6\packages\ti\csl\lib。
如果你使用的是裸机工程,经过上面两步还没有使你remove这两种报错,你就需要详细检查你的代码了。
如果你使用的是sysbios工程,经过上面两步还没有使你remove这两种报错,还有一招可以救工程于水火。例如,如果你使用了类似CpIntc_disableHostInt()的函数,那么它所在的库是在sysbios路径下的,打开你工程目录中的**.cfg**文件,将var cpIntc = xdc.useModule(‘ti.sysbios.family.c66.tci66xx.CpIntc’);添加进去即可。
sysbios的这部分实现就显得简洁了许多,笔者注释掉了2个中断映射配置中的1个,具体说明见代码注释:
void interrupt_init()
{
Int eventID_rd;
//Int eventID_wr;
Hwi_Params params_rd;
//Hwi_Params params_wr;
//--------中断配置--------------
/*
* Map the System Interrupt i.e. the Interrupt Destination 4 interrupt to the DIO ISR Handler.
* Number 116 is the system interrupt
*/
/* 该函数将中断服务函数与系统事件相映射 */
CpIntc_dispatchPlug(116, (CpIntc_FuncPtr)interrupt_ISR, 116, TRUE);
// CpIntc_dispatchPlug(117, (CpIntc_FuncPtr)write_ISR, 117, TRUE);
/*
* The configuration is for CPINTC0. We map system interrupt 116 to Host Interrupt 6.
* Number 6 is the Host interrupt
*/
/* 该函数将系统事件与主机中断相映射 */
CpIntc_mapSysIntToHostInt(0, 116, 6);
// CpIntc_mapSysIntToHostInt(0, 117, 6);
/* Enable the Host Interrupt. */
CpIntc_enableHostInt(0, 6);
// CpIntc_enableHostInt(0, 6);
/* Enable the System Interrupt */
CpIntc_enableSysInt(0, 116);
// CpIntc_enableSysInt(0, 117);
/* Get the event id associated with the host interrupt. */
eventID_rd = CpIntc_getEventId(6);
// eventID_wr = CpIntc_getEventId(6);
Hwi_Params_init(¶ms_rd);
// Hwi_Params_init(¶ms_wr);
/* Host interrupt value*/
params_rd.arg = 6;
// params_oneFrameCom.arg = 6;
/* Event id for your host interrupt */
params_rd.eventId = eventID_rd;
// params_wr.eventId = eventID_wr;
/* Enable the Hwi */
params_rd.enableInt = TRUE;
// params_wr.enableInt = TRUE;
/* This plugs the interrupt vector 4 and the ISR function. */
/* When using CpIntc, you must plug the Hwi fxn with CpIntc_dispatch */
/* so it knows how to process the CpIntc interrupts.*/
/* number 4 and 5 is the interrupt vector .user can use Vector4-15*/
/* 该函数前面的配置和CPU中断相映射 */
Hwi_create(4, &CpIntc_dispatch, ¶ms_rd, NULL);
// Hwi_create(5, &CpIntc_dispatch, ¶ms_wr, NULL);
}
大功告成,在中断服务函数中添加断点,用FPGA向DSP通过SRIO发送doorbell包,doorbell_info写4,就可以看到DSP停到中断服务函数中啦。
6678的中断配置系统到这里就告一段落了,虽然从头到尾都在讲如何配置SRIO触发的中断,但也可以使用这个套路去配置其它外设触发的中断,这里就不详细介绍了,记得遵循**“从哪里来,经过什么,到哪里去”**这个原则,利用好外设文档,CIC,INTC,那么什么中断都能配置好的。
参考文献及文档
[1]. 6678裸机中断配置例程
[2]. SRIO官方文档
[3]. 6678用户手册
[4]. 6678中断配置官方中文文档
6678的SRIO模块相关博文到这里也就全部结束了,笔者看了一下从2016年9月4日开始写第一篇SRIO相关博客,到今天一共经历了将近2年时间,SRIO对我来说不仅仅是一个技术知识,更是一种学习态度和方法的探索过程。
对于做很多事情都半途而废的笔者来说,真的是一个巨大的achievement,想谢谢几个人。
第一个人是北航的王悦人学长,如果不是他当初给了我一个SRIO的程序,让我初窥DSP开发的门径,也就没有这一系列博客了。
第二个人是北理工的倪俊学长,一个人的精神信念永远是指引行为的不竭动力。但行好事,不问前程。
还有世界上的第一代开源人,开源几乎推动了所有信息相关行业的快速发展。 敢为天下先!
接下来博客系列是:6678千兆网的相关配置。