最早的 MCU(即单片机)其 IO 口相对较少,而且用到按键过多的话, 就会占用过多的 IO。 人们为了解决这个问题就引入了“矩阵键盘”。 在矩阵键盘中,每条行线和列线在交叉处都不是直接连同, 而是通过一个按键直接相连,这样以来一个 4*4 的矩阵键盘只需要 8 根控制线就可以完成16 个按键的控制。
矩阵键盘工作原理 :
检测矩阵键盘中某一按键是否被按下, 采用的方法是列扫描法。 图中一共有 8条控制线, 4 条行控制线(ROW),4 条列控制线(COL) 。在原理图图中,我们可以看到,ROW的信号【对于FPGA】是一个输入IO,COL0、1、2、3 是一个输出IO的配置,所以FPGA 只需要读取ROW0、1、2、3 端口的信号来进行判断。
假如我们让 COL0=0,然后去读取 ROW 的值,如果 4 个 ROW 信号的电平全部为高,则表明当前列并无按键按下,则切换到扫描下一列,再去读取 4 个 ROW 信号的值,根据读到的 ROW 值判断当前是哪一个按键被按下。例如当扫描第三列的时候,即 COL=4’b1011 时,检测到 ROW=4’b1011,则说明“A”被按下。
矩阵键盘扫描驱动模块设计 :
端口描述:
矩阵键盘检测状态转移图 :(P208)
key_board.v 文件
module key_board
(
Clk,
Rst_n,
Key_Board_Row_i,
Key_flag,
Key_Value,
Key_Board_Col_o
);
input Clk ;
input Rst_n;
input [3:0]Key_Board_Row_i;
output reg Key_flag;
output reg [3:0]Key_Value;
output reg [3:0]Key_Board_Col_o;
// 输入列寄存器信号,输入以后做一次寄存
reg [3:0]Key_Board_Row_r;
reg En_Cnt ; //滤波定时器
reg [19:0] counter1; //滤波定时,时钟周期计数器
reg Cnt_Done; //滤波时间完成标志信号
reg [3:0]Col_Tmp ; // 列按键按下状态
reg Key_Flag_r; // 按键成功标志位
reg [7:0]Key_Value_tmp;
reg [10:0]state;
//按键按下标志位
[email protected](posedge Clk)
Key_flag <= Key_Flag_r;
// 状态机状态参数
localparam
IDEL = 11'b00000000001,
P_FILTER = 11'b00000000010,
READ_ROW_P = 11'b00000000100,
SCAN_C0 = 11'b00000001000,
SCAN_C1 = 11'b00000010000,
SCAN_C2 = 11'b00000100000,
SCAN_C3 = 11'b00001000000,
PRESS_RESULT = 11'b00010000000,
WAIT_R = 11'b00100000000,
R_FILTER = 11'b01000000000,
READ_ROW_R = 11'b10000000000;
// 滤波定时计数器
[email protected](posedge Clk or negedge Rst_n)
if(!Rst_n)
counter1 <= 20'd0;
else if(En_Cnt)begin
if(counter1 == 20'd999999)
counter1 <= 20'd0;
else
counter1 <= counter1 + 1'b1;
end
else
counter1 <= 20'd0;
[email protected](posedge Clk or negedge Rst_n)
if(!Rst_n)
Cnt_Done <= 1'b0;
else if(counter1 == 20'd999999)
Cnt_Done <= 1'b1;
else
Cnt_Done <= 1'b0;
//状态机
always @(posedge Clk or negedge Rst_n)
if(!Rst_n)
begin
state <= IDEL ;
Key_Board_Col_o <= 4'b0000 ; //输出端口默认设置为低电平,只有按键按下时,默认的上拉电平就会变成低电平
En_Cnt <= 1'b0;
Col_Tmp <= 4'd0;
Key_Flag_r <= 1'b0;
Key_Value_tmp <= 8'd0;
Key_Board_Row_r <= 4'b1111;
end
else
begin
case (state)
IDEL:
if(~&Key_Board_Row_i)begin // Key_Board_Row_i 不等于 4‘d1111,说明有按键按下
En_Cnt <= 1'b1 ; // 开启滤波定时器
state <= P_FILTER; // 跳转进入前级滤波状态
end
else begin // 按键未按下
En_Cnt <=1'b0 ; // 消抖滤波定时器关闭
state <=IDEL ; // 等待状态
end
P_FILTER :
if(Cnt_Done) begin //消抖滤波定时时间结束
En_Cnt <= 1'b0; //关闭滤波定时器
state <= READ_ROW_P; //跳转进入滤波检测状态
end
else begin //消抖定时未结束
En_Cnt <= 1'b1; //继续保持
state <= P_FILTER;// 状态继续保持当前值
end
READ_ROW_P:
if (~&Key_Board_Row_i) begin //滤波后,第二次确定按键确实按下
Key_Board_Row_r <= Key_Board_Row_i; // 存取当前状态的值
Key_Board_Col_o <= 4'b1110; // 设置 Key_Board_Col_o 的输出信号为 4‘b1110,其它列设置为高电平
state <= SCAN_C0; // 跳转进入第C0列的判断
end
else begin //这次是按键抖动
state <=IDEL ; // 进入空闲等待状态
Key_Board_Col_o <= 4'b0000; // 设置 Key_Board_Col_o 的输出信号为 4‘b0000
end
SCAN_C0 :
begin
Key_Board_Col_o <= 4'b1101; // 设置 Key_Board_Col_o 的输出信号为 4‘b1101
state <= SCAN_C1; // 跳转进入第C1列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0001 ; // 表示此次按下的按键是再第一列
else
Col_Tmp <= 4'b0000 ; // 表示此次按下的按键不在这列
end
SCAN_C1 :
begin
Key_Board_Col_o <= 4'b1011; // 设置 Key_Board_Col_o 的输出信号为 4‘b1011
state <= SCAN_C2; // 跳转进入第C2列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0010 | Col_Tmp ; // 表示此次按下的按键是再第2列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
SCAN_C2 :
begin
Key_Board_Col_o <= 4'b0111; // 设置 Key_Board_Col_o 的输出信号为 4'b0111
state <= SCAN_C3; // 跳转进入第C3列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0100 | Col_Tmp ; // 表示此次按下的按键是再第2列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
SCAN_C3 :
begin
state <= PRESS_RESULT; // 进入此状态后,表示按键检测已经结束,需要进入下一个按键结果分析状态
if(~&Key_Board_Row_i) // 其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b1000 | Col_Tmp ; // 表示此次按下的按键是再第3列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
PRESS_RESULT:
begin
state <= WAIT_R; // 本次结果分析完成后,进入按键松开滤波状态
Key_Board_Col_o <= 4'b0000; //按键检测查找完成,所以重新设置,输出Key_Board_Col_o的信号为 4‘b0000
if(((Key_Board_Row_r[0] + Key_Board_Row_r[1] + Key_Board_Row_r[2] + Key_Board_Row_r[3]) == 4'd3) &&
((Col_Tmp[0] + Col_Tmp[1] + Col_Tmp[2] + Col_Tmp[3]) == 4'd1)) // 检验是否只有一个按键按下
begin
Key_Flag_r <= 1'b1; //产生按键成功标志信号
Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp}; //位拼接,等下来查找
end
else
begin
Key_Flag_r <= 1'b0; //非单个按键按下,不产生按键完成信号
Key_Value_tmp <= Key_Value_tmp; //按键值保持上一次按键的值
end
end
WAIT_R :
begin
Key_Flag_r <= 0 ; // 按键成功标志信号清零
if(&Key_Board_Row_i) // Key_Board_Row_i = 4’b1111 表示(按键未按下)即,按键松开,此时我们进入后消抖状态
begin
En_Cnt =1'b1; // 开启消抖滤波的计时器
state <= R_FILTER ; //进入后消抖判断转态
end
else // 按键仍然按下,我们在这次保持等待状态
begin
state <= WAIT_R ;
En_Cnt =1'b0; // 消抖滤波的计时器暂时先不开启
end
end
R_FILTER :
if(Cnt_Done)begin //后消抖滤波定时完成
En_Cnt <= 1'b0; //关闭滤波定时器使能信号
state <= READ_ROW_R; //进入按键完全释放状态判断
end
else
begin
En_Cnt <= 1'b1; // 滤波消抖定时器正在进行
state <= R_FILTER; // 继续保持滤波状态
end
READ_ROW_R :
if(&Key_Board_Row_i) // (Key_Board_Row_i =4'd1111)即,确定按键已经松开,本次按键检测已经完成
state <= IDEL;
else // 按键还没松开,再次后消抖滤波
begin
state <= R_FILTER; // 再次进入后消抖滤波状态
En_Cnt <= 1'b1; // 开启滤波消抖定时器
end
default: state <= IDEL; // 默认,或者运行出错,进入空闲状态
endcase
end
//按键输出状态查找表 根据 : Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp}; 实现数据的查找
always @(posedge Clk or negedge Rst_n)
if(!Rst_n)
Key_Value <= 4'd0 ;
else if(Key_Flag_r) // 先判断,按键是否按下成功
begin
case (Key_Value_tmp)
8'b1110_0001 : Key_Value <= 4'd0;
8'b1110_0010 : Key_Value <= 4'd1;
8'b1110_0100 : Key_Value <= 4'd2;
8'b1110_1000 : Key_Value <= 4'd3;
8'b1101_0001 : Key_Value <= 4'd4;
8'b1101_0010 : Key_Value <= 4'd5;
8'b1101_0100 : Key_Value <= 4'd6;
8'b1101_1000 : Key_Value <= 4'd7;
8'b1011_0001 : Key_Value <= 4'd8;
8'b1011_0010 : Key_Value <= 4'd9;
8'b1011_0100 : Key_Value <= 4'd10;
8'b1011_1000 : Key_Value <= 4'd11;
8'b0111_0001 : Key_Value <= 4'd12;
8'b0111_0010 : Key_Value <= 4'd13;
8'b0111_0100 : Key_Value <= 4'd14;
8'b0111_1000 : Key_Value <= 4'd15;
default: Key_Value <= Key_Value ;
endcase
end
endmodule
矩阵键盘模型:
`timescale 1ns/1ns
module Key_Board_model(
Key_Col,
Key_Row
);
// 矩阵键盘模型
// 注: 写的是一个键盘的模型,相当于模拟矩阵的输入和输出
// 主要是模拟按键按下,信号输出给fpga 的过程,其过程包括按键抖动信号
input [3:0]Key_Col; //对于矩阵键盘来说,Key_Col 是一个 输入信号,对fpga是一个输出信号
output reg [3:0]Key_Row; //Key_Row 是一个输出信号,对pga 是有一个输入信号
reg [5:0]myrand ;
reg [3:0]Key_Row_r; //行寄存器
reg key_row_sel ;
reg [1:0]now_col,now_row ; //当前行与当前列
initial begin
now_col =0;
now_row =0;
key_row_sel =0 ;
myrand =0 ;
end
initial begin
Key_Row_r = 4'b1111 ; // 初始状态,即模型按键未按下时刻,矩阵键盘的行信号默认都是上拉电平,即全是高电平
#50000000 ;
// 顺序按键按下,
press_key(0, 0); //第一行
press_key(0, 1);
press_key(0,2);
press_key(0,3);
press_key(1, 0); //第二行
press_key(1, 1);
press_key(1,2);
press_key(1,3);
press_key(2, 0); //第三行
press_key(2, 1);
press_key(2,2);
press_key(2,3);
press_key(3, 0); //第四行
press_key(3, 1);
press_key(3,2);
press_key(3,3);
$stop;
end
// 编写press_key任务(函数) 调用方式: press_key(row,col); //参数输入按键的位置row,col
task press_key;
input [1:0]row,col;
// 按键按下进行执行下列操作
begin
key_row_sel = 0;//将行选择信号设置为有效状态
Key_Row_r = 4'b1111; // 初始状态按键未按下
Key_Row_r[row] = 0 ; // row,按键按下,让Key_Row_r 的这个row bit 未设置为0 ,表示按键按下
now_row = row;
repeat(20)begin //重复 20 次,随机产生按键按下时 20 个不同的行状态
myrand = {$random} % 65536; //随机延时
#myrand Key_Row_r[row] = ~Key_Row_r[row]; // 模拟按键抖动(电平时高时低)状态
end
key_row_sel = 1; //将行选择信号设置为有效状态
now_col = col; //消抖以后,获取当前列输入信号 col ???
#22000000;
key_row_sel =0 ;
Key_Row_r = 4'b1111; // 进入按键后滤波状态
repeat(20)begin //重复 20 次,随机产生按键松开时 20 个不同行状态
myrand = {$random} % 65536;
#myrand Key_Row_r[row] = ~Key_Row_r[row];
end
Key_Row_r = 4'b1111; // 按键完全释放
#22000000;
end
endtask
always @(*)
if (key_row_sel) //行信号有效
case(now_row) //根据输入的列信号,获取按键Key_Row输出的行信号
2'd0:Key_Row = {1'b1,1'b1,1'b1,Key_Col[now_col]};
2'd1:Key_Row = {1'b1,1'b1,Key_Col[now_col],1'b1};
2'd2:Key_Row = {1'b1,Key_Col[now_col],1'b1,1'b1};
2'd3:Key_Row = {Key_Col[now_col],1'b1,1'b1,1'b1};
endcase
else
Key_Row = Key_Row_r ;
endmodule
验证测试脚本文件:
`timescale 1ns/1ns
//矩阵键盘的仿真模型
module Key_Board_tb;
reg Clk;
reg Rst_n;
wire [3:0]Key_Row;
wire [3:0]Key_Col;
wire Key_flag;
wire [3:0]Key_Value;
key_board key_board_0(
.Clk(Clk),
.Rst_n(Rst_n),
.Key_Board_Row_i(Key_Row),
.Key_flag(Key_flag),
.Key_Value(Key_Value),
.Key_Board_Col_o(Key_Col)
);
Key_Board_model Key_Board_model_inst(Key_Col,Key_Row);
initial Clk =1 ;
always #10 Clk = ~Clk ;
initial begin
Rst_n =0;
#200
Rst_n =1;
end
endmodule
最终配置效果:
共45个贴吧,日期从2018-03-01至2021-03-01共36个月的股吧帖子,爬取股吧名称、阅读、评论、标题、作者和发帖时间,并分析总体情绪亮点回顾时间问题获取的时间未加年份,解决方法,观察发现发帖日期月份逐级递减,按获取顺序下一个时间月份在同一年内小于等于上一个月份,设一个全局变量m储存月份,因为当前为3月份,将其初始值设为12,与获取的最新月份new_m比较,若new_m>m,使当前年份减一,数据去重问题有时候爬取会因各自问题中断,当你再次续爬时数据会重复,于是我加了一个用于.
前言Android开发中,EditText的使用 非常常见本文将带来一款 附带一键删除功能 & 自定义样式丰富的 SuperEditText控件的使用,希望你们会喜欢。 已在Github开源:Super_EditText,欢迎 Star ! 目录1. 简介一款 自定义样式丰富 & 附带一键删除功能的 SuperEditText控件 已在Github开源:Super_Edit
导致错误的原因是因为使用@FeignClient注解的前提需要增加依赖引入<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version>&l...
在爬取大量数据时,由于有成千上万的数据,单线程爬虫显然不能满足我们的需求,这时候多线程爬虫就来了,本篇文章使用Threading和Queue简单介绍。很多人学习python,不知道从何学起。很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。很多已经做案例的人,却不知道如何去学习更加高深的知识。那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!??¤QQ群:623406465首先先了解多线程队列,生产消费模式的大致步骤。1.主线程
此文特别送给 一个网友 kkgbn[盖宝宁] 前言: 我的一个很早的转文:一对夫妻程序员的故事, 看到了一个由kkgbn发表的评论: 评论的大意是“希望能多发发正能量的帖子,把程序员的迷茫带走。让我们一起找到生活的出路。" ,详细内容大家可以进入该帖子查看。 个人觉得很有感触,感谢kkgbn提出的好建议,同时有感而发写此文特此送给kkgbn以及有类似感触的...
Android Kotlin项目生成文档工具Dokka一 概述二 kotin项目文档生成工具Dokka三 项目中Dokka的配置四 如何使用Dokka生成文档五 查看Dokka生成文档一 概述我们知道使用Java代码书写的Android项目,可以直接用Androidstudio自带的JavaDoc生成工具自动生成(Tools—>GenerateJavaDoc),那么使用Kotlin语言书写的Androidstudio项目和使用Kotlin和java混合开发的android项目呢,可不可以使
参考地址:https://blog.csdn.net/sifanlook/article/details/81251626最近项目应用场景:读出污水池坑中水泵状态、污水坑溢出液位报警等设备状态。引入相关maven依赖 <repositories> <repository> <releases> <enabled>false</enabled> </releases&g
1.下载和安装Javahttps://www.java.com/zh_CN/,在电脑的环境变量里新建JAVA_HOME变量,在Path中添加Java安装目录2.打开Rstudio,在Tools – Global Options –General – R version Change选项中切换为32-bit3.在Console中进入以下命令即可Sys.setenv(JAVA_...
<div> <ul> <li>菜单</li><li>菜单</li></ul></div>问题:在给div加入绝对定位后,让li左浮动,设置左间距(margin-left),让li并排且有间距的排列,但是却无法让li在ul里居中排列。解决:此时只要让ul 设display...
一、产品经理要不要懂技术?大家通常提到的产品经理,除了常规意义上全权负责产品的产品经理之外,还有产品设计师、用户体验产品经理,以及后台产品经理、需求分析师等很多种。不同的公司,产品经理负责的事务也各不相同。从行业角度,也可以分为技术型产品的 PM、设计型产品的 PM、运营导向产品的 PM。再细一点划分,还可以分出电商产品的 PM、社交产品的PM或者搜索产品的 PM等。在某招聘网站上,产品经理的...
起因Mac上使用brew services start --all指令同时启动多个服务显示成功但是,连接四个服务所在端口均无响应。仔细核对过brew指令启动服务使用的路径、配置文件路径均无问题。怀疑过是不是用户权限、系统防火墙之类影响。后检查日志信息时,发现无权限访问日志文件,此原因导致服务实际启动失败,而brew表面显示启动成功。经修改目录权限后重启服务一切正常,问题解决。brew s...
//todo stream流的使用 //todo filter:过滤操作;保留符合过滤条件的对象;这是一个中间操作;后面可以带最终操作 //todo mapToInt: 将数据根据double类型来处理; public static void main(String[] args) { List<SalesOrder> list = creat...