# FPGA入门笔记008——数码管动态扫描设计与验证
1、数码管动态扫描原理
8段数码管的结构图如图1所示:
对于共阴数码管需要给对应段以高电平才会使其点亮,而对于共阳极数码管则需要给低电平才会点亮。AC620上板载的是共阳极数码管。
不考虑小数点也就是简化为7段数码管,其共阳极数码管编码译码格式如表1所示(h管脚即为图1的dp管脚):
为了节约 IO 以及成本一般采用如图2所示的电路结构:
使用动态显示。动态显示的特点是将所有位数码管的段选线(abcdefgh)并联在一起,由位选线(sel1、sel2、sel3)控制是哪一位数码管有效。
所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。
现在举例假设将扫描时间定为1s,这三个数码管一共分成3s:
第1秒时sel数据线上为3'b100,这时数码管0被选中,这时a=0,数码管0 的LED0就可以点亮;
第2秒时sel数据线上为3'b010,这时数码管1被选中,这时b=0,数码管1的LED1就可以点亮;
第3秒时sel数据线上为3'b001,这时数码管2被选中,这时c=0,数码管2的LED2就可以点亮。
这时的效果就会是数码管0的LED0亮一秒后数码管1的LED1亮一秒最后是数码管2的LED2亮一秒,然后往复循环。
这样如果将扫描时间定为1ms,由于数码管的余辉效应以及人的视觉暂留,就会出现数码管0的LED0、数码管1的LED1以及数码管2 的LED2“同时”亮,并不会有闪烁感。
2、模块分配
1、4输入查找表(根据表1——8段共阳极数码管编码译码表,显示0123456789abcdef),8位输出(段码,十六进制格式);
2、分频模块:由于系统时钟过快(50MHz),所以需要分频到1KHz(1ms)的扫描时钟;
3、8选1多路器:设置一个32位的输入数据[31:0]disp_data,其中:
数码管0对应disp_data[3:0];
数码管1对应disp_data[7:4];
......
数码管7对应disp_data[31:28]。并设置待显示内容寄存器data_tmp[3:0]。
当选中数码管0时,data_tmp[3:0]=disp_data[3:0];
当选中数码管1时,data_tmp[3:0]=disp_data[7:4];
......
当选中数码管7时,data_tmp[3:0]=disp_data[31:28]。
该多路器选择端为当前扫描的数码管位置:sel_r[7:0]。
4、8位循环移位寄存器:利用循环移位寄存器实现0000_0001b→1000_0000b的变化,进而实现数码管的位选,即实现每个扫描时钟周期选择一个数码管。移位寄存器输出值与数码管选通的对应关系如表2所示,其中sel7为高位。
3、数码管驱动模块总逻辑电路图
其模块总逻辑电路图如图4所示:
不使用小数点的话,则为7段数码管:seg[6:0]。
4、各模块代码编写
0、设置输入输出
module HEX8(
Clk,
Rst_n,
En,
disp_data,
sel,
seg
);
input Clk; //50M
input Rst_n;
input En; //数码管显示使能,1使能,0关闭
input [31:0]disp_data;
output [7:0]sel; //数码管位选(选择当前要显示的数码管)
output reg[6:0]seg; //数码管段选(当前要显示的内容)
endmodule
1、分频器模块
将系统时钟(50MHz,20ns)经过分频器模块分为1KHz(1ms)时钟,相当于时钟每500us翻转一次,所以计数器clk_1K计数到每25000次(15位宽)分频时钟翻转一次。故设置计数器[14:0]divider_cnt。
//分频计数器模块(计数器)
reg [14:0]divider_cnt; //设置15位计数器cnt,最大计数到24999
reg clk_1K;
always@(posedge Clk or negedge Rst_n)
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;
//设定出1KHz的时钟
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
clk_1K <= 1'b0;
else if(divider_cnt == 24999)
clk_1K <= ~clk_1K;
else
clk_1K <= clk_1K;
2、8位循环移位寄存器模块
//8位循环移位寄存器模块
reg [7:0]sel_r;
always@(posedge clk_1K or negedge Rst_n)
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; //左移
3、8选1多路器模块
//8选1多路器模块
reg [3:0]data_tmp;
always@(*) //'*'表示所有输入信号都作为敏感信号(适用于组合逻辑)
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
4、LUT模块(4输入查找表)、二选一多路器
//LUT模块(4输入查找表)
always@(*)
case(data_tmp)
4'h0:seg = 7'b100_0000;
4'h1:seg = 7'b111_1001;
4'h2:seg = 7'b010_0100;
4'h3:seg = 7'b011_0000;
4'h4:seg = 7'b001_1001;
4'h5:seg = 7'b001_0010;
4'h6:seg = 7'b000_0010;
4'h7:seg = 7'b111_1000;
4'h8:seg = 7'b000_0000;
4'h9:seg = 7'b001_0000;
4'ha:seg = 7'b000_1000;
4'hb:seg = 7'b000_0011;
4'hc:seg = 7'b100_0110;
4'hd:seg = 7'b010_0001;
4'he:seg = 7'b000_0110;
4'hf:seg = 7'b100_1110;
endcase
//二选一多路器
assign sel = (En)?sel_r:8'b0000_0000;
5、仿真
`timescale 1ns/1ns
`define clk_period 20
module HEX8_tb;
reg Clk; //50M
reg Rst_n;
reg En; //数码管显示使能,1使能,0关闭
reg [31:0]disp_data;
wire [7:0]sel; //数码管位选(选择当前要显示的数码管)
wire [6:0]seg; //数码管段选(当前要显示的内容)
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(En),
.disp_data(disp_data),
.sel(sel),
.seg(seg)
);
initial Clk = 1;
always#(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
En = 1;
disp_data = 32'h12345678;
#(`clk_period*20);
Rst_n = 1;
#20000000;
disp_data = 32'h87654321;
#20000000;
disp_data = 32'h89abcdef;
#20000000;
$stop;
end
endmodule
6、板级调试与验证
为了进行板级调试,需要编写一个顶层模块HEX8_top.v,该顶层模块将驱动包含进去,同时包含调试模块。
使用Quartus自带的的In system sources and probes editor(ISSP)调试工具进行逻辑功能测试。这样测试整体模块框图就可以简化为如图5所示。
这里 ISSP 是以 IP 核的形式提供的,ISSP使用步骤如下:
1、单击 Tools→Mega Wizard Plug-In Manager 来启动 Mega Wizard 插件管理器来对 IP 核进行相关操作。
2、在弹出如下图所示的Mega Wizard 插件管理器的参数设置界面中,找到 JTAG-accessible Extensions 下选择 In-System Source and Probes,并将输出目录确定为工程文件夹下的 ip 文 件夹,并以 hex8_data 保存,单击 Next。
3、在弹出如下图所示的配置界面中将驱动源(source)位宽定义为 32,探针(probe)位宽定义为 0,然后单击 Next 即可。
4、simulation library 界面选择 next。
5、生成文件汇总界面选择 next。
6、点击 Finish 后,生成的 IP 核,自动加入了工程。可以在 setting 界面的 files 菜 单下,查看加入工程的 IP 文件是否添加成功。
7、由于 ISSP 依旧属于 IP 核,因此在使用前需要将其在工程文件中进行例化。快捷键Ctrl+o打开文件夹,双击hex8_data.v文件。
8、复制如下图所示的语句,粘贴到顶层文件HEX8_top.v中进行例化,并将HEX8_top.v设置为顶层文件。
代码如下所示:
module HEX8_top(
Clk,
Rst_n,
sel,
seg
);
input Clk; //50M
input Rst_n;
output [7:0]sel; //数码管位选(选择当前要显示的数码管)
output [6:0]seg; //数码管段选(当前要显示的内容)
wire[31:0]disp_data;
//ISSP模块
hex8_data hex8_data(
.probe(),
.source(disp_data)
);
//数码管驱动
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(1),
.disp_data(disp_data),
.sel(sel),
.seg(seg)
);
endmodule
7、串行移位寄存器驱动数码管显示设计与实现(适用于AC620)
FPGA 将点亮数码管需要的 16 位段选和位选信号通过 SPI 接口传递给 74HC595 芯片,74HC595可将串行数据转为并行数据。74HC595 芯片再将这 16 位的数据通过 16 个 IO 信号输出,从而驱动数码管的位选和段选。 74HC595 支持级联功能,由于一片74HC595 只能实现 8 位数据的串并转换,因此使用 2 片 74HC595 芯片级联实现,其电路图如图6所示。
如图为74HC595芯片的组成结构(4bit粗略版):
增加一个模块:HC595_Driver.v,将HEX8.v输出的sel和seg信号(Data)转换为连接74HC595芯片上的LATCH信号(ST_CP)、DATA信号(DS)和CLK(SH_CP)信号。3.3V供电情况下,取SH_CP时钟频率为12.5MHz(80ns),则可以让其每40ns翻转一次。也就是每2个系统时钟周期(CLK:20ns)翻转一次。代码如下:
module HC595_Driver(
Clk,
Rst_n,
Data,
S_EN, //移位使能信号,控制是否输出数据
SH_CP, //Shift_Clock
ST_CP, //LATCH_Clock
DS
);
input Clk;
input Rst_n;
input [15:0]Data;
input S_EN;
output reg SH_CP;
output reg ST_CP;
output reg DS;
//使能信号
reg [15:0]r_data;
always@(posedge Clk)
if(S_EN)
r_data <= Data;
//分频器
parameter CNT_MAX = 2; //12.5MHz,CNT_MAX = 2(每两个时钟周期翻转一次);若是6.25MHz,则CNT_MAX = 4(每四个时钟周期翻转一次)。
//计数器
reg [7:0]divider_cnt;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
divider_cnt <= 0;
else if(divider_cnt == CNT_MAX - 1'b1) //每2个时钟周期翻转一次:0、1
divider_cnt <= 0;
else
divider_cnt <= divider_cnt + 1'b1;
//sck_plus脉冲
wire sck_plus;
assign sck_plus = (divider_cnt == CNT_MAX - 1'b1);
//边沿计数器,计数到32
reg [5:0]SHCP_EDGE_CNT;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
SHCP_EDGE_CNT <= 0;
else if(sck_plus)begin
if(SHCP_EDGE_CNT == 32)
SHCP_EDGE_CNT <= 0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
//产生ST_CP、SH_CP、DS信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
ST_CP <= 1'b0;
DS <= 1'b0;
SH_CP <= 1'b0;
end
else begin
case(SHCP_EDGE_CNT)
0:begin SH_CP <= 0; ST_CP <= 1'd0; DS <= r_data[15];end
1:begin SH_CP <= 1;end
2:begin SH_CP <= 0; DS <= r_data[14];end
3:begin SH_CP <= 1;end
4:begin SH_CP <= 0; DS <= r_data[13];end
5:begin SH_CP <= 1;end
6:begin SH_CP <= 0; DS <= r_data[12];end
7:begin SH_CP <= 1;end
8:begin SH_CP <= 0; DS <= r_data[11];end
9:begin SH_CP <= 1;end
10:begin SH_CP <= 0; DS <= r_data[10];end
11:begin SH_CP <= 1;end
12:begin SH_CP <= 0; DS <= r_data[9];end
13:begin SH_CP <= 1;end
14:begin SH_CP <= 0; DS <= r_data[8];end
15:begin SH_CP <= 1;end
16:begin SH_CP <= 0; DS <= r_data[7];end
17:begin SH_CP <= 1;end
18:begin SH_CP <= 0; DS <= r_data[6];end
19:begin SH_CP <= 1;end
20:begin SH_CP <= 0; DS <= r_data[5];end
21:begin SH_CP <= 1;end
22:begin SH_CP <= 0; DS <= r_data[4];end
23:begin SH_CP <= 1;end
24:begin SH_CP <= 0; DS <= r_data[3];end
25:begin SH_CP <= 1;end
26:begin SH_CP <= 0; DS <= r_data[2];end
27:begin SH_CP <= 1;end
28:begin SH_CP <= 0; DS <= r_data[1];end
29:begin SH_CP <= 1;end
30:begin SH_CP <= 0; DS <= r_data[0];end
31:begin SH_CP <= 1;end
32:ST_CP <= 1'd1;
default:
begin
ST_CP <= 1'b0;
DS <= 1'b0;
SH_CP <= 1'd0;
end
endcase
end
endmodule
8、串行移位寄存器驱动仿真
`timescale 1ns/1ns
`define clk_period 20
module HC595_Driver_tb;
reg Clk;
reg Rst_n;
reg [15:0]Data;
reg S_EN;
wire SH_CP;
wire ST_CP;
wire DS;
HC595_Driver HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data(Data),
.S_EN(S_EN), //移位使能信号,控制是否输出数据
.SH_CP(SH_CP), //Shift_Clock
.ST_CP(ST_CP), //LATCH_Clock
.DS(DS)
);
initial Clk = 1;
always#(`clk_period/2) Clk = ~Clk;
initial begin
Rst_n = 0;
#201
Rst_n = 1;
#200
S_EN = 0;
Data = 16'h1234;
#201;
S_EN = 1;
#2000;
S_EN = 0;
#200000;
Data = 16'h8765;
S_EN = 1;
#2000;
S_EN = 0;
#2000;
$stop;
end
endmodule
9、将串行移位寄存器驱动加入到HEX_top.v中
module HEX8_top(
Clk,
Rst_n,
SH_CP,
ST_CP,
DS
);
input Clk; //50M
input Rst_n;
output SH_CP;
output ST_CP;
output DS;
wire [7:0]sel; //数码管位选(选择当前要显示的数码管)
wire [6:0]seg; //数码管段选(当前要显示的内容)
wire[31:0]disp_data;
//ISSP模块
hex8_data hex8_data(
.probe(),
.source(disp_data)
);
//数码管驱动
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(1),
.disp_data(disp_data),
.sel(sel),
.seg(seg)
);
//74HC595驱动模块
HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data({1'd0,seg,sel}),
.S_EN(1), //移位使能信号,控制是否输出数据
.SH_CP(SH_CP), //Shift_Clock
.ST_CP(ST_CP), //LATCH_Clock
.DS(DS)
);
endmodule
标签:disp,FPGA,Clk,seg,数码管,Rst,008,data
From: https://www.cnblogs.com/little55/p/18082220