根据上一篇博客描述,数码管驱动在实际应用此输出占用的管脚过多,所以为了节省FPGA的管脚,需要将输出信号并转串后(FPGA只需要输出3个管脚),再利用串转并芯片(比如74HC595)连接数码管去显示需要输出的字符。如下功能示意图:
从上图可以看到FPGA管脚从16个节省到3个。
74HC595芯片是一个8位串行移位寄存器,下图是一个4位的串行移位寄存器。
每个时钟周期Q端口的值都会发生变化
LATCH时钟不出现上升沿,OUTPUT端口数据就不发生变化
那么8位串行移位寄存器如下:
上图可以用Verilog实现,那么就可以制作成对应的芯片(74HC595)如下(芯片手册内容):
如果需要16位的移位寄存器呢:
只需要将前一芯片的输出QH-连接到下一芯片的D1就实现了16位移位寄存器
下图是电路图:
那么我们的任务就是:如何将上一篇博客的SEL输出和SEG输出两个8位共16位的值通过DIO,RCLK,SCLK传到2片级联的74HC595。
实现一个驱动逻辑,通过这3根信号线能正确的给74HC595位数据。
即需要在FPGA中实现一下逻辑功能,如下图:
并串转换逻辑。
下图是芯片输入输出时序图:
时序逻辑
代码编写:
先看一下期望实现波形图:
代码如下:
module HC595_driver(
Clk,
Reset_n,
SEG,
SEL,
DIO,
SRCLK,
RCLK
);
input Clk;
input Reset_n;
input [7:0]SEG;
input [7:0]SEL;
output reg DIO;
output reg SRCLK;
output reg RCLK;
parameter CLOCK_FREQ = 50_000_000;
parameter SRCLK_FREQ = 12_500_000;
parameter MCNT = CLOCK_FREQ/(SRCLK_FREQ * 2) -1;
//SRCLK 2V时5MHZ 5V是25MHZ 那么3.3V时就大致取12.5MHZ 40ns
//主频假设最高1GHZ 最低1hz [29:0]div_count 编译器会根据实际使用确定实际尾位宽
reg [29:0]div_count;
reg [4:0]cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
div_count <= 0;
else if (div_count == MCNT)
div_count <= 0;
else
div_count <= div_count +1'd1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
cnt <= 0;
else if (div_count == MCNT)
cnt <= cnt + 1'd1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
DIO = 1'd0; SRCLK <= 1'd0; RCLK <= 1'd0;
end
else begin
case(cnt)
0:begin DIO = SEG[7]; SRCLK <= 1'd0; RCLK <= 1'd1;end
1:begin SRCLK <= 1'd1; RCLK <= 1'd0; end
2:begin DIO = SEG[6]; SRCLK <= 1'd0; end
3:begin SRCLK <= 1'd1; end
4:begin DIO = SEG[5]; SRCLK <= 1'd0; end
5:begin SRCLK <= 1'd1; end
6:begin DIO = SEG[4]; SRCLK <= 1'd0; end
7:begin SRCLK <= 1'd1; end
8:begin DIO = SEG[3]; SRCLK <= 1'd0; end
9:begin SRCLK <= 1'd1; end
10:begin DIO = SEG[2]; SRCLK <= 1'd0; end
11:begin SRCLK <= 1'd1; end
12:begin DIO = SEG[1]; SRCLK <= 1'd0; end
13:begin SRCLK <= 1'd1; end
14:begin DIO = SEG[0]; SRCLK <= 1'd0; end
15:begin SRCLK <= 1'd1; end
16:begin DIO = SEL[7]; SRCLK <= 1'd0; end
17:begin SRCLK <= 1'd1; end
18:begin DIO = SEL[6]; SRCLK <= 1'd0; end
19:begin SRCLK <= 1'd1; end
20:begin DIO = SEL[5]; SRCLK <= 1'd0; end
21:begin SRCLK <= 1'd1; end
22:begin DIO = SEL[4]; SRCLK <= 1'd0; end
23:begin SRCLK <= 1'd1; end
24:begin DIO = SEL[3]; SRCLK <= 1'd0; end
25:begin SRCLK <= 1'd1; end
26:begin DIO = SEL[2]; SRCLK <= 1'd0; end
27:begin SRCLK <= 1'd1; end
28:begin DIO = SEL[1]; SRCLK <= 1'd0; end
29:begin SRCLK <= 1'd1; end
30:begin DIO = SEL[0]; SRCLK <= 1'd0; end
31:begin SRCLK <= 1'd1; end
endcase
end
endmodule
编写testbench测试代码:
`timescale 1ns / 1ps
module HC595_driver_tb();
reg Clk;
reg Reset_n;
reg [7:0]SEG;
reg [7:0]SEL;
wire DIO;
wire SRCLK;
wire RCLK;
HC595_driver HC595_driver1(
.Clk(Clk),
.Reset_n(Reset_n),
.SEG(SEG),
.SEL(SEL),
.DIO(DIO),
.SRCLK(SRCLK),
.RCLK(RCLK)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
SEL = 8'b0000_0001;
SEG = 8'b0101_0101;
#201
Reset_n = 1;
#5000;
SEL = 8'b0000_0010;
SEG = 8'b1010_1010;
#5000;
SEL = 8'b1010_0101;
SEG = 8'b0000_1101;
#5000;
$stop;
end
endmodule
波形图如下:
至此我们完成了74HC595芯片输入逻辑驱动设计
接下来我们将上一篇博客数码管驱动 与上面所写的74HC595芯片驱动连接起来;
因为数码管显示输入为32位数据,一般开发板无法提供这么多的引脚,所以使用2个拨码开关,设置4组数据作为测试;详情看代码:
module digitial_tube_hc595(
Clk,
Reset_n,
SW,
DIO,
SRCLK,
RCLK
);
input Clk;
input Reset_n;
input [1:0]SW;
output DIO;
output SRCLK;
output RCLK;
wire [7:0]SEL;
wire [7:0]SEG;
reg[31:0]Disp_Data;
digitial_tube_0 digitial_tube_0(
.Clk(Clk),
.Reset_n(Reset_n),
.Disp_Data(Disp_Data),
.SEL(SEL),
.SEG(SEG)
);
HC595_driver HC595_driver1(
.Clk(Clk),
.Reset_n(Reset_n),
.SEG(SEG),
.SEL(SEL),
.DIO(DIO),
.SRCLK(SRCLK),
.RCLK(RCLK)
);
always@(*)
case(SW)
0:Disp_Data <= 32'h01234567;
1:Disp_Data <= 32'h89abcdef;
2:Disp_Data <= 32'h02468ace;
3:Disp_Data <= 32'h13579bdf;
endcase
endmodule
至此,整个数码管驱动逻辑到此结束!