ZedBoard-自定义PWM外设及TTC定时器API探索

25 篇文章 0 订阅
15 篇文章 1 订阅

URL: http://www.61ic.com/FPGA/Xilinx/201211/46138.html

最近拿到了ZedBoard,玩了两三天把官方提供的例程跑了差不多一半。先把官方的HelloWorld和“按键-定时器-点灯”的裸跑工程跑一遍,熟悉一下开发环境;然后自己自定义了一个工程,用PS-GPIO和EMIO-GPIO跑了个小程序,总算弄明白了EMIO的结构与作用(这部分在Xilinx的UG585文档有介绍);最后跟着的官方的Linux实验,跑一遍编译源码,生成启动相关文件的流程,在ZedBoard上跑起了Linux。

以上都是跟着文档一步一步走,就不记录了。今天决定自定义一个外设玩玩。

这个外设是一个PWM控制器,8bit输出,8个8bit的寄存器,每个寄存器控制1bit输出信号的脉宽。寄存器值为0~255,代表输出信号占空比范围为0~255/256。

首先在Planahead新建一个工程,添加embedded sources。

双击新建的embedded sources文件,打开XPS,加入PS,加入UART和定时器(非必要,程序用)。点击菜单栏的Hardware,选择Create or Import Peripheral。

一路Next,到这步输入自定义设备的名称,也是设备的顶层名字,不能包含大写和符号。

继续Next,总线选AXI-Lite。

这里简单讨论AXI总线的三种类型各自的特点和适合的应用场合。

AXI4-Lite最大的特征是不带突发传输,是三种类型中数据吞吐量最低的,所以其协议较简单,用PL实现时消耗逻辑较少。其最常见应用是在CPU与总线设备中的寄存器(控制寄存器、状态寄存器、字节缓冲的数据寄存器)之间进行数据交换。

AXI4带有突发传输,其协议相对这三类型之中来说是最繁琐的,实现这类型总线需要较多的逻辑。此类总线适合设备中有多个数据源或数据宿,且每一个源或宿的数据量都比较大的设备。

AXI4-Stream不同于其他两种类型,它是NON-MEMORY-MAP的,数据吞吐量大,消耗逻辑少,由于是流传输,信号中是没有地址信号的,数据源设备无法控制数据的分配。此类型适合用于连接数据流向单一的设备,例如视频数据流的算法处理流水线中的各个设备。

然后继续Next,这里设置从接口寄存器数量为8。

继续一路Next。这里按个人喜好,令user_logic由verilog或vhdl语言编写;自动生成这个外设的drivers。Finish!

然后在IP列表中,多出了一个名为PULSE_CTRLR的设备。此时,设备只有AXI4总线信号,是没有输出端口的。因为我们需要输出端口,所以我们要修改刚才生成的硬件描述源文件。

打开本工程中的pcore/ pulse_ctrlr_v1_00_a/hdl文件夹,里面有两个文件夹,各有一个源文件pulse_ctrlr.vhd和user_logic.v,前者是顶层文件,后者是需要我们自己定义的逻辑。

先修改pulse_ctrlr.vhd,里面例化了两个component,一个是AXI4总线的IP,桥接AXI总线和本地读写信号;另一个就是user_logic了。我们需要修改两处:端口定义和user_logic例化。

端口定义:

引用user_logic:

例化user_logic:

然后修改user_logic,需要修改端口和增加逻辑实现部分代码。

内部信号:

具体逻辑实现:

代码修改完成!然后在菜单栏选择Hardware->Create or Import Peripheral,这次不是Create而是Import。

路径选择pcores\pulse_ctrlr_v1_00_a\data\_pulse_ctrlr_xst.prj。

选择总线类型。

工具自动匹配AXI总线信号。

继续Next直到Finish。

回到XPS主界面,双击IP列表的pulse_ctrlr,把它添加进系统里。可以看到Ports更新如下:

记得把PULSE_OUTPUT连到外部去!!

关闭XPS,回到Planahead。

Create Top Hdl,添加ucf约束文件。约束文件如下,约束了8个PWM输出引脚的位置,这里把它们连到ZedBoard的8个Led上。

<span style="font-family:Courier New;font-size:14px;">NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[0]" LOC=T22 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[1]" LOC=T21 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[2]" LOC=U22 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[3]" LOC=U21 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[4]" LOC=V22 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[5]" LOC=W22 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[6]" LOC=U19 | IOSTANDARD=LVCMOS33;
NET "pulse_ctrlr_0_PULSE_OUTPUT_pin[7]" LOC=U14 | IOSTANDARD=LVCMOS33;</span>


硬件一切就绪,综合,实现,生成配置bit文件。

启动并Export硬件信息到SDK,开始软件设计流程。新建C工程,这里直接新建应用工程,不去建立FSBL工程。

(注:JTAG启动不用建立FSBL工程,SD卡、FLASH等启动方式则需要建立FSBL工程)

程序实现的功能如下:LD0、LD2、LD4、LD6实现渐明-渐暗-渐明循环效果,LD1、LD3、LD5、LD7实现渐暗-渐明-渐暗循环效果,每个LED具有相同的亮度变化量。

这里本想参照例程的XTIMER程序来编写定时器部分程序,发现例程的定时器是逻辑实现的定时器IP,这里的定时器是PS部分的,决定自己查找PS TIMER的BSP,来编写定时器的程序。这里分析一下PS TIMER的BSP的软件结构。

在SDK左侧菜单,打开BSP工程里的libsrc文件夹,找到ttcps_v1_01_a文件夹,这就是PS TIMER的源码所在,其软件结构如下:

Xttcps_g.c主要是定义了一个指定PS配置中TTC定时器相关配置信息的结构体。

Xttcps_hw.h定义了相关的宏,以及对寄存器操作的封装。

Xttcps_options.c是定时器工作模式设置相关的API。

Xttcps_selftest.c测试用,没多管。

Xttcps_sinit获取定义器配置信息的API。

Xttcps.c大部分的API在这里。

Xttcps.h主函数需要包含这个头文件。

要令定时器按照我们所需要的间隔以中断的形式工作,需要作以下初始化:一,获取配置结构体;二,用配置结构体去初始化定时器,获取设备结构体,以后所有对这个定时器的操作都需要用到这个结构体;三,配置时钟来源(默认是内部时钟);四,配置时钟分频系数;五,设置工作模式及相关的东东;六,使能中断及启动定时器。

以上是需要对定时器做的准备工作。要令CPU正常对中断作出反应,这还不够。还需要:一,获取中断控制器GIC配置表及初始化;二,注册GIC中断处理函数(因为定时器中断是挂在GIC上的);三,注册定时器中断处理函数;四,使能GIC。

在写定时器的中断处理函数时遇到了中断不断触发的问题,显然是中断标志没有清除,于是使用定时器API里的XTtcPs_ClearInterruptStatus清中断标志,发现不起作用。翻阅UG585 定时器的章节发现这条说明。

要清定时器的中断标志,只需要读中断状态寄存器一次就可以了,但是XTtcPs_ClearInterruptStatus这个API是通过写的方式完成的,这大概是个BUG吧。于是调用读中断状态寄存器的API来清标志,一切正常。

清楚了定时器的软件结构之后,还要搞定自定义外设的驱动。

在刚才创建自定义设备的时候,曾经选择了一个选项让工具去生成外设驱动,于是将生成的驱动从XPS工程里复制过来SDK的BSP工程的libsrc文件夹里,并把相应的头文件放到include文件夹,发现缺少了xbasic_types.h的头文件,在工程里也找不到这个文件,于是只好放弃其提供的driver范例,直接通过xil_io.h提供的底层总线操作直接去访问外设寄存器。(哪位知道xbasic_types.h在哪的请告诉一下我哦)

总线操作的原型是这样的:void Xil_Out32(u32 OutAddress, u32 Value)。

万事俱备,开始编写主程序。

定时器初始化,这步暂不启动定时器,所有初始化就绪再启动。

/*########################################################################*/
tmrConfig = XTtcPs_LookupConfig(XPAR_PS7_TTC_0_DEVICE_ID);
if(tmrConfig == NULL) print("timer get config error!!\n\r");
xStatus = XTtcPs_CfgInitialize(&tmrInst, tmrConfig, tmrConfig->BaseAddress);
if(xStatus != XST_SUCCESS) print("timer initialize error!!\n\r");
XTtcPs_SetPrescaler(&tmrInst, 15);//输入时钟65536分频
xStatus = XTtcPs_SetOptions(&tmrInst, XTTCPS_OPTION_INTERVAL_MODE);
if(xStatus != XST_SUCCESS) print("timer set option error!!\n\r");
XTtcPs_SetInterval(&tmrInst, 50);//计数器上限值设为50
XTtcPs_EnableInterrupts(&tmrInst, XTTCPS_IXR_INTERVAL_MASK);
/*########################################################################*/

GIC及中断服务初始化。

/*########################################################################*/
gicConfig = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
if(gicConfig == NULL) print("gic get config error!!\n\r");
xStatus = XScuGic_CfgInitialize(&gicInst, gicConfig, gicConfig->CpuBaseAddress);
if(xStatus != XST_SUCCESS) print("gic initialize error!!\n\r");
xStatus = SetUpInterruptSystem(&gicInst);
if(xStatus != XST_SUCCESS) print("gic setup int error!!\n\r");
xStatus = XScuGic_Connect(&gicInst, 42, tmrHandler, &tmrInst);
if(xStatus != XST_SUCCESS) print("gic connect handler error!!\n\r");
XScuGic_Enable(&gicInst, 42);
/*########################################################################*/

定时器中断处理函数。

voidtmrHandler(void* data)
{
staticu8phase = 0;
staticu8widthValue = 0;
u8otherValue;
if(widthValue == 0)
{
widthValue = 120;
phase = ~phase;
}
else
widthValue = widthValue - 4;
otherValue = 120 - widthValue;
if(phase)
{
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 0, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 4, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 8, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 12, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 16, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 20, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 24, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 28, otherValue);
}
else
{
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 0, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 4, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 8, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 12, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 16, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 20, widthValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 24, otherValue);
Xil_Out32(XPAR_PULSE_CTRLR_0_BASEADDR + 28, widthValue);
}
XTtcPs_GetInterruptStatus((XTtcPs*)data);
}

下载到板上,看到了预期的8个LED渐明渐暗的效果。

至此,完成了自定义外设和TTC定时器的探索工作!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值