基于FPGA的数码管动态显示

描述

设计规划

8个数码管显示从十进制0开始计数,每0.1s加1,一直加到十进制数9999_9999,然后回到0重新计数。

理论基础

上一节介绍了静态显示,每个数码管显示的数字是一样的,但是实际生活中远远不能满足需求。因此我们要学习显示不同字符的数码管驱动方式-动态驱动。静态驱动的原理是位选信号为1111_1111,即8个数码管同时选中,再用段选信号让数码管显示理想数值,那么8个数码管的字符就一样。那么如果我们每次只选中一个数码管,再利用段选信号让它显示理想数值,就可以显示不同数值了。由于视觉暂留以及数码管的余晖效应 即使数码管不是同时点亮,我们在视觉效果上看到的也是同时点亮。(视觉暂留:视觉影像不会立即消失,动画就是利用这个原理,当一秒内帧数达到24就会感觉比较流畅。余晖效应:停止供电发光二极管亮度会维持一段时间。)实验证明,扫描间隔为1ms,即一秒点亮1000次,不会有闪烁感。那么我们只需要第1ms点亮第一个数码管,第2ms点亮第二个数码管…

BCD码:2-10进制码,将1位十进制数和4位二进制数对应的码。8421码比较常见,第0位权重为1,第1位权重为2,第三位权重为4,第4位权重为8,0对应0000,1对应0001,...,9对应1001。那么例如一个二进制数10010,对应十进制是18,对应BCD码是0001_1000。

编写代码

FPGA

通过系统框图可以看出,分为6个模块:数据生成模块,二进制转BCD模块,数码管动态显示驱动模块,74HC595模块,数码管动态显示模块,顶层模块。

1、数据生成模块data_gen

应该具有输入:时钟和复位,输出:数据data和使能seg_en。中间信号有100ms计数器cnt_100ms,标志位cnt_flag。

module data_gen
#(
parameter CNT_MAX = 23'd4999_999, //100ms计数值
parameter DATA_MAX= 27'd9999_9999 //显示的最大值
)
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output reg [26:0] data , //数码管要显示的值
output reg seg_en //数码管使能信号,高电平有效
);


//reg define
reg [22:0] cnt_100ms ; // 100ms计数器
reg cnt_flag ; //100ms标志信号


 //cnt_100ms:用50MHz时钟从0到4999_999计数即为100ms
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_100ms <= 23'd0;
 else if(cnt_100ms == CNT_MAX)
 cnt_100ms <= 23'd0;
 else
 cnt_100ms <= cnt_100ms + 1'b1;


 //cnt_flag:每100ms产生一个标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_flag <= 1'b0;
 else if(cnt_100ms == CNT_MAX - 1'b1)
 cnt_flag <= 1'b1;
 else
 cnt_flag <= 1'b0;


 //数码管显示的数据:0-9999_9999
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data <= 27'd0;
 else if((data == DATA_MAX) && (cnt_flag == 1'b1))
 data <= 27'd0;
 else if(cnt_flag == 1'b1)
 data <= data + 1'b1;
 else
 data <= data;


 //数码管使能信号给高即可
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 seg_en <= 1'b0;
 else
 seg_en <= 1'b1;


 endmodule

参数定义:首先确定计数和显示的最大值,计数100ms,用50MHz的时钟需要从0记到4999_999,显示的最大值是999_999,前者是23位,后者是27位。

cnt_100ms:复位有效和记满时归零,其他情况+1

cnt_flag:复位有效时归0,计满时拉高,其他情况都是保持低电平

data:复位有效时归0,数据达到最大值且标志位拉高时+1(数据要在999_999保持0.1ms,如果计满就+1就只能维持一个时钟周期,这就是flag的意义),其他情况保持

seg_en:复位有效时归0,其他情况都是高电平有效。

2、BCD模块bcd_8421

输入是时钟、复位和数据data,data是2进制数,要先转换成10进制,再将10进制的每一位转换成BCD码,才能使每一个数码管显示对应的字符。我们看二进制码如何变为BCD码。输出是8个数码管对应8位,每位转换BCD码是4位。输出是:个位,十位,百位,千位,万位,十万位,百万位,千万位。中间信号是移位判断计数器cnt_shift,移位判断数据寄存器data_shift,移位判断标志信号shift_flag。

FPGA

以十进制数234为例,二进制是1110_1010,BCD码应该是0010_0011_1000,那么先进行补0操作,输入的二进制码不足12位,在高位补0。再将二进制码的最高位作为BCD的最低位进行移位,并判断每一个BCD码其对应的十进制数是否大于4,如果大于4就对BCD码做加3操作,若小于等于4就让其值保持不变。然后继续移位,每次移位后都要进行判断。

module bcd_8421(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [26:0] data , //输入需要转换的数据
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun , //十万位BCD码
output reg [3:0] t_hun ,  //百万位BCD码
output reg [3:0] h_tho //千万位BCD码 
);


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




 //cnt_shift:从0到28循环计数
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 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 == 1'b0)
 data_shift <= 59'b0;
 else if(cnt_shift == 5'd0)
 data_shift <= {32'b0,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;


 //当计数器等于28时,移位判断操作完成,对各个位数的BCD码进行赋值
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 begin
 unit <= 4'b0;
 ten <= 4'b0;
 hun <= 4'b0;
 tho <= 4'b0;
 t_tho <= 4'b0;
 h_hun <= 4'b0;
 t_hun <= 4'b0;
 h_tho <= 4'b0;
 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];
 t_hun <= data_shift[54:51];
 h_tho <= data_shift[58:55];
 end
 endmodule

cnt_shift:移位判断计数器,data有27位,一共要判断27次,cnt_shift位数为5位,从0开始计数到28,其中1-27是移位判断操作。复位有效时归0,计满且flag为高点平(移位结束)时归0,flag为高点平(每次移位结束)都+1,其他情况保持不变

data_shift:移位判断数据寄存器,移位数据放在高位,data数据放在低位,输出数据一共要32位,data有27位,一共需要59位。计数器cnt_shift为0时赋初值,计数器为1-27时移位判断。复位有效时归0,计数器为0时赋初值,高32位为0,后27位是data初值。当计数器不到27且flag为低电平(还没开始判断时),就要开始判断移位了,具体是:判断data_shift[30:27]是否大于4(0-26是data,27-30是移位数据的低4位),大于4则将[30:27]+3,不满足就保持原值。...判断data_shift[58:55]是否大于4,大于4则+3,不满足保持原值,判断结束后,data_shift左移一位,其他情况维持不变

shift_flag:随着时钟周期高低变化的移位判断标志信号,复位有效时归0,其他情况取反,即一个时钟周期内为低电平表示判断,下一个时钟周期内为高点平表示移位

输出:复位有效时,个位-千万位的输出都为0。由于data有27位,要判断移位27次,计数器计数到27时结束最后一次判断移位,计数到28时进行输出的赋值。个位的4位BCD码是移位数据的低四位data_shift[31:27],千万位的4位BCD码是移位数据的高4位data_shift[58:55]

3、数码管动态显示驱动模块seg_dynamic

我们由上一节知道数码管是由位选和段选信号进行选择和控制的,那么这个模块里要将输入的data转换成对应的段选位选信号。因此,输入为时钟、复位、data、seg_en,输出为sel,seg。因为第1ms第一个数码管亮,第2ms第二个数码管亮,这里需要用1ms时钟去控制位选信号即哪个数码管亮。每100ms数码管显示的十进制数要+1,因此时钟还要控制段选信号即选中的数码管显示什么值。中间信号有实例化后的输出unit,...,h_tho,data_reg,cnt_1ms,flag_1ms,cnt_sel,sel_reg,data_disp。data_reg是数码管带显示内容寄存器,假设输入要显示的十进制数是9999_9999,那么不考虑符号时8个数码管显示9999_9999。cnt_1ms时1ms计数器。flag_1ms是标志信号,计满时拉高,控制位选。cnt_sel是位选数码管计数器,8个数码管每1ms换一个亮,也就是每个数码管8ms亮一次。cnt_sel从0-7计数,相当于给数码管编码。sel_reg是数码管位选信号寄存器,选中点亮的数码管后给他显示的值。data_disp:当前点亮数码管显示的值。

module seg_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [26:0] data , //数码管要显示的值
input wire seg_en , //数码管使能信号,高电平有效
output reg [7:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//parameter define
parameter CNT_MAX = 16'd49_999; //数码管刷新时间计数最大值
//wire define
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
wire [3:0] t_hun ;
wire [3:0] h_tho ;


//reg define
reg [31:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [7:0] sel_reg ; //位选信号
reg [3:0] data_disp; //当前数码管显示的数据


//data_reg:控制数码管显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 32'b0;
//不考虑小数点,若显示的数据是8位都非0,则8个数码管全显示
else if(h_tho)
data_reg <= {h_tho,t_hun,h_hun,t_tho,tho,hun,ten,unit};
//若显示的数据7位非0,比如1234567不是显示01234567,则7个数码管亮
else if(t_hun)
data_reg <= {4'd10,t_hun,h_hun,t_tho,tho,hun,ten,unit};//4'd10我们定义为不显示
//若显示的数据6位非0,则6个数码管亮
else if(h_hun)
data_reg <= {4'd10,4'd10,h_hun,t_tho,tho,hun,ten,unit};
//若显示的数据5位非0,则5个数码管亮
else if(t_tho)
data_reg <= {4'd10,4'd10,4'd10,t_tho,tho,hun,ten,unit};
//若显示的数据4位非0,则4个数码管亮
else if(tho)
data_reg <= {4'd10,4'd10,4'd10,4'd10,tho,hun,ten,unit};
//若显示的数据3位非0,则3个数码管亮
else if(hun)
data_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,hun,ten,unit};
//若显示的数据2位非0,则2个数码管亮
else if(ten)
data_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,ten,unit};
//若上面都不满足都只显示一位数码管
else
data_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,unit};


//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:从0到7循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'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@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 sel_reg <= 8'b0000_0000;
 else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
 sel_reg <= 8'b0000_0001;
 else if(flag_1ms == 1'b1)
 sel_reg <= sel_reg < < 1;
 else
 sel_reg <= sel_reg;

 //控制数码管的位选信号,使8个数码管轮流显示
always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data_disp <= 4'b0;
 else if((seg_en == 1'b1) && (flag_1ms == 1'b1))
 case(cnt_sel)
 3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值
 3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值
 3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值
 3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值
 3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值
 3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值
 3'd6: data_disp <= data_reg[27:24]; //给第7个数码管赋百万位值
 3'd7: data_disp <= data_reg[31:28]; //给第8个数码管赋千万位值
 default:data_disp <= 4'b0;
 endcase
 else
data_disp <= data_disp;


//控制数码管段选信号,显示数字
always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 seg <= 8'b1111_1111;
 else
 case(data_disp)
 4'd0 : seg <= {1'b1,7'b100_0000}; //显示数字0
 4'd1 : seg <= {1'b1,7'b111_1001}; //显示数字1
 4'd2 : seg <= {1'b1,7'b010_0100}; //显示数字2
 4'd3 : seg <= {1'b1,7'b011_0000}; //显示数字3
 4'd4 : seg <= {1'b1,7'b001_1001}; //显示数字4
 4'd5 : seg <= {1'b1,7'b001_0010}; //显示数字5
 4'd6 : seg <= {1'b1,7'b000_0010}; //显示数字6
 4'd7 : seg <= {1'b1,7'b111_1000}; //显示数字7
 4'd8 : seg <= {1'b1,7'b000_0000}; //显示数字8
 4'd9 : seg <= {1'b1,7'b001_0000}; //显示数字9
 4'd10 : seg <= 8'b1111_1111 ; //不显示任何字符
 default:seg <= 8'b1100_0000;
endcase


//sel:数码管位选信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 sel <= 8'b0000_0000;
 else
sel <= sel_reg;


bcd_8421 bcd_8421_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n), //复位信号,低电平有效
 .data (data ), //输入需要转换的数据
 .unit (unit ), //个位BCD码
 .ten (ten ), //十位BCD码
 .hun (hun ), //百位BCD码
 .tho (tho ), //千位BCD码
 .t_tho (t_tho ), //万位BCD码
 .h_hun (h_hun ), //十万位BCD码
 .t_hun (t_hun ), //百万位BCD码
 .h_tho (h_tho ) //千万位BCD码
 );

endmodule

参数定义:1ms计数2500000个,最大计数值49_999。

data_reg:控制数码管要显示的数据,复位有效时归0,因为没有小数点,我们考虑到有几位就只让几个数码管进行显示。不显示的数码管定义为4'd11

cnt_1ms:复位有效或计满时归0,其他情况+1

flag_1ms:1ms标志位,复位有效时归0,计满数值-1时拉高,其他情况保持低电平

cnt_sel:数码管编号,从0-7计数,复位有效时归0,计满7个数且标志位拉高时归0,标志位拉高时+1,其他情况保持不变

控制数码管位选信号:复位有效时data_disp归0,使能为高电平有效且标志信号为高时,分情况讨论,cnt_sel为0,第一个数码管赋data_reg的低四位,第一个数码管显示个位,...,需要有default语句,data_disp赋0即可,其他情况,data_disp保持不变

控制数码管段选信号:复位有效时seg所有位赋1表示每一段都不点亮,其他分情况讨论,data_disp为0时数码管显示0,0对应的段选码上一节讨论了,最高位是小数点不用都为1表示灭,低7位对应abcdefg=100_0000,以此类推,加上default语句

sel:数码管位选信号赋值,复位有效时归0,其他情况将sel_reg赋值给

实例化BCD

4、74HC595控制模块

直接用上一节的代码,需要注意的是hc595_ctrl.v中第23行对data的赋值,把最右边的数码管作为个位比较符合我们的书写习惯,所以位选信号的顺序要改,原语句是assign data={sel,seg[0],seg[1],...,seg[7]};要改做assign data={sel[0],sel[1],...,sel[7],seg[0],seg[1],...,seg[7]};否则,数码管最左边是个位,不符合书写习惯。

5、数码管动态显示模块

该模块主要是对数码管动态显示驱动模块和74HC595控制模块的实例化,以及对应信号的连接

module seg_595_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [26:0] data , //数码管要显示的值
input wire seg_en , //数码管使能信号,高电平有效
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds //串行数据输入
);
//wire define
 wire [7:0] sel; //数码管位选信号
 wire [7:0] seg; //数码管段选信号


seg_dynamic seg_dynamic_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n), //复位信号,低有效
 .data (data ), //数码管要显示的值
 .seg_en (seg_en ), //数码管使能信号,高电平有效
 .sel (sel ), //数码管位选信号
 .seg (seg ) //数码管段选信号
);


hc595_ctrl hc595_ctrl_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n), //复位信号,低有效
 .sel (sel ), //数码管位选信号
 .seg (seg ), //数码管段选信号
 .en (1'b1),
 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ) //串行数据输入
 );


endmodule

两个模块的实例化

6、顶层模块top_seg_595

顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接

module top_seg_595
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds //串行数据输入
);
 //wire define
 wire [26:0] data ; //数码管要显示的值
 wire seg_en ;//数码管使能信号,高电平有效 

 data_gen data_gen_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n), //复位信号,低电平有效
 .data (data ), //数码管要显示的值
 .seg_en (seg_en) //数码管使能信号,高电平有效
 );

 seg_595_dynamic seg_595_dynamic_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低有效
 .data (data ), //数码管要显示的值
 .seg_en (seg_en ), //数码管使能信号,高电平有效
 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ) //串行数据输入
 );
 endmodule

数据产生和动态显示模块的实例化

FPGA

编写testbench

`timescale 1ns/1ns
module tb_top_seg_595();


//wire define
wire stcp ; //输出数据存储寄时钟
wire shcp ; //移位寄存器的时钟输入
wire ds ; //串行数据输入


 //reg define
 reg sys_clk ;
 reg sys_rst_n ;


 //对sys_clk,sys_rst_n赋初始值
 initial
 begin
 sys_clk = 1'b1;
 sys_rst_n <= 1'b0;
 #100
 sys_rst_n <= 1'b1;
 end


 //clk:产生时钟
 always #10 sys_clk <= ~sys_clk;


 //重新定义参数值,缩短仿真时间
 defparam top_seg_595_inst.seg_595_dynamic_inst.seg_dynamic_inst.
 CNT_MAX=19;
 defparam top_seg_595_inst.data_gen_inst.CNT_MAX = 49;

 top_seg_595 top_seg_595_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低电平有效


 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds )//串行数据输入
 );


 endmodule

testbench已经很熟悉了。

对比波形

先看6个模块的从属关系,再分别检查6个模块的波形,比较容易debug。

FPGA

数据产生模块

FPGA

后面几个模块不细列。

分配管脚

和上一节一样

FPGA

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分