OpenEdv-开源电子网

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

【Kevin原创】《基于FPGA的简易计算器设计》第一章:玩转数码管

[复制链接]

17

主题

23

帖子

1

精华

高级会员

Rank: 4

积分
614
金钱
614
注册时间
2016-1-16
在线时间
18 小时
发表于 2016-2-21 16:47:33 | 显示全部楼层 |阅读模式

数码管在我们的整个设计中,我们输入的数据与计算的结果都需要用数码管来显示,所以说数码管还是扮演着很重要的角色的。

1.1简析数码管的工作原理

关于数码管的工作原理,为了照顾零基础的朋友,Kevin还是再进行下简单的介绍,如果是已经了解了数码管工作原理的朋友可以直接跳过这一节。

下面我们先来一张数码管的图片:

数码管是由多个发光二极管封装在一起组成的“8”字型器件,各发光二极管已经在器件内部完成连线。

对于数码管,有共阴极和共阳极之分,共阳极及共阴极数码管内部间的连线可以简化成下图:

图中的“COM”相当于是一个片选信号,例如,共阳极的位选“COM”为高时,即选中了该数码管,此时数码管才能工作起来。当“COM”选通之后,我们就可以选通相应的段选信号使数码管显示我们想要的字符了。比如说,我们想让数码管显示一个“0”(假设数码管是共阳极的),那我们相应的h~a的十六进制代码应为0xc0。

以下是共阴极与共阳极数码管的0~f编码(顺序是为h~a):
共阳:
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
共阴:
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71

1.2让数码管走起来

在这一节中,我们来做这样的一个小练习,Kevin也是想通过这样的一个小练习来让大家基本掌握数码管的工作原理,同时也了解如何使用FPGA来驱动数码管。

先说一下实验要求:数码管从0~9循环计数,变化时间的间隔为1S。

首先,咱们先画一个该设计的简单的逻辑框图:

图中“Div_cnt”模块在1s计时到了之后,就让div_flag这个信号拉高一个时钟周期。“Seg_ctrl”为数码管控制模块,产生位选sel信号及数码管的显示字符信号seg,显然我们的seg信号的变化需要根据div_flag这个信号来控制。

下面可以根据我们的这个逻辑图框图来写代码了。

首先来写div_cnt这个模块:

  • /********************************************************************
  • *        Module            Name:        div_cnt
  • *        Engineer          :        Kevin
  • *        Function        :        1s计数模块
  • *        Blog        Website        :        http://dengkanwen.com
  • *        Version                     :        v1.0
  • ********************************************************************/
  • module        div_cnt(
  •                 input        wire                sclk,                        //系统时钟为50MHz,即周期为20ns
  •                 input        wire                s_rst_n,
  •                
  •                 output        reg                    div_flag
  •                 );
  •                
  •         parameter                CNT_END        =        26'd4999_9999;        //1s计时结束
  •        
  •         reg        [25:0]        cnt;                //计数器
  •        
  •         always        @(posedge sclk or negedge s_rst_n)
  •                 if(s_rst_n == 1'b0)
  •                         cnt        <=        26'd0;
  •                 else if(cnt == CNT_END)
  •                         cnt        <=        26'd0;
  •                 else
  •                         cnt        <=        cnt + 1'b1;
  •                        
  •         always        @(posedge sclk or negedge s_rst_n)
  •                 if(s_rst_n == 1'b0)
  •                         div_flag        <=        1'b0;
  •                 else if(cnt == CNT_END)
  •                         div_flag        <=        1'b1;
  •                 else
  •                         div_flag        <=        1'b0;
  • endmodule

接下来,我们来写数码管的控制模块,在写控制模块之前,我们需要来看一下我们的电路原理图:

咱们先来简单的说一下这个数码管电路。电路图中总共有6位数码管,所以呢就有6个位选控制端,而这6个位选控制端是和三极管连在一块的,看着三极管的连法,应该知道,数码管是属于共阳极的。然而这6个数码管的位选是由3-8译码器来控制的,下面咱们再看一下3-8译码器与数码管位选端的连线图:

连线图已经出来了,可能有些朋友还不太清楚3-8译码器的原理,那咱们再来一张3-8译码器的真值表:

结合电路图及真值表,咱们可以看出,我们可以控制译码器的A、B、C这三个引脚来控制输出端Y电平的高低,从而就可以来控制我们数码管的位选端。例如,我们想让位选信号为S1的那个数码管工作起来,那我们就必须要先使之位选端有效,则需要使Y0的输出为低,所以我们ABC的信号应该为000.

在Kevin讲完之后,相信大家对咱们这个数码管电路的工作原理应该了解的差不多了。

我们只是让一个数码管亮起来,那我们索性也就只让位选信号为s1的那个数码管工作,其余数码管不工作。下面咱们就动手来写数码管控制模块的代码:

  • /********************************************************************
  • *        Module        Name        :        seg_ctrl
  • *        Engineer        :        Kevin
  • *        Function           :        数码管显示控制模块
  • *        Blog        Website        :        http://dengkanwen.com
  • *        Version                :        v1.0
  • *********************************************************************/
  • module        seg_ctrl(
  •                 input        wire                sclk,
  •                 input        wire                s_rst_n,
  •                 input        wire                div_flag,
  •                
  •                 output        wire        [2:0        sel,                //位选控制,连3-8译码器
  •                 output        reg        [7:0        seg                //段选信号
  •                 );
  •                
  • //========================共阳极数码管编码=========================================
  • //0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
  • //=================================================================================
  •                
  •         parameter        ZERO        =        8'hc0,
  •                         ONE        =        8'hf9,
  •                         TWO        =        8'ha4,
  •                         THREE        =        8'hb0,
  •                         FOUR        =        8'h99,
  •                         FIVE        =        8'h92,
  •                         SIX        =        8'h82,
  •                         SEVEN        =        8'hf8,
  •                         EIGHT        =        8'h80,
  •                         NINE        =        8'h90;
  •        
  •         reg        [3:0        num_cnt;                //用于寄存当前数码管应显示的值
  • //num_cnt       
  •         always        @(posedge sclk or negedge s_rst_n)
  •                 if(s_rst_n == 1'b0)
  •                         num_cnt        <=        4'd0;
  •                 else if(num_cnt == 4'd9 && div_flag == 1'b1)
  •                         num_cnt        <=        4'd0;
  •                 else if(div_flag == 1'b1)
  •                         num_cnt        <=        num_cnt + 1'b1;
  • //seg               
  •         always        @*
  •                 case(num_cnt)
  •                         4'd0:
  •                                 seg        <=        ZERO;
  •                         4'd1:
  •                                 seg        <=        ONE;
  •                         4'd2:
  •                                 seg        <=        TWO;
  •                         4'd3:
  •                                 seg        <=        THREE;
  •                         4'd4:
  •                                 seg        <=        FOUR;
  •                         4'd5:
  •                                 seg        <=        FIVE;
  •                         4'd6:
  •                                 seg        <=        SIX;
  •                         4'd7:
  •                                 seg        <=        SEVEN;
  •                         4'd8:       
  •                                 seg        <=        EIGHT;
  •                         4'd9:
  •                                 seg        <=        NINE;               
  •                 endcase
  • //sel
  •         assign        sel        =        3'b000;
  • endmodule

这两个模块写好了之后,下面咱们再写一个顶层模块,用于连接两个模块之间的连线。

  • /********************************************************************
  • *        Module        Name        :        seg_top
  • *        Eneigneer        :        Kevin
  • *        Function        :        数码管0~9变换顶层模块
  • *        Blog        Website        :        http://dengkanwen.com
  • *        Version                :        v1.0
  • *********************************************************************/
  • module        seg_top(
  •                 input        wire                sclk,
  •                 input        wire                s_rst_n,
  •                
  •                 output        wire        [2:0        sel,
  •                 output        wire        [7:0        seg
  •                 );
  •         wire                div_flag;
  •                
  •         div_cnt                div_cnt_inst(
  •                 .sclk                        (sclk),                //系统时钟为50MHz,即周期为20ns
  •                 .s_rst_n                (s_rst_n),
  •                 .div_flag                (div_flag)
  •                 );
  •                
  •         seg_ctrl        seg_ctrl_inst(
  •                 .sclk                        (sclk),
  •                 .s_rst_n                (s_rst_n),
  •                 .div_flag                (div_flag),
  •                 .sel                        (sel),                //位选控制,连3-8译码器
  •                 .seg                        (seg)                //段选信号
  •                 );
  • endmodule

虽然Kevin对于这种简单的设计还是比较有自信的,但是为了教给大家一个良好的设计习惯,咱们还是进行仿真,所以接下来咱们来写一个仿真文件。 当然在仿真的时候,我们可以将div_cnt中的cnt计满的值写小一点。

  • `timescale        1ns/1ns
  • module        tb_seg_top;
  •         reg                sclk;
  •         reg                s_rst_n;
  •        
  •         wire        [2:0]        sel;
  •         wire        [7:0]        seg;
  •        
  •         initial        begin
  •                 sclk        =        1;
  •                 s_rst_n        <=        0;
  •                 #20
  •                 s_rst_n        <=        1;       
  •         end
  •        
  •         always        #5        sclk        =        ~sclk;
  •        
  •         seg_top                seg_top_inst(
  •                 .sclk                (sclk),
  •                 .s_rst_n        (s_rst_n),
  •                 .sel                (sel),
  •                 .seg                (seg)
  •                 );
  • endmodule

下面是咱们的仿真波形:

根据咱们的波形,可以说暂时是没有发现错误的,当然,Kevin下板子后也是板子也能正常工作。

本章剩余部分,请看评论哦

(备注:如果有发现内容错误,请大家及时指出,Kevin的QQ:1024726016)

转载请注明:邓堪文博客 &#187; 【Kevin原创】《基于FPGA的简易计算器设计》第一章:玩转数码管


个人FPGA博客:http://dengkanwen.com  欢迎批评指导
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

17

主题

23

帖子

1

精华

高级会员

Rank: 4

积分
614
金钱
614
注册时间
2016-1-16
在线时间
18 小时
 楼主| 发表于 2016-2-21 16:48:30 | 显示全部楼层
1.3数码管进阶

数码管能实现0~9之间的跳转之后,下面我们来实现数码管从0~99变化,间隔的时间依然是1S。

在我们进行这个实验的时候,我们必须要清楚,这次的实验,我们需要用到2位数码管。在我们做0~9这个实验的时候,大家应该清楚,对于数码管,我们一次只能选通一个数码管,那我们又怎么能实现是两位数码管同时显示呢?

这里的道理很简单,虽然我们一次只能选通一个数码管的位选,但是由于有视觉停留效果,所以只要我们两位数码管之间的位选信号切换的速度达到了一定程度,超过了我们肉眼能分辨出来的频率,那我们眼睛看到的效果就是两个数码管在同时亮着。既然说到这了,那我们就定义位选信号之间变化的频率为1KHz。

对于这个实验,我们可以依然利用上面已经写好了的代码,只需要修改seg_crtl这个模块,在其中增加些许代码就OK了。那到底需要增加哪部分的代码呢?我们先来分析一下:首先肯定是需要有一个1KHz的“分频”部分,再有就是需要控制好seg,在选通第一位数码管时,我们需要将第一位数码管需要显示的字符赋给seg,选通第二数码管时需将第二位数码管应显示的字符赋给seg。

那接下来就来写代码:

/********************************************************************
*        Module        Name        :        seg_ctrl
*        Engineer        :        Kevin
*        Function        :        数码管显示控制
*        Blog        Website        :        http://dengkanwen.com
*        Version                :        Content
*                v1.0        -        initial        release
*                v1.1        -        数码管从0~99显示
*********************************************************************/
module        seg_ctrl(
                input        wire                sclk,
                input        wire                s_rst_n,
                input        wire                div_flag,
               
                output        wire        [2:0]        sel,                //位选控制,连3-8译码器
                output        wire        [7:0]        seg                //段选信号
                );
               
//========================共阳极数码管编码=========================================
//0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
//=================================================================================

               
        parameter        ZERO        =        8'hc0,
                        ONE        =        8'hf9,
                        TWO        =        8'ha4,
                        THREE        =        8'hb0,
                        FOUR        =        8'h99,
                        FIVE        =        8'h92,
                        SIX        =        8'h82,
                        SEVEN        =        8'hf8,
                        EIGHT        =        8'h80,
                        NINE        =        8'h90,
                        CNT_END        =        4_9999;                //1KHz计满
                        //CNT_END        =        4;                //1KHz计满 (用于仿真)
         
        reg        [15:0]        cnt_1k;
        reg        [3:0]        num_cnt_g;                //用于寄存当前数码管应显示的值
        reg        [3:0]        num_cnt_s;
        reg        [7:0]        seg_g;                        //个位数码管显示
        reg        [7:0]        seg_s;                        //十位数码管显示
        reg                flag_1k;                //1KHz频率标志
       
//cnt_1k
        always        @(posedge sclk or negedge s_rst_n)
                if(s_rst_n == 1'b0)
                        cnt_1k        <=        16'd0;
                else if(cnt_1k == CNT_END)
                        cnt_1k        <=        16'd0;
                else
                        cnt_1k        <=        cnt_1k + 1'b1;
//flag_1k
        always        @(posedge sclk or negedge s_rst_n)
                if(s_rst_n == 1'b0)
                        flag_1k        <=        1'b0;
                else if(cnt_1k == CNT_END)
                        flag_1k        <=        ~flag_1k;

//num_cnt_g       
        always        @(posedge sclk or negedge s_rst_n)
                if(s_rst_n == 1'b0)
                        num_cnt_g        <=        4'd0;
                else if(num_cnt_g == 4'd9 && div_flag == 1'b1)
                        num_cnt_g        <=        4'd0;
                else if(div_flag == 1'b1)
                        num_cnt_g        <=        num_cnt_g + 1'b1;
//num_cnt_s
        always        @(posedge sclk or negedge s_rst_n)
                if(s_rst_n == 1'b0)
                        num_cnt_s        <=        4'd0;
                else if(div_flag == 1'b1 && num_cnt_g == 4'd9 && num_cnt_s == 4'd9)
                        num_cnt_s        <=        4'd0;
                else if(div_flag == 1'b1 && num_cnt_g == 4'd9)
                        num_cnt_s        <=        num_cnt_s + 1'b1;
//seg_g       
        always        @*
                case(num_cnt_g)
                        4'd0:
                                seg_g        <=        ZERO;
                        4'd1:
                                seg_g        <=        ONE;
                        4'd2:
                                seg_g        <=        TWO;
                        4'd3:
                                seg_g        <=        THREE;
                        4'd4:
                                seg_g        <=        FOUR;
                        4'd5:
                                seg_g        <=        FIVE;
                        4'd6:
                                seg_g        <=        SIX;
                        4'd7:
                                seg_g        <=        SEVEN;
                        4'd8:       
                                seg_g        <=        EIGHT;
                        4'd9:
                                seg_g        <=        NINE;               
                endcase
//seg_s
        always        @*
                case(num_cnt_s)
                        4'd0:
                                seg_s        <=        ZERO;
                        4'd1:
                                seg_s        <=        ONE;
                        4'd2:
                                seg_s        <=        TWO;
                        4'd3:
                                seg_s        <=        THREE;
                        4'd4:
                                seg_s        <=        FOUR;
                        4'd5:
                                seg_s        <=        FIVE;
                        4'd6:
                                seg_s        <=        SIX;
                        4'd7:
                                seg_s        <=        SEVEN;
                        4'd8:       
                                seg_s        <=        EIGHT;
                        4'd9:
                                seg_s        <=        NINE;               
                endcase

//sel
        assign        sel        =        (flag_1k == 1'b1) ? 3'b101 : 3'b100;
//seg
        assign        seg        =        (flag_1k == 1'b1) ? seg_g : seg_s;

endmodule
模块代码写好了之后,我们还是需要先进行仿真,这里的话,Kevin就不把仿真的的结果截图了,仿真就留给大家自己完成了。

我们现在来个板子上的效果截图:

seg_disp

至此,咱们的这个实验就完成了。

课后练习

1.让数码管依次显示“I  LOVE  FPGA”,从左至右循环显示,滚动速度为1s滚动一次。



(备注:如果有发现内容错误,请大家及时指出,Kevin的QQ:1024726016)

转载请注明:邓堪文博客 &#187; 【Kevin原创】《基于FPGA的简易计算器设计》第一章:玩转数码管
个人FPGA博客:http://dengkanwen.com  欢迎批评指导
回复 支持 反对

使用道具 举报

0

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
89
金钱
89
注册时间
2016-2-17
在线时间
18 小时
发表于 2016-2-22 14:35:32 | 显示全部楼层
本帖最后由 matthewchan 于 2016-2-22 14:50 编辑

@Kevin·Deng 很不错。建议先给个目录,然后连载。语法要注意一下, 例如你的case语句是想生成组合逻辑吧,为什么要用非阻塞赋值?

我也做了一个玩具计算器,主要是为了在硬件上验证一下我写的cpu core的。

IMG_20160222_143222.jpg



IMG_20160222_140544.png



技术就像谈恋爱,你要有激情,你要行动,你要努力,你还要保鲜,当然收获是甜蜜的。
回复 支持 反对

使用道具 举报

17

主题

23

帖子

1

精华

高级会员

Rank: 4

积分
614
金钱
614
注册时间
2016-1-16
在线时间
18 小时
 楼主| 发表于 2016-2-22 16:57:22 | 显示全部楼层
matthewchan 发表于 2016-2-22 14:35
@Kevin·Deng 很不错。建议先给个目录,然后连载。语法要注意一下, 例如你的case语句是想生成组合逻辑吧, ...

感谢您的建议,确实不应该用非阻塞赋值,习惯了在always 中都写成非阻塞赋值
个人FPGA博客:http://dengkanwen.com  欢迎批评指导
回复 支持 反对

使用道具 举报

0

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
89
金钱
89
注册时间
2016-2-17
在线时间
18 小时
发表于 2016-2-22 19:26:38 | 显示全部楼层
你还是个学生吧,本科还是研究生了?如果是学生,水平很不错啦,前途无量。

顺便说一句,verilog跟c,java等语言在思维上是不一样的。
在编写每句verilog代码的时候,脑海里面应该有清晰的概念,你要实现的是什么电路(电路,电路,电路,重要的事情说3遍),而不是行为。
技术就像谈恋爱,你要有激情,你要行动,你要努力,你还要保鲜,当然收获是甜蜜的。
回复 支持 反对

使用道具 举报

17

主题

23

帖子

1

精华

高级会员

Rank: 4

积分
614
金钱
614
注册时间
2016-1-16
在线时间
18 小时
 楼主| 发表于 2016-2-23 13:33:43 | 显示全部楼层
matthewchan 发表于 2016-2-22 19:26
你还是个学生吧,本科还是研究生了?如果是学生,水平很不错啦,前途无量。

顺便说一句,verilog跟c,ja ...

@matthewchan 感谢您的建议,目前还是本科
个人FPGA博客:http://dengkanwen.com  欢迎批评指导
回复 支持 反对

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
8
金钱
8
注册时间
2016-3-4
在线时间
0 小时
发表于 2016-3-29 20:32:34 | 显示全部楼层
炫酷,感谢分享
回复 支持 反对

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-6-29 00:22

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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