赞
踩
图a为单个数码管的原理图,实际上和点灯差不多,不过这8个灯按照数字8的形状进行排布。
数码管分共阴极和共阳极两种,图b左边是共阴极数码管,需要给对应的引脚高电平以点亮,右边是共阳极数码管,需要给低电平点亮。
以6个8段数码管为例,每个数码管占用8个引脚,6个数码管就要占用48个引脚。
该方式对于fpga来说占用引脚过多,但这种方式使用起来最简单,比如友晶DE2开发板。
将每个数码管的段选信号都连在一起,然后用6bit位选信号来选择数码管进行显示,引脚占用就减少到14个了,如下图:
在这种方式下,不管位选信号是多少,同一时刻只能显示一个数字,为了让不同的数码管显示不同的数字,需要进行动态显示。
动态显示下,任意时刻只选中1个数码管进行显示,6个数码管轮流点亮,由于余晖效应,只要刷新够快,就能让所有数码管同时显示。
为了进一步减少数码管的引脚占用,使用74hc595芯片,其内部结构非常简单,就是一个8位的移位寄存器和一个8位的存储寄存器:
74hc595常用于串并转换,2片74HC595级联就能实现16位串转并,舍弃掉最后两位,就能实现14位串转并功能。
FPGA内部的14位的位选段选信号是并行的,我们需要先将其转化为串行数据输入74hc595,然后控制该芯片将串行数据转化为并行数据输出,只需控制DS、SHCP、STCP、OE四个引脚即可,这样FPGA的引脚占用就从14减小到4了。
其中,DS为串行数据,SHCP为移位时钟,STCP为锁存时钟,在两片74hc595级联的情况下,先将串行数据移位进两个8bit移位寄存器,再并行输出,即每16个SHCP上升沿对应1个STCP上升沿。
此处我们需要自己写一个74hc595的驱动,将14位并行信号转化为串行信号,系统时钟50M,SHCP时钟选择系统时钟四分频,STCP选择系统时钟64分频,即SHCP的16分频,代码如下:
module hc595( input clk, input rst_n, // input input [7:0] seg, input [5:0] sel, // output output ds, output shcp, output stcp, output oe ); //------------signals-------------- // oe低电平有效 assign oe = 1'b0; // 分频计数器 reg [5:0] cnt; // 并行数据,末尾两位舍弃不用 wire [15:0] out_data = {seg, sel, 2'b00}; //------------function------------- // 分频计数器 always @(posedge clk, negedge rst_n) begin if(!rst_n) cnt <= 0; else cnt <= cnt + 6'd1; end // shcp选择4分频,即12.5M assign shcp = cnt[1]; // stcp选择64分频,即shcp的16分频,保证16个shcp上升沿对应1个stcp上升沿 assign stcp = cnt[5:2] == 4'b0000; // 输出串行ds,保证ds在shcp的下降沿变化 assign ds = out_data[cnt[5:2]]; endmodule
有了74hc595模块,FPGA就可以使用14位的位选+段选信号来驱动数码管了,但FPGA内一般用4位BCD码表示1个数字,则6个数字需要24位BCD码,需要再写一个驱动,将24位BCD码转化为14位位选+段选信号,按照1khz的频率进行扫描,快速的扫描加上数码管的余晖效应,人眼看起来6个数码管是同时点亮的,该驱动模块RTL图如下:
verilog代码如下:
module BCD2seg_sel( // input input clk, input rst_n, input [23:0] BCD_data, // output output reg [7:0] seg, output reg [5:0] sel ); //------------signals-------------- // 计数器,用于产生约1khz扫描信号,达到动态显示效果 reg [15:0] cnt; wire cnt_max = &cnt; // 被选中的BCD码 reg [3:0] BCD_out; //------------function------------- // 计数器 always @(posedge clk, negedge rst_n) begin if(!rst_n) cnt <= 0; else cnt <= cnt + 16'd1; end // 循环移位寄存器,生成sel always @(posedge clk, negedge rst_n) begin if(!rst_n) sel <= 6'b000001; else if(cnt_max) sel <= {sel[4:0], sel[5]}; end // 根据sel选择一个BCD码 always @(*) begin case(sel) 6'b000001: BCD_out = BCD_data[3:0]; 6'b000010: BCD_out = BCD_data[7:4]; 6'b000100: BCD_out = BCD_data[11:8]; 6'b001000: BCD_out = BCD_data[15:12]; 6'b010000: BCD_out = BCD_data[19:16]; 6'b100000: BCD_out = BCD_data[23:20]; default: BCD_out = 4'h0;// 默认0 endcase end // 根据BCD_out进行数码管译码 always @(*) begin case(BCD_out) 4'h0 : seg = 8'h03; 4'h1 : seg = 8'h9f; 4'h2 : seg = 8'h25; 4'h3 : seg = 8'h0d; 4'h4 : seg = 8'h99; 4'h5 : seg = 8'h49; 4'h6 : seg = 8'h41; 4'h7 : seg = 8'h1f; 4'h8 : seg = 8'h01; 4'h9 : seg = 8'h09; default: seg = 8'h03;// 默认0 endcase end endmodule
用这个驱动显示一下012345,顶层模块代码如下,即实例化上述两个模块,并传入24位bcd码:
module HEX6( input clk, input rst_n, // 74HC595 side output ds, output shcp, output stcp, output oe ); //------------signals-------------- wire [23:0] BCD_DATA = {4'h0, 4'h1, 4'h2, 4'h3, 4'h4, 4'h5}; wire [7:0] seg; wire [5:0] sel; //------------function------------- BCD2seg_sel inst_BCD2seg_sel( .clk (clk), .rst_n (rst_n), .BCD_data (BCD_DATA), .seg (seg), .sel (sel) ); hc595 inst_hc595( .clk (clk), .rst_n (rst_n), .seg (seg), .sel (sel), .ds (ds), .shcp (shcp), .stcp (stcp), .oe (oe) );
上板验证结果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。