并行IO接口设计
文章目录
1 实验目的
掌握GPIO IP核的工作原理和使用方法;
掌握中断控制方式的IO接口设计原理;
掌握中断程序设计方法;
掌握IO接口程序控制方法。
2 实验任务与要求
- 并行IO要求:所有实验任务要求分别采用程序控制方式、普通中断方式、快速中断方式实现,中断方式时,GPIO输入、延时都采用中断实现。每位同学仅完成其中的一个任务,且需完成的实验任务编号与学号后三位模3的取值一致。
3 实验环境
- Vivado 2019.1
- Nexys4 DDR™ FPGA Board
- Xilinx SDK
4 实验内容与步骤
4.1 设计思路
4.1.1 实现逻辑
GPIO_2做中断源的中断服务程序读取SW15~0和button,根据button的状态选择输出模式,设置相应的段码,输出SW15~0的状态到七段数码管上,而七段数码管连续输出的基础就是TIMER作中断源的输出程序。
4.1.2 中断源
本次实验的中断源为GPIO_2和TIMER;
4.1.3 中断方式设计
采用按键中断和定时器中断,即中断源为所搭建硬件平台的GPIO_2和TIMER,在采用快速中断时,相比普通中断,要填中断向量寄存表,以及要设置MER寄存器的状态,但在中断服务程序中无需编写程序来清除INTC的中断请求,返回的ack信号为01时会将它清除,只须清除中断源GPIO_2的中断。
4.1.4 中断服务程序设计
- 对于中断源为GPIO_2的中断服务程序,主要功能为在通道一输出位码和在通道二输出此刻i所对应的段码,并且将位码右移一位,i加一,实现在人眼分辨不出来的极短时间内输出所有的数字到七段数码管上,每当i为8时,重置位码和i,并且在结束时清除TIMER的中断状态。即写中断状态寄存器;
- 对于中断源为TIMER的中断服务程序,主要功能为循环扫描,使显示器顺序点亮,显示相应的数字,并且由于选择速度很快,肉眼无法分辨,就认为时同时点亮了所有的数码管。
4.2 硬件电路
4.2.1 Nexys4 DDR开发板的外设
4.2.2 GPIO与外设的连接
- 本实验使用到了GPIO_0的SW7~0和LED7~0、GPIO_1的最右边一位数码管、GPIO_2的BTND、U、L、R、C。
4.2.3 并行IO中断系统
- 本实验用到了GPIO_2和TIMER作中断源
4.3 硬件平台
- 根据左东红老师的学习通视频,基于Vivado2019.1版本搭建了并行的IO中断系统。
4.4 软件流程图
4.5 设计源代码及注释
- 快速中断
/*快速中断*/
//GPIO_2的CH1对应的button和定时器TIMER_0是中断源
#include "xil_io.h"
#include "stdio.h"
#include "xgpio_l.h"
#include "xintc_l.h"
#include "xtmrctr_l.h"
#include "xio.h"
#include "xil_exception.h"
#define RESET_VALUE 100000
void Seg_TimerCounterHandler()__attribute__ ((fast_interrupt));
//快速中断服务程序Seg_TimerCounterHandler()
void BTN_sw_seg()__attribute__ ((fast_interrupt));
//快速中断服务程序BTN_sw_seg()
//二者都是可以单独响应的
short segcode[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};//段码显示缓冲区,初始默认全部显示为灭,根据所选模式将segtable内的数值写入对应的位置
char segtable[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};//段码表 16进制数字 [0-f] 的16进制码
//char scancode[5][2] = {{0x1,0xc6},{0x2,0xc1},{0x4,0xc7},{0x8,0x88},{0x10,0xa1}};//16进制 按键 对应显示
short pos=0xff7f;//位码,用于扫描
int i=0;//用于扫描
int main()
{
/*GPIO*/
/*GPIO_0*/
//GPIO:SW15~0 GPIO2:LED15~0
Xil_Out16(XPAR_AXI_GPIO_0_BASEADDR+XGPIO_TRI_OFFSET,0xffff);//设定16个开关为输入
/*GPIO_1*/
//GPIO:AN7~0 GPIO2:DP~CA
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_TRI_OFFSET,0x0);//设定位码为输出
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_TRI2_OFFSET,0x0);//设定段码为输出
/*GPIO_2*/
//GPIO:BTND,U,L,R,C GPIO2:LD17(B\G\R),LD16(B\G\R)
Xil_Out8(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_TRI_OFFSET,0x1f);//设定5个按键为输入
/*此处的中断触发是按键,如果是sw之类的那就改为GPIO0之类的中断输出*/
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_IER_OFFSET,XGPIO_IR_CH1_MASK);//CH1允许 中断,CH1对应的是按键,CH2是LD
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_GIE_OFFSET,XGPIO_GIE_GINTR_ENABLE_MASK);//允许 按键和LD所对应的GPIO IP核 GPIO_2 中断输出
/*TIMER*/
/*TIMER_0*/
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,
Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)&~XTC_CSR_ENABLE_TMR_MASK);//停止计数器,写TCSR,清除中断
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TLR_OFFSET,RESET_VALUE);//写TLR,预置计数初值
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,
Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)|XTC_CSR_LOAD_MASK);//获取计数初值
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,(Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)&~XTC_CSR_LOAD_MASK)\
|XTC_CSR_ENABLE_TMR_MASK|XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_ENABLE_INT_MASK|XTC_CSR_DOWN_COUNT_MASK);
//开始计数运行,自动获取,允许中断,减计数
/*INTC*/
//快速中断在中断服务程序中不需要写程序手动清IAR
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IAR_OFFSET,0x6);
//初始化清除中断状态,写IAR寄存器
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IMR_OFFSET,0x6);//0x0110 使得D3,D2为1
//写中断模式寄存器IMR,使中断控制器工作在快速中断模式,普通中断则不需要这一步
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IER_OFFSET,XPAR_AXI_GPIO_2_IP2INTC_IRPT_MASK|XPAR_AXI_TIMER_0_INTERRUPT_MASK);
//写IER,开放中断控制器INTC对应的各个中断源输入引脚的中断,GPIO_2和TIMER_0是中断源,此处使能GPIO_2或TIMER_0所对应的输入引脚
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_MER_OFFSET,XIN_INT_MASTER_ENABLE_MASK|XIN_INT_HARDWARE_ENABLE_MASK);
//写MER,开放中断控制器输出引脚的中断
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IVAR_OFFSET+0x4,(int)BTN_sw_seg);//偏移4
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IVAR_OFFSET+0x8,(int)Seg_TimerCounterHandler);//偏移8
//填写中断控制器维护的中断向量表 写IVAR寄存器,就是把这两个快速中断服务程序的地址填到中断向量表的指定存储位置
/*MicroBlaze*/
microblaze_enable_interrupts();
//microblaze中断开放
return 0;
}
//快速中断直接跳转到中断服务程序,而不用跳转到0x10读取跳转地址
//中断源为定时器
void Seg_TimerCounterHandler()
{
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA_OFFSET,pos);//输出选择位码
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,segcode[i]);//输出此时i对应的段码
pos=pos>>1;//右移一位,从左到右扫描
i++;
if(i==8)//重置pos和i分别 为初始位码 以及 段码的起始位
{
i=0;
pos=0xff7f;
}
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET));
//将TCSR寄存器原来所存的值再次写入到TCSR寄存器中去,清除定时器的中断状态
}
//快速中断直接跳转到中断服务程序,而不用跳转到0x10读取跳转地址
//中断源为按钮
void BTN_sw_seg()
{
char button=Xil_In8(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA_OFFSET);//读取按键键值
short cur_sw=Xil_In16(XPAR_AXI_GPIO_0_BASEADDR+XGPIO_DATA_OFFSET);//读取开关数值
if(button==0x10)//BTND
{
for(int i=0;i<8;i++){
for(int n=0;n<8;n++){
segcode[7-n]=segtable[(cur_sw>>n)&0x1];
}
}
}
else if(button==0x02)//BTNU
{
for(int i=0;i<4;i++){
for(int n=0;n<4;n++){
segcode[7-n]=segtable[(cur_sw>>(4*n))&0xf];
}
for(int n=4;n<8;n++){
segcode[7-n]=0xff;
}
}
}
else if((button==0x01))//BTNC
{
int k=cur_sw;//不改变cur_sw
for(int j=0;j<5;j++){
switch(k%10)
{
case(0):segcode[7-j]=segtable[0];break;
case(1):segcode[7-j]=segtable[1];break;
case(2):segcode[7-j]=segtable[2];break;
case(3):segcode[7-j]=segtable[3];break;
case(4):segcode[7-j]=segtable[4];break;
case(5):segcode[7-j]=segtable[5];break;
case(6):segcode[7-j]=segtable[6];break;
case(7):segcode[7-j]=segtable[7];break;
case(8):segcode[7-j]=segtable[8];break;
case(9):segcode[7-j]=segtable[9];break;
}
k/=10;
}
for(int n=5;n<8;n++){
segcode[7-n]=0xff;
}
}
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_ISR_OFFSET,Xil_In32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_ISR_OFFSET));//写ISR寄存器存的的1(申请中断状态)到ISR寄存器中去,清除GPIO_2的申请中断状态
//因为是快速中断,INTC的中断请求状态不需要程序清除(返回的ack信号为01就给他清除了),只需要清除中断源GPIO_2的中断状态
}
- 普通中断
/*普通中断*/
//GPIO_2的CH1对应的button和定时器IMER_0是中断源
#include "xil_io.h"
#include "stdio.h"
#include "xgpio_l.h"
#include "xintc_l.h"
#include "xtmrctr_l.h"
#define RESET_VALUE 100000
void Seg_TimerCounterHandler();
void BTN_sw_seg();
//普通中断服务程序的子程序
void My_ISR() __attribute__ ((interrupt_handler));
//总的 普通中断服务程序
short segcode[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};///段码显示缓冲区,初始默认全部显示为灭,根据所选模式将segtable内的数值写入对应的位置
char segtable[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};//段选表 16进制数字 [0-f] 的16进制码
//char scancode[5][2] = {{0x1,0xc6},{0x2,0xc1},{0x4,0xc7},{0x8,0x88},{0x10,0xa1}};//16进制 按键 对应显示
short pos=0xff7f;//位选,用于扫描
int i=0;//用于扫描
int main()
{
/*GPIO*/
/*GPIO_0*/
//GPIO:SW15~0 GPIO2:LED15~0
Xil_Out16(XPAR_AXI_GPIO_0_BASEADDR+XGPIO_TRI_OFFSET,0xffff);//设定16个开关为输入
/*GPIO_1*/
//GPIO:AN7~0 GPIO2:DP~CA
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_TRI_OFFSET,0x0);//设定位码为输出
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_TRI2_OFFSET,0x0);//设定段码为输出
/*GPIO_2*/
//GPIO:BTND,U,L,R,C GPIO2:LD17(B\G\R),LD16(B\G\R)
Xil_Out8(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_TRI_OFFSET,0x1f);//设定5个按键为输入
/*此处的中断触发是按键,如果是sw之类的那就改为GPIO_0之类的中断输出*/
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_IER_OFFSET,XGPIO_IR_CH1_MASK);//CH1允许 中断,CH1对应的是按键,CH2是LD
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_GIE_OFFSET,XGPIO_GIE_GINTR_ENABLE_MASK);//允许 按键和LD所对应的GPIO IP核 GPIO_2 中断输出
/*TIMER*/
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,
Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)&~XTC_CSR_ENABLE_TMR_MASK);//停止计数器,写TCSR,清除中断
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TLR_OFFSET,RESET_VALUE);//写TLR,预置计数初值
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,
Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)|XTC_CSR_LOAD_MASK);//获取计数初值
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,(Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET)&~XTC_CSR_LOAD_MASK)\
|XTC_CSR_ENABLE_TMR_MASK|XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_ENABLE_INT_MASK|XTC_CSR_DOWN_COUNT_MASK);
//开始计数运行,自动获取,允许中断,减计数
/*INTC*/
//普通中断在中断服务程序中要写程序手动清IAR
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IAR_OFFSET,0x6);//不清理也没啥大问题
//初始化清除中断状态,写IAR寄存器
//不需要写中断模式寄存器IMR
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IER_OFFSET,XPAR_AXI_GPIO_2_IP2INTC_IRPT_MASK|XPAR_AXI_TIMER_0_INTERRUPT_MASK);
//写IER,开放中断控制器INTC对应的各个中断源输入引脚的中断,GPIO_2和TIMER_0是中断源,此处使能GPIO_2或TIMER_0所对应的输入引脚
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_MER_OFFSET,XIN_INT_MASTER_ENABLE_MASK|XIN_INT_HARDWARE_ENABLE_MASK);
//写MER,开放中断控制器输出引脚的中断
//无需填写中断控制器维护的中断向量表,跳转至中断向量表内的0x10处即可
/*MicroBlaze*/
microblaze_enable_interrupts();
//microblaze中断开放
return 0;
}
//在中断向量表内的0x10处的imm和brai分别记录了My_ISR()地址的高、低16位
void My_ISR()
{
int status;
status=Xil_In32(XPAR_AXI_INTC_0_BASEADDR+XIN_ISR_OFFSET);//读取INTC的中断状态寄存器ISR
if((status&XPAR_AXI_TIMER_0_INTERRUPT_MASK)==XPAR_AXI_TIMER_0_INTERRUPT_MASK)//查询是否为TIMER产生的中断
{
Seg_TimerCounterHandler();//是就调用计时器的中断服务程序,即扫描输出相应位码以及所对应的7段数码管上应显示的数字的16位进制数
}
else if((status&XPAR_AXI_GPIO_2_IP2INTC_IRPT_MASK)==XPAR_AXI_GPIO_2_IP2INTC_IRPT_MASK)//查询是否为GPIO_2产生的中断
{
BTN_sw_seg();
}
Xil_Out32(XPAR_AXI_INTC_0_BASEADDR+XIN_IAR_OFFSET,status);
//普通中断,INTC需要将现在的中断状态ISR写到IAR寄存器中去,清除中断状态,而快速中断不用
}
//中断源为TIMER
void Seg_TimerCounterHandler()
{
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA_OFFSET,pos);//输出选择位码
Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,segcode[i]);//输出此时i对应的段码
pos=pos>>1;//右移一位,从左到右扫描
i++;
if(i==8)//重置pos和i分别 为初始位码 以及 段码的起始位
{
i=0;
pos=0xff7f;
}
Xil_Out32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET,Xil_In32(XPAR_AXI_TIMER_0_BASEADDR+XTC_TCSR_OFFSET));
//将TCSR寄存器原来所存的值再次写入到TCSR寄存器中去,清除定时器的中断状态
}
//中断源为GPIO_2
void BTN_sw_seg()
{
char button=Xil_In8(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA_OFFSET);
short cur_sw=Xil_In16(XPAR_AXI_GPIO_0_BASEADDR+XGPIO_DATA_OFFSET);
if(button==0x10)//BTND
{
for(int i=0;i<8;i++)
{
for(int n=0;n<8;n++){
segcode[7-n]=segtable[(cur_sw>>n)&0x1];
}
}
}
else if(button==0x02)//BTNU
{
for(int i=0;i<4;i++){
for(int n=0;n<4;n++){
segcode[7-n]=segtable[(cur_sw>>(4*n))&0xf];
}
for(int n=4;n<8;n++){
segcode[7-n]=0xff;
}
}
}
else if((button==0x01))//BTNC
{
int k=cur_sw;//不改变cur_sw
for(int j=0;j<5;j++){
switch(k%10)
{
case(0):segcode[7-j]=segtable[0];break;
case(1):segcode[7-j]=segtable[1];break;
case(2):segcode[7-j]=segtable[2];break;
case(3):segcode[7-j]=segtable[3];break;
case(4):segcode[7-j]=segtable[4];break;
case(5):segcode[7-j]=segtable[5];break;
case(6):segcode[7-j]=segtable[6];break;
case(7):segcode[7-j]=segtable[7];break;
case(8):segcode[7-j]=segtable[8];break;
case(9):segcode[7-j]=segtable[9];break;
}
k/=10;
}
for(int n=5;n<8;n++){
segcode[7-n]=0xff;
}
}
Xil_Out32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_ISR_OFFSET,Xil_In32(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_ISR_OFFSET));
//无论是是快速中断还是普通中断,都需要清除GPIO_2的中断状态,写ISR寄存器存的的1(申请中断状态)到ISR寄存器中去,清除GPIO_2的申请中断状态
}
4.5 下载及功能验证
在经过调试和修改代码后,下载到实验FPGA上,能够实现要求的功能,验收,功能验证图片如下:
5 实验心得、体会
深入学习了硬件平台的搭建,中断的原理,在SDK上以c语言实现各种中断的功能以及GPIO,TIMER等的具体功能。
版权声明:本文为Antonioxv原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。