写在前面:
STM32单片机按键消抖和FPGA按键消抖大全
按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。
1. 单片机中按键消抖程序
1.1 单片机中,比如STM32中,一般的方法(最简单的方法)
软件消抖程序:
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
{
delay_ms(20);//延时20ms再去检测按键值
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相当于下降沿
{
KEY1 = 1; //表示KEY1被按下
}
}
1.2 比较全面的按键消抖程序及按键状态检测程序
第一步:初始化全局时间戳的定时器,一般采用SysTick定时器来产生,每ms一次tick即可。
第二步:初始化按键对应的IO,复用为边沿触发的外部中断。
第三步:在外部中断函数中添加按键事件处理函数。
代码部分:
typedef struct _Key_t
{
u32 last_time;
enum
{
May_Press,
Release,
}private_state;
enum
{
No_Press,
Short_Press,
Long_Press,
}state;
}Key_t;
#define Is_ShortPress_Threshold 1500
简单定义一个按键状态的结构体,用于管理每个按键的状态。顺便再定义一个长短按的识别阈值,用于区分按键的长短按。
if(key_state.private_state==Release)
{
if(KEY==0)
{
key_state.private_state=May_Press;
key_state.last_time=course_ms();
}
}
else if(key_state.private_state==May_Press)
{
if(KEY==1)
{
if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time
{
key_state.state=Short_Press;
key_state.private_state=Release;
}
else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)
{
key_state.state=Long_Press;
key_state.private_state=Release;
}
else
key_state.private_state=Release;
}
}
以上为需要添加到中断处理函数的按键事件处理函数,算法的核心是一个状态机。在本例中,按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。
思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。
效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。经实际测试,消抖效果可以达到其他两种消抖算法的水平。
2. FPGA按键消抖程序
首先,做两个假定,以方便后面的描述
假定按键的默认状态为0,被按下后为1
假定按键抖动时长小于20ms,也即使用20ms的消抖时间
核心:方案
最容易想到的方案
在按键电平稳定的情况下,当第一次检测到键位电平变化,开始20ms计时,计时时间到后将按键电平更新为当前电平
或许这才是最容易想的方案
在20ms计时的过程中,有任何的电平变化都立即复位计时
消除按键反应延时抖方案
在有电平变化时立即改变按键输出电平,并开始20ms计时,忽略这其中抖动
测试平台设计(修改代码以仿真的1us代替实际1ms)
无抖动 上升沿抖动5毫秒
下降沿抖动15毫秒
上升和下降沿均抖动19毫秒
附加测试(可以不通过)
抖动25毫秒
代码
方案1
module debounce(
input wire clk, nrst,
input wire key_in,
output reg key_out
);
// 20ms parameter
// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test
// variable
reg [20:0] cnt;
reg key_cnt;
// debounce time passed, refresh key state
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0;
else if(cnt == TIME_20MS - 1)
key_out <= key_in;
end
// while in debounce state, count, otherwise 0
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0;
else if(key_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
//
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0;
else if(key_cnt == 0 && key_in != key_out)
key_cnt <= 1;
else if(cnt == TIME_20MS - 1)
key_cnt <= 0;
end
方案2
module debounce(
input wire clk, nrst,
input wire key_in,
output reg key_out
);
// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000;
reg key_cnt;
reg [20:0] cnt;
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0;
else if(cnt == TIME_20MS - 1)
key_cnt <= 0;
else if(key_cnt == 0 && key_out != key_in)
key_cnt <= 1;
end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0;
else if(key_cnt) begin
if(key_out == key_in)
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 0;
end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0;
else if(cnt == TIME_20MS - 1)
key_out <= key_in;
end
方案3
module debounce(
input wire clk, nrst,
input wire key_in,
output reg key_out
);
// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test
reg key_cnt;
reg [20:0] cnt;
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0;
else if(key_cnt == 0 && key_out != key_in)
key_cnt <= 1;
else if(cnt == TIME_20MS - 1)
key_cnt <= 0;
end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0;
else if(key_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0;
else if(key_cnt == 0 && key_out != key_in)
key_out <= key_in;
end
测试代码
// 按键消抖测试电路
// 时间单位
`timescale 1ns/10ps
// module
module debounce_tb;
// time period parameter
localparam T = 20;
// variable
reg clk, nrst;
reg key_in;
wire key_out;
// instantiate
debounce uut(
.clk (clk ),
.nrst (nrst ),
.key_in (key_in ),
.key_out(key_out)
);
// clock
initial begin
clk = 1;
forever #(T/2) clk = ~clk;
end
// reset
initial begin
nrst = 1;
@(negedge clk) nrst = 0;
@(negedge clk) nrst = 1;
end
// key_in
initial begin
// initial value
key_in = 0;
// wait reset
repeat(3) @(negedge clk);
// no bounce
// key down
key_in = 1;
// last 60ms
repeat(3000) @(negedge clk);
// key up
key_in = 0;
// wait 50ms
repeat(2500) @(negedge clk);
// down 5ms, up 15ms
// key down, bounce 5ms
repeat(251) @(negedge clk) key_in = ~key_in;
// last 60ms
repeat(3000) @(negedge clk);
// key up, bounce 15ms
repeat(751) @(negedge clk) key_in = ~key_in;
// wait 50ms
repeat(2500) @(negedge clk);
// down 19ms, up 19ms
// key down, bounce 19ms
repeat(951) @(negedge clk) key_in = ~key_in;
// last 60ms
repeat(3000) @(negedge clk);
// key up, bounce 19ms
repeat(951) @(negedge clk) key_in = ~key_in;
// wait 50ms
repeat(2500) @(negedge clk);
// additional, this situation shoud not ever happen
// down 25ms, up 25ms
// key down, bounce 25ms
repeat(1251) @(negedge clk) key_in = ~key_in;
// last 60ms
repeat(3000) @(negedge clk);
// key up, bounce 25ms
repeat(1251) @(negedge clk) key_in = ~key_in;
// wait 50ms
repeat(2500) @(negedge clk);
// stop
$stop;
end
放在最后的,并不一定是最不重要的
对于上面的三种方案,我比较喜欢第三种方案,它更贴合实际的按键状态,以上的代码我都做过modelsim仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。
上一篇:stm32的库文件的用法解释
下一篇:stm32单片机检测12V电路
推荐阅读
史海拾趣
对于快速充电IC(bq2002)电路,网友可能还会有多种问题,以下是一些常见问题及其回答:
一、电路设计相关问题
- BQ2002的BAT引脚如何正确接入电池电压?
- 回答:BQ2002的BAT引脚用于接收电池电压的取样信号。通常,电池电压会经过一个电阻分压网络后接入BAT引脚,以确保BQ2002能够正确检测电池电压。电阻分压网络的输入电阻不应小于200kΩ,以避免对电池电压造成过大影响。
- 如何设置BQ2002的充电速率?
- 回答:BQ2002的充电速率可以通过TM引脚进行设置。当TM引脚接地时,充电速率为1C(即电池容量的1倍)。此外,根据数据手册,TM引脚的不同电平还可能对应不同的充电速率,如C/2或2C,但具体设置需参考具体的数据手册或应用指南。
- BQ2002的CC引脚如何控制充电电流?
- 回答:BQ2002的CC引脚用于控制充电电流的开关,而不是直接调节充电电流的大小。CC引脚有两种状态:高阻态和接地态。当CC引脚为高阻态时,充电电流可以流动;当CC引脚接地时,充电电流被抑制。充电电流的大小通常由外部恒流源(如LM317等)的电阻设置决定,与CC引脚的状态无关。
二、功能实现与调试问题
- 为什么BQ2002没有进入快速充电模式?
- 回答:BQ2002没有进入快速充电模式可能由多种原因造成,如电池电压或温度不符合快速充电条件、TS引脚电压不在正常范围内、或外部电路设计问题等。建议检查电池电压和温度是否满足快速充电条件,同时检查TS引脚电压是否介于0.25VCC和0.4VCC之间(或1.25V和2.0V之间),并确认外部电路设计无误。
- BQ2002在充电过程中突然停止充电怎么办?
- 回答:BQ2002在充电过程中突然停止充电可能是由于电池过热、电池电压过高或外部电路故障等原因造成的。建议检查电池温度是否过高,电池电压是否超出BQ2002的承受范围,并检查外部电路是否有短路或断路等故障。如果问题依旧存在,可能需要更换BQ2002芯片或重新设计外部电路。
- 如何调整BQ2002的充电截止电压?
- 回答:BQ2002的充电截止电压通常是通过外部电路设计来调整的。具体方法可能因电路设计而异,但一般可以通过调整与BAT引脚相连的分压电阻的阻值来改变BQ2002检测到的电池电压值,从而调整充电截止电压。需要注意的是,在调整充电截止电压时,应确保电池不会因过充而损坏。
三、其他常见问题
- BQ2002支持哪些类型的电池?
- 回答:BQ2002通常支持多种类型的可充电电池,如镍氢电池、锂离子电池等。但具体支持的电池类型可能因BQ2002的版本或制造商而有所不同。因此,在使用BQ2002进行电池充电时,应参考具体的数据手册或应用指南以了解支持的电池类型。
- BQ2002的功耗如何?
- 回答:BQ2002的功耗通常较低,但具体功耗值可能因工作条件(如输入电压、输出电流、环境温度等)的不同而有所变化。在设计电路时,应充分考虑BQ2002的功耗对系统整体性能的影响,并采取相应的措施来降低功耗。
以上是针对快速充电IC(bq2002)电路的一些常见问题及其回答。需要注意的是,由于BQ2002的具体应用可能因电路设计、电池类型等因素而有所不同,因此在实际应用中应参考具体的数据手册或应用指南以获取准确的信息。
经过几年的技术积累和市场探索,智烽维在2013年成功完成了叠片型超级电容器的研发。这一产品的推出,不仅极大地丰富了公司的产品线,也进一步提升了智烽维在超级电容器领域的技术实力。叠片型超级电容器以其高性能、高可靠性和长寿命等特点,受到了市场的广泛关注和好评。
1955年,一家名为Dielectric Laboratories(DLI)的公司正式成立,它以其独特的电子元件研发能力在电子行业中崭露头角。起初,DLI专注于电容器的研发和生产,以其高品质和稳定性赢得了市场的认可。随着业务的逐步扩大,DLI不断投入研发资金,探索新的技术领域,为公司的长远发展奠定了坚实的基础。
ABI Electronics公司的起点可以追溯到其对电路板测试技术的深入研究。在创立初期,ABI便以开发出高精度、高效率的电路板故障检测仪为目标。通过对电路板测试技术的不断钻研和创新,ABI成功推出了一系列性能卓越的测试设备,这些设备不仅提高了电路板测试的准确性和效率,也极大地降低了测试成本,赢得了市场的广泛认可。
CETC深知人才是企业发展的根本。因此,公司一直注重人才培养和团队建设。公司设立了完善的培训体系,为员工提供各种学习和发展的机会。同时,CETC还积极引进国内外优秀人才,打造了一支高素质、专业化的团队。这支团队在公司的各个领域都发挥着重要作用,为公司的持续创新和发展提供了有力保障。
在电子薄膜电容器领域,EFC公司一直以其技术创新而闻名。公司创始人李博士带领着一支由资深工程师组成的研发团队,不断挑战技术极限。某年,他们成功研发出一种新型材料,这种材料不仅大大提高了电容器的性能,还降低了制造成本。这一突破性的创新使EFC公司在市场上脱颖而出,赢得了大量客户的青睐。
我知道wince中中断分中断服务例程(ISR)和中断服务线程(IST),那我要写一个按键的中断,需要完成哪些工作? 下面是终端过程: ①当内核的异常处理代码接收到一个来自硬件的中断时,内核会侦测到一个异常情况发生,并会提交这个硬件中断。 ②内核 ...… 查看全部问答∨ |
|
boot loader是如何引导内核的? 现在我的boot loader已经正常运行,kernel image也已经build,可是我想通过boot loader下载kernel image到flash,然后引导kernel的运行,但是每次都会出现“data abort”异常,请问是boot loader的那个部分出了问题 ...… 查看全部问答∨ |
问题描述: 一个串口类,采用线程,然后定义两个类对象,分别用于两个通信协议, RS485的MODBUS通信协议(假设为A协议)和用于GPRS上的通信协议(假设为B协议), 那么我原本是在串口类中采用回调函数来处理接收到的数据, 但是由于,底层的驱动是是每接收 ...… 查看全部问答∨ |
在本人的设计中,STM32的USART(波特率115200)需每秒发送4000+字节到上位机。开启了STM32串口dma发送方式(normal mo ...… 查看全部问答∨ |
这两天在做一个SD卡+usb mass storage的原型验证 老版的库被我误删除了 新网站貌似还没完全好 链接打不开 或者页面部分出处 方便的话发份给俺把 谢了 east3@163.com 老网站挺好的么 看来老外也喜欢折腾 哈哈… 查看全部问答∨ |
在同一个变量中,不要对同一个变量赋值,如果对同一变量多次赋值,那么它只执行最后一次赋值操作。 module test(clk,datain,dataout); input clk;input [2:0] datain;output [2:0] dataout; reg [2:0] dataout; always@(posedge clk)begin& ...… 查看全部问答∨ |
|
想搞个设计,把CVBS电视信号转换到群创5.6寸TFT LCD屏显示,请高手指引一下方向。 看过有公司用台湾MST芯片的方案做的产品,但苦于找不到资料,不知有没有其它的好方案?… 查看全部问答∨ |