🎉欢迎来到FPGA专栏~三线制数码管驱动
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
🎉 目录-三线制数码管驱动
- 一、效果演示
- 二、电路结构
- 三、代码详解
- 3.1 总体结构设计
- 3.2 驱动74HC595芯片
- 3.3 HEX8模块
- 3.4 顶层模块
- 四、按键控制改变数据值
一、效果演示
🥝ISSP调试演示:
程序配置完成:
调试:
🥝按键控制演示:
二、电路结构
在三线制的数码管驱动中,使用74HC595芯片来减少FPGA的管脚数量使用。
关于74HC595芯片的介绍和时序图,参考文章:74HC595介绍 和 74HC595 驱动。
在AC620开发板上的数码管驱动电路:
AC620开发板使用的是74HC595芯片的级联来驱动显示:
三、代码详解
3.1 总体结构设计
先上RTL视图:
HEX8模块将seg和sel信号传给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 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;
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)begin
if(!Rst_n)
r_data <= 16'd0;
else if(S_EN)
r_data <= Data;
else
r_data <= r_data;
end
//clock divide
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 16'd0;
else if(divider_cnt == CNT_MAX)
divider_cnt <= 16'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
assign sck_pluse = (divider_cnt == CNT_MAX);
always@(posedge Clk or negedge Rst_n)begin
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'b1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
end
always@(posedge Clk or negedge Rst_n)begin
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
default:begin SH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0; end
endcase
end
end
endmodule
RTL视图:
3.3 HEX8模块
该模块的设计是在该文章的讲解基础之上进行修改:【FPGA零基础学习之旅#11】数码管动态扫描。
上述参考文章中的模块可以称为HEX6,驱动了6个数码管,在此我们需要驱动8个数码管,故可以将模块命名为HEX8。
需要注意的是,在设计数码管位选的时候,一定要看清使用板子的电路结构,弄清楚是高电平位选还是低电平位选!
HEX8.v:
module HEX8(
input Clk, //50M
input 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-1
reg clk_1K;
reg [3:0]data_tmp;//待显示数据缓存
//1KHz分频计数器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 15'd0;
else if(!En)
divider_cnt <= 15'd0;
else if(divider_cnt == 24999)
divider_cnt <= 15'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
//1KHz扫描时钟
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
clk_1K <= 1'b0;
else if(divider_cnt == 24999)
clk_1K <= ~clk_1K;
else
clk_1K <= clk_1K;
end
//--------<6位循环移位寄存器>--------
always@(posedge clk_1K or negedge Rst_n)begin
if(!Rst_n)
sel_r <= 8'b0000_0001;
else if(sel_r == 8'b1000_0000)
sel_r <= 8'b0000_0001;
else
sel_r <= sel_r << 1;
end
//--------<6选1多路器>--------
always@(*)begin
case(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;
endcase
end
//--------<LUT>--------
always@(*)begin
case(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;
endcase
end
//--------<2选1多路器>--------
assign sel = (En)?(sel_r):8'b1111_1111;
endmodule
3.4 顶层模块
在顶层模块中需要调用ISSP这样的一个IP核,操作过程和调试方法参考:【FPGA零基础学习之旅#11】数码管动态扫描。
smg.v:
module smg(
input Clk, //50M
input Rst_n,
//input [31:0] disp_data,
output SH_CP, //shift clock
output ST_CP, //latch data clock
output 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视图来理解整体框架:
按下按键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
);
//按键的四个状态
localparam
IDLE = 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)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);
assign pedge = (key_tmp0) & (!key_tmp1);
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
简单编写了一个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)begin
if(!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;
end
endmodule
顶层模块KeyCtrlSmg.v:
module KeyCtrlSmg(
input Clk,
input Rst_n,
input KeyIn1,
input KeyIn2,
output SH_CP, //shift clock
output ST_CP, //latch data clock
output 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 20
module 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;
end
endmodule
仿真结果:
🧸结尾
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA】串口通信讲解-状态机判断数据值
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制