【原创】FPGA应用(二)——74HC595驱动
0赞一、设计需求
设计一个驱动74HC595芯片工作的功能模块,并在CB哥的开发板上8盏led灯上实现流水灯的效果。
二、设计思路
1、74HC595介绍及分析
图1所示为74HC595芯片的封装及引脚分布。74HC595是由8位移位寄存器和8位三态并行输出的D型锁存器组成,如图2所示。
图1 74HC595封装及引脚分布
图2 74HC595逻辑图
74HC595具有以下特征:
(1) 移位寄存器接收串行数据、提供串行或8位并行数据输出;
(2) 移位寄存器和锁存器拥有独立的时钟输入;
(3) 移位寄存器拥有异步复位信号
图3所示为74HC595的功能表,于是可总结为:
当复位信号RESET为低电平时,
(1) 移位寄存器输出为低电平(包括并行和串行输出);
(2) 当输出使能信号为低电平时,若锁存器时钟上升沿到来,则最后输出为低电平(移位寄存器输出),否则保持不变;当输出使能信号为高电平时,最后输出为高阻态。
当复位信号RESET为高电平时,
(1) 当移位时钟上升沿到来时,移位寄存器输出与输入内容一样,否则保持不变;
(2) 当输出使能信号为低电平时,若锁存器时钟上升沿到来,则最后输出与输入内容(移位寄存器输出),否则保持不变;当输出使能信号为高电平时,最后输出为高阻态。
图3 74HC595功能表
图4所示为74HC595的时序图。由时序图可以看出,串行输入数据在移位时钟的上升沿被读进移位寄存器中;当复位信号有效时,移位寄存器被清零;在输出使能为低电平且锁存时钟的上升沿时,移位寄存器的值被锁存输出;当输出使能为高电平时,输出高阻态。而串行输出接口则是输出移位寄存器的最高位,且不受输出使能的控制。
图4 74HC595时序图
本次的led流水灯设计不需要高阻态,故将输出使能始终置为低电平,同时也不需要复位信号,将其置为高电平,永不复位。此外也不需要串行输出。在不考虑这三个信号的情况下,重新给出简化后的74HC595时序图,如图5所示。这里考虑了输入数据对移位时钟上升沿的建立时间和保持时间,如图6所示,由于芯片供电电压为3.3V且当前的室温是在25摄氏度到85摄氏度之间,故最小建立时间和保持时间分别为50ns和5ns。而移位时钟上升沿对锁存时钟上升沿的最小建立时间为70ns。
图5 简化后的74HC595时序图
图6 建立时间和保持时间要求
此外,移位时钟的最大频率可以是10MHz,如图7所示。但为了满足时序要求,移位时钟采用5MHz即200ns,输入数据只能在移位时钟的下降沿改变,这里建立时间和保持时间裕量分别为50ns和95ns。锁存时钟也在移位时钟的下降沿产生,于是建立时间裕量还有30ns。
图7 移位时钟最大频率与温度、电压的关系
2、设计分析
如图8所示,本设计由三个模块组合,分别为子模块led_ctrl、driver_74595和顶层模块led_water,它们的作用分别为:
led_ctrl模块 :负责产生流水灯显示的数据;
driver_74595模块 :负责驱动74HC595芯片工作并发送led数据;
led_water顶层模块 :例化led_ctrl和driver_74595模块,完成流水灯设计。
图8 设计组织框架
至于led_ctrl模块设计思路可参考上上篇博文“FPGA应用(一)——流水灯”,这里主要分析driver_74595模块的设计。该模块采用状态机的方案来实现,如图9所示。状态机中有四个状态,分别为IDLE、CLK_L、CLK_H和FINISH。在IDLE中主要完成led数据的加载和发送;在CLK_L中产生移位时钟的低电平;在CLK_H中产生移位时钟的高电平和led数据的发送;在FINISH中产生锁存时钟。
图9 74HC595驱动状态机
三、设计实现
led_water顶层模块:
/**********************************************版权申明************************************************* ** 电子技术应用网站, CrazyBird ** http://www.chinaaet.com, http://blog.chinaaet.com/crazybird ** **--------------------------------------------文件信息-------------------------------------------------- ** 文件名: led_water.v ** 创建者: CrazyBird ** 创建日期: 2015-7-11 ** 版本号: v1.0 ** 功能描述: 该模块完成74HC595的驱动并实现流水灯的功能 ** ********************************************************************************************************/ // synopsys translate_off `timescale 1 ns / 1 ps // synopsys translate_on module led_water( rst_n, clk, shift_clock, lacth_clock, led_dout ); //****************************************************************************** // 参数定义 //****************************************************************************** // 修改以下参数以满足需求 parameter CLK_CYCLE = 20; // 时钟周期,单位ns parameter LED_WIDTH = 8; // led数据位宽 // 修改以上参数以满足需求 //****************************************************************************** // 输入/输出端口定义 //****************************************************************************** input rst_n; // 全局复位信号 input clk; // 全局时钟信号,50MHz output shift_clock; // 74HC595的移位时钟信号 output lacth_clock; // 74HC595的锁存时钟信号 output led_dout; // 74HC595的串行数据输入 //****************************************************************************** // 变量定义 //****************************************************************************** wire [LED_WIDTH-1:0] led_data; // led灯数据输出 wire led_flag; // led灯数据输出标志 //****************************************************************************** // 模块连接 //****************************************************************************** // 例化led_ctrl模块 led_ctrl #( .CLK_CYCLE(CLK_CYCLE), .LED_WIDTH(LED_WIDTH) ) u_led_ctrl( .rst_n ( rst_n ), .clk ( clk ), .led_data ( led_data ), .led_flag ( led_flag ) ); // 例化driver_74595模块 driver_74595 #( .CLK_CYCLE(CLK_CYCLE), .LED_WIDTH(LED_WIDTH) ) u_driver_74595( .rst_n ( rst_n ), .clk ( clk ), .led_data ( led_data ), .led_flag ( led_flag ), .shift_clock ( shift_clock ), .latch_clock ( lacth_clock ), .led_dout ( led_dout ) ); //****************************************************************************** endmodule //*********************************************文件结束*****************************************************
led_ctrl模块:
/**********************************************版权申明************************************************* ** 电子技术应用网站, CrazyBird ** http://www.chinaaet.com, http://blog.chinaaet.com/crazybird ** **--------------------------------------------文件信息-------------------------------------------------- ** 文件名: led_ctrl.v ** 创建者: CrazyBird ** 创建日期: 2015-7-11 ** 版本号: v1.0 ** 功能描述: 该模块主要负责产生led灯流水显示的数据 ** ********************************************************************************************************/ // synopsys translate_off `timescale 1 ns / 1 ps // synopsys translate_on module led_ctrl( rst_n, clk, led_data, led_flag ); //****************************************************************************** // 参数定义 //****************************************************************************** // 修改以下参数以满足需求 parameter CLK_CYCLE = 20; // 时钟周期,单位ns parameter T0 = 500_000_000; // 0.5s,流水灯流动速率 // parameter T0 = 5000; // 测试用 parameter LED_WIDTH = 8; // led数据位宽 parameter DELAY0 = 25; // 计数器位宽 // 修改以上参数以满足需求 // 以下参数不要修改 parameter T0_VAL = (T0/CLK_CYCLE)-1; // 0.5s,流水灯流动速率 // 以上参数不要修改 //****************************************************************************** // 输入/输出端口定义 //****************************************************************************** input rst_n; // 全局复位信号 input clk; // 全局时钟信号,50MHz output reg [LED_WIDTH-1:0] led_data; // led灯数据输出 output reg led_flag; // led灯数据输出标志 //****************************************************************************** // 计数器 //****************************************************************************** reg [DELAY0-1:0] cnt; wire cnt_done; // 0.5计数完成标志位 always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) cnt <= {(LED_WIDTH){1'b0}}; else if(cnt_done==1'b1) cnt <= {(LED_WIDTH){1'b0}}; else cnt <= cnt + 1'b1; end assign cnt_done = (cnt==T0_VAL); //****************************************************************************** // 流水灯数据的产生 //****************************************************************************** always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) begin led_data <= {{(LED_WIDTH-1){1'b0}},1'b1}; led_flag <= 1'b0; end else if(cnt_done==1'b1) begin led_data <= {led_data[LED_WIDTH-2:0],led_data[LED_WIDTH-1]}; led_flag <= 1'b1; end else begin led_data <= led_data; led_flag <= 1'b0; end end //****************************************************************************** endmodule //*********************************************文件结束*****************************************************
driver_74595模块:
/**********************************************版权申明************************************************* ** 电子技术应用网站, CrazyBird ** http://www.chinaaet.com, http://blog.chinaaet.com/crazybird ** **--------------------------------------------文件信息-------------------------------------------------- ** 文件名: driver_74595.v ** 创建者: CrazyBird ** 创建日期: 2015-7-11 ** 版本号: v1.0 ** 功能描述: 该模块完成对74HC595的驱动 ** ********************************************************************************************************/ // synopsys translate_off `timescale 1 ns / 1 ps // synopsys translate_on module driver_74595( rst_n, clk, led_data, led_flag, shift_clock, latch_clock, led_dout ); //****************************************************************************** // 参数定义 //****************************************************************************** // 修改以下参数以满足需求 parameter CLK_CYCLE = 20; // 时钟周期,单位ns parameter T0 = 100; // 100ns,移位时钟高低电平时间长度 parameter T1 = 200; // 200ns,锁存时钟周期 parameter LED_WIDTH = 8; // led数据位宽 parameter W0 = 3; // 100ns计数器位宽 parameter W1 = 4; // 200ns计数器位宽 parameter W2 = 4; // 计算移位时钟周期数 // 修改以上参数以满足需求 // 以下参数不要修改 parameter T0_VAL = (T0/CLK_CYCLE)-1; // 100ns,移位时钟高低电平时间长度 parameter T1_VAL = (T1/CLK_CYCLE)-1; // 200ns,锁存时钟周期 parameter IDLE = 2'b00, CLK_L = 2'b01, CLK_H = 2'b10, FINISH = 2'b11; // 以上参数不要修改 //****************************************************************************** // 输入/输出端口定义 //****************************************************************************** input rst_n; // 全局复位信号 input clk; // 全局时钟信号,50MHz input [LED_WIDTH-1:0] led_data; // led灯数据输出 input led_flag; // led灯数据输出标志 output reg shift_clock; // 74HC595的移位时钟信号 output reg latch_clock; // 74HC595的锁存时钟信号 output led_dout; // 74HC595的串行数据输入 //****************************************************************************** // 变量定义 //****************************************************************************** wire shift_clock_cnt_done; // 100ns计数完成标志位 wire latch_clock_cnt_done; // 200ns计数完成标志位 wire period_cnt_done; // 生成8个移位时钟标志位 //****************************************************************************** // 状态机实现74HC595驱动 //****************************************************************************** reg [1:0] state; reg [LED_WIDTH-1:0] led_data_r; // led灯的加载变量 reg shift_clock_cnt_en; // 100ns计数使能 reg latch_clock_cnt_en; // 200ns计数使能 reg period_cnt_en; // 移位时钟周期计数使能 always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) begin state <= IDLE; led_data_r <= {(LED_WIDTH){1'b0}}; shift_clock <= 1'b0; latch_clock <= 1'b0; shift_clock_cnt_en <= 1'b0; latch_clock_cnt_en <= 1'b0; period_cnt_en <= 1'b0; end else begin case(state) IDLE : begin if(led_flag) begin state <= CLK_L; led_data_r <= led_data; // 数据加载 shift_clock_cnt_en <= 1'b1; end else state <= IDLE; end CLK_L : begin shift_clock <= 1'b0; if(shift_clock_cnt_done==1'b1) state <= CLK_H; else state <= CLK_L; end CLK_H : begin shift_clock <= 1'b1; period_cnt_en <= 1'b1; if(shift_clock_cnt_done==1'b1) begin period_cnt_en <= 1'b0; if(period_cnt_done==1'b1) begin state <= FINISH; shift_clock_cnt_en <= 1'b0; latch_clock_cnt_en <= 1'b1; shift_clock <= 1'b0; end else begin state <= CLK_L; led_data_r <= {led_data_r[LED_WIDTH-2:0],1'b0}; end end else state <= CLK_H; end FINISH : begin latch_clock <= 1'b1; if(latch_clock_cnt_done==1'b1) begin state <= IDLE; latch_clock_cnt_en <= 1'b0; latch_clock <= 1'b0; end else state <= FINISH; end endcase end end //****************************************************************************** // 各种计数器 //****************************************************************************** // 移位时钟计数器 reg [W0-1:0] shift_clock_cnt; always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) shift_clock_cnt <= {(W0){1'b0}}; else if(shift_clock_cnt_en==1'b1) begin if(shift_clock_cnt_done==1'b1) shift_clock_cnt <= {(W0){1'b0}}; else shift_clock_cnt <= shift_clock_cnt + 1'b1; end else shift_clock_cnt <= {(W0){1'b0}}; end assign shift_clock_cnt_done = (shift_clock_cnt==T0_VAL); // 锁存时钟计数器 reg [W1-1:0] latch_clock_cnt; always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) latch_clock_cnt <= {(W1){1'b0}}; else if(latch_clock_cnt_en) begin if(latch_clock_cnt_done==1'b1) latch_clock_cnt <= {(W1){1'b0}}; else latch_clock_cnt <= latch_clock_cnt + 1'b1; end else latch_clock_cnt <= {(W1){1'b0}}; end assign latch_clock_cnt_done = (latch_clock_cnt==T1_VAL); // 移位时钟周期计数器 reg [W2-1:0] period_cnt; always @(posedge clk or negedge rst_n) begin if(rst_n==1'b0) period_cnt <= {(W2){1'b0}}; else if((period_cnt_en==1'b1)&&(shift_clock_cnt_done==1'b1)) begin if(period_cnt_done==1'b1) period_cnt <= {(W2){1'b0}}; else period_cnt <= period_cnt + 1'b1; end else period_cnt <= period_cnt; end assign period_cnt_done = (period_cnt==7); //****************************************************************************** // led数据输出 //****************************************************************************** assign led_dout = led_data_r[LED_WIDTH-1]; //****************************************************************************** endmodule //*********************************************文件结束*****************************************************
仿真结果如下:
很明显,从仿真结果来看,本设计已实现了功能。接着,就可以对设计进行综合、布局布线、生成bit流文件和下载到板子上。在开发板上可以看到8盏led灯在做流水运动。