差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
数码管模块 [2017/05/27 09:16] anran [相关资料] |
数码管模块 [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 //74HC595的SER管脚 | ||
); | ); | ||
- | /* | ||
- | 因使用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'b1011; end | + | |
- | STATE2: begin c_state <= STATE3; row <= 4'b0111; end | + | |
- | 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 <= HIGH; end | ||
+ | 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 <= LOW; state <= 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第二代]]的数码管扫描程序: 后续会有下载连接 待更新 | ||
\\ | \\ | ||
- | 后期会有链接到云盘 | + | 使用[[STEP-MAX10]]的数码管扫描程序: 后续会有下载连接 待更新 |
\\ | \\ |