本帖最后由 正点原子运营官 于 2021-1-11 15:11 编辑
1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739
6)关注正点原子公众号,获取最新资料
第六章定时器中断实验
前面已经介绍过利用AXI Uartlite中断通信的方法,本章实验将介绍另一个重要的IP核AXI timer。AXI Timer具有AXI总线接口,能够产生不同时间周期和占空比的时钟、脉冲产生电路、产生与时间有关的中断和用于电机控制的脉宽调制信号。本章实验我们将介绍AXI Timer中断信号的使用。
本章包括以下几个部分:
66.1 简介
6.2实验任务
6.3硬件设计
6.4程序设计
6.5下载验证
6.1
简介
AXI Timer IP核提供了一个AXI4 Lite接口用于与处理器通信;它内部有两个可编程的定时器,具有中断、事件生成和事件捕获功能,用户可根据自身需求选择8、16、32位定时器的计数宽度;通过对两个定时器联合操作,可以输出一个脉宽调制信号,我们可以通过该信号控制一些外设,如LED等;我们也可以对两个32位宽的定时器进行级联操作,生成一个64位宽的定时器;在软件调试期间冻结停止计数器的输入。
下图是AXI Timer IP核的结构框图:
图 6.1.1 AXI Timer IP核的结构框图
下面我们将介绍下各个模块的功能:
AXI4-Lite Interface(AXI4-Lite接口):该AXI4-lite接口模块被设计成AXI4-lite从接口,用于访问内存映射的定时器寄存器,我们也可以通过该接口对各个寄存器模块进行配置。
Timer Registers(定时器寄存器):该模块是一组32位寄存器。这组寄存器包含加载寄存器(Load Register)、定时器/计数器寄存器和控制/状态寄存器(Control/Status Registers)。加载寄存器保存用于事件生成的计数器的初始值或捕获值。控制/状态寄存器包含定时器模块的控制位和状态位。
32-bit Counters(32位寄存器):定时器/计数器模块有两个32位计数器,每个计数器可设置为递增或递减计数,并可从加载寄存器中加载一个值。
Interrupt Control(中断控制):中断控制模块根据操作模式生成单个中断。
Pulse Width Modulation (PWM,脉宽调制):PWM模块能够产生具有指定频率和占空比的脉冲信号PWM0。它使用Timer0作为PWM0周期,Timer1作为PWM0输出宽度。
6.2实验任务
本章的实验任务是通过定时器产生中断,控制LED灯闪烁。
6.3硬件设计
从实验任务我们可以画出如下的系统框图:
图 6.3.1 系统框图
本次实验,AXI Timer产生中断,MicroBlaze处理器通过中断控制器的中断请求信号控制LED闪烁,AXI UART打印处理器发送的信息。
本次的硬件设计我们在《Hello World》实验的基础上进行,我们打开《Hello World》实验中的Vivado工程“hello world”,打开后在菜单栏中依次点击“File->Project->Save As...”,将工程名改为“axi_timer_intr”,工程路径保持不变。
在Flow Navigator中,点击IP INTEGRATOR下的“Open Block Design”,在Diagram界面添加一个AXI GPIO IP核,并将其位宽设置为4;一个AXI INTC IP核;然后添加AXI Timer IP核,如图 6.3.2所示:
图 6.3.2 添加AXI Timer
双击打开AXI Timer,进入配置页面,由于我们本次实验只是用定时器中断功能,因此不需要对Capture(捕获)、Generate(生成)进行配置,我们直接点击“OK”,图 6.3.3是AXI Timer配置界面。
图 6.3.3 AXI Timer配置界面
这里我们简单介绍下该界面中个选项的作用。
Enable 64-bit mode(使能64位模式):选择该选项后AXI Timer的两个32定时器会被级联成一个64位寄存器,选用该模式后,Enable Timer2选项会被禁用。一般情况下我们都是使用32位模式就可以了,只有在需要的寄存器超过了32位才用该模式。
Width of the Timer/Counter(定时器/计数器位宽)(bits):根据用户需求选择8、16、32位计数位宽。
Active state of Capture Trigger(捕获触发器的活动状态):该选项可以设置捕获活动高信号还是活动低信号。
Active state of Generate Out signal(生成输出信号的活动状态):此选项可以设置生成的信号是高还是低。
Enable Timer2(使能定时器2):该选项可以打开定时器2,定时器2的捕获和生成两处的配置方法与定时器1相同,但可以独立配置。
接下来点击“Run Connection Automation”按钮,在弹出的页面选中所有信号,点击“OK”,完成连线,如图 6.3.4所示:
图 6.3.4 自动连线
连线完成后可以点击“Regenerate Layout”按钮重新布局,以便观察布局布线,然后将gpio_rtl_0改为“LED”。
将AXI Timer的interrupt接口与AXI Interrupt Controller的intr[0:0]接口连接,将AXI Interrupt Controller的interrupt接口与MicroBlaze的INTERRUPT接口连接,如图 6.3.5所示:
图 6.3.5 连接中断信号
最后整体的布局如图 6.3.6所示:
图 6.3.6 最终布局视图
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl+S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
然后我们双击打开Sources目录下的system_wrapper.xdc,添加约束信息,管脚约束代码如下:
- create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
- set_property PACKAGE_PIN R4 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
- set_property PACKAGE_PIN U2 [get_ports sys_rst_n]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
- set_property PACKAGE_PIN U5 [get_ports UART_rxd]
- set_property PACKAGE_PIN T6 [get_ports UART_txd]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[3]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[2]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[1]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[0]}]
- set_property PACKAGE_PIN Y2 [get_ports {led_tri_io[3]}]
- set_property PACKAGE_PIN V2 [get_ports {led_tri_io[2]}]
- set_property PACKAGE_PIN R3 [get_ports {led_tri_io[1]}]
- set_property PACKAGE_PIN R2 [get_ports {led_tri_io[0]}]
复制代码
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中依次点击“File->Export->Export hardware”导出硬件,并在弹出的对话框中,勾选“Include bitstream”,在导出路径最后添加“/vitis”。然后在菜单栏依次点击“Tools->Launch Vitis”,启动Vitis软件。
6.4软件设计
在将硬件导出至Vitis,并打开Vitis开发环境后,创建应用工程的步骤都是一样的,这里不再赘述,新创建的应用工程命名为“axi_timer_intr”。
我们打开system_wrapper目录下的platform.spr文件,找到axi_timer_0,可以看到定时器的文档和导入示例,如图 6.4.1所示:
图 6.4.1 platform.spr文件
如果我们点击“Import Examples”,会弹出下图所示的导入示例界面,关于定时器有7个示例,根据施压你任务,我们选择第三个实例,如图 6.4.2所示:
图 6.4.2 导入示例
我们将根据官方实例来设计本次实验的软件部分。
这里我们不导入官方的例程,而是新建一个源文件。在timer/src目录上右键,依次点击“New->Source File”。在弹出的对话框中Source file一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧axi_timer_intr/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
- 1 #include
- 2 #include "xparameters.h"
- 3 #include "xintc.h"
- 4 #include "xtmrctr.h"
- 5 #include "xil_exception.h"
- 6 #include "xgpio.h"
- 7 #include "xil_printf.h"
- 8
- 9 #define LED_DEV_ID XPAR_GPIO_0_DEVICE_ID //LED ID
- 10 #define INTC_ID XPAR_INTC_0_DEVICE_ID //中断控制器ID
- 11 #define TMRCTR_DEVICE_ID XPAR_TMRCTR_0_DEVICE_ID //定时器中断ID
- 12
- 13 #define TMRCTR_INTR_ID XPAR_INTC_0_TMRCTR_0_VEC_ID //定时中断ID
- 14
- 15 #define XIL_EXCEPTION_ID_INT 16U //中断异常ID
- 16
- 17 #define LED_Channel 1
- 18
- 19 XIntc Intc; //中断控制器实例
- 20 XGpio led_gpio; //LED实例
- 21 XTmrCtr Timer; //定时器实例
- 22
- 23 void timer_intr_hander(void *InstancePtr);
- 24
- 25 int main(){
- 26 print ("timer interrupt testn");
- 27 //初始化LED
- 28 XGpio_Initialize(&led_gpio, LED_DEV_ID);
- 29 //为指定的GPIO信道设置所有独立信号的输入/输出方向
- 30 XGpio_SetDataDirection(&led_gpio, 1, 0);
- 31 //设置LED初始值
- 32 XGpio_DiscreteWrite(&led_gpio, 1, 0x0f);
- 33 //定时器初始化
- 34 XTmrCtr_Initialize(&Timer, TMRCTR_DEVICE_ID);
- 35 //为指定的计时器启用指定的选项。
- 36 XTmrCtr_SetOptions(&Timer, 0,XTC_INT_MODE_OPTION | //中断操作
- 37 XTC_AUTO_RELOAD_OPTION | //自动加载
- 38 XTC_DOWN_COUNT_OPTION); //递减计数
- 39
- 40 //设置指定计时器的重置值
- 41 XTmrCtr_SetResetValue(&Timer, 0, 50000000);
- 42 //设置计时器回调函数,指定的计时器满一个周期时驱动程序将调用该回调函数
- 43 XTmrCtr_SetHandler(&Timer, timer_intr_hander,&Timer);
- 44 //开启定时器
- 45 XTmrCtr_Start(&Timer, 0);
- 46 //中断控制器初始化
- 47 XIntc_Initialize(&Intc, INTC_ID);
- 48 //关联中断源和中断处理函数
- 49 XIntc_Connect(&Intc, TMRCTR_INTR_ID,
- 50 (XInterruptHandler)XTmrCtr_InterruptHandler,&Timer);
- 51 //开启中断控制器
- 52 XIntc_Start(&Intc, XIN_REAL_MODE);
- 53 //使能中断控制器
- 54 XIntc_Enable(&Intc, TMRCTR_INTR_ID);
- 55 //设置并打开中断异常处理
- 56 Xil_ExceptionInit();
- 57 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
- 58 (Xil_ExceptionHandler)XIntc_InterruptHandler,
- 59 &Intc);
- 60 Xil_ExceptionEnable();
- 61
- 62 while(1);
- 63 }
- 64
- 65 void timer_intr_hander(void *InstancePtr) //回调函数
- 66 {
- 67 static int led_state = 0x00;
- 68 //检测定时器是否满一个计数周期
- 69 if (XTmrCtr_IsExpired(&Timer, 0)){
- 70 led_state = ~led_state; //LED状态翻转
- 71 XGpio_DiscreteWrite(&led_gpio, 1, led_state); //输出LED值
- 72 }
- 73 }
代码第28到32行我们完成了对AXI GPIO的设置,接着代码第34到45行我们对定时器进行了一系列设置。代码第34行对AXI Timer初始化;代码第36到39行XTmrCtr_SetOptions函数为指定的定时器计数器(32bit计数器)启用指定的操作,在本次实验中,我们指定了XTC_INT_MODE_OPTION(启用中断控制)、XTC_AUTO_RELOAD_OPTION(自动加载操作)和XTC_DOWN_COUNT_OPTION(递减计数操作)三种操作。我们在XTmrCtr_SetOptions函数中设置了这三种操作,当定时器启用后,计数器就从生成值开始递减计数,计数到0时产生中断并重新从加载计数器中加载生成值,如此循环产生中断。代码第41行设置生成值,这里我们设为50000000,即0.5秒(由于处理器时钟频率为100M)。代码第43行设置回调函数,当指定的定时器满一个周期时驱动程序将调用该回调函数。代码第45行启动指定的定时器。
接下来代码第47到59行就是设置中断系统和中断异常处理,这两部分内容和之前的实验基本相同,需要注意的是,这里XIntc_Connect函数关联的中断处理函数不再是我们自定义的服务函数,而是官方提供的一个服务函数。
代码第64到70行就是我们在代码第43行指定的一个回调函数。当我们指定的定时器产生中断后,中断服务函数调用该回调函数。此时检测定时器是否计满一个计数周期,当定时器满一个周期后,LED翻转一次,并向AXI GPIO写入LED的值,如代码第68行所示。
保存main.c并编译工程。
6.5下载验证
首先我们将下载器与达芬奇开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
打开Vitis的Terminal,然后开始下载程序,软件程序下载完成后,在下方的Terminal中可以看到应用程序打印的信息如图 6.5.1所示:
图 6.5.1 程序打印结果
并且观察到开发板上LED闪烁。实验结果图如下:
图 6.5.2 LED灯闪烁
6.6代码调试
代码调试之前,先要把Terminal关掉,否则在调试界面串口(Vitis Serial Terminal)中无法打印出信息。
打开调试界面:鼠标右击应用工程axi_timer_intr,选择“Debug As”,然后选择第一项“1 Launch on Hardware (System Debugger)”,如图 6.6.1所示:
图 6.6.1 打开调试界面
如果出现下图所示的WARNING界面,直接点击“OK”按钮,如果不想以后再出现,可以勾选“Do not show this warning again”。
图 6.6.2 WARNING提示界面
进入图 6.6.3所示的调试界面,程序首先从main 函数开始运行。
图 6.6.3 调试界面
图 6.6.3标注为1的位置是用于调试的工具栏;标注为2的位置可以观察程序中变量的值;标注为3的位置即为我们所要调试的代码,图中高亮的代码行就是接下来将要执行的代码。
请详细观察用于调试的工具栏界面,如图 6.6.4所示:
图 6.6.4 调试工具栏
图 6.6.4标注为1的按钮表示程序继续运行(Resume,快捷键F8);标注为2的按钮表示暂停(Suspend,只有程序在运行时才可以点击);标注为3的按钮表示单步执行(Step Into,快捷键F5);标注为4的按钮表示单步执行结束(Step Over,快捷键F6);标注为5的按钮表示执行完并返回(Step Return,快捷键F7)。
首先点击Vitis Serial Terminal 窗口的界面,接着点击右侧的红色框内的“+”按钮来连接串口,便于观察程序调试的结果。串口连接完毕后如图 6.6.5所示:
图 6.6.5 Vitis Serial Terminal
接下来开始调试代码。
分别双击行数25、26,设置两个断点(再次双击即可取消断点)。如图 6.6.6所示:
图 6.6.6 设置两处断点
点击“Step Over”图标或者按下快捷键 F6 来执行代码,高亮部分到28行时,说明前两个断点已经执行完毕,会在串口会打印信息。执行结果如图 6.6.7所示:
图 6.6.7 断点执行完毕
点击“Step Into”图标或者按下快捷键F5,开始跳转至XGpio_Initialize函数,如图 6.6.8所示:
图 6.6.8 执行XGpio_Initialize函数
可以继续点击“Step Into”图标查看代码,若想要立刻跳出XGpio_Initialize函数,只需要点击“Step Return”图标或者按下快捷键 F7,即跳出该函数准备执行XGpio_SetDataDirection函数。跳转结果如图 6.6.9所示:
图 6.6.9 跳出XGpio_Initialize函数
继续点击“Step Over”执行程序。待程序执行完32行的XGpio_DiscreteWrite函数后,开发板上的LED灯会全部被点亮。
点击“Resume”或者按下快捷键F8,程序一直执行下去。待整体程序执行完62行后,将看不到深色部分,同时可以观察到开发板上的LED灯不停地闪烁。
如果想要暂停执行,点击“Suspend”图标即可,重新点击“Resume”即可继续执行程序。
待程序调试完毕,点击右上角红色框内按钮即可回到退出调试回到Design界面。如图 6.6.10所示:
图 6.6.10 退出调试
|