【读书笔记】详细解析74HC595驱动程序
2赞【主题】:详细解析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;
过于对于初学者,应该不好想出来,那就看仿真图吧
第一张图是用组合逻辑实现的,可以明显看到确实是可以检测到数据的变化,但是是一个尖峰,基本没有时间,因此,用一级寄存判定数据变化时,不得用组合逻辑。
第二张图是用时序逻辑实现的,可以明显看到在上升沿时候,由于此刻的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是在上升沿捕获数据,你只有在下降沿将数据设置好,也就是让数据稳定在两个下降沿之间,使得上升沿处在数据的正中间,这样的话可以使建立时间和保持时间最合适。
具体看图:
可以看到数据稳定在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、对于上升沿锁存的外设,要使数据保持在两个下降沿之间
以上仅为个人见解,如果有不同意见,欢迎交流!