模块:4x4矩阵键盘
功能:4x4矩阵键盘识别与编码.可以实现双键识别,以及同行的多键(两个以上)识别.不能去除"井字形"的误判断现象.可以实现重复触发,即类似电脑键盘的长按功能,可以用参数控制长按时间与长按后的触发频率.键盘每次键值改变都会产生中断信号给nios,具体处理由软核判断处理.
说明:这个模块是应学长的要求,想做成模块化的东西,前段时间做了一下,这个是比较简单的版本,没有做时序的优化,也没有挂到Avalon总线上去.只是实现了当初提出的要求功能.如果只是应用,是可以了的,但是还有很大的改进空间.最近有别的事情,等忙完了,再改进改进,写个文档挂上来.
1.矩阵键盘电路原理图:
2.verilog源程序:
key4x4
module KEYTEST_01(
//input
clk, //50M时钟
rst_n, //复位
col, //列输入4bits
//output
row, //行扫描4bits
interrupt, //中断信号
value //键值16bits
);
input clk,rst_n;
input [3:0]col;
output interrupt;
output [3:0]row;
output [15:0]value;
//可调参数列表
parameter
DUP_PERIOD_CNT=32'd10000000, //长按后产生重复中断时间间隔(默认为500x1ms=200ms)
DUP_JUDGE_CNT=32'd300000000; //长按与否判断阈值(默认为5000x1ms=3s)
reg [5:0]cnt_clk_scan; //400kHz扫描时钟计数器
reg [20:0]cnt_debounce; //20ms去抖计数器
always@(posedge clk)
begin
cnt_clk_scan<=cnt_clk_scan+1;
cnt_debounce<=cnt_debounce+1'b1;
end
reg clk_scan; //产生扫描时钟信号
always@(posedge clk)
if (cnt_clk_scan==6'h3f) begin clk_scan<=~clk_scan; end
else clk_scan<=clk_scan;
reg [1:0]state; //状态机状态寄存器
reg [3:0]row,col1,col2,col3; //行列寄存器
reg [15:0]value_reg0; //实时采集的键值
always@(posedge clk_scan or negedge rst_n)
begin
if(!rst_n)
begin
state<=0; row<=4'b0111; value_reg0<=16'hffff;
end
else
case (state)
0:
begin
col1<=col[3:0]; //填第一行(1-4位)数据
state<=1; row<=4'b1011; //扫描第二行
end
1:
begin
col2<=col[3:0]; //填第二行(5-8位)数据
state<=2; row<=4'b1101; //扫描第三行
end
2:
begin
col3<=col[3:0]; //填第三行(9-12位)数据
state<=3; row<=4'b1110; //扫描第四行
end
3:
begin
value_reg0<={col1,col2,col3,col[3:0]}; //填第四行(13-16位)数据
state<=0; row<=4'b0111; //扫描第一行
end
default :
begin state<=0; row<=4'b0111; value_reg0<=value_reg0; end
endcase
end
reg [15:0]value_reg1; //延时20ms去抖
always@(posedge clk or negedge rst_n)
begin
if (!rst_n) value_reg1<=16'hffff;
else
if (cnt_debounce==20'hfffff) value_reg1<=value_reg0;
else value_reg1<=value_reg1;
end
reg [15:0]value; //去抖后的键值
always@(posedge clk or negedge rst_n)
begin
if (!rst_n) value<=16'hffff;
else
if (value_reg1==value_reg0) value<=value_reg1;
else value<=value;
end
reg [15:0]value_pre; //前一键值寄存器
reg interrupt_state; //状态改变中断信号
reg [31:0]dup_judge_cnt;
reg dup_inte_en;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin value_pre<=16'hffff; interrupt_state<=1'b0; dup_judge_cnt<=32'd0; dup_inte_en<=1'b0;end
else
begin
value_pre<=value;
if (value_pre!=value)
begin interrupt_state<=1'b1; dup_inte_en<=1'b0; dup_judge_cnt<=0; end
else if ((value==value_pre)&&(value==16'hffff))
begin interrupt_state<=1'b0; dup_inte_en<=1'b0; dup_judge_cnt<=0; end
else if (dup_judge_cnt>=DUP_JUDGE_CNT)
begin interrupt_state<=1'b0; dup_inte_en<=1'b1; dup_judge_cnt<=dup_judge_cnt; end
else
begin interrupt_state<=1'b0; dup_inte_en<=1'b0; dup_judge_cnt<=dup_judge_cnt+1'b1; end
end
end
//长按后的重复中断产生模块
reg [31:0]dup_period_cnt;
reg interrupt_dup;
always@(posedge clk or negedge rst_n)
begin
if (rst_n==1'b0)
begin
dup_period_cnt<=32'd0; //重复时间间隔计数器
interrupt_dup<=1'b0; //长按重复中断
end
else if (dup_inte_en==1'b1)
if (dup_period_cnt<=DUP_PERIOD_CNT)
begin dup_period_cnt<=dup_period_cnt+1'b1; interrupt_dup<=1'b0; end
else
begin dup_period_cnt<=0; interrupt_dup<=1'b1; end
else begin dup_period_cnt<=0; interrupt_dup<=1'b0; end
end
assign interrupt=interrupt_state||interrupt_dup;
endmodule
//input
clk, //50M时钟
rst_n, //复位
col, //列输入4bits
//output
row, //行扫描4bits
interrupt, //中断信号
value //键值16bits
);
input clk,rst_n;
input [3:0]col;
output interrupt;
output [3:0]row;
output [15:0]value;
//可调参数列表
parameter
DUP_PERIOD_CNT=32'd10000000, //长按后产生重复中断时间间隔(默认为500x1ms=200ms)
DUP_JUDGE_CNT=32'd300000000; //长按与否判断阈值(默认为5000x1ms=3s)
reg [5:0]cnt_clk_scan; //400kHz扫描时钟计数器
reg [20:0]cnt_debounce; //20ms去抖计数器
always@(posedge clk)
begin
cnt_clk_scan<=cnt_clk_scan+1;
cnt_debounce<=cnt_debounce+1'b1;
end
reg clk_scan; //产生扫描时钟信号
always@(posedge clk)
if (cnt_clk_scan==6'h3f) begin clk_scan<=~clk_scan; end
else clk_scan<=clk_scan;
reg [1:0]state; //状态机状态寄存器
reg [3:0]row,col1,col2,col3; //行列寄存器
reg [15:0]value_reg0; //实时采集的键值
always@(posedge clk_scan or negedge rst_n)
begin
if(!rst_n)
begin
state<=0; row<=4'b0111; value_reg0<=16'hffff;
end
else
case (state)
0:
begin
col1<=col[3:0]; //填第一行(1-4位)数据
state<=1; row<=4'b1011; //扫描第二行
end
1:
begin
col2<=col[3:0]; //填第二行(5-8位)数据
state<=2; row<=4'b1101; //扫描第三行
end
2:
begin
col3<=col[3:0]; //填第三行(9-12位)数据
state<=3; row<=4'b1110; //扫描第四行
end
3:
begin
value_reg0<={col1,col2,col3,col[3:0]}; //填第四行(13-16位)数据
state<=0; row<=4'b0111; //扫描第一行
end
default :
begin state<=0; row<=4'b0111; value_reg0<=value_reg0; end
endcase
end
reg [15:0]value_reg1; //延时20ms去抖
always@(posedge clk or negedge rst_n)
begin
if (!rst_n) value_reg1<=16'hffff;
else
if (cnt_debounce==20'hfffff) value_reg1<=value_reg0;
else value_reg1<=value_reg1;
end
reg [15:0]value; //去抖后的键值
always@(posedge clk or negedge rst_n)
begin
if (!rst_n) value<=16'hffff;
else
if (value_reg1==value_reg0) value<=value_reg1;
else value<=value;
end
reg [15:0]value_pre; //前一键值寄存器
reg interrupt_state; //状态改变中断信号
reg [31:0]dup_judge_cnt;
reg dup_inte_en;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin value_pre<=16'hffff; interrupt_state<=1'b0; dup_judge_cnt<=32'd0; dup_inte_en<=1'b0;end
else
begin
value_pre<=value;
if (value_pre!=value)
begin interrupt_state<=1'b1; dup_inte_en<=1'b0; dup_judge_cnt<=0; end
else if ((value==value_pre)&&(value==16'hffff))
begin interrupt_state<=1'b0; dup_inte_en<=1'b0; dup_judge_cnt<=0; end
else if (dup_judge_cnt>=DUP_JUDGE_CNT)
begin interrupt_state<=1'b0; dup_inte_en<=1'b1; dup_judge_cnt<=dup_judge_cnt; end
else
begin interrupt_state<=1'b0; dup_inte_en<=1'b0; dup_judge_cnt<=dup_judge_cnt+1'b1; end
end
end
//长按后的重复中断产生模块
reg [31:0]dup_period_cnt;
reg interrupt_dup;
always@(posedge clk or negedge rst_n)
begin
if (rst_n==1'b0)
begin
dup_period_cnt<=32'd0; //重复时间间隔计数器
interrupt_dup<=1'b0; //长按重复中断
end
else if (dup_inte_en==1'b1)
if (dup_period_cnt<=DUP_PERIOD_CNT)
begin dup_period_cnt<=dup_period_cnt+1'b1; interrupt_dup<=1'b0; end
else
begin dup_period_cnt<=0; interrupt_dup<=1'b1; end
else begin dup_period_cnt<=0; interrupt_dup<=1'b0; end
end
assign interrupt=interrupt_state||interrupt_dup;
endmodule
3.nios软核c程序(根据中断读键值):
key4x4_c
#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"
#include "sys/alt_irq.h"
#include "priv/alt_busy_sleep.h"
volatile alt_u32 done = 0; // 信号量:通知外部中断
事件发生
static void KeyDown_interrupts(void* context, alt_u32 id)
{
/* 清中断捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_INTE_BASE, 0);
/* 通知外部有中断事件发生 */
done++;
}
void InitPIO(void)
{
/* 初始化LED_PIO为输出,KEY为输入 */
IOWR_ALTERA_AVALON_PIO_DIRECTION(PIO_INTE_BASE, 0x00);
/* 开KEY的中断 */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(PIO_INTE_BASE, 0xff);
/* 清边沿捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_INTE_BASE, 0x00);
/* 注册中断服务子程序 */
alt_irq_register(PIO_INTE_IRQ, NULL, KeyDown_interrupts);
}
int main()
{
int value1,value2,i;
InitPIO();
while(1)
{
if(done != 0)
{
/* 中断事件数量减1 */
done--;
value1=IORD_16DIRECT(PIO_VALUE_BASE,0);
printf("%d ",value1);
}
else;
}
}
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"
#include "sys/alt_irq.h"
#include "priv/alt_busy_sleep.h"
volatile alt_u32 done = 0; // 信号量:通知外部中断
事件发生
static void KeyDown_interrupts(void* context, alt_u32 id)
{
/* 清中断捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_INTE_BASE, 0);
/* 通知外部有中断事件发生 */
done++;
}
void InitPIO(void)
{
/* 初始化LED_PIO为输出,KEY为输入 */
IOWR_ALTERA_AVALON_PIO_DIRECTION(PIO_INTE_BASE, 0x00);
/* 开KEY的中断 */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(PIO_INTE_BASE, 0xff);
/* 清边沿捕获寄存器 */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_INTE_BASE, 0x00);
/* 注册中断服务子程序 */
alt_irq_register(PIO_INTE_IRQ, NULL, KeyDown_interrupts);
}
int main()
{
int value1,value2,i;
InitPIO();
while(1)
{
if(done != 0)
{
/* 中断事件数量减1 */
done--;
value1=IORD_16DIRECT(PIO_VALUE_BASE,0);
printf("%d ",value1);
}
else;
}
}
先记这么多,以后再完善...