關於小梅哥74HC595驅動設計的思考

關於74HC595芯片的規格參數以及時序圖可參考德州儀器CD74HC595或其他博客,在此不多做分析。

現如今,小梅哥用兩片74HC595級聯,從而驅動八個八段8位的數碼管。輸入分別爲位選信號sel[7:0]和段選信號seg[7:0],經過設計的驅動電路,從而產生輸入到74HC595上的DS信號、移位信號SHCP以及存儲信號STCP。驅動原理圖如下:

源碼

RTL

對應小梅哥的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

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文件,仿真結果如下:

分析

  1. 所有的代碼生成的電路都運行在全局時鐘clk之下,沒有用到門控時鐘。
  2. CNT_MAX = 4時,在全局時鐘下,每當divide_cnt == CNT_MAX時,divide_cnt被清零,同時sck_pluse產生一個時鐘週期的脈衝信號,該脈衝信號時隔五個時鐘週期,如圖:
  3. 在全局時鐘的大前提下,每產生一個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。

  1. 而對於SH_CP信號來說,由於SHCP信號在case語句中產生,代碼爲:
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信號的十分頻。。

  1. 對於ST_CP信號,由於在case語句中只有當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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章