在电子体系中,一般都需求有输出设备来输出或显现必定的信息,以指示当时体系运转的状况。在以单片机和ARM为主的电子体系中,液晶屏是抱负的输出设备。而FPGA则由于其共同的硬件结构,假设用RTL级电路来驱动五颜六色液晶屏来显现必定的数据,必然是十分不划算的挑选,并且驱动也极为杂乱。数码管作为一种能够直观显现必定数据信息的输出设备,具有驱动简略,显现直观的特色,特别合适作为FPGA体系的输出设备。本节,小梅哥就将和咱们一同进行数码管驱动的开发。
试验意图
完结6位7段数码管的驱动,待显现数据以BCD格局输入。数码管改写时钟为1KHz。试验运用了4个独立按键作为输入,经过按键来改动需求数码管显现的数据,以验证数码管驱动的正确性,一同也可查验独立按键消抖模块的可靠性。
试验原理
数码管所谓的动态扫描,便是利用人眼的视觉暂留特性,在人眼能分辩的改动速度以外,快速分时的点亮各个数码管对应的段。由于别离点亮一切数码管一次所用时刻小于人眼的视觉暂留,因而,在人们眼里看来,这些数码管都是一同继续点亮的,并不会有闪耀的感觉。
图2-1 数码管实物图
关于数码管的详细原理,请咱们网上查阅,小梅哥一个人精力有限,没办法在这儿从最低层的原理给咱们一步一步讲起,假设咱们有不明白的,请自行百度。这儿小梅哥就用最简略粗犷的方法给咱们简略介绍一下。
图2-2 数码管简略等效电路
上图为3位7段数码管的等效电路图,在这个图中,能够显着的看到24个发光二极管被分为了三组,每一组的8个发光二极管正极被接在了一同,经过一个三极管与VCC相连。三极管的基极衔接到了FPGA的IO上,因而,只需求FPGA对应的IO上给出低电平,三极管便会导通。而三组LED中一切的相同编号的LED的负极被衔接在了一同,并接到了FPGA的IO上。假设咱们期望将最左面一组的led0、led5、led7三个编号的led灯点亮,其它led不亮,则只需求给Q0的基极(sel0)衔接上低电平,并将led0、led5、led7的负极(a、f、h)衔接上低电平,其它一切端口都输出高电平,则最左面一组的对应的三个led灯就会被点亮,而其它led则会处于平息状况。
假设咱们需求在三秒时刻内,完结以下三次操作:榜首次操作,点亮最左面一组led灯的led0、led5、led7;第2次操作,点亮中心一组led灯的led1、led2、led3;第三次操作,点亮最右边一组led灯的led2、led4、led6;那么咱们只需求依照如下表格中列出的真值表操作即可:
榜首秒第二秒第三秒
sel0011
sel1101
sel2110
a011
b101
c100
d101
e110
f011
g110
h011
依照以上表格,咱们就能知道该怎么操作了,只需求在不同的时刻给各个IO不同的电平,便能完结咱们想要的亮灭组合。以上咱们是以1秒为单位进行led组的切换的,假设咱们将切换速度加速,变为1毫秒一切换,会是什么情况呢?在1毫秒一切换的速度下,完结一切操作所需时刻为3ms,远远超出了咱们人眼所能辨识的改动速度规模。假设咱们让以上三个操作永久循环的进行下去,那么咱们将看见三组led灯中,咱们点亮的那几个led是一同且一向处于亮着的状况的,这便是动态扫描的原理,假设咱们把每个led做成一个长条型的,并依照如下形状摆放,便便是咱们常见的数码管了。
图2-3 数码管段散布
硬件规划
图2-2仅仅一个为了叙述数码管原理简化了的电路模型,常见的数码管电路结构如下图所示:
图3-1 数码管典型电路
在这个图中,共有6位数码管,每个数码管的正极被接在一个驱动三极管上,三极管的基极衔接到三八译码器的Y端,则FPGA只需求三个引脚就可最多操控8个数码管的位选。数码管的段选在串接了470欧姆的电阻后与FPGA的IO相连。这儿470欧姆的电阻首要起到限流的作用,确保流过数码管的电流在正常规模内。
架构规划
本试验由一共四个模块组成,别离为数码管驱动模块、独立按键检测模块、操控模块和顶层模块,其架构如下:
图4-1 led试验模块安排结构图
由图可知本试验有1个输出端口,对应驱动了38译码器的三个挑选端和数码管的8个段选脚。6个输入端口,对应了4个独立按键输入和一个时钟输入以及一个复位输入。详细端口名及其含义如下
代码安排方法
本试验中,数码管的驱动采用了组合逻辑译码的方法进行,详细将在代码解读时解说。
试验中还规划了一个操控器,该操控器首要经过读取按键信息来改动待数码管待显现的数据内容。
按键检测部分运用前一节开发的独立按键的驱动,因而这儿不进行过多的剖析介绍。
要害代码解读
由于数码管归于低速设备,其正常的扫描频率为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’b0001:Dig_Led_Data <= Dig_Led_Data + 23’d1;
4’b0010:Dig_Led_Data <= Dig_Led_Data + 23’d100;
4’b0100:Dig_Led_Data <= Dig_Led_Data + 23’d10000;
4’b1000:Dig_Led_Data <= Dig_Led_Data + 23’d100000;
default:Dig_Led_Data <= Dig_Led_Data;
endcase
end