由于本文的工程相对较大,文件的代码压缩后传到CSDN,其中本文的设计源码为test9,所用quartus版本好位quartus15.1,链接如下http://download.csdn.net/detail/noticeable/9909850
设计目的:
选择四个按键, 通过按键使数码管数据可以稳定进行加1减1的操作,KEY3可以对数码管的显示位进行位选,key0可以进行复位操作,通过数码管位数和led显示的稳定加一,可以验证按键是否成功消抖。
知识点:1、什么是按键消抖
2、testbench 中随机数发生函数$random的使用。
进行按键消抖的原因和方法
对于机械按键,其按下后,由于其机械特性,会由于弹性产生一个抖动,其在电平输出上表现的就是一个如下的前沿抖动核后延抖动,对于这个 抖动,对于电平敏感的数据接收端来说是不希望出现的,因为其会导致接收端错误的计算按键按下的次数,使项目出现不确定的现象,所以对于按键的抖动,一般需要进行一个消抖的过程。
对于按键消抖,一般有两种方法,(1)经验表明,按键抖动一般持续的时间在5ms~10ms之间,所以理论上可以在检测到跳变后延时20ms再观察是否真的产生了跳变,这个方法被称之为延时重采样法,这个方法在单片机中用到的比较普遍。(2)第二章方法是当检测到按键处于某电平时,在之后的N个时钟周期内连续检测此按键的电平,如果一直不变,则读出此按键的电平值,这种方法叫做持续采样法。理论上来讲,后者的采样率相对于前者来说是大大增加了的,所以在准确性上来说,后者的准确性更高。这次设计,我们即采用持续采样法进行数据滤波。
设计过程:
首先,建立工程,编辑滤波文件key_filter.v
1 /* 2 系统上电后默认进入空闲IDEL状态,key_flag和key_state一起表示消抖的状态 3 key_flag key_state 4 0 1 平常状态 5 0 0 按下状态 6 1 0 确定是否抖动状态 7 */ 8 module key_filter(clk,rst_n,key_in,key_flag,key_state); 9 10 input clk; 11 input rst_n; 12 input key_in; 13 14 output reg key_flag; 15 output reg key_state; 16 17 18 localparam 19 IDEL = 4'b0001, //空闲状态 20 FILTER0 =4'b0010,//前抖动滤波 21 DOWN =4'b0100,//按键稳定时态 22 FILTER1 =4'b1000;//后抖动滤波 23 24 reg [3:0]state; 25 reg [19:0]cnt; 26 27 reg key_temp0,key_temp1; 28 reg en_cnt; 29 reg cnt_full; 30 wire pedge,nedge; 31 32 33 34 //边沿检测通过,俩个寄存器进行前后高低状态进行采样后对比 35 always@(posedge clk or negedge rst_n) 36 if(!rst_n) 37 begin 38 key_temp0<=1'b0; 39 key_temp1<=1'b0; 40 end 41 else begin 42 key_temp0<=key_in; 43 key_temp1<=key_temp0; 44 end 45 46 //反相器加一个与门,对寄存器里的两个值进行比较 47 assign nedge=!key_temp0&key_temp1; 48 assign pedge=key_temp0&(!key_temp1);//(!key_temp1)逻辑取反,按位取反(~key_temp1) 49 50 51 always@(posedge clk or negedge rst_n) 52 if (!rst_n) begin 53 54 state<=IDEL; 55 en_cnt=1'b0; 56 key_flag<=1'b0; 57 key_state<=1'b1; 58 end 59 else begin 60 case (state) 61 IDEL: 62 begin 63 key_flag<=1'b0; 64 if(nedge) begin 65 state=FILTER0; 66 en_cnt<=1'b1;//打开计数器开始计数 67 end 68 else 69 state <=IDEL; 70 end 71 FILTER0: 72 if(cnt_full)begin //就是按键事件是否满了20ms,且在这个过程中没有发生数据跳变,是的话说明没有问题,此时是稳定状态,可以进行下一个状态 73 en_cnt<=1'b0;//计数满了也需要关闭计数 74 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生 75 key_state<=1'b0; 76 state<=DOWN; 77 end 78 else if(pedge) begin 79 state <=IDEL;//检测到上升沿,说明是抖动状态,回到上一状态,并关闭计数器。 80 en_cnt<=1'b0; 81 end 82 else 83 state<=FILTER0; 84 85 86 DOWN: 87 begin 88 key_flag<=1'b0;//到了这个状态说明可以确定是按键电平 89 key_state<=1'b0; 90 if(pedge)begin //等待释放的上升沿信号,进入释放滤波状态 91 state <=FILTER1; 92 en_cnt<=1'b1; 93 end 94 else state<=DOWN; 95 end 96 97 98 99 FILTER1: 100 if(cnt_full)begin //20ms计数满了,没有发生数据跳变(即抖动),说明没有问题,可以释放了 101 102 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生 103 key_state<=1'b1; 104 state<=IDEL; 105 en_cnt<=0; 106 end 107 else if(nedge) begin 108 state <=DOWN;//检测到下降沿,说明是抖动状态,回到上一状态,并关闭计数器。 109 en_cnt<=1'b0; 110 end 111 else 112 state<=FILTER1; 113 default:begin 114 115 state<=IDEL; 116 en_cnt<=1'b0; 117 key_flag<=1'b0; 118 key_state <=1'b1; 119 end 120 endcase 121 end 122 123 124 125 //定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数20_000_000/20-1=999_999 //20MS/周期-1的数据,需要20位的寄存器。 126 always@(posedge clk or negedge rst_n) 127 if (!rst_n) 128 cnt<=20'd0; 129 else if (en_cnt) 130 cnt<=cnt+1'b1; 131 else 132 cnt<=20'd0; 133 134 always@(posedge clk or negedge rst_n) 135 if (!rst_n) 136 cnt_full <=1'd0; 137 else if(cnt==999_999) 138 cnt_full<=1'b1; 139 else 140 cnt_full <= 1'b0; 141 142 endmodule
程序编写完成后,可以通过state machine viewer 观察自己编写的状态机是否合理。
本次状态机的状态切换如下
编写testbench文件,并设定路径
`timescale 1ns/1ns `define clk_period 20 module key_filter_tb; reg clk; reg rst_n; reg key_in; wire key_flag; wire key_state; key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); initial clk =1; always #(`clk_period/2) clk=~clk; initial begin rst_n<=1'b0; key_in=1'b1; #(`clk_period*10) rst_n<=1'b1; key_in=1; #(`clk_period*10) //模拟按键抖动 key_in=0; #1000; key_in=1; #1400 key_in=0; #1340; key_in=1; #2600; key_in=0; #1600; key_in=1; #2340; key_in=0; #1300; key_in=1; #2000; key_in=0; #1000; key_in=1; #2050; key_in=0; #20_000_100; #100000;//保持稳定一段式时间。 key_in=1;//释放抖动 #2600; key_in=0; #1600; key_in=1; #2340; key_in=0; #1300; key_in=1; #20000; key_in=0; #1000; key_in=1; #2050; key_in=1; #20_100_000; #1000000; $stop; end endmodule
点击仿真按钮进行仿真(仿真时可把clk从wave显示中删掉,这样可以大大缩短仿真时间),仿真波形如图,说明整个设计是合理的。
通过观察-模拟抖动的testbench可以看出我们自己模拟抖动的过程又臭又长,毫无美感和技术可言,对此,我们可以采用task任务和随机数发生函数$random来进行精简。
对testbench修改后的文件如下
`timescale 1ns/1ns `define clk_period 20 module key_filter_tb; reg clk; reg rst_n; reg key_in; wire key_flag; wire key_state; reg [15:0]myrand; key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); initial clk =1; always #(`clk_period/2) clk=~clk; initial begin rst_n<=1'b0; key_in=1'b1; #(`clk_period*10) rst_n<=1'b1; press_key;//直接调用press_key即可模拟一次按键过程了 #100000; press_key; #10000; $stop; end task press_key; begin //按下抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key_in=~key_in; end key_in=0; #20_500_500 //释放抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key_in=~key_in; end key_in=1; #20_500_500; end endtask endmodule
仿真结果相对来说也更加明显清晰了许多。
仔细观察代码,感觉还是有些缀余,这里添加一个文件,将按键模型写到文件中,
`timescale 1ns/1ns `define clk_period 20 //模拟一个按键模型 module key_module(key); output reg key; reg [15:0]myrand; initial begin #(`clk_period*10) press_key;//直接调用press_key即可模拟一次按键过程了 #100000; press_key; #10000; $stop; end task press_key; begin //按下抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key=~key; end key=0; #20_500_500 //释放抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key=~key; end key=1; #20_500_500; end endtask endmodule
然后通过tb文件对模型进行调用(这里是想通过这个testbench的编写,了解到大型项目的文件分级的方法,使得项目程序更富有层次性。)
`timescale 1ns/1ns `define clk_period 20 module key_filter_tb; reg clk; reg rst_n; wire key_in; wire key_flag; wire key_state; key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); key_module key_module1(.key(key_in)); initial clk =1; always #(`clk_period/2) clk=~clk; initial begin rst_n<=1'b0; #(`clk_period*10) rst_n<=1'b1; end endmodule
将key_module添加到路径中
再点击仿真,能看到仿真波形也是正确的。
之前只是编写的是按键滤波程序,为了使完成项目,还需要很多其他的内容来进行补充。
对输入入的异步信号key_In,当采样点在不定态(即两个输入都在跳变得过程中),那么两个信号的输出就是不确定的,这时候就需要对异步信号进行处理,将其进行同步,为了使输出同步,需要加上两级的D触发器。
/* 系统上电后默认进入空闲IDEL状态,key_flag和key_state一起表示消抖的状态 key_flag key_state 0 1 平常状态 1 0 20ms没有发生跳变,说明按键事件已经可以确认发生了 0 1 说明现在保持着一直按下的状态 1 1 按键已经从按下状态释放了 */ module key_filter(clk,rst_n,key_in,key_flag,key_state); input clk; input rst_n; input key_in; output reg key_flag; output reg key_state; localparam IDEL = 4'b0001, //空闲状态 FILTER0 =4'b0010,//前抖动滤波 DOWN =4'b0100,//按键稳定时态 FILTER1 =4'b1000;//后抖动滤波 reg [3:0]state; reg [19:0]cnt; reg key_temp0,key_temp1; reg en_cnt; reg cnt_full; wire pedge,nedge; reg key_in_s0,key_in_s1; //key_in异步信号进行同步处理。 always@(posedge clk or negedge rst_n) if(!rst_n)begin key_in_s0<=1'b0; key_in_s1<=1'b0; end else begin key_in_s0<=key_in; key_in_s1<=key_in_s0; end //边沿检测通过,俩个寄存器进行前后高低状态进行采样后对比 always@(posedge clk or negedge rst_n) if(!rst_n) begin key_temp0<=1'b0; key_temp1<=1'b0; end else begin key_temp0<=key_in_s1; key_temp1<=key_temp0; end //反相器加一个与门,对寄存器里的两个值进行比较 assign nedge=!key_temp0&key_temp1; assign pedge=key_temp0&(!key_temp1);//(!key_temp1)逻辑取反,按位取反(~key_temp1) always@(posedge clk or negedge rst_n) if (!rst_n) begin state<=IDEL; en_cnt=1'b0; key_flag<=1'b0; key_state<=1'b1; end else begin case (state) IDEL: begin key_flag<=1'b0; if(nedge) begin state=FILTER0; en_cnt<=1'b1;//打开计数器开始计数 end else state <=IDEL; end FILTER0: if(cnt_full)begin //就是按键事件是否满了20ms,且在这个过程中没有发生数据跳变,是的话说明没有问题,此时是稳定状态,可以进行下一个状态 en_cnt<=1'b0;//计数满了也需要关闭计数 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生 key_state<=1'b0; state<=DOWN; end else if(pedge) begin state <=IDEL;//检测到上升沿,说明是抖动状态,回到上一状态,并关闭计数器。 en_cnt<=1'b0; end else state<=FILTER0; DOWN: begin key_flag<=1'b0;//到了这个状态说明可以确定是按键电平 key_state<=1'b0; if(pedge)begin //等待释放的上升沿信号,进入释放滤波状态 state <=FILTER1; en_cnt<=1'b1; end else state<=DOWN; end FILTER1: if(cnt_full)begin //20ms计数满了,没有发生数据跳变(即抖动),说明没有问题,可以释放了 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生 key_state<=1'b1; state<=IDEL; en_cnt<=0; end else if(nedge) begin state <=DOWN;//检测到下降沿,说明是抖动状态,回到上一状态,并关闭计数器。 en_cnt<=1'b0; end else state<=FILTER1; default:begin state<=IDEL; en_cnt<=1'b0; key_flag<=1'b0; key_state <=1'b1; end endcase end //定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数20_000_000/20-1=999_999 //20MS/周期-1的数据,需要20位的寄存器。 always@(posedge clk or negedge rst_n) if (!rst_n) cnt<=20'd0; else if (en_cnt) cnt<=cnt+1'b1; else cnt<=20'd0; always@(posedge clk or negedge rst_n) if (!rst_n) cnt_full <=1'd0; else if(cnt==999_999) cnt_full<=1'b1; else cnt_full <= 1'b0; endmodule
驱动程序写完后就需要补充应用程序了。
顶层文件的框图应该如图所示,在后面对其进行添加
图中rst_n和clk没连,意思是每个文件都需要连上。
下面,首先编写led_ctrl.v文件。
module led_ctrl(clk,rst_n,key_flag1,key_flag2,key_state1,key_state2,led); input clk,rst_n,key_flag1,key_flag2,key_state1,key_state2; output reg [3:0]led; always@(posedge clk or negedge rst_n) if(!rst_n) led<=4'b0000; else if(key_flag1&&!key_state1) led<=led+1'b1; else if(key_flag2&&!key_state2) led<=led-1'b1; else led<=led; endmodule
然后,根据上篇文章对segment的了解,在工程中添加文件segment.v对数码管显示进行控制:
这里为了设计的代码整洁及逻辑性,将文件进行分层,首先将segment的译码器segment_data单独写一个文件
module segment_data(clk,rst_n,data_temp,seg) ; input clk,rst_n; input [3:0]data_temp; output reg[6:0]seg; always@(posedge clk or negedge rst_n) if(!rst_n) seg<=7'b0000000; else begin case (data_temp) 4'h0: seg<=7'b1000000; 4'h1: seg<=7'b1111001; 4'h2: seg<=7'b0100100; 4'h3: seg<=7'b0110000; 4'h4: seg<=7'b0011001; 4'h5: seg<=7'b0010010; 4'h6: seg<=7'b0000010; 4'h7: seg<=7'b1111000; 4'h8: seg<=7'b0000000; 4'h9: seg<=7'b0010000; 4'ha: seg<=7'b0001000; 4'hb: seg<=7'b0000011; 4'hc: seg<=7'b1000110; 4'hd: seg<=7'b0100001; 4'he: seg<=7'b0000110; 4'hf: seg<=7'b0001110; endcase end endmodule
然后编写segment的显示控制主文件,要求显示时间的跳动,并且可以控制单加,并且能够通过按键快速调节显示,主文件segment_2.v文件如下
module segment_2(rst_n,clk,en ,key_flag1,key_flag2,key_flag3,key_state1,key_state2,key_state3,data_out); input clk;//50M input rst_n; input en; //使能端口,确定按键是否有效 reg cnt_full,second,miniter,hour; reg [5:0]sel; //位选(选择控制的哪个数码) reg [23:0]data; reg [25:0]cnt; reg [3:0]data_temp0,data_temp1,data_temp2,data_temp3,data_temp4,data_temp5;//待显示数据缓存 wire [6:0] seg_temp0,seg_temp1,seg_temp2,seg_temp3,seg_temp4,seg_temp5,seg_temp; input key_flag1,key_flag2,key_flag3,key_state1,key_state2,key_state3; output reg[41:0]data_out; segment_data segment_data0(.clk(clk), .rst_n(rst_n), .data_temp(data_temp0), .seg(seg_temp0)) ; segment_data segment_data1(.clk(clk), .rst_n(rst_n), .data_temp(data_temp1), .seg(seg_temp1)) ; segment_data segment_data2(.clk(clk), .rst_n(rst_n), .data_temp(data_temp2), .seg(seg_temp2)) ; segment_data segment_data3(.clk(clk), .rst_n(rst_n), .data_temp(data_temp3), .seg(seg_temp3)) ; segment_data segment_data4(.clk(clk), .rst_n(rst_n), .data_temp(data_temp4), .seg(seg_temp4)) ; segment_data segment_data5(.clk(clk), .rst_n(rst_n), .data_temp(data_temp5), .seg(seg_temp5)) ; always@(posedge clk or negedge rst_n) if (!rst_n)begin //按下key0键 data<=0; sel<=0; end else if(key_flag1&&!key_state1)begin//按下key1键 if(data==24'd235959) data<=0; else begin case(sel) 0: data=data+1'b1; 1: if(data[3:0]==4'b1001)//最大显示位为9 data[3:0]<=0; else data[3:0]<= data[3:0]+1'b1; 2: if(data[7:4]==4'b0101)//最大显示位为5 data[7:4]<=0; else data[7:4]<= data[7:4]+1'b1; 3: if(data[11:8]==4'b1001)//最大显示位为9 data[11:8]<=0; else data[11:8]<=data[11:8]+1'b1; 4:if(data[15:12]==4'b0101)//最大显示位为5 data[15:12]<=0; else data[15:12]<= data[15:12]+1'b1; 5: if(data[19:16]==4'b1001)//最大显示位为9 data[19:16]<=0; else data[19:16]<= data[19:16]+1'b1; 6: if(data[19:16]>=4'b0011) begin // 第5位大于3时 if(data[23:20]==4'b0001)//最大显示位为1 data[23:20]<=0; else if(data[23:20]==4'b0010)//最大显示位为2 data[23:20]<=0; end else data[23:20]=data[23:20]+1'b1; default sel<=0; endcase end end else if(key_flag2&&!key_state2) begin//按下key2键 if(data==0) data<=0; else begin case(sel) 0: data<=data-1'b1; 1: if(data[3:0]==0) data[3:0]<=0; else data[3:0]<= data[3:0]-1'b1; 2: if(data[7:4]==0) data[7:4]<=0; else data[7:4]<= data[7:4]-1'b1; 3: if(data[11:8]==0) data[11:8]<=0; else data[11:8]<= data[11:8]-1'b1; 4: if(data[15:12]==0) data[15:12]<=0; else data[15:12]<= data[15:12]-1'b1; 5: if(data[19:16]==0) data[19:16]<=0; else data[19:16]<= data[19:16]-1'b1; 6: if(data[23:20]==0) data[23:20]<=0; else data[23:20]<= data[23:20]-1'b1; default sel<=0; endcase end end else if(key_flag3&&!key_state3) begin //按下key3键,可以通过这个按键改固定位数 if(sel>=6) sel<=0; else begin sel<=sel+1; end end //时钟信号,每秒更换一次显示 else if(cnt==26'd49_999_999) //时钟跳变及跳变逻辑 begin if(data>=24'b0010_0011_0101_1001_0101_1001) //时钟到了最大时间23小时59分59秒? data<=0; else if(data<=24'b0010_0011_0101_1001_0101_1001) begin if(data[7:0]>=8'b0101_1001) begin //秒针到了59 data[7:0]<=0; if(data[15:8]>=8'b0101_1001) begin //分钟到了59 data[15:8]<=0; if(data[19:16]>=4'b0011)begin data[19:16]<=0; data[23:16]<=data[23:16]+1'b1;end else data[19:16]<=data[19:16]+1'b1; end else begin if(data[11:8]>=4'b1001) begin data[11:8]<=0; data[15:12]<=data[15:12]+1'b1; end else data[11:8]<=data[11:8]+1'b1; end end else if(data[7:0]<=8'b0101_1001) begin if(data[3:0]>=4'b1001)begin data[3:0]<=0; data[7:4]<=data[7:4]+1'b1;end else data[3:0]<=data[3:0]+1'b1; end end end //译码器 always@(*) begin data_temp0<=data[3:0]; data_temp1<=data[7:4]; data_temp2<=data[11:8]; data_temp3<=data[15:12]; data_temp4<=data[19:16]; data_temp5<=data[23:20]; data_out[6:0]<=seg_temp0; data_out[13:7]<=seg_temp1; data_out[20:14]<=seg_temp2; data_out[27:21]<=seg_temp3; data_out[34:28]<=seg_temp4; data_out[41:35]<=seg_temp5 ; end //定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数1000_000_000/20-1=49_999_999 //20MS/周期-1的数据,需要26位的寄存器。 always@(posedge clk or negedge rst_n) if (!rst_n) cnt<=26'd0; else if (en)begin if(cnt>26'd49_999_999) cnt<=0; else cnt<=cnt+1'b1;end else cnt<=26'd0; endmodule
编写key_top_tb文件,并进行链接,链接时需要把module模块添加到路径中。
`timescale 1ns/1ns `define clk_period 20 module key_top_tb; reg clk; reg rst_n; wire key_in1,key_in2; wire [3:0]led; reg press1,press2; key_top key_top0(.clk(clk), .rst_n(rst_n), .key_in1(key_in1), .key_in2(key_in2), .led(led) ); key_module key_module3( .press(press1), .key(key_in1) ); key_module key_module4( .press(press2), .key(key_in2) ); initial clk =1; always #(`clk_period/2) clk=~clk; initial begin rst_n = 1'b0; press1 = 0; press2 = 0; #(`clk_period*10) rst_n = 1'b1; #(`clk_period*10 + 1); press1 = 1; #(`clk_period*3) press1 = 0; #80_000_000; press1 = 1; #(`clk_period*3) press1 = 0; #80_000_000; press2 = 1; #(`clk_period*3) press2 = 0; #80_000_000; press2 = 1; #(`clk_period*3) press2 = 0; #80_000_000; $stop; end endmodule
编写过程中发现需要对module.v文件进行修改,否者无法对按键是key_in?进行区分,修改后如下
`timescale 1ns/1ns `define clk_period 20 //模拟一个按键模型 module key_module(press,key); output reg key; input press; reg [15:0]myrand; // initial begin // #(`clk_period*10) // press_key;//直接调用press_key即可模拟一次按键过程了 // #100000; // press_key; // #10000; // $stop; // end initial begin key=1'b1; end always@(posedge press) press_key; task press_key; begin //按下抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key=~key; end key=0; #20_500_500 //释放抖动过程 repeat(50)begin myrand=($random)%65536; //random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。 //加过好即为0~65535,不加就是-65535~65535 #myrand key=~key; end key=1; #20_500_500; end endtask endmodule
仿真波形如下,按照仿真文件,led数据变化为1111~1110~1101~1110~1111,加减操作都成功完成了
设定引脚。
烧写程序,查看现象,将程序烧写到友晶的开发板中,可以观察到最后两个数码管按照秒钟的规律跳变,并且通过默认的按键KEY1,KEY2可以加减计数6位的数码管,数码管可以正常进位,按下key3后,可以通过按键次数选择要调节的数码管的位数。