FPGA数码管显示基础设计

1.数码管简介

数码管是一种半导体发光器件,其基本单元是发光二极管。一般分为七段数码管和八段数码管,多的一段是小数点。也有其他如N型、米型数码管以及16段、24段管等。本次设计的是八段数码管

1.1 数码管硬件结构


公阴极数码管高电平亮,公阳极数码管低电平亮。AC620上搭载的是公阳极数码管。
数码管的显示有静态和动态两种:静态的特点是每个数码管的段必须接一个八位数据线来保持显示的字形码,输送一次字形码后,显示字形可一直保持。但是这种方法需要很多的IO口,很少采用。

这就实现了另一种显示模式,动态显示。动态显示将所有的段选线连一起,由位选控制哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向数码管送出字形码和相应的位选,利用发光管的余晖和人眼视觉暂留作用,使人的感觉好像数码管都在显示。

1.2 三线制数码管电路设计

AC620上配备的是八位八段数码管,如果按照上述设计,仍然需要16个IO口。为了继续节省IO口,大多使用串行移位寄存器来节省IO口。移位寄存器,就是一端使用串行接口如SPI接口来按位接收多位数据,另一端则将接收到的数据分别使用一个独立的管脚输出,实现串并转换
具体实现就是使用一片到多片SPI接口的串行移位寄存器,例如74HC595芯片。FPGA将点亮数码管需要的16位段选和位选信号通过SPI接口传递给74HC595芯片,74HC595再将这16位的数据通过16个IO信号输出,从而驱动数码管的位选和段选。一位74HC595只能实现8位数据的串并转换,因此使用两片74HC595芯片级联实现


从该原理图可以看出输入的16位信号为{sel,seg},其中位选高位代表数码管右边,低位左边,段选为{a,b,c,d,e,f,g,h}

2.数码管模块功能划分

由于数码管显示较为自由,本模块的划分也较多,可以根据各自不同的使用情况将模块自由的组合起来。日后想起来也可补充。
1.74HC595驱动模块
本模块接收来自其他模块的位选和段选信号,通过74HC595驱动模块发送至74HC595芯片

oe端在本开发板上直接接地,不需要程序操作

2.32位转位选段选信号模块
四位代表一个数码管显示的符号,八位一共需32位。可根据需要扩展或收缩。

3.二进制转十进制。
FPGA中数字都是二进制,如果累加的话上述驱动无法正常显示,因此需要二进制转十进制传给其他模块来显示。

3.模块编写

3.1 74HC595驱动模块

3.1.1 原理

本模块负责将数据发送给74HC595芯片。由原理图可知:
ds/dio引脚是数据线。负责发送数据。
STCP/RCLK引脚是寄存器时钟输入线。上升沿将移位寄存器的数据取出显示在八位管脚上。平时为低电平,数据传输完成为拉高。
SHCP/SCLK引脚是移位寄存器时钟线。上升沿将数据移位寄存至移位寄存器中。

3.1.2 程序

// FPGA   : 小梅哥AC620
// EDA    : Quartus II 13.0sp1 (64-bit) and ModelSim SE-64 10.5 
// Author : FPGA小白758 https://blog.csdn.net/q1594?spm=1010.2135.3001.5343
// File   : hc595_ctrl.v
// Create : 2022-05-14 01:13:06
// Revise : 2022-05-14 01:13:06
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
// 74HC595控制模块
module hc595_ctrl(
    input   wire            sys_clk             ,   //系统时钟,频率 50MHz
    input   wire            sys_rst_n           ,   //复位信号,低有效
    input   wire    [7:0]   sel                 ,   //数码管位选信号
    input   wire    [7:0]   seg                 ,   //数码管段选信号
    
    output  reg             stcp                ,   //数据存储器时钟
    output  reg             shcp                ,   //移位寄存器时钟
    output  reg             ds                      //串行数据输入
);
//reg define
reg     [1:0]       cnt_4                       ;   //分频计数器
reg     [4:0]       cnt_bit                     ;   //传输位数计数器
//wire define
wire [15:0] data ; //数码管信号寄存

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//将数码管信号寄存
assign data={sel,seg};

//分频计数器:0~3 循环计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_4 <= 2'd0;
    else if(cnt_4 == 2'd3)
        cnt_4 <= 2'd0;
    else
        cnt_4 <= cnt_4 + 1'b1;

//cnt_bit:每输入一位数据加一
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <= 4'd0;
    else if(cnt_4 == 2'd3 && cnt_bit == 4'd15)
        cnt_bit <= 4'd0;
    else if(cnt_4 == 2'd3)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;

//stcp:16个信号传输完成之后产生一个上升沿
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        stcp <= 1'b0;
    else if(cnt_bit == 4'd15 && cnt_4 == 2'd3)
        stcp <= 1'b1;
    else
        stcp <= 1'b0;

//shcp:产生四分频移位时钟
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        shcp <= 1'b0;
    else if(cnt_4 >= 4'd2)
        shcp <= 1'b1;
    else
        shcp <= 1'b0;

//ds:将寄存器里存储的数码管信号输入即
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        ds <= 1'b0;
    else if(cnt_4 == 2'd0)
        ds <= data[cnt_bit];
    else
        ds <= ds;

endmodule

3.2 译码模块

3.2.1 原理

本模块将八位数码管上需要显示的数据用一个特定的数字表示出来,然后用位选每1ms显示一位在数码管上。相当于译码。可根据需要增加数据线或更改译码规则来显示不同的信息。本代码只显示0~9。

3.2.2 程序

// FPGA   : 小梅哥AC620
// EDA 	  : Quartus II 13.0sp1 (64-bit) and ModelSim SE-64 10.5 
// Author : FPGA小白758 https://blog.csdn.net/q1594?spm=1010.2135.3001.5343
// File   : seg_dynamic.v
// Create : 2022-05-14 17:08:38
// Revise : 2022-05-14 18:32:37
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
module seg_dynamic
(
    input   wire        		sys_clk , //系统时钟,频率 50MHz
    input   wire       		 	sys_rst_n , //复位信号,低有效
    input   wire 	[31:0] 		data , //数码管要显示的值
    input   wire 	[ 7:0]  	point , //小数点显示,高电平有效
    input   wire        		seg_en , //数码管使能信号,高电平有效

    output  reg  	[7:0]  		sel , //数码管位选信号
    output  reg  	[7:0]  		seg //数码管段选信号
 );
 
parameter CNT_MAX = 16'd49_999; //数码管刷新时间计数最大值
 


reg		[ 6:0]				data_disp 	; //当前数码管的段选信号 

reg 	[15:0] 				cnt_1ms 	; //1ms 计数器
reg 						flag_1ms 	; //1ms 标志信号
reg 	[3:0] 				cnt_sel 	; //数码管位选计数器
reg 	[3:0] 				data_tmp 	; //当前数码管显示的数据
	

//cnt_1ms:1ms 循环计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_1ms <= 16'd0;
    else if(cnt_1ms == CNT_MAX)
        cnt_1ms <= 16'd0;
    else
        cnt_1ms <= cnt_1ms + 1'b1;
        
//flag_1ms:1ms 标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_1ms <= 1'b0;
    else if(cnt_1ms == CNT_MAX - 1'b1)
        flag_1ms <= 1'b1;
    else
        flag_1ms <= 1'b0;

//cnt_sel:从 1 到 8 循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sel <= 3'd0;
    else if((cnt_sel == 4'd7) && (flag_1ms == 1'b1))
        cnt_sel <= 3'd0;
    else if(flag_1ms == 1'b1)
        cnt_sel <= cnt_sel + 1'b1;
    else
        cnt_sel <= cnt_sel;

//
always @(*)begin
	case(cnt_sel)
		3'd0 	:data_tmp = data[3:0];
		3'd1 	:data_tmp = data[7:4];
		3'd2 	:data_tmp = data[11:8];
		3'd3 	:data_tmp = data[15:12];
		3'd4 	:data_tmp = data[19:15];
		3'd5 	:data_tmp = data[23:20];
		3'd6 	:data_tmp = data[27:24];
		3'd7 	:data_tmp = data[31:28];
		default:data_tmp = data[3:0];
	endcase
end
//不同信号对应的段选(a,b,c,d,e,f,g)
always @(*)begin
	case(data_tmp)
		'd0 	:	data_disp	=	7'b000_0001;	//显示0
		'd1 	:	data_disp 	=	7'b100_1111; 	//显示1
		'd2 	:	data_disp 	=	7'b001_0010;	//显示2
		'd3 	:	data_disp 	=	7'b000_0110;	//显示3
		'd4 	:	data_disp 	=	7'b100_1100;	//显示4
		'd5 	: 	data_disp	=	7'b010_0100;	//显示5
		'd6 	: 	data_disp 	=	7'b010_0000;	//显示6
		'd7 	: 	data_disp 	=	7'b000_1111;	//显示7
		'd8 	: 	data_disp 	=	7'b000_0000;	//显示8
		'd9 	: 	data_disp 	=	7'b000_0100;	//显示9
		'd10 	: 	data_disp 	=	7'b000_1000;	//显示a
		'd11 	:	data_disp 	=	7'b110_0000;	//显示b
		'd12 	: 	data_disp 	=	7'b011_0001;	//显示c
		'd13 	: 	data_disp 	=	7'b100_0010;	//显示d
		'd14 	: 	data_disp 	=	7'b011_0000;	//显示e
		'd15 	: 	data_disp 	=	7'b011_0000;	//显示f
		default	:	data_disp 	=	7'b111_1111;
	endcase
end

//控制数码管的段选信号,使八个数码管轮流显示
always@(*)begin
	if (seg_en == 1'b0) begin
		sel = 8'd0;
	end
	else    case(cnt_sel)
		        4'd0: {sel,seg} = {8'b1000_0000,data_disp,~point[0]}; //给第 1 个数码管赋值
		        4'd1: {sel,seg} = {8'b0100_0000,data_disp,~point[1]}; //给第 2 个数码管赋值
		        4'd2: {sel,seg} = {8'b0010_0000,data_disp,~point[2]}; //给第 3 个数码管赋值
		        4'd3: {sel,seg} = {8'b0001_0000,data_disp,~point[3]}; //给第 4 个数码管赋值
		        4'd4: {sel,seg} = {8'b0000_1000,data_disp,~point[4]}; //给第 5 个数码管赋值
		        4'd5: {sel,seg} = {8'b0000_0100,data_disp,~point[5]}; //给第 6 个数码管赋值
		        4'd6: {sel,seg} = {8'b0000_0010,data_disp,~point[6]}; //给第 7 个数码管赋值
		        4'd7: {sel,seg} = {8'b0000_0001,data_disp,~point[7]}; //给第 8 个数码管赋值            
	    		default:{sel,seg} = {8'b0000_0000,8'b1111_1111};
	    	endcase	
end
      
endmodule

这两个模块已经能做出基本的显示的功能了,但是对传输进来的数字有要求。必须与数码管位数一一对应。但是数据在FPGA中是二进制,而一般生活中常用的是十进制,这两个并不能在数码管中对应起来。需要经过处理。于是有了下面二进制转十进制模块。

3.3二进制转十进制模块

3.3.1 原理


1.补零。对二进制数高位补零,零的个数是对应的十进制数*4。
2.移位。二进制码依次左移一位,每次移位都要判断补充的位数以四个为单位有没有大于四的,如果有,则对该四位加3。移位的次数为原二进制码的个数。

3.3.2 程序

module  bcd_8421(
    input           sys_clk,
    input           sys_rst_n,
    input   [26:0]  data,
    
    output  reg [3:0]   unit    ,//个位
    output  reg [3:0]   ten     ,//十位
    output  reg [3:0]   hun     ,//百位
    output  reg [3:0]   tho     ,//千位
    output  reg [3:0]   t_tho   ,//万位
    output  reg [3:0]   h_hun   ,//十万位
    output  reg [3:0]   mil     ,//百万位
    output  reg [3:0]   t_mil   //千万位
);

reg [4:0]   cnt_shift;//移位判断计数器
reg [58:0]  data_shift;//移位判断数据寄存器
reg         shift_flag;//移位判断标志位

//cnt_shift:移位判断计数器,每移一位计数器加1;
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 0)
        cnt_shift <= 5'd0;
    else    if((cnt_shift == 5'd28) && (shift_flag == 1'b1))
        cnt_shift <= 5'd0;
    else    if(shift_flag == 1'b1)
        cnt_shift <= cnt_shift + 1'b1;
    else
        cnt_shift <= cnt_shift;
        
//data_shift:计数器为0时赋初值。计数器为1~27时进行移位判断操作
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 0)
        data_shift <= 58'd0;
    else    if(cnt_shift == 5'd0)
        data_shift <= {32'd0,data};
    else    if((cnt_shift <= 27) && (shift_flag == 1'b0))
        begin
            data_shift[30:27] <= (data_shift[30:27] > 4) ? (data_shift[30:27] + 2'd3) : (data_shift[30:27]);
            data_shift[34:31] <= (data_shift[34:31] > 4) ? (data_shift[34:31] + 2'd3) : (data_shift[34:31]);
            data_shift[38:35] <= (data_shift[38:35] > 4) ? (data_shift[38:35] + 2'd3) : (data_shift[38:35]);
            data_shift[42:39] <= (data_shift[42:39] > 4) ? (data_shift[42:39] + 2'd3) : (data_shift[42:39]);
            data_shift[46:43] <= (data_shift[46:43] > 4) ? (data_shift[46:43] + 2'd3) : (data_shift[46:43]);
            data_shift[50:47] <= (data_shift[50:47] > 4) ? (data_shift[50:47] + 2'd3) : (data_shift[50:47]);
            data_shift[54:51] <= (data_shift[54:51] > 4) ? (data_shift[54:51] + 2'd3) : (data_shift[54:51]);
            data_shift[58:55] <= (data_shift[58:55] > 4) ? (data_shift[58:55] + 2'd3) : (data_shift[58:55]);
        end
    else    if((cnt_shift <= 27) && (shift_flag == 1'b1))
        data_shift <= data_shift << 1;
    else
        data_shift <= data_shift;

//shift_flag:移位判断标志信号,用于控制移位判断的先后顺序
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        shift_flag <= 1'b0;
    else
        shift_flag <= ~shift_flag;
        
//当计数器等于 20 时,移位判断操作完成,对各个位数的 BCD 码进行赋值
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            unit    <= 4'd0;
            ten     <= 4'd0;
            hun     <= 4'd0;
            tho     <= 4'd0;
            t_tho   <= 4'd0;
            h_hun   <= 4'd0;
            mil     <= 4'd0;
            t_mil   <= 4'd0;
        end
    else if(cnt_shift == 5'd28)
        begin
            unit    <= data_shift[30:27];
            ten     <= data_shift[34:31];
            hun     <= data_shift[38:35];
            tho     <= data_shift[42:39];
            t_tho   <= data_shift[46:43];
            h_hun   <= data_shift[50:47];
            mil     <= data_shift[54:51];
            t_mil   <= data_shift[58:55];
        end    
endmodule


物联沃分享整理
物联沃-IOTWORD物联网 » FPGA数码管显示基础设计

发表评论