🎉欢迎来到FPGA专栏~数码管动态扫描
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
🎉 目录-数码管动态扫描
- 一、效果演示
- 二、电路结构
- 三、代码详解
- 四、AV4开发板演示
- 五、Spirit_V2开发板演示
一、效果演示
🥝Spirit_V2开发板按键控制数码管:
🥝AV4开发板数码管动态扫描:
二、电路结构
上图电路结构的子模块介绍:
名称 | 功能描述 |
---|---|
divider | 分频产生1KHz的扫描时钟 |
shift6 | 6位循环移位寄存器 |
MUX6 | 数据输入选择 |
MUX2 | 使能选择 |
LUT | 数据译码器 |
由于各子模块的代码量不会特别大,因此将在一个文件中编写。
三、代码详解
先上代码:
smg_HEX.v:
module smg_HEX(
input Clk, //50M
input Rst_n, //复位
input En, //数码管显示使能
input [23:0] disp_data, //6 × 4 = 24(6个数码管,数据格式为hex,总共输入24位)
output reg [7:0] seg, //数码管段选
output [5:0] sel //数码管位选(数码管选择)
);
reg [5: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 <= 6'b000_001;
else if(sel_r == 6'b100_000)
sel_r <= 6'b000_001;
else
sel_r <= sel_r << 1;
end
//--------<6选1多路器>--------
always@(*)begin
case(sel_r)
6'b00_0001:data_tmp = disp_data[3:0];
6'b00_0010:data_tmp = disp_data[7:4];
6'b00_0100:data_tmp = disp_data[11:8];
6'b00_1000:data_tmp = disp_data[15:12];
6'b01_0000:data_tmp = disp_data[19:16];
6'b10_0000:data_tmp = disp_data[23:20];
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):6'b111_111;
endmodule
需要注意端口列表:
module smg_HEX(
input Clk, //50M
input Rst_n, //复位
input En, //数码管显示使能
input [23:0] disp_data, //6 × 4 = 24(6个数码管,数据格式为hex,总共输入24位)
output reg [7:0] seg, //数码管段选
output [5:0] sel //数码管位选(数码管选择)
);
En使能信号只有在高电平时,数码管显示。加入使能信号端口是为了低功耗设计的实现。
接下来做仿真测试:
smg_HEX_tb.v:
`timescale 1ns/1ns
`define clock_period 20
module smg_HEX_tb;
reg Clk; //50M
reg Rst_n;
reg En; //数码管显示使能
reg [23:0] disp_data;
wire [7:0] seg; //数码管段选
wire [5:0] sel; //数码管位选(数码管选择)
smg_HEX Usmg_HEX(
.Clk(Clk), //50M
.Rst_n(Rst_n),
.En(En), //数码管显示使能
.disp_data(disp_data),
.seg(seg), //数码管段选
.sel(sel) //数码管位选(数码管选择)
);
initial Clk = 1;
always#(`clock_period / 2) Clk =~Clk;
initial begin
Rst_n = 1'b0;
En = 1;
disp_data = 24'h123456;
#(`clock_period*20);
Rst_n = 1'b1;
#(`clock_period*20);
#20000000;
disp_data = 24'h89abcd;
#20000000;
$stop;
end
endmodule
仿真结果:
RTL:
四、AV4开发板演示
上述代码为数码管动态扫描的驱动代码,使用时需要添加上层模块。
AV4开发板开箱视频:【FPGA-AV4】火热售卖中!欢迎大家抢购!小月电子~(含购买链接)。
在AV4开发板上的实现,主要使用到了ISSP调试工具,ISSP的ip核创建:
🥝创建一个新的ip核:
🥝选择ISSP,选择好Verilog HDL和路径:
🥝该项目只需要用到source端口:
🥝下一步:
🥝完成:
issp.v:
// megafunction wizard: %In-System Sources and Probes%VBB%
// GENERATION: STANDARD
// VERSION: WM1.0
// MODULE: altsource_probe
// ============================================================
// File Name: issp.v
// Megafunction Name(s):
// altsource_probe
//
// Simulation Library Files(s):
// altera_mf
// ============================================================
// ************************************************************
// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!
//
// 13.0.0 Build 156 04/24/2013 SJ Full Version
// ************************************************************
//Copyright (C) 1991-2013 Altera Corporation
//Your use of Altera Corporation's design tools, logic functions
//and other software and tools, and its AMPP partner logic
//functions, and any output files from any of the foregoing
//(including device programming or simulation files), and any
//associated documentation or information are expressly subject
//to the terms and conditions of the Altera Program License
//Subscription Agreement, Altera MegaCore Function License
//Agreement, or other applicable license agreement, including,
//without limitation, that your use is for the sole purpose of
//programming logic devices manufactured by Altera and sold by
//Altera or its authorized distributors. Please refer to the
//applicable agreement for further details.
module issp (
probe,
source);
input probe;
output [23:0] source;
endmodule
// ============================================================
// CNX file retrieval info
// ============================================================
// Retrieval info: PRIVATE: INTENDED_DEVICE_FAMILY STRING "Cyclone IV E"
// Retrieval info: LIBRARY: altera_mf altera_mf.altera_mf_components.all
// Retrieval info: CONSTANT: ENABLE_METASTABILITY STRING "NO"
// Retrieval info: CONSTANT: INSTANCE_ID STRING "NONE"
// Retrieval info: CONSTANT: PROBE_WIDTH NUMERIC "0"
// Retrieval info: CONSTANT: SLD_AUTO_INSTANCE_INDEX STRING "YES"
// Retrieval info: CONSTANT: SLD_INSTANCE_INDEX NUMERIC "0"
// Retrieval info: CONSTANT: SOURCE_INITIAL_VALUE STRING " 0"
// Retrieval info: CONSTANT: SOURCE_WIDTH NUMERIC "24"
// Retrieval info: USED_PORT: probe 0 0 0 0 INPUT NODEFVAL "probe"
// Retrieval info: USED_PORT: source 0 0 24 0 OUTPUT NODEFVAL "source[23..0]"
// Retrieval info: CONNECT: @probe 0 0 0 0 probe 0 0 0 0
// Retrieval info: CONNECT: source 0 0 24 0 @source 0 0 24 0
// Retrieval info: GEN_FILE: TYPE_NORMAL issp.v TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL issp.inc TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL issp.cmp TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL issp.bsf TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL issp_inst.v TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL issp_bb.v TRUE
// Retrieval info: LIB_FILE: altera_mf
将该ip核添加到项目文件中,并在顶层文件中例化模块:
module smg_ISSP(
input Clk, //50M
input Rst_n, //复位
output [7:0] seg, //数码管段选
output [5:0] sel //数码管位选(数码管选择)
);
wire [23:0]disp_data;
smg_HEX Usmg_HEX(
.Clk(Clk), //50M
.Rst_n(Rst_n), //复位
.En(1'b1), //数码管显示使能
.disp_data(disp_data), //6 × 4 = 24(6个数码管,数据格式为hex,总共输入24位)
.seg(seg), //数码管段选
.sel(sel) //数码管位选(数码管选择)
);
issp Uissp(
.probe(),
.source(disp_data)
);
endmodule
将上述程序配置好之后,数码管显示如下:
当程序配置好之后,使用ISSP调试工具,打开步骤:
🍋在Tools中打开:
🍋未发现设备,先点击ok:
🍋在Hardware中选择对应的设备:
🍋将数据显示格式调整为hex格式:
🍋将数据改为123456:
🍋测试结果:
🍋将数据改为ABCDEF:
🍋测试结果:
五、Spirit_V2开发板演示
Spirit_V2开发板介绍:【FPGA-Spirit_V2】小精灵V2开发板初使用。
接下来在Spirit_V2开发板上实验,通过按键控制数码管亮灭,实现简单的低功耗设计:
先上RTL视图,便于理解编程思路:
按键信号经过按键消抖模块之后,有效信号进入数码管数据和使能模块,最后信号进入数码管驱动模块。
按键消抖模块的详细讲解:【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
DataAndEn.v:
module DataAndEn(
input Clk,
input Rst_n,
input key_flag,
input key_state,
output reg [23:0] disp_data,
output reg En
);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
En <= 1'b0;
else if(key_state == 1'b0)
En <= 1'b1;
else
En <= 1'b0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
disp_data <= 24'h0;
else if(!key_state)
disp_data <= 24'h89abcd;
else
;
end
endmodule
数码管驱动模块的代码保持不变,接下来为顶层模块:
smg_top.v:
module smg_top(
input Clk,
input Rst_n,
input key_in,
output [7:0] seg,
output [5:0] sel
);
wire key_flag;
wire key_state;
wire En;
wire [23:0] disp_data;
KeyFilter UKeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
DataAndEn UDataAndEn(
.Clk(Clk),
.Rst_n(Rst_n),
.key_flag(key_flag),
.key_state(key_state),
.disp_data(disp_data),
.En(En)
);
smg_HEX Usmg_HEX(
.Clk(Clk),
.Rst_n(Rst_n),
.En(En),
.disp_data(disp_data),
.seg(seg),
.sel(sel)
);
endmodule
🧸结尾
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA】串口通信讲解-状态机判断数据值
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制