关于74HC595芯片的规格参数以及时序图可参考德州仪器CD74HC595或其他博客,在此不多做分析。
现如今,小梅哥用两片74HC595级联,从而驱动八个八段8位的数码管。输入分别为位选信号sel[7:0]和段选信号seg[7:0],经过设计的驱动电路,从而产生输入到74HC595上的DS信号、移位信号SHCP以及存储信号STCP。驱动原理图如下:
对应小梅哥的verilog代码如下:
/***************************************************
* Module Name : HC595_Driver
* Engineer : 小梅哥
* Target Device : EP4CE10F17C8
* Tool versions : Quartus II 13.0
* Create Date : 2017-3-31
* Revision : v1.0
* Description : 74HC595移位寄存器驱动设计
**************************************************/
module HC595_Driver(
Clk,
Rst_n,
Data,
S_EN,
SH_CP,
ST_CP,
DS
);
parameter DATA_WIDTH = 16;
input Clk;
input Rst_n;
input [DATA_WIDTH-1 : 0] Data; //data to send
input S_EN; //send en
output reg SH_CP; //shift clock
output reg ST_CP; //latch data clock
output reg DS; //shift serial data
parameter CNT_MAX = 4; //4
reg [15:0] divider_cnt;//分频计数器
wire sck_pluse;
reg [4:0]SHCP_EDGE_CNT;//SH_CP EDGE counter
reg [15:0]r_data;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
r_data <= 16'd0;
else if(S_EN)
r_data <= Data;
else
r_data <= r_data;
//clock divide
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
divider_cnt <= 16'd0;
else if(divider_cnt == CNT_MAX)
divider_cnt <= 16'd0;
else
divider_cnt <= divider_cnt + 1'b1;
assign sck_pluse = (divider_cnt == CNT_MAX);
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
SHCP_EDGE_CNT <= 5'd0;
else if(sck_pluse)begin
if(SHCP_EDGE_CNT == 5'd31)
SHCP_EDGE_CNT <= 5'd0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'd1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
SH_CP <= 1'b0;
ST_CP <= 1'b0;
DS <= 1'b0;
end
else begin
case(SHCP_EDGE_CNT)
5'd0:begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end
5'd1:begin SH_CP <= 1'b1; ST_CP <= 1'b0;end
5'd2:begin SH_CP <= 1'b0; DS <= r_data[14];end
5'd3:begin SH_CP <= 1'b1; end
5'd4:begin SH_CP <= 1'b0; DS <= r_data[13];end
5'd5:begin SH_CP <= 1'b1; end
5'd6:begin SH_CP <= 1'b0; DS <= r_data[12];end
5'd7:begin SH_CP <= 1'b1; end
5'd8:begin SH_CP <= 1'b0; DS <= r_data[11];end
5'd9:begin SH_CP <= 1'b1; end
5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end
5'd11:begin SH_CP <= 1'b1; end
5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end
5'd13:begin SH_CP <= 1'b1; end
5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end
5'd15:begin SH_CP <= 1'b1; end
5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end
5'd17:begin SH_CP <= 1'b1; end
5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end
5'd19:begin SH_CP <= 1'b1; end
5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end
5'd21:begin SH_CP <= 1'b1; end
5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end
5'd23:begin SH_CP <= 1'b1; end
5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end
5'd25:begin SH_CP <= 1'b1; end
5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end
5'd27:begin SH_CP <= 1'b1; end
5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end
5'd29:begin SH_CP <= 1'b1; end
5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end
5'd31:begin SH_CP <= 1'b1; end
endcase
end
endmodule
testbench代码如下:
`timescale 1ns/1ns
`define clk_period 20
module HC595_Driver_tb;
reg Clk;
reg Rst_n;
reg [15 : 0] Data; //data to send
reg S_EN; //send en
wire SH_CP; //shift clock
wire ST_CP; //latch data clock
wire DS; //shift serial data
HC595_Driver HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data(Data),
.S_EN(S_EN),
.SH_CP(SH_CP),
.ST_CP(ST_CP),
.DS(DS)
);
initial Clk = 1;
always#(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
S_EN = 1;
Data = 16'b1010_1111_0110_0101;
#(`clk_period*20);
Rst_n = 1;
#(`clk_period*20);
#5000;
Data = 16'b0101_0101_1010_0101;
#5000;
$stop;
end
endmodule
为了只测试74HC595_driver.v,在quartus软件中将74HC595_driver.v设置为工程顶层,将74HC595_driver_tb.v设置为testbench文件,仿真结果如下:
CNT_MAX = 4
时,在全局时钟下,每当divide_cnt == CNT_MAX
时,divide_cnt被清零,同时sck_pluse产生一个时钟周期的脉冲信号,该脉冲信号时隔五个时钟周期,如图:SHCP_EDGE_CNT
会进行自加1操作(除非记满清零),代码为always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
SHCP_EDGE_CNT <= 5'd0;
else if(sck_pluse)begin
if(SHCP_EDGE_CNT == 5'd31)
SHCP_EDGE_CNT <= 5'd0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'd1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
由此可知,在全局时钟的条件下,每五个clk周期产生一个sck_pluse的脉冲信号,相应地,每隔五个clk周期SHCP_EDGE_CNT信号加1。
always@(posedge Clk or negedge Rst_n)
...
...
case(SHCP_EDGE_CNT)
5'd0:begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end
5'd1:begin SH_CP <= 1'b1; ST_CP <= 1'b0;end
5'd2:begin SH_CP <= 1'b0; DS <= r_data[14];end
5'd3:begin SH_CP <= 1'b1; end
5'd4:begin SH_CP <= 1'b0; DS <= r_data[13];end
......
endcase
end
end
当SHCP_EDGE_CNT == 5'd0;
,SH_CP信号为0;
当SHCP_EDGE_CNT == 5'd1;
,SH_CP信号为1;
当SHCP_EDGE_CNT == 5'd2;
,SH_CP信号为0;
当SHCP_EDGE_CNT == 5'd3;
,SH_CP信号为1;
…
即信号SHCP_EDGE_CNT
每五个时钟周期变化一次,
当其为偶数时,SH_CP为0;
当其为奇数是,SH_CP为1;
故SH_CP则以每10个clk周期为一个SH_CP的时钟周期,巧妙地实现了clk信号的十分频。。
SHCP_EDGE_CNT == 5'd0;
时才会为高电平,其余时候都为低电平,同时高电平也正好在SH_CP信号的低电平时产生。而且此case语句中,正好是传输完16个数据之后,ST_CP对这16个数据进行了一次采集,实在巧妙。由于SH_CP信号是clk信号的十分频,究其原因是CNT_MAX = 4
,此种方式欠妥,应该改为CNT_MAX = 10
,且divider_cnt清零的条件为(divider_cnt == CNT_MAX/2 - 1)
,此方式为最佳。同时sck_pluse的赋值条件也应为sck_pluse = (divider_cnt == CNT_MAX/2 - 1)
。