FPGA学习(10)-数码管
前3节视频目的是实现显示0~F的数码管仿真,后3节是用驱动芯片驱动数码管。
目录
1.数码管显示原理
2.代码过程
2.1仿真结果
3.串行移位寄存器原理
3.1原理
编辑 3.2 数据手册
3.3 先行设计思路
4.程序
4.1确定SRCLK的频率
4.2序列计数器
4.3 不同counter值干不同事
4.4仿真
5.实验结果
1.数码管显示原理
分为共阳极与共阴极驱动,共阴极:发光二极管负极都接地,a~dp接入端口引脚,输出高电平就点亮,共阳极与之相反。
不同位的高低电平可以组成不同的数字符号,后面查找表的值就看这张图。
在驱动多个数码管场景中,往往a~dg与每个数码管对应的二极管是互联的,而sel端的电压可以控制三极管是否导通,以此可以单独让某个数码管亮灭。
根据以上分析可以画出电路图:
1.首先SEL决定了点亮哪个数码管,需要用到38译码器进行选择,而38译码器的输出选择需要用到一个3位的状态计数切换器。
2.如何让状态切换,即加入一个计数器记到1ms,当大于1ms时,让状态加1,且计数器本身清0。
3.SEG决定了数码管哪段亮,在没有规律的情况下可以用查表法,把这些状态一一列出来,例如这里有16个状态,那么需要一个4位的变量来表示对应的索引号。
4.将状态计数切换器值传到8选1数据选择器,分别代表8个数码管对应的通道值,作为索引。
2.代码过程
1.实现1ms的计数器;
2.位切换逻辑,计数器每满一次,状态位切换+1;
3.38译码器实现连接哪个数码管;
4.位选择控制逻辑。
(1)查找表case,完成SEG的配置。
(2)位切换逻辑将8个数码管对应的值当作索引传进去。这里数组顺序错了。
代码:
module Shuma_guan(
input clk,
input reset,
input[31:0] Dis_data,
output reg[7:0] SEL,
output reg[7:0] SEG
);
reg[29:0] counter_1ms;
reg[2:0] which_sel;
reg[3:0] data_temp; //存放索引值
//数码管亮的时间
parameter Base_freq = 50_000_000;
parameter goal_freq = 1000;
parameter cworth = Base_freq/goal_freq-1;
//计数器
always@(posedge clk or negedge reset)
begin
if(!reset)
counter_1ms <= 1'd0;
else if(counter_1ms == cworth)
counter_1ms <= 1'd0;
else
counter_1ms <= counter_1ms+1'd1;
end
//数码管状态切换
always@(posedge clk or negedge reset)
begin
if(reset)
which_sel <= 1'd0;
else if(counter_1ms == cworth)
which_sel <= which_sel+1'd1;
else
which_sel <= which_sel;
end
//38译码器确定哪个数码管亮
always@(posedge clk)
begin
case(which_sel)
0:SEL <= 8'b0000_0001;
1:SEL <= 8'b0000_0010;
2:SEL <= 8'b0000_0100;
3:SEL <= 8'b0000_1000;
4:SEL <= 8'b0001_0000;
5:SEL <= 8'b0010_0000;
6:SEL <= 8'b0100_0000;
7:SEL <= 8'b1000_0000;
endcase
end
//建立查找表,共阳
always@(posedge clk)
begin
case(data_temp)
0:SEG <= 8'b1100_0000;
1:SEG <= 8'b1111_1001;
2:SEG <= 8'b1010_0100;
3:SEG <= 8'b1011_0000;
4:SEG <= 8'b1001_1001;
5:SEG <= 8'b1001_0010;
6:SEG <= 8'b1000_0010;
7:SEG <= 8'b1111_1000;
8:SEG <= 8'b1000_0000;
9:SEG <= 8'b1001_0000;
10:SEG <= 8'b1000_1000;
11:SEG <= 8'b1000_0011;
12:SEG <= 8'b1100_0110;
13:SEG <= 8'b1010_0001;
14:SEG <= 8'b1000_0110;
15:SEG <= 8'b1000_1110;
endcase
end
//将状态值传给索引
always@(*)
begin
case(which_sel)
0:data_temp <= Dis_data[3:0];
1:data_temp <= Dis_data[7:4];
2:data_temp <= Dis_data[11:8];
3:data_temp <= Dis_data[15:12];
4:data_temp <= Dis_data[19:16];
5:data_temp <= Dis_data[23:20];
6:data_temp <= Dis_data[27:24];
7:data_temp <= Dis_data[31:28];
endcase
end
endmodule
仿真:
`timescale 1ns / 1ns
module Shuma_guan_tb();
reg clk;
reg reset;
reg[31:0] Dis_data;
wire[7:0] SEL;
wire[7:0] SEG;
Shuma_guan Shuma_guan_inst0(
.clk(clk),
.reset(reset),
.Dis_data(Dis_data),
.SEL(SEL),
.SEG(SEG)
);
initial clk = 1;
always #10 clk=~clk;
initial begin
reset = 0;
Dis_data = 32'h12345678;
#201;
reset = 1;
#20_000_000;
Dis_data = 32'h9abcdef0;
#20_000_000;
$stop;
end
endmodule
2.1仿真结果
切换状态计数没有随着计数器增加到49999而加1,原因是在切换状态的时序逻辑中,复位没有取反,直接if(reset),相当于复位信号为1时,状态计数就会为0,所以一直不会增加。
修改后正确:
3.串行移位寄存器原理
3.1原理
为了节省IO口引脚,一般会使用驱动芯片74HC595驱动数码管,芯片的原理是基于串行移位寄存器。将上节输出的SEG与SEL传输到74HC595。
基本原理为,顺次连接4个D触发器,每经过一个时钟,就依次将DATA存储的值传送到下一个D触发器,例如,经过5个时钟周期后, 此时DFF3~DFF0存储的值为:1001。如果想在这个时刻把每个D触发器存储的值取出来,就在每个D触发器的输出端再接入一个D触发器,加的这4个D触发器单独用一个时钟进行控制,然后将输出分别接至各自端口。
当要移位8位,16位时,直接加更多的D触发器就行。
74HC595能够驱动8位移位寄存器,SER是数据传输口,SRCLK是第一路串联触发器的时钟,SRCLR非应该是复位信号,RCLK是是每个单独加入的触发器的时钟,OE非是输出的使能信号。第1个74HC595的输出串联到了第2个74HC595的输入,以此构成16位移位寄存器输出。
左边的74HC连接的是选择使能哪个数码管,右边的74HC是决定数码管哪段亮。DIO是数据输入,SRCLK是第一路串联触发器的时钟,RCLK是加入单个D触发器的时钟。
3.2 数据手册
从时序图可以看出,RCLK有方波时,才有输出。
移位寄存器时钟为上升沿时,其实也就是让移位寄存器存前一阶段的数据。RCLK为上升沿时,移位寄存器的数据存储在存储寄存器中。
时钟频率最低为5MHz,其中,SRCLK/RCLK的高低电平时间至少为时钟周期的一半,电平为2V时,SER在SRCLK上升沿前125ns必须稳定,SRCLK的上升沿必须RCLK上升沿之前的94ns。
3.3 先行设计思路
4.程序
4.1确定SRCLK的频率
取50MHz的乘除整数倍,取SRCLK的频率为12.5MHz,定义一个分频计数器,在SRCLK下降沿将数据传到DIO,上升沿DIO将数据传给驱动芯片的寄存器。计数器计数两次为1个SRCLK周期,可根据counter找到SRCLK的上升沿和下降沿。
4.2序列计数器
根据时序图与原理图,先传的SEG,后传的SEL。1ms切换了一次,人眼看不出来,实际上应该是闪烁的数字循环跳变。共32个状态,需要拉低拉高。
4.3 不同counter值干不同事
counter=0,拉低SRCLK,将SEG[7]的值传给DIO,将RCLK拉高。
counter=1,拉高SRCLK(驱动芯片自动会将DIO的数据传给寄存器),RCLK拉低。
......
counter=31,拉高SRCLK
4.4仿真
仿真的RCLK刚开始就拉高了,这是因为if后面没有跟else,直接按照每个时钟沿开始执行程序了。
正确的仿真波形:
此时代码:
module tube_active(
input clk,
input reset,
input[7:0] SEL,
input[7:0] SEG,
output reg SRCLK,
output reg RCLK,
output reg DIO
);
reg[29:0] divi_counter;
reg[4:0] state_counter; //序列计数器
//确定SRCLK的频率为12.5MHz
parameter base_frequence = 50_000_000;
parameter goal_frequence = 12500_000;
parameter goal_counter = base_frequence/(goal_frequence*2)-1;
//分频计数器
always@(posedge clk or negedge reset)
begin
if(!reset)
divi_counter <= 1'd0;
else if(divi_counter == goal_counter)
divi_counter <= 1'd0;
else
divi_counter <= divi_counter +1'd1;
end
//序列计数器
always@(posedge clk or negedge reset)
begin
if(!reset)
state_counter <= 1'd0;
else if(divi_counter == goal_counter)
state_counter <= state_counter+1'd1;
else
state_counter <= state_counter;
end
//不同counter值做不同事,下降沿将值传给DIO,上升沿将DIO的值送给驱动芯片
always@(posedge clk or negedge reset)
begin
if(!reset)begin
SRCLK <= 1'd0;
RCLK <= 1'd0;
DIO <= 1'd0;
end
else begin
case(state_counter)
0:begin DIO <= SEG[7]; SRCLK <= 1'd0; RCLK <= 1'd1; end
1:begin SRCLK <= 1'd1; RCLK <= 1'd0; end
2:begin DIO <= SEG[6]; SRCLK <= 1'd0;end
3:begin SRCLK <= 1'd1; end
4:begin DIO <= SEG[5]; SRCLK <= 1'd0;end
5:begin SRCLK <= 1'd1; end
6:begin DIO <= SEG[4]; SRCLK <= 1'd0;end
7:begin SRCLK <= 1'd1; end
8:begin DIO <= SEG[3]; SRCLK <= 1'd0;end
9:begin SRCLK <= 1'd1; end
10:begin DIO <= SEG[2]; SRCLK <= 1'd0;end
11:begin SRCLK <= 1'd1; end
12:begin DIO <= SEG[1]; SRCLK <= 1'd0;end
13:begin SRCLK <= 1'd1; end
14:begin DIO <= SEG[0]; SRCLK <= 1'd0;end
15:begin SRCLK <= 1'd1; end
16:begin DIO <= SEL[7]; SRCLK <= 1'd0;end
17:begin SRCLK <= 1'd1; end
18:begin DIO <= SEL[6]; SRCLK <= 1'd0;end
19:begin SRCLK <= 1'd1; end
20:begin DIO <= SEL[5]; SRCLK <= 1'd0;end
21:begin SRCLK <= 1'd1; end
22:begin DIO <= SEL[4]; SRCLK <= 1'd0;end
23:begin SRCLK <= 1'd1; end
24:begin DIO <= SEL[3]; SRCLK <= 1'd0;end
25:begin SRCLK <= 1'd1; end
26:begin DIO <= SEL[2]; SRCLK <= 1'd0;end
27:begin SRCLK <= 1'd1; end
28:begin DIO <= SEL[1]; SRCLK <= 1'd0;end
29:begin SRCLK <= 1'd1; end
30:begin DIO <= SEL[0]; SRCLK <= 1'd0;end
31:begin SRCLK <= 1'd1; end
endcase
end
end
endmodule
仿真代码:
`timescale 1ns / 1ns
module tube_active_tb();
reg clk;
reg reset;
reg [7:0]SEL;
reg [7:0]SEG;
wire SRCLK;
wire RCLK;
wire DIO;
tube_active tube_active_inst0(
.clk(clk),
.reset(reset),
.SEL(SEL),
.SEG(SEG),
.SRCLK(SRCLK),
.RCLK(RCLK),
.DIO(DIO)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
reset = 0;
SEL = 8'b0000_0001;
SEG = 8'b0101_0101;
#201;
reset = 1;
#5000;
SEL = 8'b0000_0010;
SEG = 8'b1010_1010;
#5000;
SEL = 8'b1010_0101;
SEG = 8'b0000_1101;
#5000;
$stop;
end
endmodule
在现有模块添加新的源文件,必须点击这里的+,左上角的会出错。新源文件调用了上面的两个模块,让这两个模块连在一起,然后引出三根线与驱动芯片连接。
module tube_shuma_test(
input clk,
input reset,
output SRCLK,
output RCLK,
output DIO,
input [1:0] SW
);
wire[7:0] SEL,SEG; //将两个例化的端口连接起来
reg[31:0] Dis_data;
//调用的并转串模块
tube_active tube_active_inst0(
.clk(clk),
.reset(reset),
.SEL(SEL),
.SEG(SEG),
.SRCLK(SRCLK),
.RCLK(RCLK),
.DIO(DIO)
);
//调用的hex8模块
Shuma_guan Shuma_guan_inst0(
.clk(clk),
.reset(reset),
.Dis_data(Dis_data),
.SEL(SEL),
.SEG(SEG)
);
always@(*)
begin
case(SW)
0:Dis_data <= 32'h01234567;
1:Dis_data <= 32'h89abcdef;
2:Dis_data <= 32'haaaabbbb;
3:Dis_data <= 32'h66666666;
endcase
end
endmodule
5.实验结果
根据拨码开关的开关情况,数码管显示不同的数字。