实验前请点击获取配套资料
关于LAB4:
- 本实验对应实验指导书的第 7 章 "数据存储器与流水灯外设"
- 本实验将以键盘模块实验为基础, 了解 CPU 的中断处理以及使用 C 语言高效地编程.
- 实验过程中遇到不明白的地方, 强烈建议回看实验指导书第三章中关于 Cortex-M0 相关的异常与中断的处理过程.
使用矩阵键盘控制数码管显式
在这次试验中, 我们需要把矩阵键盘的全部 16 个按键连接到 IRQ0 端口上, 当任何一个按键被按下时, CPU 进入中断处理程序, 读取按键信息, 根据按下的按键编号在数码管上显示对应的数值.
外设简介
数码管显示的具体代码见工程文件夹下 "Segdisp.v、seg_led_decoder.v、seg_sel_decoder.v" 文件.
由于所有按键连接到同一个中断端口, 因此键盘外设除了基本的消抖检测模块以外, 还需要使用一个寄存器组将被按下的键盘的序号记录下来. 本节用到了矩阵键盘的全部按键, 因此不能像上一节中为了使用某一行的 4 个按键, 将该行的行信号线拉高. 为了实现对矩阵键盘全部 16 个按键的检测, 需要进行扫描.
由于 FPGA 板载时钟频率为 50MHz, 所以还需要先对时钟信号分频得到扫描时钟信号, 按扫描时钟信号周期性地改变行信号线的值. 当第一行行信号线为低电平时, 此时检测列信号的值便可判断出是第一行的某个按键被按下. 以此类推, 使用这种扫描方法可以检测矩阵键盘 16 个按键中是哪一个按键被按下 (扫描模块的代码可以参考实验指导书).
除了扫描模块以外, 键盘模块还包括消抖模块和记录按键信息的寄存器组 (具体代码可以参考实验指导书).
最后实现的键盘外设模块中, 把每个按键的脉冲信号 key_pulse 通过和运算以后得到键盘中断信号key_interrupt. 只要有一个按键被按下, 中断信号都将产生一个脉冲, 使CPU进入中段处理程序.
提示可以看出, 本节介绍的方法中中断信号和上一节中的有很大不同, 应当认真体会!
SoC硬件部分
上一节中按键模块的中断信号为 4 位 key_interrupt 信号, 本节中该信号为 1 位, 因此在 CortexM0_SoC.v 文件做如下修改.
wire [31:0] IRQ;
wire [3:0] key_interrupt;
/*Connect the IRQ with keyboard*/
assign IRQ = {28'b0,key_interrupt};
/***************************/
改为:
wire [31:0] IRQ;
wire key_interrupt;
/*Connect the IRQ with keyboard*/
assign IRQ = {31'd0,key_interrupt};
/***************************/
然后在上一节实验的 vivado 约束文件的基础上添加对数码管的约束.
启动代码与 C 编程
由于中断信号的变化, 我们需要对启动代码进行修改. 上一节中已经介绍过 _main 函数和 __user_initial_stackheap 函数, 在本节中我们只需要修改中断向量表与中断复位函数即可.
修改__Vector中断向量表如下:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD 0 ; NMI Handler
DCD 0 ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; SVCall Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; PendSV Handler
DCD 0 ; SysTick Handler
DCD Keyboard_Handler ; IRQ0 Handler
现在只有一个中断复位函数, 因此还需要修改中断复位函数的入口:
KEY0_Handler PROC
EXPORT KEY0_Handler [WEAK]
IMPORT KEY0
PUSH {R0,R1,R2,LR}
BL KEY0
POP {R0,R1,R2,PC}
ENDP
KEY1_Handler PROC
EXPORT KEY1_Handler [WEAK]
IMPORT KEY1
PUSH {R0,R1,R2,LR}
BL KEY1
POP {R0,R1,R2,PC}
ENDP
KEY2_Handler PROC
EXPORT KEY2_Handler [WEAK]
IMPORT KEY2
PUSH {R0,R1,R2,LR}
BL KEY2
POP {R0,R1,R2,PC}
ENDP
KEY3_Handler PROC
EXPORT KEY3_Handler [WEAK]
IMPORT KEY3
PUSH {R0,R1,R2,LR}
BL KEY3
POP {R0,R1,R2,PC}
ENDP
改为:
Keyboard_Handler PROC
EXPORT Keyboard_Handler [WEAK]
IMPORT KEY_ISR
PUSH {R0,R1,R2,LR}
BL KEY_ISR
POP {R0,R1,R2,PC}
ENDP
然后, 我们需要定义外设的地址, 以及自己实现的函数, 参考 CMSIS 编写自己头文件. 具体代码见
"/Task5/keil/code_def.h".
#include <stdint.h>
//INTERRUPT DEF
#define NVIC_CTRL_ADDR (*(volatile unsigned *)0xe000e100)
//KEYBOARD DEF
#define Keyboard_keydata_clear (*(volatile unsigned *)0x40000000)
//SEGDISP DEF
#define Segdisp_data (*(volatile unsigned *)0x40000010)
void KEY_ISR(void);
其中 Keyboard_keydata_clear 即连到之前介绍的键盘模块中的 clear 端口, 用于按键信息寄存器组的清零. Segdisp_data 为数码管上需要显示的数据. 中断处理函数 KEY_ISR 将变量 key_flag 的值改为 1, 用于 C 语言程序中判断是否有按键被按下产生了中断, 具体见
"/Task5/keil/keyboard.c".
void KEY_ISR(void)
{
key_flag = 1;
}
最后, 编写主函数文件, 具体见 "/Task5/keil/main.c", 需要注意的是, 在中断启动之前, 我们需要使能所用到的中断.
#include "code_def.h"
#include <string.h>
#include <stdint.h>
extern uint32_t key_flag;
int main()
{
NVIC_CTRL_ADDR = 1;
while(1){
while(!key_flag);
uint32_t din;
din = Keyboard_keydata_clear;
int i = 0;
int ans = 0;
for (i = 0; i < 16; i++) {
if ((din >> i) & 1) {
ans = i;
Segdisp_data = 16 + ans; //enable
break;
}
}
key_flag = 0;
Keyboard_keydata_clear = 1;
}
}
当任一按键被按下后, 键盘模块会产生中断信号, CPU 进入中断处理程序, key_flag 的值变为 1. 此时, main 函数中内层 while 循环被跳过, 开始读取键盘的值. 键盘寄存器组的值被读到变量 din 中, 接着用一个 for 循环找出哪一位为 1, 记录在变量 ans 中, 同时向数码管外设写值 16+ans, 跳出循环. 因为数码管模块中 5 位输入数据最高位是使能信号 (表示数据输入是否有效), 因此在写入低四位的数据基础上, 还需要加上 16, 让使能信号有效. 最后就是在每次循环处理完成之后将 key_flag 等变量以及键盘外设的寄存器组清零.
调试与运行结果
将相关的 Verilog 文件添加到 Vivado 工程中, 将 Vivado 生成的比特流 (bitstream) 文件下载到 FPGA 开发板上, 使用 Keil 调试. 在按下按键时, 可以看到数码管上显示了按键编号 (最左边的数码管), 之前的显示结果右移一位.
END
文章来源:
推荐内容:
- 【课程实验 LAB0】FPGA简介
- 【课程实验 LAB0】为什么是 FPGA?
- 【课程实验 LAB0】设计方法
- 【课程实验 LAB0】平台介绍
- 【课程实验 LAB0】软硬件关系及软件Modelsim, Vivado, Keil的介绍
- 【课程实验 LAB1】“施法”让CPU动起来:搭建 Keil 环境
- 【课程实验 LAB1】“施法”让CPU动起来:运行吧,第一个汇编程序
- 【课程实验 LAB1】实现你的首个SoC:函数调用
- 【课程实验 LAB2】实现你的首个SoC:硬件部分说明
- 【课程实验 LAB2】实现你的首个SoC:搭建Keil工程和Modelsim 仿真
- 【课程实验 LAB2】实现你的首个SoC:下载比特流到 FPGA 和使用 Keil 调试
- 【课程实验 LAB3】流水灯的几种点法:数据存储器
- 【课程实验 LAB3】流水灯的几种点法:GPIO外设实现流水灯
- 【课程实验 LAB3】流水灯的几种点法:硬件流水灯
- 【课程实验 LAB4】如何召唤"沉睡的软件":使用矩阵键盘4个按键控制流水灯模式
更多内容请关注微处理器系统结构与嵌入式系统设计专栏