FPGA 之 MicroBlaze定时器中断实验
AXI Timer 具有 AXI 总线接口,能够产生不同时间周期和占空比的时钟、脉冲产生电路、产生与时间有关的中断和用于电机控制的脉宽调制信号。
AXI Timer IP 核提供了一个 AXI4 Lite 接口用于与处理器通信;它内部有两个可编程的定时器,具有中断、事件生成和事件捕获功能,用户可根据自身需求选择 8、16、32 位定时器的计数宽度;通过对两个定时器联合操作,可以输出一个脉宽调制信号,我们可以通过该信号控制一些外设,如 LED 等;我们也可以对两个 32 位宽的定时器进行级联操作,生成一个 64 位宽的定时器;在软件调试期间冻结停止计数器的输入。
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 输出宽度。
本章的实验任务是 通过定时器产生中断,控制 LED 灯闪烁。
实验框图 如下
为了方便记忆 我们拿出最小系统
多了的就是 一个 AXI Timer 和 GPIO-LED
每次实验 我们都会介绍一下 当前使用的IP的各个选项意思及作用
配置完成之后 设计的总体结构布局 如图所示
我们修改 XDC 文件
c
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]}]
我们可以从 plantform 可以找到我们想要的 板级验证包 说实话 我觉得vitis 要比 SDK好用
c
#include <stdio.h>
#include "xparameters.h"
#include "xintc.h"
#include "xtmrctr.h"
#include "xil_exception.h"
#include "xgpio.h"
#include "xil_printf.h"
#define LED_DEV_ID XPAR_GPIO_0_DEVICE_ID //LED ID
#define INTC_ID XPAR_INTC_0_DEVICE_ID //中断控制器 ID
#define TMRCTR_DEVICE_ID XPAR_TMRCTR_0_DEVICE_ID //定时器中断 ID
#define TMRCTR_INTR_ID XPAR_INTC_0_TMRCTR_0_VEC_ID //定时中断 ID
#define XIL_EXCEPTION_ID_INT 16U //中断异常 ID
#define LED_Channel 1
XIntc Intc; //中断控制器实例
XGpio led_gpio; //LED 实例
XTmrCtr Timer; //定时器实例
void timer_intr_hander(void *InstancePtr);
int main(){
print ("timer interrupt test\n");
//初始化 LED
XGpio_Initialize(&led_gpio, LED_DEV_ID);
//为指定的 GPIO 信道设置所有独立信号的输入/输出方向
XGpio_SetDataDirection(&led_gpio, 1, 0);
//设置 LED 初始值
XGpio_DiscreteWrite(&led_gpio, 1, 0x0f);
//定时器初始化
XTmrCtr_Initialize(&Timer, TMRCTR_DEVICE_ID);
//为指定的计时器启用指定的选项。
XTmrCtr_SetOptions(&Timer, 0,XTC_INT_MODE_OPTION | //中断操作
XTC_AUTO_RELOAD_OPTION | //自动加载
XTC_DOWN_COUNT_OPTION); //递减计数
//设置指定计时器的重置值
XTmrCtr_SetResetValue(&Timer, 0, 50000000);
//设置计时器回调函数,指定的计时器满一个周期时驱动程序将调用该回调函数
XTmrCtr_SetHandler(&Timer, timer_intr_hander,&Timer);
//开启定时器
XTmrCtr_Start(&Timer, 0);
//中断控制器初始化
XIntc_Initialize(&Intc, INTC_ID);
//关联中断源和中断处理函数
XIntc_Connect(&Intc, TMRCTR_INTR_ID,
(XInterruptHandler)XTmrCtr_InterruptHandler,&Timer);
//开启中断控制器
XIntc_Start(&Intc, XIN_REAL_MODE);
//使能中断控制器
XIntc_Enable(&Intc, TMRCTR_INTR_ID);
//设置并打开中断异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XIntc_InterruptHandler,
&Intc);
Xil_ExceptionEnable();
while(1);
}
void timer_intr_hander(void *InstancePtr) //回调函数
{
static int led_state = 0x00;
//检测定时器是否满一个计数周期
if (XTmrCtr_IsExpired(&Timer, 0)){
led_state = ~led_state; //LED 状态翻转
XGpio_DiscreteWrite(&led_gpio, 1, led_state); //输出 LED 值
}
}
我们根据之前的对于中断的理解 可以这么理解
我们很明显的发现代码其实分为3个部分
一开始是 include 包含头文件
c
#include <stdio.h>
#include "xparameters.h"
#include "xintc.h"
#include "xtmrctr.h"
#include "xil_exception.h"
#include "xgpio.h"
#include "xil_printf.h"
接下来是
c
#define LED_DEV_ID XPAR_GPIO_0_DEVICE_ID //LED ID
#define INTC_ID XPAR_INTC_0_DEVICE_ID //中断控制器 ID
#define TMRCTR_DEVICE_ID XPAR_TMRCTR_0_DEVICE_ID //定时器中断 ID
#define TMRCTR_INTR_ID XPAR_INTC_0_TMRCTR_0_VEC_ID //定时中断 ID
#define XIL_EXCEPTION_ID_INT 16U //中断异常 ID
#define LED_Channel 1
都是前面介绍过的
接下来的 19到 23行
c
XIntc Intc; //中断控制器实例
XGpio led_gpio; //LED 实例
XTmrCtr Timer; //定时器实例
void timer_intr_hander(void *InstancePtr);
是我们结构体介绍 还有声明 中断处理函数
接下来28到32行
//初始化 LED
XGpio_Initialize(&led_gpio, LED_DEV_ID);
//为指定的 GPIO 信道设置所有独立信号的输入/输出方向
XGpio_SetDataDirection(&led_gpio, 1, 0);
//设置 LED 初始值
XGpio_DiscreteWrite(&led_gpio, 1, 0x0f);
完成对基本的GPIO设置
(我们发现窍门没有 都是先初始化GPIO , 再设置一下 GPIO的 信号到底是输入还是输出 ,再确定
接下来轮到我们新使用的AXI Timer
c
//定时器初始化
XTmrCtr_Initialize(&Timer, TMRCTR_DEVICE_ID);
//为指定的计时器启用指定的选项。
XTmrCtr_SetOptions(&Timer, 0,XTC_INT_MODE_OPTION | //中断操作
XTC_AUTO_RELOAD_OPTION | //自动加载
XTC_DOWN_COUNT_OPTION); //递减计数
//设置指定计时器的重置值
XTmrCtr_SetResetValue(&Timer, 0, 50000000);
//设置计时器回调函数,指定的计时器满一个周期时驱动程序将调用该回调函数
XTmrCtr_SetHandler(&Timer, timer_intr_hander,&Timer);
//开启定时器
XTmrCtr_Start(&Timer, 0);
我们掐头去尾 仔细分析 头尾 是 这个block 中 其他的 关于中断也有的东西 拿到一个新的东西 先用来 初始化 最后 开启定时器 或者最后 使能中断控制器
我们现在来看中间的配置
c
//为指定的计时器启用指定的选项。
XTmrCtr_SetOptions(&Timer, 0,XTC_INT_MODE_OPTION | //中断操作
XTC_AUTO_RELOAD_OPTION | //自动加载
XTC_DOWN_COUNT_OPTION); //递减计数
//设置指定计时器的重置值
XTmrCtr_SetResetValue(&Timer, 0, 50000000);
//设置计时器回调函数,指定的计时器满一个周期时驱动程序将调用该回调函数
XTmrCtr_SetHandler(&Timer, timer_intr_hander,&Timer);
其实是一个 从现实来讲 很连贯的问题 我们初始化定时器了 那么要定时器执行什么功能
XTmrCtr_SetOptions(&Timer, 0,XTC_INT_MODE_OPTION | //中断操作
XTC_AUTO_RELOAD_OPTION | //自动加载
XTC_DOWN_COUNT_OPTION); //递减计数
ok 既然如此 那么至少设定一个初始值 让它到哪里停止 和从哪里开始 既然是递减 那么到最后了我们需要的干什么 执行回调函数代码第 41行设置生成值,这里我们设为 50000000,即 0.5 秒(由于处理器时钟频率为 100M)。代码第 43 行设置回调函数,当指定的定时器满一个周期时驱动程序将调用该回调函数。代码第 45 行启动指定的定时器。
在下面就是中断异常 启动等等 设计了
c
//中断控制器初始化
XIntc_Initialize(&Intc, INTC_ID);
//关联中断源和中断处理函数
XIntc_Connect(&Intc, TMRCTR_INTR_ID,
(XInterruptHandler)XTmrCtr_InterruptHandler,&Timer);
//开启中断控制器
XIntc_Start(&Intc, XIN_REAL_MODE);
//使能中断控制器
XIntc_Enable(&Intc, TMRCTR_INTR_ID);
//设置并打开中断异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XIntc_InterruptHandler,
&Intc);
Xil_ExceptionEnable();
然后 再下面 就是while 循环的 主要工作了
c
while(1)
没了
最后一个是 回调函数的定义
c
void timer_intr_hander(void *InstancePtr) //回调函数
{
static int led_state = 0x00;
//检测定时器是否满一个计数周期
if (XTmrCtr_IsExpired(&Timer, 0)){
led_state = ~led_state; //LED 状态翻转
XGpio_DiscreteWrite(&led_gpio, 1, led_state); //输出 LED 值
}
}
整个调试窗口