OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9776|回复: 6

小梅哥和你一起深入学习FPGA之数码管动态扫描

[复制链接]

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
发表于 2014-10-27 23:16:40 | 显示全部楼层 |阅读模式

 

         在电子系统中,通常都需要有输出设备来输出或显示一定的信息,以指示当前系统运行的状态。在以单片机和ARM为主的电子系统中,液晶屏是理想的输出设备。而FPGA则因为其独特的硬件结构,如果用RTL级电路来驱动彩色液晶屏来显示一定的数据,势必是非常不划算的选择,而且驱动也极为复杂。数码管作为一种能够直观显示一定数据信息的输出设备,具有驱动简单,显示直观的特点,尤其适合作为FPGA系统的输出设备。本节,小梅哥就将和大家一起进行数码管驱动的开发。

        

一、        实验目的

实现67段数码管的驱动,待显示数据以BCD格式输入。数码管刷新时钟为1KHz。实验使用了4个独立按键作为输入,通过按键来改变需要数码管显示的数据,以验证数码管驱动的正确性,同时也可检验独立按键消抖模块的可靠性。

二、        实验原理

数码管所谓的动态扫描,就是利用人眼的视觉暂留特性,在人眼能分辨的变化速度以外,快速分时的点亮各个数码管对应的段。因为分别点亮所有数码管一次所用时间小于人眼的视觉暂留,因此,在人们眼里看来,这些数码管都是同时持续点亮的,并不会有闪烁的感觉。


2-1 数码管实物图

关于数码管的具体原理,请大家网上查阅,小梅哥一个人精力有限,没办法在这里从最低层的原理给大家一步一步讲起,如果大家有不明白的,请自行百度。这里小梅哥就用最简单粗暴的方式给大家简单介绍一下。


2-2 数码管简单等效电路

上图为37段数码管的等效电路图,在这个图中,可以明显的看到24个发光二极管被分为了三组,每一组的8个发光二极管正极被接在了一起,通过一个三极管与VCC相连。三极管的基极连接到了FPGAIO上,因此,只需要FPGA对应的IO上给出低电平,三极管便会导通。而三组LED中所有的相同编号的LED的负极被连接在了一起,并接到了FPGAIO上。如果我们希望将最左边一组的led0led5led7三个编号的led灯点亮,其它led不亮,则只需要给Q0的基极(sel0)连接上低电平,并将led0led5led7的负极(afh)连接上低电平,其它所有端口都输出高电平,则最左边一组的对应的三个led灯就会被点亮,而其它led则会处于熄灭状态。

假如我们需要在三秒时间内,完成以下三次操作:第一次操作,点亮最左边一组led灯的led0led5led7;第二次操作,点亮中间一组led灯的led1led2led3;第三次操作,点亮最右边一组led灯的led2led4led6;那么我们只需要按照如下表格中列出的真值表操作即可:

第一秒

第二秒

第三秒

sel0

0

1

1

sel1

1

0

1

sel2

1

1

0

a

0

1

1

b

1

0

1

c

1

0

0

d

1

0

1

e

1

1

0

f

0

1

1

g

1

1

0

h

0

1

1

按照以上表格,我们就能知道该如何操作了,只需要在不同的时间给各个IO不同的电平,便能实现我们想要的亮灭组合。以上我们是以1秒为单位进行led组的切换的,假如我们将切换速度加快,变为1毫秒一切换,会是什么情况呢?在1毫秒一切换的速度下,完成所有操作所需时间为3ms,远远超出了我们人眼所能辨识的变化速度范围。如果我们让以上三个操作永远循环的进行下去,那么我们将看见三组led灯中,我们点亮的那几个led是同时且一直处于亮着的状态的,这便是动态扫描的原理,假如我们把每个led做成一个长条型的,并按照如下形状摆放,便就是我们常见的数码管了。


2-3 数码管段分布

三、        硬件设计

2-2只是一个为了讲述数码管原理简化了的电路模型,常见的数码管电路结构如下图所示:

3-1 数码管典型电路

         在这个图中,共有6位数码管,每个数码管的正极被接在一个驱动三极管上,三极管的基极连接到三八译码器的Y端,则FPGA只需要三个引脚就可最多控制8个数码管的位选。数码管的段选在串接了470欧姆的电阻后与FPGAIO相连。这里470欧姆的电阻主要起到限流的作用,保证流过数码管的电流在正常范围内。

四、        架构设计

本实验由总共四个模块组成,分别为数码管驱动模块、独立按键检测模块、控制模块和顶层模块,其架构如下:


4-1 led实验模块组织结构图fficeffice" />

由图可知本实验有1个输出端口,对应驱动了38译码器的三个选择端和数码管的8个段选脚。6个输入端口,对应了4个独立按键输入和一个时钟输入以及一个复位输入。详细端口名及其意义如下

端口说明

端口名

端口功能或意义

Rst_n

全局复位

Dig_Led_sel

数码管位选输出,接三八译码器

Dig_Led_seg

数码管段选输出,接数码管8个段

Key_in

按键输入端口

Clk

系统时钟输入端口

4-1 独立按键检测实验端口说明

 

因为存在模块间的连接,因此有部分内部信号,下表为内部信号的名称和功能说明

内部信号说明

内部信号名

内部信号功能或意义

Data

数码管待显示数据,共24位,含每4位组成一个BCD码,对应一个数码管需要显示的数据内容

Key_Value

按键检测结果输出

Key_Flag

按键检测成功标志信号

4-2 独立按键检测实验内部信号说明

 

 

一、        代码组织方式

         本实验中,数码管的驱动采用了组合逻辑译码的方式进行,具体将在代码解读时讲解。

实验中还设计了一个控制器,该控制器主要通过读取按键信息来改变待数码管待显示的数据内容。

按键检测部分使用前一节开发的独立按键的驱动,因此这里不进行过多的分析介绍。

 

二、        关键代码解读

因为数码管属于低速设备,其正常的扫描频率为500~10KHz,扫描频率太快,会导致系统功耗增加,显示效果变暗。扫描频率太慢,会有明显的闪烁感。本实验通过调试观察,选择以1KHz作为扫描频率,实际显示效果非常好。

因此本实验首先就需要产生一个1KHz的扫描时钟,该时钟由系统时钟分频得到。产生1KHz扫描时钟的代码如下:

 

         parameter system_clk = 50_000_000;

 

localparam cnt1_MAX = system_clk/1000/2-1;

 

//1KHz时钟分频计数器

         always@(posedge Clk)

         begin

                   if(!Rst_n)cnt1<=0;

                   else if(cnt1==cnt1_MAX)cnt1<=0;

                   else cnt1<=cnt1+1'b1;

         end

 

         //得到1KHz时钟

         always@(posedge Clk or negedge Rst_n)

         if(!Rst_n)clk_1K<=0;

         else if(cnt1==cnt1_MAX)

                   clk_1K<=~clk_1K;//翻转扫描时钟信号

         else ;

其中,定义了一个全局参数system_clk,该参数为Clk的频率,不同的时钟频率,只需要更改该参数,就可改变分频计数器的最大计数值,以保证1KHz分频的精准性。

 

在驱动中,数码管的位选以扫描时钟的速率进行切换,因为只有6位数码管,因此当位选计数到6-1后必须清零从头开始计数。相关代码如下:

//位选信号控制

         always@(posedge clk_1K or negedge Rst_n)

         if(!Rst_n)sel_r<=3'd0;

         else if(sel_r == 3'd5)

                   sel_r<=3'd0;

         else

                   sel_r<=sel_r+1'b1;

 

每个数码管需要显示的内容都不相同,由Data中相应的位指定,Data中各位与数码管的位对应关系如下:

Data

Data[23:20]

Data[19:16]

Data[15:12]

Data[11:8]

Data[7:4]

Data[3:0]

数码管位

数码管0

数码管1

数码管2

数码管3

数码管4

数码管5

因此需要从Data中将每个数码管被选中时需要显示的数据提取出来,提取数据的代码如下所示:

         //根据不同的数码管位选择不同的待显示数据

         always@(*)

         if(!Rst_n)

                   disp_data<=4'd0;

         else

         begin

                   case(sel_r)

                            0:disp_data<=Data[23:20];

                            1:disp_data<=Data[19:16];

                            2:disp_data<=Data[15:12];

                            3:disp_data<=Data[11:8];

                            4:disp_data<=Data[7:4];

                            5:disp_data<=Data[3:0];

                            default :disp_data<=4'd0;

                   endcase

         end

 

因为提取出来的数据还是BCD码的形式,还需要将BCD码对应的数据翻译成为数码管显示对应字符时应该点亮或熄灭的对应的LED的控制信号,因此必须还有一个BCD码译码的过程,该过程代码如下图所示:

         //数据译码,将待显示数据翻译为符合数码管显示的编码

         always@(*)

         if(!Rst_n)

                   seg_r<=8'hff;

         else

         begin

                   case(disp_data)

                            4'd0:         seg_r<=8'hc0;

                            4'd1:         seg_r<=8'hf9;

                            4'd2:         seg_r<=8'ha4;

                            4'd3:         seg_r<=8'hb0;

                            4'd4:         seg_r<=8'h99;

                            4'd5:         seg_r<=8'h92;

                            4'd6:         seg_r<=8'h82;

                            4'd7:         seg_r<=8'hf8;

                            4'd8:         seg_r<=8'h80;

                            4'd9:         seg_r<=8'h90;

                            4'd10:       seg_r<=8'h88;

                            4'd11:       seg_r<=8'h83;

                            4'd12:       seg_r<=8'hc6;

                            4'd13:       seg_r<=8'ha1;

                            4'd14:       seg_r<=8'h86;

                            4'd15:       seg_r<=8'h8e;

                            default : seg_r<=8'hff;

                   endcase

         end

 

最后,需要将位选和段选信号输出:

         assign Dig_Led_seg = seg_r;

         assign Dig_Led_sel = sel_r;

 

控制部分相对简单,只需要根据对应的 按键信息,给待显示的数据加上一个对应的值,该部分代码如下所示:

         always @(posedge Clk or negedge Rst_n)

         if(!Rst_n)

                   Dig_Led_Data <= 24'd0;

         else if(Key_Flag)

         begin

                   case(Key_Value)

                            4'b0001ig_Led_Data <= Dig_Led_Data + 23'd1;

                            4'b0010ig_Led_Data <= Dig_Led_Data + 23'd100;

                            4'b0100ig_Led_Data <= Dig_Led_Data + 23'd10000;

                            4'b1000ig_Led_Data <= Dig_Led_Data + 23'd100000;

                            defaultig_Led_Data <= Dig_Led_Data;

                   endcase

         end

 

三、        测试平台设计

本实验主要对数码管驱动引脚的状态与预期进行比较和分析,通过仿真,验证设计的正确性和合理性。数码管驱动模块的testbench如下所示:

`timescale 1ns/1ns

 

module DIG_LED_DRIVE_tb;

 

         reg [23:0]data;

         reg clk;

         reg rst_n;

         wire [7:0]seg;

         wire [2:0]sel;

        

         DIG_LED_DRIVE DIG_LED_DRIVE_inst1(

                   .Data(data),

                   .Clk(clk),

                   .Rst_n(rst_n),

                   .Dig_Led_seg(seg),

                   .Dig_Led_sel(sel)

         );

 

         initial begin

                   data = 0;

                   clk = 1;

                   rst_n = 0;

                   #200;

                   rst_n = 1;

                   data = 24'h012345;

                   #10000;

                   data = 24'h518918;

                   #10000;

                   data = 24'h543210;

                   #10000;

                   $stop;     

         end

        

         always #10 clk = ~clk;

 

endmodule

每隔一段时间,更换数码管的Data输入数据,观察数码管的输出是否正确。

 

四、        仿真分析


具体的仿真结果小梅哥就不一一给大家分析了,读者只需要对着图和代码中的编码比较,便能获知设计的正确性。


一、        下板验证


手头暂无开发板,板级验证略。


过段时间等小梅哥有了自己的开发板,再来补上板级验证结果。

http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2014-10-27 23:31:41 | 显示全部楼层
不错,谢谢分享。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

23

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2013-6-11
在线时间
16 小时
发表于 2014-10-28 13:07:18 | 显示全部楼层
mark…………
回复 支持 反对

使用道具 举报

13

主题

45

帖子

0

精华

初级会员

Rank: 2

积分
166
金钱
166
注册时间
2013-8-27
在线时间
12 小时
发表于 2014-10-28 23:00:59 | 显示全部楼层
之前已经有好多人出过类似的教程、视频等等,希望楼主能突出自己的特色,不然又会重复前人的工作无意义。刚开始学FPGA,很喜欢楼主的testbench,因为看特权视频,对于仿真介绍的比较少(刚看了前几讲,后面的不知道),希望正好可以学着从基本的开始学习testbench。加油↖(^ω^)↗
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2014-10-29 09:46:59 | 显示全部楼层
回复【4楼】tangkunjyy:
---------------------------------
他们的充其量就只是实现功能,展示FPGA有多牛逼,能干多少事,可重用性不强。我学习原子哥的教程风格,所有外设驱动做成标准接口,可直接重用。为快速建立新项目做铺垫。今天会出一个综合实验的文档,并在其中重点介绍我的这种组织架构
http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

4

主题

12

帖子

0

精华

新手上路

积分
48
金钱
48
注册时间
2014-3-14
在线时间
0 小时
发表于 2015-1-3 20:05:09 | 显示全部楼层
回复【5楼】小梅哥:
---------------------------------
但我也有点感觉楼主的代码不好移植呀
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-1-3 22:09:43 | 显示全部楼层
回复【6楼】open:
---------------------------------
我这个做成了标准接口的形式啊,要显示什么内容,只需要将待显示的内容放到data端口就行了,不需要移植的。这里data要求是6个BCD码的格式,如果需要显示2进制数,如123456,我们有法宝:BIN转BCD的代码。只需要将123456接到BIN端,BCD端出来的就是可以直接显示的123456的BCD格式了
http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 16:46

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表