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
本文作者:little55
本文链接:https://www.cnblogs.com/little55/p/18082220
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步