【红色飓风Nano二代测评】外接键盘
0赞今天是星期一,按照惯例应该早上5点就起床赶高铁出差的,由于要开会什么的推迟一天,嘻嘻总算是有空隙可以揣口气了(唉,瞧咱这点追求)。今天一早来到实验室,首先登入http://blog.chinaaet.com/,看到小伙伴们的博文写的很不错,顿时感到很惭愧。于是下决心今后不管多忙,一定要随时将自己做的工程什么的拿出来分享,这样做有几个好处:1.对自己做过的工程进行一次梳理,做个总结,留个记录;2.在这个处处是大神的世界里,抛砖引玉,共同学习;3.敢于抛砖也要敢于挨砖,大家的批评指正是今后前进路上的垫脚石;低头拉车,的同时也要抬头看路...(写个博客有必要这么兴奋么,废话不多说了,进入正题)。
看到宋恒写的插补博文了,感触很多。想当初,我们设计完了直线插补后再做圆弧插补,完成圆弧插补之后,我们发现了一些bug,比方说对于特殊的圆弧段会出现卡死的情况,个别情况下运行的脉冲数会和设定的脉冲数有1到2个脉冲的出入。然后我们专门花了1个星期的时间找问题然后修复问题,然后各种角度去验证IP核的稳定性。可以说现在的插补已经相当完美了。在那段时间里我们苦恼过,迷茫过,彷徨过,顿悟过。呵呵,呵呵...
看这架势,宋恒准备是做一个数控系统啊,好的,我这边锦上添花将这几天做的一个按键扫描的程序发出来,欢迎拍砖(纳尼,说这么多就是为了引出这句话?)
我所选用的是一款164的键盘,也就是通过74ls164这个芯片,数据手册:http://wenku.baidu.com/view/231f66e94afe04a1b071de5c.html,作为矩阵键盘的列扫描,然后用另外一组IO口做行扫描,这样只需要两个IO口(164芯片的时钟口,数据口),就可以扫描8列,如果我再用三个IO口作为行的接受扫描,这样只要5个IO口就可以扫描24个按键。而且164这个芯片可以级联,这样就可以扫描更多的列了。
如上图1.1所示,是164芯片的时序图,从图中可以看到,AB同时为高的时候,在时钟的上升沿,会将当前的H状态锁存在QA口,并随着时钟的上升沿,后续的QB,QC...始终保持着前一端口的状态。对于这一点,正是键盘列扫描需要的。只要我在时钟的第一个上升沿之前将AB同时拉高,这样时钟的上升沿到来之后,QA口就是高电平了。然后在时钟的第二个上升沿之前将AB拉低。这样地二个时钟上升沿到来后,QA为低、QB为高,如此循环。经过8个上升沿后,QA-G为低、QH为高。如果此时我们不对AB操作,则再过一个时钟周期,QA-H都为低了(这个并不是我们希望的,我们希望像流水灯一样,在QA-H中任意时间有且只有一个为高,这样就完成列扫描了)。所以此时我们应该在第九个时钟的上升沿到来之前将AB再同时拉高,然后第十个时钟上升沿到来之前将AB拉低。
于是我们设计一个三位的计数器shift,计数器为零的时候将AB拉高,其他情况下将AB拉低。考虑到每次时钟的上升沿锁存数据,我们最好是在时钟的下降沿将这个三位的计数器自加一。这样加到7后,再加一又归零了,省了判断了。这样行扫描就做完了。
下面我们再做行扫描,行扫描就相对简单多了,只要我们判断我们三个行的IO口不全为零,就表示有按键按下了,这个时候我们需要停止行扫描(即停止行扫描时钟),此时QA-H会一直保持当前状态。这个时候我们就可以延时消抖了。延迟一段时间后(一般是10ms),我们再次判断行的三个IO口状态,如果是和消抖前的状态一样,就表示确实是当前按键按下了。这个时候我们就可以输出当前的按键值。我们的按键值由两部分组成,一个就是三个行的IO口状态,一个就是扫描到哪一列了,呵呵,我们之前的那个三位的计数器shift不就可以反映出当前高电平在哪一列么?所以 键值 = {iDat[2:0],shift[2:0]};
下面是源代码,欢迎拍砖:
module KeyScan ( input clk, //时钟 input rst_n, //复位 output reg[7:0] KDate, //键值 ////-------------------------------------164键盘 output KeyClk, //键盘时钟 output KeyDat, //键盘扫描码 input[2:0] iDat //按键行 ); reg START; //下降沿变换 上升沿锁存数据 reg[6:0] clkCount; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin clkCount <= 7'd0; end else if(START) begin clkCount <= clkCount + 1'b1; end end //KeyClk 为50MHz时钟64分频 即:781.25Khz assign KeyClk = clkCount[6]; reg[2:0] clkr; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin clkr <= 3'd7; end else begin clkr <= {clkr[1:0],KeyClk}; end end //wire clkRising = (clkr[1:0] == 2'b01); wire clkFalling = (clkr[1:0] == 2'b10); //得到164时钟下降沿 reg CLR; reg[2:0] shift; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin shift <= 3'd0; end else if(clkFalling) begin shift <= shift + 1'b1; end end assign KeyDat = ~(shift == 3'd0); reg[19:0] Delay10MS; reg clrDly; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin Delay10MS <= 20'd0; end else if(clrDly) begin Delay10MS <= 20'd0; end else begin Delay10MS <= Delay10MS + 1'b1; end end reg[1:0] Status; reg[2:0] shiftr; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin START <= 1'b1; Status <= 2'd0; clrDly <= 1'b1; KDate <= 8'd0; shiftr <= 3'd0; end else begin case(Status) 2'd0: begin clrDly <= 1'b1; START <= 1'b1; KDate <= 8'd0; shiftr <= 3'd0; Status <= (iDat != 3'b111) ? (Status + 1'b1) : (Status); end 2'd1: begin //延时10ms START <= 1'b0; //停止扫描 clrDly <= 1'b0; Status <= (Delay10MS >= 20'd500000) ? (Status + 1'b1) : (Status); end 2'd2: begin shiftr <= (KeyClk) ? (shift) : (shift - 1'b1); //这里这么做的主要原因是:如果我在时钟下降沿和时钟上升沿之间按下键盘了,此时shift已经自加一了,但是当前端口上面锁存的是(shift-1)的值 Status <= (iDat != 3'b111) ? (Status + 1'b1) : (8'd0); end 2'd3: begin KDate <= {1'b0,shiftr,1'b0,iDat}; Status <= (iDat != 3'b111) ? (Status) : (8'd0); end endcase end end endmodule
程序很简单,细心的童鞋可能会发现一点问题,如果是几个键同时按下会出现什么情况呢?对于目前来说同一列上面任意几个键按下可以识别,但是如果在不同的列上按下会优先识别先按下的那个键。功能还有待丰富,比方说组合键,单击、双击、多击等...
有什么好的建议和意见请及时与我联系QQ584291057(就说是chinaaet上看到的,额貌似这句话有点耳熟)。