差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
数码管模块 [2017/05/26 14:16]
anran [基于STEP FPGA的矩阵按键驱动]
数码管模块 [2022/07/20 10:27] (当前版本)
zhijun [小结]
行 1: 行 1:
-======STEP FPGA驱动基于74HC595的数码管模块======+## STEP FPGA驱动基于74HC595的数码管模块
  
 本节将和大家一起使用FPGA驱动底板上的6位数码管实现动态显示。 本节将和大家一起使用FPGA驱动底板上的6位数码管实现动态显示。
  
  
-====硬件说明==== +### 硬件说明
-------- +
-在键盘中按键数量较多时,为了减少I/​O口的占用,通常将按键排列成矩阵形式,使用行线和列线分别连接到按键开关的两端,这样我们就可以通过4根行线和4根列线(共8个I/​O口)连接16个按键,而且按键数量越多优势越明显。+
  
-FPGA驱动矩阵按键模块首先我们来了解矩阵按键硬件连接+在前面之前的入门教程中[[4. 数码管显示| 数码管独立显示 ]]章节已为大家介绍了数码管独立显示的相关内容关于独立显示这里就不在赘述。我们的底板上有6位数码管,根据驱动方法不同,有以下比较
 \\ \\
-{{ :​矩阵按键.jpg?​800 |}}+独立显示:控制每个数码管至少需要8个I/​O口控制,6位数码管就需要6*8 = 48根信号线才能分别显示。独立显示实现简单,但是需要大量的信号线。
 \\ \\
-上图为4x4矩阵按键硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),时列线通过上拉电阻连接到VCC电压(3.3V),对于矩阵按键来讲: +扫描显示:将每位数码管的同一段选信号连接,这样我们就需要8段选信号和6根位选信号,共计14信号。扫描显示可以有效节约I/​O口资源实现起来稍显复杂 
-  - 4根行线是输入的,是由FPGA控制拉高或拉低, +{{ :​6位数码管.jpg?​1600 |}}
-  - 4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA +
-当某时刻FPGA控制4根行线分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时, +
-  *   ​对于K1、K2、K3、K4按键:按下时对应4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,不按时对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, +
-  *   ​对于K5~~~K16之间的按键:无论按下与否,对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, +
-通过上面的描述:在一时刻有K1、K2、K3、K4按键被按下,才会导致4列线输出COL1=0、COL2=0、COL3=0、COL4=0,否则COL1=1、COL2=1、COL3=1、COL4=1,反之当FPGA检测到列线(COL1、COL2、COL3、COL4)中有低电平信号对应的K1、K2、K3、K4按键应该是被按下了。 +
- +
-按照扫描的方式,一分为4个时刻,分别对应4行线中的一根拉低,4个时刻一次循环,这样就完成了矩阵按键的全部扫描检测,我们在程序中以这4个时刻对应状态机的4个状态。 +
-至于循环的周期,根据我们基础教程里知,按键抖动的不稳定时间在10ms所以对同一个按键采样的周期大于10ms,这同样取20ms时间。20ms时间对应4个状态,每5分钟进行一次状态转换+
 \\ \\
-{{ :​矩阵按键程序框图.jpg?​800 |}}+我们小脚丫底板上使用的6位共阴极数码管,分析扫描显示的原理如下:
 \\ \\
-{{ :​矩阵按键扫描法原理.jpg |}}+当某一时刻,FPGA控制8根公共的段选接口输出数字1对应的数码管字库数据8'​h06(DP=0、G=0、F=0、E=0、D=0、C=1、B=1、A=0)时,同时控制6位数码管只有第1位使能(DIG1=0、DIG2=1、DIG3=1、DIG4=1、DIG5=1、DIG6=1)这样我们会看到第1位数码管显示数字1,其余5位数码管不显示,如果不明白可以参考入门教程中实验四:[[4数码管显示数码管独立显示 ]]章节
 \\ \\
-====Verilog代码==== +按照扫描的方式,一共分为6个时刻,段选端口分别对应输出6位数码管需要显示的字库数据,位选端口保持每个时刻只有1位数码管处于使能状态,6个时刻依次循环,当扫描频率足够高(例如当扫描频率等于100Hz)时,则在人眼看到的数码管显示就是连续的,我们看到的就是6个不同的数字。 
-------+\\ 
 +上面为大家介绍了数码管的独立显示和扫描显示两种方法,扫描显示的方式使用了14个I/​O口控制,相对于简单的处理器来讲14个I/​O口也是非常多了,这里我们又使用了一款常见的驱动芯片74HC595,下面我们一起了解一下: 
 +\\ 
 +74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/​O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的6位数码管上,可以轻松完成数码管驱动任务。 
 +\\ 
 +{{ :​74hc595电路.jpg?​1000 |}} 
 +\\ 
 +不同的IC厂家都可以生产74HC595芯片,功能都是一样的,然而不同厂家的芯片手册对于管脚的命名会存在差异,管脚顺序相同,大家可以对应识别 
 +上图是本设计中74HC595芯片的硬件电路连接,参考74HC595数据手册了解其具体用法,下图中我们了解到OE#​(G#​)和MR#​(SCLR#​)信号分别为输出使能(低电平输出)和复位管脚(低电平复位),OE#​(G#​)我们接GND让芯片输出使能,MR#​(SCLR#​)我们接VCC让芯片的移位寄存器永远不复位,如此FPGA只需要控制SH_CP(SCK)、ST_CP(RCK)和DS(SER)即可。 
 +\\ 
 +{{ :​74hc595引脚功能.jpg |74hc595引脚功能}} 
 +\\ 
 +{{ :​74hc595逻辑图.jpg |74hc595逻辑图}} 
 +\\ 
 +{{ :​74hc595时序图.jpg |74hc595时序图}} 
 +\\ 
 +{{ :​数码管程序框图.jpg?​1000 |数码管驱动程序框图}} 
 +\\ 
 + 
 +### Verilog代码 
 <code verilog> <code verilog>
  
行 34: 行 44:
 // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​ // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
-// Module: ​Array_KeyBoard+// Module:Segment_scan ​
 //  // 
 // Author: Step // Author: Step
 //  // 
-// Description: ​Array_KeyBoard+// Description: ​Display with Segment tube 
 +//  
 +// Web: www.stepfpga.com
 //  // 
-// Web: www.stepfapga.com 
-// 
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
 // Code Revision History : // Code Revision History :
行 48: 行 58:
 // V1.0     ​|2015/​11/​11 ​  ​|Initial ver // V1.0     ​|2015/​11/​11 ​  ​|Initial ver
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
-module ​Array_KeyBoard #+module ​Segment_scan
 ( (
- parameter NUM_FOR_200HZ = 60000 //​定义计数器cnt的计数范围,例化时可更改 +input clk_in,​ //​系统时钟 
-+input rst_n_in,​ //​系统复位,低有效 
-+input [3:0] seg_data_1, //SEG1 数码管要显示的数据 
- input clk_in,​ //​系统时钟 +input [3:0] seg_data_2, //SEG2 数码管要显示的数据 
- input rst_n_in,​ //​系统复位,低有效 +input [3:0] seg_data_3,​ //​SEG3 数码管要显示的数据 
- input [3:0] col, //矩阵按键列接口 +input [3:​0] seg_data_4,​ //​SEG4 数码管要显示的数据 
- output reg [3:0] row, //矩阵按键行接口 +input [3:​0] seg_data_5,​ //​SEG5 数码管要显示的数据 
- output reg [15:0] key_out //消抖后信号+input [3:​0] seg_data_6,​ //​SEG6 数码管要显示的数据 
 +input [5:​0] seg_data_en,​ //​各位数码管数据显示使能,[MSB~LSB]=[SEG6~SEG1] 
 +input [5:​0] seg_dot_en,​ //​各位数码管小数点显示使能,[MSB~LSB]=[SEG6~SEG1] 
 +output reg rclk_out,​ //​74HC595的RCK管脚 
 +output reg sclk_out,​ //​74HC595的SCK管脚 
 +output reg sdio_out //74HC595SER管脚
 ); );
-/* 
-因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态 
-在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样 
-周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间 
-对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理 
-*/  
- localparam STATE0 = 2'b00; 
- localparam STATE1 = 2'b01; 
- localparam STATE2 = 2'b10; 
- localparam STATE3 = 2'b11; 
  
- //计数分频实现5ms周期信号clk_200hz +parameter CLK_DIV_PERIOD = 600; //​分频系数 
- reg [15:0] cnt+ 
- reg clk_200hz+localparam IDLE = 3'​d0;​ 
- always@(posedge clk_in or negedge rst_n_in) begin +localparam MAIN = 3'​d1;​ 
- if(!rst_n_in) begin //​复位时计数器cnt清零,clk_200hz信号起始电平为低电平 +localparam WRITE = 3'​d2;​ 
- cnt <= 16'​d0;​ + 
- clk_200hz ​<= 1'​b0;​ +localparam LOW = 1'​b0;​ 
- end else begin +localparam HIGH = 1'​b1;​ 
- if(cnt >= ((NUM_FOR_200HZ>>​1) ​- 1)) begin //​数字逻辑中右移1位相当于除2 + 
- cnt <= 16'd0+//创建码管的字库,字库数据依段码顺序有关 
- clk_200hz <= ~clk_200hz;​ //​clk_200hz信号取反 +//​这里字库数据[MSB~LSB]={DP,​G,​F,​E,​D,​C,​B,​A} 
- end ​else begin +reg[7:0] seg [15:0];  
- cnt <= cnt + 1'b1; +initial begin 
- clk_200hz <= clk_200hz;​ +    seg[0] = 8'​h3f; ​  // ​ 0 
- end +    seg[1] = 8'​h06; ​  // ​ 1 
- end+    seg[2] = 8'​h5b; ​  // ​ 2 
 +    seg[3] = 8'​h4f; ​  // ​ 3 
 +    seg[4] = 8'​h66; ​  // ​ 4 
 +    seg[5] = 8'​h6d; ​  // ​ 5 
 +    seg[6] = 8'​h7d; ​  // ​ 6 
 +    seg[7] = 8'​h07; ​  // ​ 7 
 +    seg[8] = 8'​h7f; ​  // ​ 8 
 +    seg[9] = 8'​h6f; ​  // ​ 9 
 + seg[10] = 8'​h77; ​  // ​ A 
 +    seg[11] = 8'​h7c; ​  // ​ b 
 +    seg[12] = 8'​h39; ​  // ​ C 
 +    seg[13] = 8'​h5e; ​  // ​ d 
 +    seg[14] = 8'​h79; ​  // ​ E 
 +    seg[15] = 8'​h71; ​  // ​ F 
 +end  
 +  
 +//计数器对系统时钟信号进行计数 
 +reg[9:0] cnt=0
 +always@(posedge clk_in or negedge rst_n_in) begin 
 + if(!rst_n_in) begin 
 + cnt <= 1'​b0;​ 
 + end else begin 
 + if(cnt>​=(CLK_DIV_PERIOD-1)) cnt <= 1'b0
 + else cnt <= cnt + 1'b1;
  end  end
 +end
  
- reg [1:​0] c_state;​ +//根据计数器计数的周期产生分频的脉冲信号 
- //状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效 +reg clk_div; ​ 
- always@(posedge ​clk_200hz ​or negedge rst_n_in) begin +always@(posedge ​clk_in ​or negedge rst_n_in) begin 
- if(!rst_n_in) begin + if(!rst_n_in) begin 
- c_state ​<= STATE0; + clk_div ​<= 1'b0
- row <= 4'b1110+ end else begin 
- end else begin + if(cnt==(CLK_DIV_PERIOD-1)) clk_div ​<= 1'b1
- case(c_state) + else clk_div ​<= 1'b0;
- STATE0: begin c_state <STATE1; row <4'​b1101;​ end //​状态c_state跳转及对应状态下矩阵按键的row输出 +
- STATE1: begin c_state ​<= STATE2; row <= 4'b1011end +
- STATE2: begin c_state ​<= STATE3; row <= 4'b0111end +
- STATE3: begin c_state <= STATE0; row <= 4'​b1110;​ end +
- default:​begin c_state <= STATE0; row <= 4'​b1110;​ end +
- endcase +
- end+
  end  end
-  +end 
- //因为每个状态中单行有效,通过对列接口电平状态采样得到对应4个按键状态,依次循环 + 
- always@(negedge clk_200hz ​or negedge rst_n_in) begin +//使用状态机完成数码管扫描和74HC595时序实现 
- if(!rst_n_in) begin +reg [15:​0] data_reg;​ 
- key_out ​<= 16'hffff+reg [2:​0] cnt_main;​ 
- end else begin +reg [5:​0] cnt_write;​ 
- case(c_state+reg [2:0] state = IDLE; 
- STATE0:key_out[3:0] <= col; //采集当前状态的数据赋值给对应寄存器位 +always@(posedge clk_in ​or negedge rst_n_in) begin 
- STATE1:key_out[7:4] <= col+ if(!rst_n_in) begin //​复位状态下,各寄存器置初值 
- STATE2:key_out[11:8] <= col+ state ​<= IDLE; 
- STATE3:key_out[15:12] <= col+ cnt_main <= 3'd0
- default:key_out ​<= 16'hffff+ cnt_write <= 6'​d0;​ 
- endcase + sdio_out <= 1'​b0;​ 
- end+ sclk_out <= LOW; 
 + rclk_out <= LOW; 
 + end else begin 
 + case(state) 
 + IDLE:​begin //​IDLE作为第一个状态,相当于软复位 
 + state <= MAIN; 
 + cnt_main <= 3'​d0;​ 
 + cnt_write <= 6'​d0;​ 
 + sdio_out <= 1'​b0;​ 
 + sclk_out <= LOW; 
 + rclk_out <= LOW; 
 + end 
 + MAIN:begin 
 + if(cnt_main >= 3'd5) cnt_main <= 1'​b0;​ 
 + else cnt_main <= cnt_main + 1'​b1;​ 
 + case(cnt_main) 
 + //​对6位数码管逐位扫描 
 + 3'​d0: begin  
 + state ​<= WRITE; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序 
 + data_reg <= {seg[seg_data_1]|(seg_dot_en[0]?​8'​h80:​8'​h00),​seg_data_en[0]?​8'​hfe:​8'​hff};​ 
 + //​data_reg[15:​8]为段选,data_reg[7:​0]为位选 
 + //​seg[seg_data_1] ​ 是根据端口输入获取相应字库数据 
 + //​seg_dot_en[0]?​8'​h80:​8'​h00 ​ 是根据小数点显示使能信号 控制SEG1数码管的小数点DP段的电平 
 + //​seg_data_en[0]?​8'​hfe:​8'​hff ​ 是根据数据显示使能信号 控制SEG1数码管的位选引脚的电平 
 + end 
 + 3'​d1: begin  
 + state <= WRITE; 
 + data_reg <= {seg[seg_data_2]|(seg_dot_en[1]?​8'​h80:8'​h00),​seg_data_en[1]?​8'​hfd:​8'​hff};​  
 + end 
 + 3'​d2:​ begin  
 + state ​<= WRITE
 + data_reg <= {seg[seg_data_3]|(seg_dot_en[2]?​8'​h80:8'​h00),​seg_data_en[2]?​8'​hfb:8'hff};  
 + end 
 + 3'​d3:​ begin  
 + state <= WRITE; 
 + data_reg <= {seg[seg_data_4]|(seg_dot_en[3]?​8'​h80:​8'​h00),​seg_data_en[3]?​8'​hf7:​8'​hff};​  
 + end 
 + 3'​d4:​ begin  
 + state ​<= WRITE; 
 + data_reg <= {seg[seg_data_5]|(seg_dot_en[4]?​8'​h80:​8'​h00),​seg_data_en[4]?​8'​hef:​8'​hff};​ 
 + end 
 + 3'​d5:​ begin  
 + state <= WRITE; 
 + data_reg <= {seg[seg_data_6]|(seg_dot_en[5]?​8'​h80:​8'​h00),​seg_data_en[5]?​8'​hdf:​8'​hff};​  
 + end 
 + default:​ state <= IDLE; 
 + endcase 
 + end 
 + WRITE:begin 
 + if(clk_div) begin //​74HC595的串行时钟有速度要求,需要按照分频后的节拍 
 + if(cnt_write >= 6'd33) cnt_write <= 1'​b0;​ 
 + else cnt_write <= cnt_write + 1'​b1;​ 
 + case(cnt_write) 
 + //​74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用 
 + //​74HC595的时序实现,参考74HC595的芯片手册 
 + 6'​d0: ​ begin sclk_out <= LOW; sdio_out <= data_reg[15]; end //​SCK下降沿时SER更新数据 
 + 6'​d1 begin sclk_out <= HIGH; end //​SCK上升沿时SER数据稳定 
 + 6'​d2: ​ begin sclk_out <= LOW; sdio_out <= data_reg[14];​ end 
 + 6'​d3: ​ begin sclk_out <= HIGH; end 
 + 6'​d4: ​ begin sclk_out <= LOW; sdio_out <= data_reg[13];​ end 
 + 6'​d5: ​ begin sclk_out <= HIGH; end 
 + 6'​d6: ​ begin sclk_out <= LOW; sdio_out <= data_reg[12]; end 
 + 6'​d7: ​ begin sclk_out ​<= HIGHend 
 + 6'​d8 begin sclk_out ​<= LOW; sdio_out <= data_reg[11];​ end 
 + 6'​d9: ​ begin sclk_out <= HIGH; end 
 + 6'​d10:​ begin sclk_out <= LOW; sdio_out <= data_reg[10];​ end 
 + 6'​d11:​ begin sclk_out <= HIGH; end 
 + 6'​d12:​ begin sclk_out <= LOW; sdio_out <= data_reg[9];​ end 
 + 6'​d13:​ begin sclk_out <= HIGH; end 
 + 6'​d14:​ begin sclk_out <= LOW; sdio_out <= data_reg[8];​ end 
 + 6'​d15:​ begin sclk_out <= HIGH; end 
 + 6'​d16:​ begin sclk_out <= LOW; sdio_out <= data_reg[7];​ end 
 + 6'​d17:​ begin sclk_out <= HIGH; end 
 + 6'​d18:​ begin sclk_out <= LOW; sdio_out <= data_reg[6];​ end 
 + 6'​d19:​ begin sclk_out <= HIGH; end 
 + 6'​d20:​ begin sclk_out <= LOW; sdio_out <= data_reg[5];​ end 
 + 6'​d21:​ begin sclk_out <= HIGH; end 
 + 6'​d22:​ begin sclk_out <= LOW; sdio_out <= data_reg[4];​ end 
 + 6'​d23:​ begin sclk_out <= HIGH; end 
 + 6'​d24:​ begin sclk_out <= LOW; sdio_out <= data_reg[3];​ end 
 + 6'​d25:​ begin sclk_out <= HIGH; end 
 + 6'​d26:​ begin sclk_out <= LOW; sdio_out <= data_reg[2];​ end 
 + 6'​d27:​ begin sclk_out <= HIGH; end 
 + 6'​d28:​ begin sclk_out <= LOW; sdio_out <= data_reg[1];​ end 
 + 6'​d29:​ begin sclk_out <= HIGH; end 
 + 6'​d30:​ begin sclk_out <= LOW; sdio_out <= data_reg[0];​ end 
 + 6'​d31:​ begin sclk_out <= HIGH; end 
 + 6'​d32:​ begin rclk_out <= HIGH; end //​当16位数据传送完成后RCK拉高,输出生效 
 + 6'd33: begin rclk_out <= LOWstate <= MAIN; end 
 + default:​ state <= IDLE; 
 + endcase 
 + end else begin 
 + sclk_out <= sclk_out; 
 + sdio_out <= sdio_out; 
 + rclk_out <= rclk_out; 
 + cnt_write <= cnt_write;​ 
 + state <= state; 
 + end 
 + end 
 + default: state <= IDLE; 
 + endcase
  end  end
 +end
  
 endmodule endmodule
行 126: 行 249:
 \\  ​ \\  ​
 \\  ​ \\  ​
-====引脚分配==== + 
-------- + 
-综合(synthesize)完成之后一定配置FPGA引脚到应的外设,这样下载FPGA程序后才能达到我们想要的效果+### 小结 
 + 
 +本节主为大家讲解了数码管显示的相关原理及软件大家掌握同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试
 \\ \\
-我们使用PCLK充当程序中的clk_in,使用按键KEY1充当rst_n_in,col和row的引脚按照高低顺序分频,key_out可以分配给LED灯、三色灯、PMOD等FPGA控制输出的引脚上,通过观察或示波器测量检验设计 +如果你对Diamond软件的使用不了解,请参考这里:[[lattice_fpga|Diamond的使用]]。 
-\\ + 
-{{ :​step_baseboard_v2.2_引脚分配.jpg?​1200 |}} +### 相关资料
-\\ +
-====小结==== +
------- +
-本节主要为大家讲解了矩阵按键的工作原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。 +
-\\ +
-如果你对Diamond软件的使用不了解,请参考这里:[[lattice_diamond的使用|Diamond的使用]]。+
  
-====相关资料==== 
------- 
 \\ \\
-使用[[STEP-MXO2第二代]]的矩阵按键程序:{{:​array_keyboard_for_mxo2.rar|}}+使用[[STEP-MXO2第二代]]的数码管扫描程序: ​ ​后续会有下载连接 ​ 待更新
 \\ \\
-使用[[STEP-MAX10]]的矩阵按键程序:{{:​array_keyboard_for_max10.rar|}}+使用[[STEP-MAX10]]的数码管扫描程序: ​ ​后续会有下载连接 ​ 待更新
 \\ \\