【FPGA零基础学习之旅#12】三线制数码管驱动(74HC595)串行移位寄存器驱动

🎉欢迎来到FPGA专栏~三线制数码管驱动


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

🎉 目录-三线制数码管驱动

  • 一、效果演示
  • 二、电路结构
  • 三、代码详解
    • 3.1 总体结构设计
    • 3.2 驱动74HC595芯片
    • 3.3 HEX8模块
    • 3.4 顶层模块
  • 四、按键控制改变数据值

遇见未来

一、效果演示

🥝ISSP调试演示:
程序配置完成:
程序配置

调试:
测试结果

🥝按键控制演示:
按键控制

二、电路结构

三线制的数码管驱动中,使用74HC595芯片来减少FPGA的管脚数量使用。

关于74HC595芯片的介绍和时序图,参考文章:74HC595介绍 和 74HC595 驱动。

在AC620开发板上的数码管驱动电路:
电路1
AC620开发板使用的是74HC595芯片的级联来驱动显示:
电路2

三、代码详解

3.1 总体结构设计

先上RTL视图:
RTL
HEX8模块将segsel信号传给m74HC595_Driver模块,然后将接收到的信号转换为DS、SH_CP和ST_CP信号;ISSP模块用于调试。

3.2 驱动74HC595芯片

关于74HC595芯片的驱动,主要参考该时序图进行代码编写:
时序图

74HC595是8位串行移位寄存器,带有存储寄存器和三态寄存器,其中移位寄存器和存储寄存器分别采用不同的时钟。其可以把串行的信号转为并行的信号,因此常用做各种数码管以及点阵屏的驱动芯片。

该芯片的主要IO:

IO名称功能
DS / SER串行数据输入端
STCP / RCK存储寄存器的时钟输入。上升沿时移位寄存器中的数据进入存储寄存器,下降沿时存储寄存器中的数据保持不变。应用时通常将 ST_CP 置为低点平,移位结束后再在 ST_CP 端产生一个正脉冲更新显示数据。
SHCP / SCK移位寄存器的时钟输入。上升沿时移位寄存器中的数据依次移动一位,即 Q0 中的数据移到 Q1 中,Q1 中的数据移到 Q2 中,依次类推;下降沿时移位寄存器中的数据保持不变。

由于在AC620开发板中芯片采用3.3V供电,这样在设计74HC595工作频率时,直接使用50M晶振四分频后的时钟作为其工作时钟。

74HC595的驱动代码,由于模块命名不能以数字开头,所以加了个m:

m74HC595_Driver.v:

module m74HC595_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 sendinput 		S_EN;						//send enoutput reg 	SH_CP;						//shift clockoutput reg 	ST_CP;						//latch data clockoutput reg 	DS;							//shift serial dataparameter CNT_MAX = 4;reg [15:0] divider_cnt;//分频计数器wire sck_pluse;reg [4:0]SHCP_EDGE_CNT;//SH_CP EDGE counterreg [15:0]r_data;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)r_data <= 16'd0;else if(S_EN)r_data <= Data;elser_data <= r_data;end//clock dividealways@(posedge Clk or negedge Rst_n)beginif(!Rst_n)divider_cnt <= 16'd0;else if(divider_cnt == CNT_MAX)divider_cnt <= 16'd0;elsedivider_cnt <= divider_cnt + 1'b1;endassign sck_pluse = (divider_cnt == CNT_MAX);always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)SHCP_EDGE_CNT <= 5'd0;else if(sck_pluse)beginif(SHCP_EDGE_CNT ==  5'd31)SHCP_EDGE_CNT <= 5'd0;elseSHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;endelseSHCP_EDGE_CNT <= SHCP_EDGE_CNT;end	always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginSH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0;	endelse begincase(SHCP_EDGE_CNT)5'd0: begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end5'd1: begin SH_CP <= 1'b1; ST_CP <= 1'b0;end5'd2: begin SH_CP <= 1'b0; DS <= r_data[14];end5'd3: begin SH_CP <= 1'b1; end5'd4: begin SH_CP <= 1'b0; DS <= r_data[13];end5'd5: begin SH_CP <= 1'b1; end5'd6: begin SH_CP <= 1'b0; DS <= r_data[12];end5'd7: begin SH_CP <= 1'b1; end5'd8: begin SH_CP <= 1'b0; DS <= r_data[11];end5'd9: begin SH_CP <= 1'b1; end5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end5'd11:begin SH_CP <= 1'b1; end5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end5'd13:begin SH_CP <= 1'b1; end5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end5'd15:begin SH_CP <= 1'b1; end5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end5'd17:begin SH_CP <= 1'b1; end5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end5'd19:begin SH_CP <= 1'b1; end5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end5'd21:begin SH_CP <= 1'b1; end5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end5'd23:begin SH_CP <= 1'b1; end5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end5'd25:begin SH_CP <= 1'b1; end5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end5'd27:begin SH_CP <= 1'b1; end5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end5'd29:begin SH_CP <= 1'b1; end5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end5'd31:begin SH_CP <= 1'b1; enddefault:begin SH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0;	endendcase	endendendmodule

RTL视图:

RTL74

3.3 HEX8模块

该模块的设计是在该文章的讲解基础之上进行修改:【FPGA零基础学习之旅#11】数码管动态扫描。

上述参考文章中的模块可以称为HEX6,驱动了6个数码管,在此我们需要驱动8个数码管,故可以将模块命名为HEX8。

需要注意的是,在设计数码管位选的时候,一定要看清使用板子的电路结构,弄清楚是高电平位选还是低电平位选!

HEX8.v:

module HEX8(input 					Clk,		//50Minput 					Rst_n,		//复位input 					En,			//数码管显示使能input 		[31:0]		disp_data,	//8 × 4 = 32(8个数码管,数据格式为hex,总共输32位)output reg 	[7:0]		seg, 		//数码管段选output 		[7:0]		sel	 		//数码管位选(数码管选择)
);reg [7:0]sel_r;//--------<分频器>--------reg [14:0]divider_cnt;//25000-1reg clk_1K;reg [3:0]data_tmp;//待显示数据缓存//1KHz分频计数器always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)divider_cnt <= 15'd0;else if(!En)divider_cnt <= 15'd0;else if(divider_cnt == 24999)divider_cnt <= 15'd0;elsedivider_cnt <= divider_cnt + 1'b1;end//1KHz扫描时钟always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)clk_1K <= 1'b0;else if(divider_cnt == 24999)clk_1K <= ~clk_1K;elseclk_1K <= clk_1K;end//--------<6位循环移位寄存器>--------	always@(posedge clk_1K or negedge Rst_n)beginif(!Rst_n)sel_r <= 8'b0000_0001;else if(sel_r == 8'b1000_0000)sel_r <= 8'b0000_0001;elsesel_r <= sel_r << 1;end	//--------<6选1多路器>--------		always@(*)begincase(sel_r)8'b0000_0001:data_tmp = disp_data[3:0];8'b0000_0010:data_tmp = disp_data[7:4];8'b0000_0100:data_tmp = disp_data[11:8];8'b0000_1000:data_tmp = disp_data[15:12];8'b0001_0000:data_tmp = disp_data[19:16];8'b0010_0000:data_tmp = disp_data[23:20];8'b0100_0000:data_tmp = disp_data[27:24];8'b1000_0000:data_tmp = disp_data[31:28];default:data_tmp = 4'b0000;endcaseend//--------<LUT>--------		always@(*)begincase(data_tmp)4'h0:seg = 8'hc0;4'h1:seg = 8'hf9;4'h2:seg = 8'ha4;4'h3:seg = 8'hb0;4'h4:seg = 8'h99;4'h5:seg = 8'h92;4'h6:seg = 8'h82;4'h7:seg = 8'hf8;4'h8:seg = 8'h80;4'h9:seg = 8'h90;4'ha:seg = 8'h88;4'hb:seg = 8'h83;4'hc:seg = 8'hc6;4'hd:seg = 8'ha1;4'he:seg = 8'h86;4'hf:seg = 8'h8e;endcaseend//--------<2选1多路器>--------		assign sel = (En)?(sel_r):8'b1111_1111;endmodule

3.4 顶层模块

在顶层模块中需要调用ISSP这样的一个IP核,操作过程和调试方法参考:【FPGA零基础学习之旅#11】数码管动态扫描。

smg.v:

module smg(input 			Clk,		//50Minput 			Rst_n,//input [31:0] 	disp_data,output 			SH_CP,		//shift clockoutput 			ST_CP,		//latch data clockoutput 			DS			//shift serial data
);wire [7:0] sel;//数码管位选(选择当前要显示的数码管)wire [7:0] seg;//数码管段选(当前要显示的内容)	wire  [31:0] disp_data;ISSP UISSP(.probe(),.source(disp_data));HEX8 UHEX8(.Clk(Clk),.Rst_n(Rst_n),.En(1'b1),.disp_data(disp_data),.sel(sel),.seg(seg));m74HC595_Driver Um74HC595_Driver(.Clk(Clk),.Rst_n(Rst_n),.Data({seg,sel}),.S_EN(1'b1),.SH_CP(SH_CP),.ST_CP(ST_CP),.DS(DS));endmodule

四、按键控制改变数据值

项目要求: 通过控制按键,使得数码管显示不同的数据内容。

实现效果:
实现效果
先看RTL视图来理解整体框架:
RTL3
按下按键1,数码管显示12345678;按下按键2,数码管显示89abcdef。

按键消抖模块的设计参考该文章:【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)。

在此贴出按键消抖的代码:

KeyFilter.v:

//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
//
module KeyFilter(input Clk,input Rst_n,input key_in,output reg key_flag,output reg key_state
);//按键的四个状态localparamIDLE 		= 4'b0001,FILTER1 	= 4'b0010,DOWN 		= 4'b0100,FILTER2 	= 4'b1000;//状态寄存器reg [3:0] curr_st;//边沿检测输出上升沿或下降沿wire pedge;wire nedge;//计数寄存器reg [19:0]cnt;//使能计数寄存器reg en_cnt;//计数满标志信号reg cnt_full;//计数满寄存器//------<边沿检测电路的实现>------//边沿检测电路寄存器reg key_tmp0;reg key_tmp1;//边沿检测always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginkey_tmp0 <= 1'b0;key_tmp1 <= 1'b0;endelse beginkey_tmp0 <= key_in;key_tmp1 <= key_tmp0;end	endassign nedge = (!key_tmp0) & (key_tmp1);assign pedge = (key_tmp0)  & (!key_tmp1);//------<状态机主程序>------	//状态机主程序always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)begincurr_st <= IDLE;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endelse begincase(curr_st)IDLE:beginkey_flag <= 1'b0;if(nedge)begincurr_st <= FILTER1;en_cnt <= 1'b1;endelsecurr_st <= IDLE;endFILTER1:beginif(cnt_full)beginkey_flag <= 1'b1;key_state <= 1'b0;curr_st <= DOWN;en_cnt <= 1'b0;end	else if(pedge)begincurr_st <= IDLE;en_cnt <= 1'b0;endelsecurr_st <= FILTER1;endDOWN:beginkey_flag <= 1'b0;if(pedge)begincurr_st <= FILTER2;en_cnt <= 1'b1;endelsecurr_st <= DOWN;endFILTER2:beginif(cnt_full)beginkey_flag <= 1'b1;key_state <= 1'b1;curr_st <= IDLE;en_cnt <= 1'b0;end	else if(nedge)begincurr_st <= DOWN;en_cnt <= 1'b0;endelsecurr_st <= FILTER2;enddefault:begincurr_st <= IDLE;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endendcaseendend//------<20ms计数器>------		//20ms计数器//Clk 50_000_000Hz//一个时钟周期为20ns//需要计数20_000_000 / 20 = 1_000_000次always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)cnt <= 20'd0;else if(en_cnt)cnt <= cnt + 1'b1;elsecnt <= 20'd0;endalways@(posedge Clk or negedge Rst_n)beginif(!Rst_n)cnt_full <= 1'b0;else if(cnt == 999_999)cnt_full <= 1'b1;elsecnt_full <= 1'b0;endendmodule

简单编写了一个KeyData模块用于不同数据的输入

KeyData.v:

module KeyData(input Clk,input Rst_n,input Key_state1,input Key_flag1,input Key_state2,input Key_flag2,output reg [31:0] dis_data
);always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)dis_data <= 32'h00000000;else if(Key_flag1 && !Key_state1)dis_data <= 32'h12345678;else if(Key_flag2 && !Key_state2)dis_data <= 32'h89abcdef;else dis_data <= dis_data;endendmodule

顶层模块KeyCtrlSmg.v:

module KeyCtrlSmg(input 	Clk,input 	Rst_n,input 	KeyIn1,input 	KeyIn2,output 	SH_CP,		//shift clockoutput 	ST_CP,		//latch data clockoutput 	DS			//shift serial data
);wire key_state1;wire key_flag1;wire key_state2;wire key_flag2;wire [7:0] sel;//数码管位选(选择当前要显示的数码管)wire [7:0] seg;//数码管段选(当前要显示的内容)	wire  [31:0] dis_data;KeyFilter KeyFilter1(.Clk(Clk),.Rst_n(Rst_n),.key_in(KeyIn1),.key_flag(key_flag1),.key_state(key_state1));KeyFilter KeyFilter2(.Clk(Clk),.Rst_n(Rst_n),.key_in(KeyIn2),.key_flag(key_flag2),.key_state(key_state2));KeyData UKeyData(.Clk(Clk),.Rst_n(Rst_n),.Key_state1(key_state1),.Key_flag1(key_flag1),.Key_state2(key_state2),.Key_flag2(key_flag2),.dis_data(dis_data));HEX8 UHEX8(.Clk(Clk),.Rst_n(Rst_n),.En(1'b1),.disp_data(dis_data),.sel(sel),.seg(seg));m74HC595_Driver Um74HC595_Driver(.Clk(Clk),.Rst_n(Rst_n),.Data({seg,sel}),.S_EN(1'b1),.SH_CP(SH_CP),.ST_CP(ST_CP),.DS(DS));endmodule

测试激励文件:

`timescale 1ns/1ns
`define clock_period 20module KeyCtrlSmg_tb;reg Clk;reg Rst_n;reg KeyIn1;reg KeyIn2;wire SH_CP;wire ST_CP;wire DS;KeyCtrlSmg UKeyCtrlSmg(.Clk(Clk),.Rst_n(Rst_n),.KeyIn1(KeyIn1),.KeyIn2(KeyIn2),.SH_CP(SH_CP),		//shift clock.ST_CP(ST_CP),		//latch data clock.DS(DS)				//shift serial data);initial Clk = 1;always#(`clock_period / 2) Clk = ~Clk;initial begin Rst_n = 0;KeyIn1 = 1;KeyIn2 = 1;#200;Rst_n = 1;#200;KeyIn1 = 0;KeyIn2 = 1;#(`clock_period*10000)KeyIn1 = 1;KeyIn2 = 1;#(`clock_period*10000)KeyIn1 = 1;KeyIn2 = 0;#(`clock_period*10000)$stop;endendmodule

仿真结果:

仿真结果

csdn

🧸结尾


  • ❤️ 感谢您的支持和鼓励! 😊🙏
  • 📜您可能感兴趣的内容:
  • 【FPGA】串口通信讲解-状态机判断数据值
  • 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
  • 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
  • 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
    遇见未来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/101665.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IBM Spectrum LSF Explorer 为要求苛刻的分布式和任务关键型高性能技术计算环境提供强大的工作负载管理

IBM Spectrum LSF Explorer 适用于 IBM Spectrum LSF 集群的强大、轻量级报告解决方案 亮点 ● 允许不同的业务和技术用户使用单一解决方案快速创建和查看报表和仪表板 ● 利用可扩展的库提供预构建的报告 ● 自定义并生成性能、工作负载和资源使用情况的报…

springboot初试elasticsearch

引入依赖 elasticsearch的依赖版本与你elasticsearch要一致 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency> 索引库的操作 创建索引库 impo…

跳槽面试:如何转换工作场所而不失去优势

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

c++ noexcept

引入noexcept原因&#xff1a; 异常规范的检查是在运行期而不是编译期&#xff0c;因此程序员不能保证所有异常都得到了 catch 处理。由于第一点的存在&#xff0c;编译器需要生成额外的代码&#xff0c;在一定程度上妨碍了优化。模板函数中无法使用。赋值函数、拷贝构造函数和…

小红书笔记爬虫

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

【网络安全带你练爬虫-100练】第20练:数据处理-并写入到指定文档位置

目录 一、目标1&#xff1a;解码去标签 二、目标2&#xff1a;提取标签内内容 三、目标3&#xff1a;处理后的数据插入原位置 四、目标4&#xff1a;将指定的内容插入指定的位置 五、目标5&#xff1a;设置上下文字体格式 六、目标6&#xff1a;向多个不同位置插入不同的…

ClickHouse 存算分离改造:小红书自研云原生数据仓库实践

ClickHouse 作为业界性能最强大的 OLAP 系统&#xff0c;在小红书内部被广泛应用于广告、社区、直播和电商等多个业务领域。然而&#xff0c;原生 ClickHouse 的 MPP 架构在运维成本、弹性扩展和故障恢复方面存在较大局限性。为应对挑战&#xff0c;小红书数据流团队基于开源 C…

嵌入式开发-11 Linux下GDB调试工具

目录 1 GDB简介 2 GDB基本命令 3 GDB调试程序 1 GDB简介 GDB是GNU开源组织发布的一个强大的Linux下的程序调试工具。 一般来说&#xff0c;GDB主要帮助你完成下面四个方面的功能&#xff1a; 1、启动你的程序&#xff0c;可以按照你的自定义的要求随心所欲的运行程序&#…

C语言之初阶总结篇

目录 NO.1 NO.2 NO.3 NO.4 NO.5 NO.6 NO.7 NO.8 NO.9 NO.10 NO.11 NO.12.概念tips NO.13.求最小公倍数 NO.14.最大公因数 NO.15.输入读取字符串 NO.16.倒置字符串 今天是一些C语言题目&#xff0c;最近天气炎热&#xff0c;多喝水。 NO.1 下面程序执行后&am…

C++:类和对象(二)

本文主要介绍&#xff1a;构造函数、析构函数、拷贝构造函数、赋值运算符重载、const成员函数、取地址及const取地址操作符重载。 目录 一、类的六个默认成员函数 二、构造函数 1.概念 2.特性 三、析构函数 1.概念 2.特性 四、拷贝构造函数 1.概念 2.特征 五、赋值…

软件设计模式(三):责任链模式

前言 前面荔枝梳理了有关单例模式、策略模式的相关知识&#xff0c;这篇文章荔枝将沿用之前的写法根据示例demo来体会这种责任链设计模式&#xff0c;希望对有需要的小伙伴有帮助吧哈哈哈哈哈哈~~~ 文章目录 前言 责任链模式 1 简单场景 2 责任链模式理解 3 Java下servl…

前端面试题JS篇(1)

JS 的各种位置&#xff0c;比如 clientHeight,scrollHeight,offsetHeight ,以及 scrollTop, offsetTop,clientTop 的区别 clientHeight&#xff1a;表示的是可视区域的高度&#xff0c;不包含 border 和滚动条offsetHeight&#xff1a;表示可视区域的高度&#xff0c;包含了 b…