FPGA 15 基础 基于HC595的动态数码管实现
数码管显示驱动魔块(包括内部实现) 如下所示:
模块名称 : HEX8
主要功能 :本次设计了 8个数码管显示电路,输入数据端,disp_data [31:0]共32位,每4位作为一个数码管的显示数据,这样的话一次输入了8个数码管的数据,数据输出端口,分别输出对应数码管的数据编码信号和对应数据的数码管片选信号。
设计流程:我们在这次实验中,为了减少FPGA 端口的占用,因此,我们使用了数据移位的方式来进行显示. 所以,在模块的内部,我们设计了 1Khz 的时钟分频模块来进行单个数据的显示,每来一个时钟周期,对数据端口进行选择,然后再输出: 端口移位寄存器输出解码的数码管信号和对应数码管的片选信号。
此外:为了再次减少IO 占用 ,我们将seg[7:0]和 sel[7:0]的信号转换成串行数据发送到外部端口(3个端口)。外部使用了HC595对数据进行串并转换,结构如下:
数码管模块代码如下所示:
// 8个数码管驱动模块
module HEX8(
Clk ,
Rst_n ,
En ,
disp_data ,
sel ,
seg ,
);
input Clk ; //50M
input Rst_n ;
input En ; //数码管显示使能信号
input [31:0]disp_data ; //8个数码管数据
output [7:0]sel ; //数码管位选 8个数码管
output reg [6:0]seg ; //数码管段选(当前选中的数据)
// 内部寄存器(变量)定义
/* 分频器1KHz模块 */
reg [14:0]divder_cnt ;
/* 【8位循环】移位寄存器模块 */
reg [7:0]sel_r ;
/* 8选1数据选择模块 */
reg [3:0]data_temp ; //数码管待显示的数据
// 分频器1KHz模块
//分频器模块 周期是1S = 50_000_000(f)* 1(cnt) 20ns(Clk)
//我们要分的周期 1S = 1000(f)*50_000(cnt) * 20ns(C'l'k) ==》cnt/2 = 25000 执行一次翻转
reg clk_1K ;
//分频计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
divder_cnt <= 15'd0 ;
else if(!En)
divder_cnt <= 15'd0 ;
else if( divder_cnt == 24999)begin
divder_cnt <= 15'd0 ;
end
else
divder_cnt <= divder_cnt + 1'b1 ;
//获取 clk_1K时钟
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
clk_1K <= 1'b0 ;
else if(divder_cnt == 24999)
clk_1K <= ~ clk_1K ;
else
clk_1K <= clk_1K ;
//【8位循环】移位寄存器模块设计
//注意: 这里使用的是 clk_1K 作为时钟信号
//这个在大型系统是不被允许的
// 8位循环移位寄存器,控制数码管选中信号
always@(posedge clk_1K or negedge Rst_n)
if(!Rst_n)
sel_r <= 8'b0000_00001 ;
else if(sel_r == 8'b1000_0000)
sel_r <= 8'b0000_00001 ;
else
sel_r <= sel_r << 1'b1 ;
// 8选1数据选择模块(mux8)
// 组合逻辑 always@(*) 使用* 表示什么信号都可以触发该信号的变化
// sel 和 数码管数据显示控制引脚
always@(*)
case(sel_r)
8'b0000_0001: data_temp = disp_data[3:0] ;
8'b0000_0010: data_temp = disp_data[17:4] ;
8'b0000_0100: data_temp = disp_data[11:8] ;
8'b0000_1000: data_temp = disp_data[15:12];
8'b0001_0000: data_temp = disp_data[19:16];
8'b0010_0000: data_temp = disp_data[23:20];
8'b0100_0000: data_temp = disp_data[27:24];
8'b1000_0000: data_temp = disp_data[31:28];
default : data_temp = 4'b0000 ;
endcase
//数据查找表模块设计
// 组合逻辑 always@(*) 使用* 表示什么信号都可以触发该信号的变化
// 在always 块中, seg要定义为 reg 类型的,而 output 默认定义为 wire 类型的
//数码编解码模块
always@(*)
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;
// 这里可以不要default,上面把所有的情况都写出来了
endcase
// 2选1 数码管显示使能控制器
assign sel = (En)? sel_r : 8'b0000_0000 ;
endmodule
FPGA 设计电路
为了节约FPGA 的IO 口资源,我们将seg[7:0]和 sel[7:0]的信号装换成串行数据到外部端口。
这个是串转并的内部原理图,我们通过串联多个D 触发器来将串行信号转换成并行信号
在本次实际电路中:由于seg(数据) 和sel(数码管片选) 一共由 7(实际也是8个,有一个是右下角的 小数点)7+8 =15个并行的信号。
所以我们使用了 2 个 HC595 模块将信号串转并行信号输出进行控制。
外部电路如下所示:
可以看到,我们由原来的 15根信号控制变成3根信号线来控制。
图中: DS表示的是串行数据 SCK_12.5Mhx 表示的是 外部芯片的时钟 LATCH 表示的串并转换的使能信号。
HC595驱动代码如下所示:
module Hc595_Driver(
Clk ,
Rst_n ,
Data ,
S_EN ,
SH_CP ,
ST_CP ,
DS
);
input Clk ;
input Rst_n ;
input [15:0]Data ;
input S_EN ;
output reg SH_CP ; // cLK
output reg ST_CP ; // 串并转换使能信号
output reg DS ;
parameter CNT_MAX =2 ; // 时钟分频
//
reg [15:0]r_data ;
always@(posedge Clk)
if(S_EN)
r_data <= Data ;
reg [7:0]divder_cnt ; //分频计数器
//同步时钟2分频计数器 -->25mHZ
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
divder_cnt <=0 ;
else if(divder_cnt == CNT_MAX - 1'b1)
divder_cnt <=0 ;
else
divder_cnt <= divder_cnt + 1'b1 ;
wire sck_plus ;
// 注:sck_plus 这是一个脉冲信号,虽然这里是周期信号,实际上,如果把 CNT_MAX 调大以后,就是个时钟脉冲信号
assign sck_plus = (divder_cnt == 1)? 1'b1 : 1'b0 ;
/*
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
SHCP <=0 ;
else if(sck_plus == 1'b1)
SHCP <= ~SHCP ;
*/
reg [5:0]SHCP_EDGE_CNT ; //至少6位
//边缘计数器
always@(posedge Clk or negedge Rst_n)
if(Rst_n == 1'b0)
SHCP_EDGE_CNT <= 0 ;
else if(sck_plus == 1'b1) begin
if(SHCP_EDGE_CNT == 32) //25Mhz 的时钟
SHCP_EDGE_CNT <= 0 ;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1 ;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT ;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) begin
ST_CP <= 1'b0 ;
DS <=1'b0 ;
SH_CP <= 1'd0;
end
else begin
case(SHCP_EDGE_CNT)
0 : begin SH_CP <=0 ; ST_CP <= 1'd0 ;DS <= r_data[15] ; end //信号准备阶段
// SHCP_EDGE_CNT 为 25Mhz // SH_CP 将得到的信号 2个clk为1次 =》 25Mhz/2 =12.5Mhz
// SH_CP 最开始为0 , 即数据还没开始通过D触发器, DS <= r_data[15]最高位的时候将数据先复制过去
// 下一阶段 CH_CP = 1 时刻,数据将会通过D 触发器存取
1 : begin SH_CP <=1 ; end // SH_CP <=1 D触发器开始存取最高位的数据
2 : begin SH_CP <=0 ; DS <= r_data[14] ; end
3 : begin SH_CP <=1 ; end
4 : begin SH_CP <=0 ; DS <= r_data[13] ; end
5 : begin SH_CP <=1 ; end
6 : begin SH_CP <=0 ; DS <= r_data[12] ; end
7 : begin SH_CP <=1 ; end
8 : begin SH_CP <=0 ; DS <= r_data[11] ; end
9 : begin SH_CP <=1 ; end
10 : begin SH_CP <=0 ; DS <= r_data[10]; end
11 : begin SH_CP <=1 ; end
12 : begin SH_CP <=0 ; DS <= r_data[9] ; end
13 : begin SH_CP <=1 ; end
14 : begin SH_CP <=0 ; DS <= r_data[8] ; end
15 : begin SH_CP <=1 ; end
16 : begin SH_CP <=0 ; DS <= r_data[7] ; end
17 : begin SH_CP <=1 ; end
18 : begin SH_CP <=0 ; DS <= r_data[6] ; end
19 : begin SH_CP <=1 ; end
20 : begin SH_CP <=0 ; DS <= r_data[5] ; end
21 : begin SH_CP <=1 ; end
22 : begin SH_CP <=0 ; DS <= r_data[4] ; end
23 : begin SH_CP <=1 ; end
24 : begin SH_CP <=0 ; DS <= r_data[3] ; end
25 : begin SH_CP <=1 ; end
26 : begin SH_CP <=0 ; DS <= r_data[2] ; end
27 : begin SH_CP <=1 ; end
28 : begin SH_CP <=0 ; DS <= r_data[1] ; end
29 : begin SH_CP <=1 ; end
30 : begin SH_CP <=0 ; DS <= r_data[0] ; end
31 : begin SH_CP <=1 ; end
32 : ST_CP <= 1'd1 ; //串并信号转换
default :
begin
ST_CP <= 1'b0 ;
DS <=1'b0 ;
SH_CP <= 1'd0;
end
endcase
end
endmodule
数码管实验要注意的重要知识点:
1、 串并转换的概念,本次首先是将8段数码管的信号在fpga内部处理,通过IP核的方式进行生成一个模块进行数据的调试
2、8个数码管的片选sel 和数码管数据显示seg 的区分,sel控制哪个数码管亮,seg控制数码管显示
3、seg数码管显示之前有一个编码,这个是根据数码管的特性进行编码和解码得到的。也就是说,原来输入的是原始数据,我们要显示在数码管上,进行编码得到我们实际在真实物理世界上的显示效果。
4、HC595 的驱动,我们使用了2个HC595 芯片来驱动8个LED 数码管,而不是通过FPGA直接驱动,目的是为了给FPGA节省一定的IO口资源,用两个HC595 是为了让 sel 和seg串行输出数据,让后通过HC595 得到并行数据来控制sel 和seg信号。
5、分频 12.5MHz概念,获取同步分频,信号让50Mhz 得到 25Mhz,让25Mhz的时钟脉冲信号(因为这里是2分频,所以得到的是2分频的时钟周期信号)来生成. 12.5Mhz的信号,不是直接通过50Mhz 的always语句得到的,主要是为了时钟同步。通过HC595编写驱动,可以多看几次,加深印象。
6、该实验有两个模块 ,1、数据数码管显示和控制模块 2、HD595驱动模块(也可以理解为内部信号转换成HD595芯片可驱动的逻辑信号)