LinCoding

【读书笔记】详细解析74HC595驱动程序

2
阅读(5759)

【主题】:详细解析74HC595驱动程序

【作者】:LinCoding

【时间】:2016.11.15  

       首先感谢好友送了一本特权同学新出的《例说FPGA》,因为最近有事情,不知道什么时候会开始看,这一段时间把之前CB书上的代码做一下整理和解析,理清思路。

       第一个首先来理一理74HC595的驱动程序。原理什么的大家请百度,或者去看CB的书,我这里只分析下代码,当然如果有不同见解的可以相互交流。

(源码出自CrazyBingo)

`timescale 1ns/1ns
module HC595_driver
(
	
	input			clk,			//global clock
	input			rst_n,			//global reset
	
	input	[7:0]	        hc595_value,

	output			hc595_clk,
	output			hc595_dout,
	output			hc595_latch
);

第一部分是定义输入输出,没什么好说的。

//---------------------------------------
//detect input change
reg	[7:0]	hc595_value_r;
reg		update_flag;	//input data chage flag
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		begin
			hc595_value_r	<= 8'd0;
			update_flag	<= 1'b1;
		end
	else
		begin
			hc595_value_r	<= hc595_value;
			update_flag	<= ( hc595_value_r != hc595_value ) ? 1'b1 : 1'b0;
		end
end

第二部分是,利用一个寄存器来判定输入是否变化

有两点需要注意:

1、因为输入是来自于FPGA内部(一般为LED计数或者数码管扫描),因此无需打2拍做同步,直接利用一级寄存器寄存前1个CLK的值和本次的值来做对比即可。

2、为什么不把updata_flag写成组合逻辑呢?

即:wire    updata_flag    = ( hc595_value_r != hc595_value ) ? 1'b1 : 1'b0;

过于对于初学者,应该不好想出来,那就看仿真图吧

blob.png          blob.png

      第一张图是用组合逻辑实现的,可以明显看到确实是可以检测到数据的变化,但是是一个尖峰,基本没有时间,因此,用一级寄存判定数据变化时,不得用组合逻辑

      第二张图是用时序逻辑实现的,可以明显看到在上升沿时候,由于此刻的hc595_value的值与之前的值不同,也就是当前hc595_value已经变为了aa,并在此刻将aa赋给hc595_value_r,但是,由于update_flag的更新与之是同步执行,拿来用做比较的hc595_value_r还是用的aa之前的值进行比较的,因此会产生一个周期为一个CLK的使能信号。

//---------------------------------------
localparam	DELAY_TOP = 4'd8;
reg	[3:0]	delay_cnt;			//counter
reg			shift_state;		//counter enable
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		begin
			delay_cnt  <= 3'd0;
		end
	else if ( shift_state )
		begin
			delay_cnt  <= ( delay_cnt < DELAY_TOP ) ? ( delay_cnt + 1'b1 ) : 3'd1; 
		end
	else
		begin
			delay_cnt  <= delay_cnt;
		end
end
wire	shift_clk 	= ( delay_cnt > DELAY_TOP>>1 ) ? 1'b1 : 1'b0;
wire	shift_flag	= ( delay_cnt == DELAY_TOP ) ? 1'b1 : 1'b0;

 这段代码主要是对主时钟进行8分频以产生74HC595用的时钟信号,并在时钟下降沿时候,触发使能信号。

这段代码需要注意以下几点:

1、计数器为什么需要使能信号shift_state

      因为要保证只有输入的数据发生了变化,需要串行输出数据时才有时钟输出,平时则没有时钟输出,这样可以降低功耗。

2、为什么要一个shift_flag信号

      因为shift_clk是给74HC595硬件的,而在程序中我们需要知道什么时候给74HC595发送数据,而这个flag就告诉了其他always块,在74HC595时钟的下降沿发送数据。

3、为什么用组合逻辑?

      对于计数器的输出一般使用的是组合逻辑,因为这样不会滞后时钟

4、为什么是下降沿?

       很简单,因为74HC595是在上升沿捕获数据,你只有在下降沿将数据设置好,也就是让数据稳定在两个下降沿之间,使得上升沿处在数据的正中间,这样的话可以使建立时间和保持时间最合适。

具体看图:

blob.png

 可以看到数据稳定在74HC595两个下降沿之间,而上升沿正好处于两者之间,当然了,这没有考虑到输出的一些物理延时,但是对于入门的同学来讲,这样就完全可以了。

//---------------------------------------
localparam	IDLE	= 1'b0;
localparam	START	= 1'b1;
reg	[3:0]	cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		begin
			cnt			<= 4'd0;
			shift_state <= 1'b0;
		end
	else
		case ( shift_state )
			IDLE:
				begin
					if ( update_flag )
						shift_state	<= START;
					else
						shift_state	<= IDLE;
				end
			START:
				begin
					if ( shift_flag )
						begin
							if ( cnt < 4'd8 )
								begin
								    cnt	<= cnt + 1'b1;
								    shift_state<= shift_state;
								end
							else
								begin
								    cnt	<= 4'd0;
								    shift_state	<= IDLE;
								end
						end
					else
						begin
							cnt		<= cnt;
							shift_state	<= shift_state;
						end
				end
			default:;
		endcase
end

assign	hc595_clk	= ( shift_state && cnt < 4'd8 ) ? shift_clk : 1'b0;
assign	hc595_dout	= ( shift_state && cnt < 4'd8 ) ? hc595_value[3'd7-cnt] : 1'b0;
assign	hc595_latch	= ( shift_state && cnt == 4'd8 )? 1'b1 : 1'b0;

考虑两个问题:

1、为什么用状态机?

      因为需要等待使能信号,也就是说如果需要等待使能信号,那么最好使用状态机。此外,这里很巧妙了把状态的转移变量shift_state用做了计数器的使能信号,一举两得

2、为什么输出采用了组合逻辑?

     这个暂时说不上来,但是根据CB串口的例程、LCD_driver的驱动例程,对于驱动的输出都是采用的组合逻辑,可能是由于对于这样一些外设的驱动,用组合逻辑更加的灵活。


总结:

       可见一个小小的74HC595的例程,可以学到的东西还是很多的,尤其是学会了如何驱动上升沿锁存数据的外设,如果能灵活运用,则可以驱动其他的上升沿锁存的外设。

       其实,学FPGA这么一段时间了,感觉Verilog语言是十分灵活的,我们要形成自己的套路,用这些固有的模式来处理各种各样的问题。

模式总结:

1、一级寄存器寄存数据(打一拍),判定数据变化的标志位须用时序逻辑

2、计数器的输出采用组合逻辑

3、如果有使能信号的,采用状态机,利用IDLE态来时刻判定使能信号的到来

4、外设的驱动引脚采用组合逻辑驱动

5、对于上升沿锁存的外设,要使数据保持在两个下降沿之间


以上仅为个人见解,如果有不同意见,欢迎交流!