转自:http://blog.sina.com.cn/s/blog_98740ded0102uyzo.html
Xilinx软核AXI Timer 和AXI INTC 的使用心得
(2014-08-08 08:39:46)
这两天做定时器和中断控制器的实验。
在搭建的Microblaze软核基础上,添加了一个AXI Timer和一个AXI INTC。
编译得到比特流文件后,导出到SDK中。编写定时器中断的例程,原本以为跟GPIO 、SPI什么的用法一样,直接调用xtmrctr.c/h 、 xintc.c/h就能完成。
但是,现实就是,无论怎么搞都搞不定。
在SDK中,对于每一个外设,如gpio、spi、timer、intc、tft等等,都提供了三种层次的驱动函数,无论在哪一种层次上编程都应该能实现期望的功能。
下面以timer为例。
1.最低层次是,只提供了读写寄存器的函数,函数参数需要外设地址、寄存器偏移地址、掩码等。这些函数在xtmrctr_l.h中,当然也提供了寄存器偏移的宏变量,与gpio的数据手册中的寄存器描述是一致的。
XTmrCtr_WriteReg(BaseAddress, TmrCtrNumber, RegOffset, ValueToWrite)或者XGpio_Out32(...)
XTmrCtr_ReadReg(BaseAddress, TmrCtrNumber, RegOffset)或者XGpio_In32(....)
#define XTC_DEVICE_TIMER_COUNT 2
#define XTC_TIMER_COUNTER_OFFSET 16
#define XTC_TCSR_OFFSET 0
#define XTC_TLR_OFFSET 4
#define XTC_TCR_OFFSET 8
#define XTC_CSR_CASC_MASK 0x00000800
#define XTC_CSR_ENABLE_ALL_MASK 0x00000400
#define XTC_CSR_ENABLE_PWM_MASK 0x00000200
#define XTC_CSR_INT_OCCURED_MASK 0x00000100
#define XTC_CSR_ENABLE_TMR_MASK 0x00000080
#define XTC_CSR_ENABLE_INT_MASK 0x00000040
#define XTC_CSR_LOAD_MASK 0x00000020
#define XTC_CSR_AUTO_RELOAD_MASK 0x00000010
#define XTC_CSR_EXT_CAPTURE_MASK 0x00000008
#define XTC_CSR_EXT_GENERATE_MASK 0x00000004
#define XTC_CSR_DOWN_COUNT_MASK 0x00000002
#define XTC_CSR_CAPTURE_MODE_MASK 0x00000001
在这个层次上,大家可以看到,不同的外设的读写函数归根到底都是Xil_Out32和Xil_In32。
2.低层次的,是提供了读写不同寄存器的方式,需要用到的函数参数为外设地址,掩码。这些驱动函数还是在xtmrctr_l.h和xtmrctr_l.c中,此时相对于1,是把偏移地址给隐藏起来了,通过函数的字面意思就可以理解到函数的含义,一般只需提供基地址,timer中由于有两个计数器,所以还有一个编号:
控制寄存器写函数:XTmrCtr_SetControlStatusReg(BaseAddress, TmrCtrNumber, RegisterValue)
控制寄存器写函数:XTmrCtr_GetControlStatusReg(BaseAddress, TmrCtrNumber)
读计数器值函数:XTmrCtr_GetTimerCounterReg(BaseAddress, TmrCtrNumber)
写加载寄存器值函数:XTmrCtr_SetLoadReg(BaseAddress, TmrCtrNumber, RegisterValue)
定时器使能函数: XTmrCtr_Enable(BaseAddress, TmrCtrNumber)
定时器使能中断函数: XTmrCtr_EnableIntr(BaseAddress, TmrCtrNumber)
等等,未举完。
3.高层次的,被封装成结构体的形式,用户定义好一个结构体变量,对它使用相应的初始化函数之后。再要进行程序编写,完全不需要用到外设地址、或者寄存器偏移等等。相关函数比较多,xtmrctr_g.c(本工程的所有定时器实例的参数表,由xparameters.h可以得到),xtmrctr_options.c,xtmrctr_selftest.c.c, xtmrctr_intr.c, xtmrctr_stats.c,当然还有最最重要的xtmrctr.h和xtmrctr.c两个文件了。
xtmrctr.h包含了这里好所有的c文件函数的声明。所以在这一层次上调用函数的话,可以直接看这个文件里面的函数定义。
其中最重要的结构体定义是:
typedef struct {
XTmrCtrStats Stats;
u32 BaseAddress;
u32 IsReady;
u32 IsStartedTmrCtr0;
u32 IsStartedTmrCtr1;
XTmrCtr_Handler Handler;
void *CallBackRef;
} XTmrCtr;
然后是各种函数的定义,他们的参数基本上都是上面这个结构体的指针。
int XTmrCtr_Initialize(XTmrCtr * InstancePtr, u16 DeviceId);
void XTmrCtr_Start(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
void XTmrCtr_Stop(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
u32 XTmrCtr_GetValue(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
void XTmrCtr_SetResetValue(XTmrCtr * InstancePtr, u8 TmrCtrNumber,
u32 ResetValue);
u32 XTmrCtr_GetCaptureValue(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
int XTmrCtr_IsExpired(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
void XTmrCtr_Reset(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
XTmrCtr_Config *XTmrCtr_LookupConfig(u16 DeviceId);
void XTmrCtr_SetOptions(XTmrCtr * InstancePtr, u8 TmrCtrNumber, u32 Options);
u32 XTmrCtr_GetOptions(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
void XTmrCtr_GetStats(XTmrCtr * InstancePtr, XTmrCtrStats * StatsPtr);
void XTmrCtr_ClearStats(XTmrCtr * InstancePtr);
int XTmrCtr_SelfTest(XTmrCtr * InstancePtr, u8 TmrCtrNumber);
void XTmrCtr_SetHandler(XTmrCtr * InstancePtr, XTmrCtr_Handler FuncPtr,
void *CallBackRef);
void XTmrCtr_InterruptHandler(void *InstancePtr);
而且从函数名的字面意义上去理解,也是非常简单的,其实挺喜欢用这一层次的做法,它封装了很多底层细节的东西,编程起来代码看着更舒服。甚至,我们不需要再去看数据手册。
以上是背景,然后我就要开始吐槽了。
**************************************************************************************
我最开始写了一个定时器中断的例子,用到的是高层次里面的驱动函数。(里面有各种被注释掉的代码,就不用看了,懂的人会知道为什么会有这么多恶心的注释掉代码)。
#include
#include "platform.h"
#include "xparameters.h"
#include "xgpio.h"
#include "xil_io.h"
#include "mb_interface.h"
#include "xtmrctr.h"
#include "xintc.h"
#define BTN_BASEADDR XPAR_AXI_GPIO_2_BASEADDR
#define BTN_DEVICE_ID XPAR_AXI_GPIO_2_DEVICE_ID
#define BTN_IRTP_ID XPAR_AXI_INTC_0_AXI_GPIO_2_IP2INTC_IRPT_INTR
#define SW_BASEADDR XPAR_AXI_GPIO_1_BASEADDR
#define SW_DEVICE_ID XPAR_AXI_GPIO_1_DEVICE_ID
#define SW_IRTP_ID XPAR_AXI_INTC_0_AXI_GPIO_1_IP2INTC_IRPT_INTR
#define TIMER_BASEADDR XPAR_AXI_TIMER_0_BASEADDR
#define TIMER_DEVICE_ID XPAR_AXI_TIMER_0_DEVICE_ID
#define TIMER_IRTP_ID XPAR_AXI_INTC_0_AXI_TIMER_0_INTERRUPT_INTR
#define INTC_DEVICE_ID XPAR_AXI_INTC_0_DEVICE_ID
XGpio btn;
XGpio sw;
XTmrCtr timer;
XIntc intCtrl;
XGpio led;
char str[100];
void print(char *str);
void PushButtonHandle(void *pshButton);
void SwitchHandle(void *sw);
void TimerHandle(void *timer);
static int i = 0;
unsigned int flag = 0 ;
void test_pwm();
void delay(int t);
int main()
{
init_platform();
xil_printf("Hello World\n");
// test_pwm();
XGpio_Initialize(&btn, BTN_DEVICE_ID);
XGpio_Initialize(&sw, SW_DEVICE_ID);
//对定时器的初始化
XTmrCtr_Initialize(&timer,TIMER_DEVICE_ID);
//对中断控制器的初始化
XIntc_Initialize(&intCtrl, INTC_DEVICE_ID);
XGpio_SetDataDirection(&sw, 1, 0xf);
XGpio_SetDataDirection(&btn, 1, 0xf);
// XIntc_Connect(&intCtrl, BTN_IRTP_ID, PushButtonHandle, &btn);
// XIntc_Connect(&intCtrl, SW_IRTP_ID, SwitchHandle, &sw);
//将定时器中断注册到中断控制器上,中断服务函数为TimerHandle
XIntc_Connect(&intCtrl, TIMER_IRTP_ID , TimerHandle, &timer);
// XIntc_Enable(&intCtrl, BTN_IRTP_ID);
// XIntc_Enable(&intCtrl, SW_IRTP_ID);
//使能定时器中断
XIntc_Enable(&intCtrl, TIMER_IRTP_ID);
//将中断控制器的中断注册到处理器上,中断服务函数为默认的XIntc_DeviceInterruptHandler
microblaze_register_handler(XIntc_DeviceInterruptHandler, INTC_DEVICE_ID);
//使能处理器的中断
microblaze_enable_interrupts();
//启动中断控制器
XIntc_Start(&intCtrl, XIN_REAL_MODE);
// XGpio_InterruptEnable(&btn, 1);
// XGpio_InterruptGlobalEnable(&btn);
// XGpio_InterruptEnable(&sw, 1);
// XGpio_InterruptGlobalEnable(&sw);
// u32 options = XTmrCtr_GetOptions(&timer,0);
// options = XTmrCtr_ReadReg(TIMER_BASEADDR,0,XTC_TCSR_OFFSET);
// xil_printf("options = X\n",options);
// XTmrCtr_WriteReg(TIMER_BASEADDR, 0, XTC_TCSR_OFFSET, 0x7d6);
//设置定时器的工作模式,使能所有的计数器,使能中断,使能自动装载,使能向下计数
XTmrCtr_SetOptions(&timer,0,XTC_ENABLE_ALL_OPTION| XTC_INT_MODE_OPTION |XTC_AUTO_RELOAD_OPTION | XTC_DOWN_COUNT_OPTION);
//设定计数溢出值
XTmrCtr_SetResetValue(&timer,0,1<<31);//2000000);//10ms
//启动定时器
XTmrCtr_Start(&timer,0);
XGpio_Initialize(&led, XPAR_AXI_GPIO_0_DEVICE_ID);
XGpio_SetDataDirection(&led, 1, 0x00);
XGpio_DiscreteWrite(&led, 1, 0x1);
XGpio_DiscreteWrite(&led, 1, 0x2);
XGpio_DiscreteWrite(&led, 1, 0x3);
while(1)
{
// delay(153000);//about 10ms
// u32 countervalue = XTmrCtr_GetValue(&timer,0);
// xil_printf("***Main Loop counter value = X\n",countervalue);
// XGpio_DiscreteWrite(&led, 1, i);
// xil_printf("***i = %d\n\r",i);
}
return 0;
}
//void PushButtonHandle(void *btn)
//{
//
// XGpio* PushButton = (XGpio*) btn;
// u32 btnState = XGpio_DiscreteRead(PushButton, 1);
// print("PushButtonHandle\n");
// xil_printf("i = %d\n\r",i);
//
// i++;
// XGpio_InterruptClear(PushButton, 0xff);
//}
//void SwitchHandle(void *sw)
//{
//
// XGpio* Switch = (XGpio*) sw;
// u32 btnState = XGpio_DiscreteRead(Switch, 1);
// xil_printf("SwitchHandle\n");
// xil_printf("i = %d\n\r",i);
//
// i++;
// XGpio_InterruptClear(Switch, 0xff);
//}
void TimerHandle(void *timer)
{
XTmrCtr* Timer = (XTmrCtr*) timer;
u32 countervalue = XTmrCtr_GetValue(Timer,0);
u32 options = XTmrCtr_GetOptions(Timer,0);
//XGpio_DiscreteWrite(&led, 1, 1<<(i%4));
flag = 1;
i++;
if(i >= 10){
xil_printf("TimerHandle\n");
xil_printf("i = %d\n\r",i);
xil_printf("counter value = X\n",countervalue);
xil_printf("options = X\n",options);
XGpio_DiscreteWrite(&led, 1, i);
i = 0;
}
//XTmrCtr_ClearStats(Timer);
}
void test_pwm(){
XTmrCtr_WriteReg(TIMER_BASEADDR,0,XTC_TLR_OFFSET,1000);
XTmrCtr_WriteReg(TIMER_BASEADDR,1,XTC_TLR_OFFSET,300);
XTmrCtr_WriteReg(TIMER_BASEADDR,0,XTC_TCSR_OFFSET,0x6f6);
XTmrCtr_WriteReg(TIMER_BASEADDR,1,XTC_TCSR_OFFSET,0x6f6);
u32 options;
options = XTmrCtr_ReadReg(TIMER_BASEADDR,0,XTC_TCSR_OFFSET);
xil_printf("options = X\n",options);
options = XTmrCtr_ReadReg(TIMER_BASEADDR,1,XTC_TCSR_OFFSET);
xil_printf("options = X\n",options);
options = XTmrCtr_ReadReg(TIMER_BASEADDR,0,XTC_TLR_OFFSET);
xil_printf("options = X\n",options);
options = XTmrCtr_ReadReg(TIMER_BASEADDR,1,XTC_TLR_OFFSET);
xil_printf("options = X\n",options);
XGpio_Initialize(&led, XPAR_AXI_GPIO_0_DEVICE_ID);
XGpio_SetDataDirection(&led, 1, 0x00);
while(1){
// XGpio_DiscreteWrite(&led, 1, 0x1);
// XGpio_DiscreteWrite(&led, 1, 0x0);2.4MHz
XGpio_WriteReg(XPAR_AXI_GPIO_0_BASEADDR,XGPIO_DATA_OFFSET,0x1);
delay(1000);
XGpio_WriteReg(XPAR_AXI_GPIO_0_BASEADDR,XGPIO_DATA_OFFSET,0x0);//8.3MHz,120ns,60ns ,12*cycle
delay(1000);//65us,65ns,一个自减一和判断真假操作,凶耗约65ns/5= 13*cycle;
}
}
void delay(int t){
while(t--);
}
虽然,它能工作,但是,它特别坑啊。我无论将定时器做成1s的,还是10ms的,亦或是10s的,它都工作及其的不正常,时间间隔完全不是那么回事儿,而且还会有卡死的感觉,这些无论是观察led的点亮效果,还是控制台打印信息都可以感受出来。
看着这么优雅的代码,就此逝去。我也是醉了,SDK软件本身的各种BUG就不说了,我觉得他们家的mdm软核调试器模块也有问题,一加上调试器,运行时间就不对了。还得仰仗串口或者led什么的来观察。哎~
于是我绝望的搞起寄存器了。注意这个里面用到的定时器的处理函数都是第二层次的方式。其他的GPIO,INTC中断控制器都是很优雅的结构体操作啊。对于处女座来说,简直就是灾难,有木有~~~~
********************************************************************************************
#include
#include "platform.h"
#include "xparameters.h"
#include "xutil.h"
#include "xintc.h"
#include "xtmrctr.h"
#include "xil_macroback.h"
#include "xgpio.h"
void timer_int_handler(void);
void button_int_handler(void);
void switch_int_handler(void);
unsigned int timer_cnt;
unsigned int btn_cnt;
unsigned int sw_cnt;
#define BTN_BASEADDR XPAR_AXI_GPIO_2_BASEADDR
#define BTN_DEVICE_ID XPAR_AXI_GPIO_2_DEVICE_ID
#define BTN_IRTP_ID XPAR_AXI_INTC_0_AXI_GPIO_2_IP2INTC_IRPT_INTR
#define SW_BASEADDR XPAR_AXI_GPIO_1_BASEADDR
#define SW_DEVICE_ID XPAR_AXI_GPIO_1_DEVICE_ID
#define SW_IRTP_ID XPAR_AXI_INTC_0_AXI_GPIO_1_IP2INTC_IRPT_INTR
#define TIMER_BASEADDR XPAR_AXI_TIMER_0_BASEADDR
#define TIMER_DEVICE_ID XPAR_AXI_TIMER_0_DEVICE_ID
#define TIMER_IRTP_ID XPAR_AXI_INTC_0_AXI_TIMER_0_INTERRUPT_INTR
#define INTC_BASEADDR XPAR_AXI_INTC_0_BASEADDR
#define INTC_DEVICE_ID XPAR_AXI_INTC_0_DEVICE_ID
XGpio btn;
XGpio sw;
XGpio led;
int main()
{
timer_cnt = 0;
xil_printf("--start the program test---\r\n");
//led灯的初始化,方向设置
XGpio_Initialize(&led, XPAR_AXI_GPIO_0_DEVICE_ID);
XGpio_SetDataDirection(&led, 1, 0x00);
XGpio_DiscreteWrite(&led, 1, 0x1);
//button 和switch的初始化、方向设置
XGpio_Initialize(&btn, BTN_DEVICE_ID);
XGpio_Initialize(&sw, SW_DEVICE_ID);
XGpio_SetDataDirection(&sw, 1, 0xf);
XGpio_SetDataDirection(&btn, 1, 0xf);
//设定定时器的载入值为10000000,在时钟为100MHz时,每隔100ms产生一次中断
XTmrCtr_mSetLoadReg(TIMER_BASEADDR, 0, 10000000);
//设定定时器的状态位为:允许定时器,允许中断,自动载入,向下计数(减计数)
XTmrCtr_mSetControlStatusReg(TIMER_BASEADDR, 0,
XTC_CSR_ENABLE_TMR_MASK | XTC_CSR_ENABLE_INT_MASK |
XTC_CSR_AUTO_RELOAD_MASK | XTC_CSR_DOWN_COUNT_MASK);
//使能MB的中断
microblaze_enable_interrupts();
//注册中断控制器到MB上
microblaze_register_handler(
XIntc_DeviceInterruptHandler,
INTC_DEVICE_ID);
//将定时器中断注册到中断控制器上
XIntc_RegisterHandler(
INTC_BASEADDR,
XPAR_AXI_INTC_0_AXI_TIMER_0_INTERRUPT_INTR,
(XInterruptHandler)timer_int_handler,
(void *)0
);
//将button的中断注册到中断控制器上
XIntc_RegisterHandler(
INTC_BASEADDR,
XPAR_AXI_INTC_0_AXI_GPIO_2_IP2INTC_IRPT_INTR,
(XInterruptHandler)button_int_handler,
(void *)0
);
//将switch的中断注册到中断控制器上
XIntc_RegisterHandler(
INTC_BASEADDR,
XPAR_AXI_INTC_0_AXI_GPIO_1_IP2INTC_IRPT_INTR,
(XInterruptHandler)switch_int_handler,
(void *)0
);
//使能btn、sw的全局中断,和每一个位上的中断
XGpio_InterruptEnable(&btn, 0x7);
XGpio_InterruptGlobalEnable(&btn);
XGpio_InterruptEnable(&sw, 0xf);
XGpio_InterruptGlobalEnable(&sw);
//使能中断控制器的主使能,即ME
XIntc_mMasterEnable(INTC_BASEADDR);
//使能中断控制器上的定时器中断源、button中断源核switch中断源
XIntc_mEnableIntr(INTC_BASEADDR,
XPAR_AXI_TIMER_0_INTERRUPT_MASK|
XPAR_AXI_GPIO_2_IP2INTC_IRPT_MASK|
XPAR_AXI_GPIO_1_IP2INTC_IRPT_MASK);
while(1)
{;
}
return 0;
}
extern XGpio led;
int i = 0;
//定时器的中断服务程序
void timer_int_handler(void)
{
unsigned int timer_csr;
timer_cnt++;
//读取定时器的状态寄存器,确认是否真的产生了中断
timer_csr = XTmrCtr_mGetControlStatusReg(TIMER_BASEADDR, 0);
if(timer_csr & XTC_CSR_INT_OCCURED_MASK)
{
// xil_printf("--timer interrupt happened times = %d!--\r\n", timer_cnt);
//将状态寄存器写回
XTmrCtr_mSetControlStatusReg(TIMER_BASEADDR, 0, timer_csr);
XGpio_DiscreteWrite(&led, 1, i);
xil_printf("***i = %d\n\r",i);
i++;
}
}
//button的中断服务程序
void button_int_handler(void)
{
btn_cnt ++;
xil_printf("--button interrupt happened times = %d!--\r\n",btn_cnt);
//完成中断服务程序后,需要清楚中断状态
XGpio_InterruptClear(&btn,0xf);
}
//switch的中断服务程序
void switch_int_handler(void )
{
sw_cnt ++;
xil_printf("--switch interrupt happened times = %d!--\r\n",sw_cnt);
//完成中断服务程序后,需要清楚中断状态
XGpio_InterruptClear(&sw,0xf);
}
然而,这种做法挺靠谱的了,10ms就是10ms的用户体验,1s就是1s的用户体验。节奏很对的了。
严厉谴责他们家的关于定时器的程序员。拉出来鞭尸,如下:
* Ver Who Date Changes
* ----- ---- -------- -----------------------------------------------
* 1.00a ecm 08/16/01 First release
* 1.00b jhl 02/21/02 Repartitioned the driver for smaller files
* 1.10b mta 03/21/07 Updated to new coding style.
* 1.11a sdm 08/22/08 Removed support for static interrupt handlers from the MDD
* file
* 2.00a ktn 10/30/09 Updated to use HAL API's. _m is removed from all the macro
* definitions.
* 2.01a ktn 07/12/10 Renamed the macro XTimerCtr_ReadReg as XTmrCtr_ReadReg
* for naming consistency (CR 559142).
* 2.02a sdm 09/28/10 Updated the driver tcl to generate the xparameters
* for the timer clock frequency (CR 572679).
* 2.03a rvo 11/30/10 Added check to see if interrupt is enabled before further
* processing for CR 584557.
* 2.04a sdm 07/12/11 Added support for cascade mode operation.
* The cascade mode of operation is present in the latest
* versions of the axi_timer IP. Please check the HW
* Datasheet to see whether this feature is present in the
* version of the IP that you are using.
* 2.05a adk 15/05/13 Fixed the CR:693066
* Added the IsStartedTmrCtr0/IsStartedTmrCtr1 members to the
* XTmrCtr instance structure.
* The IsStartedTmrCtrX will be assigned XIL_COMPONENT_IS_STARTED in
* the XTmrCtr_Start function.
* The IsStartedTmrCtrX will be cleared in the XTmrCtr_Stop function.
* There will be no Initialization done in the
* XTmrCtr_Initialize if both the timers have already started and
* the XST_DEVICE_IS_STARTED Status is returned.
* Removed the logic in the XTmrCtr_Initialize function
* which was checking the Register Value to know whether
* a timer has started or not.
*
*
都什么破烂玩意儿。
以上吐槽完毕。
bwb@STI 2014.08.08