新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > STEP FPGA驱动基于74HC595的数码管模块

STEP FPGA驱动基于74HC595的数码管模块

作者:时间:2023-11-28来源:电子森林收藏

硬件说明

在前面之前的入门教程中 独立显示 章节已为大家介绍了独立显示的相关内容,关于独立显示这里就不在赘述。我们的底板上有6位,根据驱动方法不同,有以下比较:
独立显示:控制每个数码管至少需要8个I/O口控制,6位数码管就需要6*8 = 48根信号线才能分别显示。独立显示实现简单,但是需要大量的信号线。
扫描显示:将每位数码管的同一段选信号连接在一起,这样我们就只需要8根段选信号和6根位选信号,共计14根信号。扫描显示可以有效节约I/O口资源,实现起来稍显复杂。

本文引用地址:http://www.eepw.com.cn/article/202311/453344.htm

我们小脚丫底板上使用的6位共阴极数码管,分析扫描显示的原理如下:
当某一时刻,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位数码管不显示,如果不明白可以参考入门教程中实验四: 数码管独立显示 章节
按照扫描的方式,一共分为6个时刻,段选端口分别对应输出6位数码管需要显示的字库数据,位选端口保持每个时刻只有1位数码管处于使能状态,6个时刻依次循环,当扫描频率足够高(例如当扫描频率等于100Hz)时,则在人眼看到的数码管显示就是连续的,我们看到的就是6个不同的数字。
上面为大家介绍了数码管的独立显示和扫描显示两种方法,扫描显示的方式使用了14个I/O口控制,相对于简单的处理器来讲14个I/O口也是非常多了,这里我们又使用了一款常见的驱动芯片74HC595,下面我们一起了解一下:
74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的6位数码管上,可以轻松完成数码管驱动任务。

不同的IC厂家都可以生产74HC595芯片,功能都是一样的,然而不同厂家的芯片手册对于管脚的命名会存在差异,管脚顺序相同,大家可以对应识别 上图是本设计中74HC595芯片的硬件电路连接,参考74HC595数据手册了解其具体用法,下图中我们了解到OE#(G#)和MR#(SCLR#)信号分别为输出使能(低电平输出)和复位管脚(低电平复位),OE#(G#)我们接GND让芯片输出使能,MR#(SCLR#)我们接VCC让芯片的移位寄存器永远不复位,如此FPGA只需要控制SHCP(SCK)、STCP(RCK)和DS(SER)即可。

74hc595引脚功能

74hc595逻辑图

74hc595时序图

数码管驱动程序框图

Verilog代码

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module:Segment_scan
//
// Author: Step
//
// Description: Display with Segment tube
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2015/11/11   |Initial ver
// --------------------------------------------------------------------
module Segment_scan
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input [3:0] seg_data_1, //SEG1 数码管要显示的数据
input [3:0] seg_data_2, //SEG2 数码管要显示的数据
input [3:0] seg_data_3, //SEG3 数码管要显示的数据
input [3:0] seg_data_4, //SEG4 数码管要显示的数据
input [3:0] seg_data_5, //SEG5 数码管要显示的数据
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管脚)

parameter CLK_DIV_PERIOD = 600; //分频系数 
localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam WRITE = 3'd2; 
localparam LOW = 1'b0;
localparam HIGH = 1'b1; //创建数码管的字库,字库数据依段码顺序有关//这里字库数据[MSB~LSB]={DP,G,F,E,D,C,B,A}
reg[7:0] seg [15:0]; initial begin
   seg[0] = 8'h3f;   //  0
   seg[1] = 8'h06;   //  1
   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;   //  Fend  //计数器对系统时钟信号进行计数
   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;
endend //根据计数器计数的周期产生分频的脉冲信号
reg clk_div;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
clk_div <= 1'b0;
end else begin
if(cnt==(CLK_DIV_PERIOD-1)) clk_div <= 1'b1;
else clk_div <= 1'b0;
endend //使用状态机完成数码管的扫描和74HC595时序的实现
reg [15:0] data_reg;
reg [2:0] cnt_main;
reg [5:0] cnt_write;
reg [2:0] state = IDLE;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin //复位状态下,各寄存器置初值
state <= IDLE;
cnt_main <= 3'd0;
cnt_write <= 6'd0;
sdio_out <= 1'b0;
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; s
tate <= 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 
endmodule

小结

本节主要为大家讲解了数码管显示的相关原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。



评论


相关推荐

技术专区

关闭