官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

2.6 数码管动态扫描

发布时间:2021-08-22   作者:admin 浏览量:

本文的文档编号:001600000017  

需要看对应的视频,请点击视频编号:001600000099 

1.至简原理与应用配套的案例文档
2.实现开发板上数码管显示的功能,使8个数码管有规律的按照时间进行显示。复位后,数码管0显示数字“0”;1秒后,数码管1显示数字"1";再1秒后,数码管2显示数字"2";以此类推,每隔疫苗变化一次,最后当数码管7显示数字"7"后再次回到数码管0显示"0";按这样的规律循环。
3. 这是ALTERA入门学习案例文档


第二阶段  项目实践          

                                           第六章  数码管动态扫描




1 项目背景

led数码管(LED Segment Displays)是由多个发光二极管封装在一起组成“8”字型的器件,引线已在内部连接完成,只引出它们的各个笔划,公共电极。led数码管常用段数一般为7段,如上图中的abcdefg,有的还会有一个小数点,如图中的h

240

数码管要正常显示,就要用驱动电路来驱动数码管的各个段码,从而显示出我们要的数字。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极COM接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极COM接到地线GND上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。

下表列出了要显示的数字,以及对应的abcdefg的值。

显示

数字

共阳abcdefg

2进制

共阳abcdefg

16进制

共阴abcdefg

2进制

共阴abcdefg

16进制

0

7b0000001

7h01

7b 1111110

7h7e

1

7b 1001111

7h4f

7b 0110000

7h30

2

7b 0010010

7h12

7b 1101101

7h6d

3

7b 0000110

7h06

7b 1111001

7h79

4

7b 1001100

7h4c

7b 0110011

7h33

5

7b 0100100

7h24

7b 1011011

7h5b

6

7b 0100000

7h20

7b 1011111

7h3f

7

7b 0001111

7h0f

7b 1110000

7h70

8

7b 0000000

7h00

7b 1111111

7h7f

9

7b 0000100

7h04

7b 1111011

7h7b

例如,共阳数码管中,abcdefg的值分别是1001111时,也就是bc字段亮,其他字段不亮,这时就显示了数字“1”。

如果要显示多个数码管,根据数码管的驱动方式的不同,可以分为静态式和动态式两类。

静态驱动也称直流驱动。静态驱动是指每个数码管的每一个段码都由一个单片机的I/O端口进行驱动,或者使用如BCD码二-十进制译码器译码进行驱动。静态驱动的优点是编程简单,显示亮度高,缺点是占用I/O端口多,如驱动5个数码管静态显示则需要5×8=40I/O端口来驱动,要知道一个89S51单片机可用的I/O端口才32个,实际应用时必须增加译码驱动器进行驱动,增加了硬件电路的复杂性。

数码管动态显示接口是应用最为广泛的一种显示方式之一,动态驱动是将所有数码管的8个显示笔划"a,b,c,d,e,f,g,dp"的同名端连在一起,另外为每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制,当要输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于单片机对位选通COM端电路的控制,所以我们只要将需要显示的数码管的选通控制打开,该位就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的的COM端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为12ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。

明德扬开发板上一共有24位的共阳数码管,也就是说一共有8个共阳数码管。数码管的配置电路如下。

241

图中的SEG_ASEG_B~SEG_DP,是段选信号,这些信号都是8个数码管共用的。

DIG1~DIG8是位选信号,分别对应8个数码管。对应的位选信号为0,就表示将段选信号的值赋给该数码管。例如DIG30,表示将段选信号SEG_A~SEG_DP的值赋给数码管3

SEG_A~SEG_DPDIG1~DIG8,都是连接到电阻,如下图。

242

由此可见,SEG_A~SEG_DP是由SEG0~SEG7产生的,DIG1~DIG8是由DIG_EN1~DIG_EN8产生的。

SEG0~SEG7DIG_EN1~DIG_EN8直接连到FPGAIO上。

243

这些信号与FPGA管脚的对应关系如下表。

信号线

信号线

FPGA管脚

SEG_E

SEG0

Y6

SEG_DP

SEG1

W6

SEG_G

SEG2

Y7

SEG_F

SEG3

W7

SEG_D

SEG4

P3

SEG_C

SEG5

P4

SEG_B

SEG6

R5

SEG_A

SEG7

T3

DIG1

DIG_EN1

T4

DIG2

DIG_EN2

V4

DIG3

DIG_EN3

V3

DIG4

DIG_EN4

Y3

DIG5

DIG_EN5

Y8

DIG6

DIG_EN6

W8

DIG7

DIG_EN7

W10

DIG8

DIG_EN8

Y10

也就是说,FPGA通过控制上面中的管脚,就控制了数码管的显示。


2 设计目标

开发板或者模块是有8位数码管,本次设计需要使用8个数码管,实现数码管显示功能,具体要求如下:

复位后,数码管0显示数字01秒后,轮到数码管1显示数字11秒后,轮到数码管2显示数字2;以此类推,每隔1秒变化,最后是数码管7显示数字7。然后再次循环。

上板效果图如下图所示。

244


3 设计实现

3.1 顶层信号

新建目录:D:mdy_bookmy_seg在该目录中,新建一个名为my_seg.v的文件,并用GVIM打开,开始编写代码。

我们要实现的功能,概括起来就是控制8个数码管,让数码管显示不同的数字。要控制8个数码管,就需要控制位选信号,即FPGA要输出一个8位的位选信号,设为seg_sel,其中seg_sel[0]对应数码管0seg_sel[1]对应数码管1,以此类推,seg_sel[7]对应数码管7

要显示不同的数字,就需要控制段选信号,不需要用到DP,一共有7根线,即FPGA要输出一个7位的段选信号,设为seg_mentseg_ment[6]~segm_ment[0]分别对应数码管的abcdefg(注意对应顺序)。我们还需要时钟信号和复位信号来进行工程控制。

综上所述,我们这个工程需要4个信号,时钟clk,复位rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment。其中,seg_selseg_ment的对应关系下如下:

器件

信号线

信号线

FPGA管脚

内部信号

U6,U7

SEG_E

SEG0

Y6

seg_ment[2]

SEG_DP

SEG1

W6

未用到

SEG_G

SEG2

Y7

seg_ment[0]

SEG_F

SEG3

W7

seg_ment[1]

SEG_D

SEG4

P3

seg_ment[3]

SEG_C

SEG5

P4

seg_ment[4]

SEG_B

SEG6

R5

seg_ment[5]

SEG_A

SEG7

T3

seg_ment[6]

DIG1

DIG_EN1

T4

seg_sel[0]

DIG2

DIG_EN2

V4

seg_sel[1]

DIG3

DIG_EN3

V3

seg_sel[2]

DIG4

DIG_EN4

Y3

seg_sel[3]

DIG5

DIG_EN5

Y8

seg_sel[4]

DIG6

DIG_EN6

W8

seg_sel[5]

DIG7

DIG_EN7

W10

seg_sel[6]

DIG8

DIG_EN8

Y10

seg_sel[7]

X1

SYS_CLK

G1

clk

K1

SYS_RST

AB12

rst_n

module的名称定义为my_seg。并且我们已经知道该模块有4个信号:clkrst_nseg_selseg_ment,代码如下:

1

2

3

4

5

6

module  my_seg(

rst_n       ,

clk         ,

seg_sel     ,

segment

);

其中clkrst_n1位的输入信号,seg_sel8位的输出信号,seg_ment7位的输出信号,根据此,补充输入输出端口定义。代码如下:

1

2

3

4

input                    clk ;

input                    rst_n ;

output [ 7:0]            seg_sel ;

output [ 6:0]            segment ;


3.2 信号设计

我们先分析要实现的功能,数码管0显示数字0,翻译成信号就是seg_sel的值为8’b1111_1110seg_ment的值为7’b000_0001。数码管1显示数字1,也就是说seg_sel的值为8’b1111_1101seg_ment的值为7’b100_1111。以此类推,数码管7显示数字7,就是seg_sel的值为8’b0111_1111seg_ment的值为7’b000_1111

再留意下,以上都是每隔1秒进行变化,并且是8个数码管轮流显示,那么波形示意图如下图所示。

上图就是seg_selseg_seg信号的变化波形图。在显示第1个时,seg_sel=8’hfeseg_ment=7’h01并持续1秒;在第1个时,seg_sel=8’hfdseg_ment=7’h4f并持续1秒;以此类推,第8个时,seg_sel=8’h7fseg_ment=7’h0f并持续1秒。然后又再次重复。

由波形图可知,我们需要1个计数器用来计算1秒的时间。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到1_000_000_000/20=50_000_000个,我们就能知道1秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assign add_cnt==1。综上所述,该计数器的代码如下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt0 <= 0;

end

else if(add_cnt0)begin

if(end_cnt0)

cnt0 <= 0;

else

cnt0 <= cnt0 + 1;

end

end

assign add_cnt0 = 1 ;

assign end_cnt0 = add_cnt0 && cnt0==50_000_000-1 ;

再次观察波形图,我们发现有第1个,第2个直到第8个,说明这还需要另外一个计数器来表示第几个。该计数器表示第几个,自然是完成1秒就加1,因为加1条件可为end_cnt0。该计数器一共要数8次。所以代码为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt1 <= 0;

end

else if(add_cnt1)begin

if(end_cnt1)

cnt1 <= 0;

else

cnt1 <= cnt1 + 1;

end

end

assign add_cnt1 = end_cnt0;

assign end_cnt1 = add_cnt1 && cnt1==8-1 ;

有了两个计数器,我们来思考输出信号seg_sel的变化。概括起来,在第1次的时候输出值为8’hfe;在第2次的时候输出值为8’hfd;以此类推,在第8次的时候输出值为8’h7f。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==7的时候输出值为8’h7f。再进一步翻译成代码,就变成如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

always  @(posedge clk or negedge rst_n)begin

if(rst_n==1'b0)begin

seg_sel <= 8'hfe;

end

else if(cnt1==0) begin

seg_sel <= 8'hfe;

end

else if(cnt1==1) begin

seg_sel <= 8'hfd;

end

else if(cnt1==2) begin

seg_sel <= 8'hfb;

end

else if(cnt1==3) begin

seg_sel <= 8'hf7;

end

else if(cnt1==4) begin

seg_sel <= 8'hef;

end

else if(cnt1==5) begin

seg_sel <= 8'hdf;

end

else if(cnt1==6) begin

seg_sel <= 8'hbf;

end

else if(cnt1==7) begin

seg_sel <= 8'h7f;

end

end

读者有没有发现,上面代码基本上和文字描述是一模一样的,这进一步展现了verilog是“硬件描述语言”。上面的代码是能正确实现seg_sel功能的,从实现角度和资源角度来说,都挺好。但代码进一步概括,可以化简如下:

1

2

3

4

5

6

7

8

always  @(posedge clk or negedge rst_n)begin

if(rst_n==1'b0)begin

seg_sel <= 8'hfe ;

end

else begin

seg_sel <= ~(8'b1<

end

end

对上面代码解释一下,第131行是指先将8’b1向左移位,再取反后的值,赋给seg_sel。假设此时cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe;假设cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’b1111_0111,即8’hf7。与第一种写法的结果都是相同的。

我们来思考输出信号seg_ment的变化。概括起来,在第1次的时候输出值为7’h01;在第2次的时候输出值为7’h4f;以此类推,在第8次的时候输出值为7’h0f。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为7’h01;在cnt1==1的时候输出值为7’h4f;以此类推,在cnt1==7的时候输出值为7’h0f。再进一步翻译成代码,就变成如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

always  @(posedge clk or negedge rst_n)begin

if(rst_n==1'b0)begin

seg_ment <= 7'h01;

end

else if(cnt1==0) begin

seg_ment <= 7'h01;

end

else if(cnt1==1) begin

seg_ment <= 7'h4f;

end

else if(cnt1==2) begin

seg_ment <= 7'h12;

end

else if(cnt1==3) begin

seg_ment <= 7'h06;

end

else if(cnt1==4) begin

seg_ment <= 7'h4c;

end

else if(cnt1==5) begin

seg_ment <= 7'h24;

end

else if(cnt1==6) begin

seg_ment <= 7'h20;

end

else if(cnt1==7) begin

seg_ment <= 7'h0f;

end

end

上面的代码正确地实现了seg_ment的功能,对于本工程说已经完美。但我们分析一下,就知道上面代码实现了类似译码的功能,将数字设成数码管显示的值,代码里只对0~7进行译码。很自然的,我先做一个通用的译码模块,将0~9都进行译码,以后就方便调用了。例如改成下面代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

always  @(posedge clk or negedge rst_n)begin

if(rst_n==1'b0)begin

seg_ment <= 7'h01;

end

else if(data==0) begin

seg_ment <= 7'h01;

end

else if(data==1) begin

seg_ment <= 7'h4f;

end

else if(data==2) begin

seg_ment <= 7'h12;

end

else if(data==3) begin

seg_ment <= 7'h06;

end

else if(data==4) begin

seg_ment <= 7'h4c;

end

else if(data==5) begin

seg_ment <= 7'h24;

end

else if(data==6) begin

seg_ment <= 7'h20;

end

else if(data==7) begin

seg_ment <= 7'h0f;

end

else if(data==8) begin

seg_ment <= 7'h00;

end

else if(data==9) begin

seg_ment <= 7'h04;

end

end

然后我们只要控制好data就能实现想要在数码管显示的数字,如下面代码。

1

assign data = cnt1 ;

cnt1=0,则数码管会显示0。当cnt1=1,则数码管会显示1

在代码的最后一行写下endmodule

1

endmodule

至此,主体程序已经完成。接下来是将module补充完整。


3.3 信号定义

接下来定义信号类型。

cnt0是用always产生的信号,因此类型为regcnt0计数的最大值为50_000_000,需要用26根线表示,即位宽是26位。add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg    [25:0]            cnt0        ;

wire                     add_cnt0    ;

wire                     end_cnt0    ;

cnt1是用always产生的信号,因此类型为regcnt1计数的最大值为7,需要用3根线表示,即位宽是3位。add_cnt1end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者11根线表示即可。因此代码如下:

1

2

3

reg    [ 2:0]            cnt1        ;

wire                     add_cnt1    ;

wire                     end_cnt1    ;

seg_sel是用always方式设计的,因此类型为reg,其一共有8根线,即位宽为8。因此代码如下:

1

reg    [ 7:0]            seg_sel ;

seg_ ment是用always方式设计的,因此类型为reg,其一共有7根线,即位宽为7。因此代码如下:

1

reg    [ 6:0]            segment ;

如果做了译码电路,即用到了data这个信号。那么data是用assign设计的,所以类型为wire,其最大值为9,所以需要4位位宽。代码如下:

1

wire  [3:0]   data     ;

至此,整个代码的设计工作已经完成。下一步是新建工程和上板查看现象。


4 综合与上板

4.1 新建工程

1.)点击File File菜单中选择New Project Wizard....

245

2.弹出Introduction界面选择next

246

(2.)工程文件夹,工程名,顶层模块名设置界面

点击next之后进入此界面

按如下路径建立文件夹D/my_seg

点击“选择工程文件夹(what is the working directory for this project?)”后面的键找到之前建立的立文件夹D/my_seg

将工程名命名栏(what is the name of this project)中输入“my_seg

在顶层模块命名栏(what is the name of the top-level design entity for this project?This name is case sensitive and must exactly match the entity name in the design file)中输入“my_seg

5.命名完毕后点击next,在出现的界面选择empty project

247

248

(3.)文件添加界面

1.从上一界面进入此界面之后点击浏览文件夹

249

2.找到我们之前写的.v文件之后选中它并点击打开

250

之后点击add添加此.v文件

251

(4.)器件选择界面

1.Device family这一项之中选择 Cyclone IV E

2.在下部的Available device 选择EP4CE15F23C8这一项

完成后点击next

252

EDA工具界面(采用默认配置即可)

点击next

253

总结界面

直接点击finish即可

254


4.2 综合

1.点击此键进行编译

255

之后等待编译成功,出现以下界面。

256


4.3 配置管脚

257

在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。

258

在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。

器件

信号线

信号线

FPGA管脚

内部信号

U6,U7

SEG_E

SEG0

Y6

seg_ment[2]

SEG_DP

SEG1

W6

未用到

SEG_G

SEG2

Y7

seg_ment[0]

SEG_F

SEG3

W7

seg_ment[1]

SEG_D

SEG4

P3

seg_ment[3]

SEG_C

SEG5

P4

seg_ment[4]

SEG_B

SEG6

R5

seg_ment[5]

SEG_A

SEG7

T3

seg_ment[6]

DIG1

DIG_EN1

T4

seg_sel[0]

DIG2

DIG_EN2

V4

seg_sel[1]

DIG3

DIG_EN3

V3

seg_sel[2]

DIG4

DIG_EN4

Y3

seg_sel[3]

DIG5

DIG_EN5

Y8

seg_sel[4]

DIG6

DIG_EN6

W8

seg_sel[5]

DIG7

DIG_EN7

W10

seg_sel[6]

DIG8

DIG_EN8

Y10

seg_sel[7]

X1

SYS_CLK

G1

clk

K1

SYS_RST

AB12

rst_n

配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。


4.4 再次综合

259

在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。

260

出现上面的界面,就说明编译综合成功。


4.5 连接开发板

261


4.6 上板

右键Program Device 选择Open 进入烧录界面

262

2.点击add file添加.sof文件,点击start开始烧录程序

263

Progress这一栏变为绿色显示百分之百successful即代表烧录成功

264

265




下一篇:2.16 AD采集
   拓展阅读