ZYNQ开发板LED灯的闪烁呼吸以及控制
- 1 新建工程
- 2 程序源码
- 2.1 LED顶层模块
- 2.2 按键检测模块
- 2.3 按键控制LED闪烁模块
- 2.4 呼吸灯子模块
- 2.5 LED闪烁模块
- 2.6 约束文件
- 3 生成原理图
- 4 实际效果
- 5 固化到QSPI Flash中
- 6 固化到SD卡中
- 7 遇到的问题
- 8 其它小技巧
- 9 程序仿真
- 9.1仿真文件
- 9.2 仿真结果
- 10 LED实验
- 10.1 实验问题
- 10.2 经验总结
1 新建工程
1、新建一个工程,芯片类型选择如下,ZYNQ7010。
2、源文件已经在原子哥代码的基础上改好,新建工程时直接勾选增加源文件,分两次分别增加源文件和约束文件。
3、程序文件结构如下图,其中srcs文件夹包含程序的源码.v以及约束文件。jou与log为日志文件,每次都会新生成一个,可以进行删除。
4 编译完成后打开硬件管理器
5 连接单板烧录程序,进行连接下载操作,此时程序并未固化到单板上,上电或者复位会导致程序丢失。ZYNQ是通过PS端启动PL端,因此如果需要固化有更加复杂的操作。
2 程序源码
文件层次结构如下图所示,包含一个顶层文件和4个子文件,这种结构化的层次是通过对模块进行例化自动进行的。第一个顶层文件定义了主模块的输入输出IO,第二个为例化后的按键检测模块,第三个为例化后的LED控制模块,第四个为例化后的LED闪烁模块,第五个为例化后的呼吸灯模块。子模块的名字为例化的模块名字。有点类似C语言编程的结构,在C语言中是写一个main文件,然后定义各种模块的子文件,通过include头文件还有函数引用进行包含调用。FPGA是通过例化进行调用。
2.1 LED顶层模块
top_key_led模块定义了一个model,包含全部的输入输出信号,完整的源代码如下:
module top_key_led(
input sys_clk ,
input sys_rst_n ,
input key ,
output [1:0] led12, //LED灯
output led4 , //按键灯
output led3 //呼吸灯
);
//wire define
wire key_value ;
wire key_flag ;
//*****************************************************
//** main code
//*****************************************************
//例化按键消抖模块
key_debounce u_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_value (key_value),
.key_flag (key_flag)
);
//例化蜂鸣器控制模块
led4_control u_led4_control(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_value (key_value),
.key_flag (key_flag),
.led4 (led4)
);
//呼吸灯模块
breath_led u_breath_led(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led3 (led3)
);
//跑马灯模块
led_twinkle u_led_twinkle(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led12 (led12)
);
endmodule
2.2 按键检测模块
第一个子模块按键检测模块代码如下,程序设计了一个20mS的消抖操作。
module key_debounce(
input sys_clk ,
input sys_rst_n ,
input key , //外部输入的按键值
output reg key_value , //消抖后的按键值
output reg key_flag //消抖后的按键值的效标志
);
//reg define
reg [19:0] cnt ;
reg key_reg ;
parameter DELAYTIME=20'd100_0000;//定义消抖时间
//*****************************************************
//** main code
//*****************************************************
//按键值消抖
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
cnt <= 20'd0; //系统复位时赋值0
key_reg <= 1'b1;
end
else begin
key_reg <= key; //将按键值延迟一拍
if(key_reg != key) begin //如果当前按键值和前一拍的按键值不一样,即按键被按下或松开
cnt <= DELAYTIME; //则将计数器置为20'd100_0000,
//即延时100_0000 * 20ns(1s/50MHz) = 20ms
end
else begin //如果当前按键值和前一个按键值一样,即按键没有发生变化
if(cnt > 20'd0) //则计数器递减到0
cnt <= cnt - 1'b1;
else
cnt <= 20'd0;
end
end
end
//将消抖后的最终的按键值送出去
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
key_value <= 1'b1;
key_flag <= 1'b0;
end
//在计数器递减到1时送出按键值
else if(cnt == 20'd1) begin
key_value <= key;
key_flag <= 1'b1;
end
else begin
key_value <= key_value;
key_flag <= 1'b0;
end
end
endmodule
2.3 按键控制LED闪烁模块
第二个子模块控制LED灯闪烁的代码如下,当检测到按键时翻转LED灯的状态。
module led4_control(
input sys_clk,
input sys_rst_n,
input key_value,
input key_flag,
output reg led4
);
//*****************************************************
//** main code
//*****************************************************
//每次按键按下时,就翻转蜂鸣器的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led4 <= 1'b1;
else if(key_flag && (key_value == 1'b0))
led4 <= ~led4;
end
endmodule
2.4 呼吸灯子模块
第三个子模块呼吸灯子模块的源代码如下,呼吸灯的频率为1KHz,通过调整占空比来控制LED的呼吸亮灭。
module breath_led(
input sys_clk , //时钟信号50Mhz
input sys_rst_n , //复位信号
output led3 //呼吸灯
);
//reg define
reg [15:0] period_cnt ; //周期计数器频率:1khz 周期:1ms 计数值:1ms/20ns=50000
reg [15:0] duty_cycle ; //占空比数值
reg inc_dec_flag ; //0 递增 1 递减
//*****************************************************
//** main code
//*****************************************************
//根据占空比和计数值之间的大小关系来输出LED
assign led3 = (period_cnt >= duty_cycle) ? 1'b1 : 1'b0;
//周期计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
period_cnt <= 16'd0;
else if(period_cnt == 16'd50000)
period_cnt <= 16'd0;
else
period_cnt <= period_cnt + 1'b1;
end
//在周期计数器的节拍下递增或递减占空比
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
duty_cycle <= 16'd0;
inc_dec_flag <= 1'b0;
end
else begin
if(period_cnt == 16'd50000) begin //计满1ms
if(inc_dec_flag == 1'b0) begin //占空比递增状态
if(duty_cycle == 16'd50000) //如果占空比已递增至最大
inc_dec_flag <= 1'b1; //则占空比开始递减
else //否则占空比以25为单位递增
duty_cycle <= duty_cycle + 16'd25;
end
else begin //占空比递减状态
if(duty_cycle == 16'd0) //如果占空比已递减至0
inc_dec_flag <= 1'b0; //则占空比开始递增
else //否则占空比以25为单位递减
duty_cycle <= duty_cycle - 16'd25;
end
end
end
end
endmodule
2.5 LED闪烁模块
第四个子模块LED闪烁模块每隔0.5S跳转两个LED灯,类似一个跑马灯。源代码如下
module led_twinkle(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效
output [1:0] led12 //LED灯
);
//reg define
reg [25:0] cnt ;
//*****************************************************
//** main code
//*****************************************************
//对计数器的值进行判断,以输出LED的状态
assign led12 = (cnt < 26'd2500_0000) ? 2'b01 : 2'b10 ;
//计数器在0~5000_000之间进行计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 26'd0;
else if(cnt < 26'd5000_0000)
cnt <= cnt + 1'b1;
else
cnt <= 26'd0;
end
endmodule
2.6 约束文件
约束文件为管脚的约束,对信号的脚位进行定义电平定义等,源代码如下,移植时需要根据单板的不同进行修改。
set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports {led3}]
set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports {led12[0]}]
set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports {led12[1]}]
set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports {led4}]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports {key}]
set_property -dict {PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
当然也可以直接使用可视化界面对其进行更改,下图为IO planing的配置界面,修改完后软件会自动生成一个约束文件。
3 生成原理图
生成的RTL原理图如下所示,通过例化后,这种模块间的相互关系就表现非常清晰,点击模块上的+号可以对模块展开看下一层次的原理图。这里也是实际真实的硬件。
4 实际效果
实际运行效果和程序设计相同,这里需要了解的是这四个模块程序是并行进行的,而ARM程序是串行进行,这是一个很大的差异,也就是使用FPGA可以进行数据的并行处理。对于摄像头摄像视频解码并行数据处理非常有优势。
5 固化到QSPI Flash中
1、上述程序是掉电丢失,如果要固化到Flash中必须创建IP核,ZYNQ7最小系统
2、勾选SPI Flash,配置内存,其它的都不勾选
创建输出产品以及HDL wrapper
此时上文中的top_led_key文件就不需要了,需要在生成的HDL文件中,增加输入输出接口以及增加模块例化ed_flash_wrapper。
`timescale 1 ps / 1 ps
module led_flash_wrapper
(DDR_addr,
DDR_ba,
DDR_cas_n,
DDR_ck_n,
DDR_ck_p,
DDR_cke,
DDR_cs_n,
DDR_dm,
DDR_dq,
DDR_dqs_n,
DDR_dqs_p,
DDR_odt,
DDR_ras_n,
DDR_reset_n,
DDR_we_n,
FIXED_IO_ddr_vrn,
FIXED_IO_ddr_vrp,
FIXED_IO_mio,
FIXED_IO_ps_clk,
FIXED_IO_ps_porb,
FIXED_IO_ps_srstb,
sys_clk , //输入时钟
sys_rst_n ,//复位信号
key , //按键信号
led12, //跑马灯
led4, //按键灯
led3 ); //呼吸灯
input sys_clk; //输入时钟
input sys_rst_n; //复位信号
input key; //按键信号
output [1:0] led12; //LED灯
output led4; //按键灯
output led3; //呼吸灯
inout [14:0]DDR_addr;
inout [2:0]DDR_ba;
inout DDR_cas_n;
inout DDR_ck_n;
inout DDR_ck_p;
inout DDR_cke;
inout DDR_cs_n;
inout [3:0]DDR_dm;
inout [31:0]DDR_dq;
inout [3:0]DDR_dqs_n;
inout [3:0]DDR_dqs_p;
inout DDR_odt;
inout DDR_ras_n;
inout DDR_reset_n;
inout DDR_we_n;
inout FIXED_IO_ddr_vrn;
inout FIXED_IO_ddr_vrp;
inout [53:0]FIXED_IO_mio;
inout FIXED_IO_ps_clk;
inout FIXED_IO_ps_porb;
inout FIXED_IO_ps_srstb;
wire [14:0]DDR_addr;
wire [2:0]DDR_ba;
wire DDR_cas_n;
wire DDR_ck_n;
wire DDR_ck_p;
wire DDR_cke;
wire DDR_cs_n;
wire [3:0]DDR_dm;
wire [31:0]DDR_dq;
wire [3:0]DDR_dqs_n;
wire [3:0]DDR_dqs_p;
wire DDR_odt;
wire DDR_ras_n;
wire DDR_reset_n;
wire DDR_we_n;
wire FIXED_IO_ddr_vrn;
wire FIXED_IO_ddr_vrp;
wire [53:0]FIXED_IO_mio;
wire FIXED_IO_ps_clk;
wire FIXED_IO_ps_porb;
wire FIXED_IO_ps_srstb;
wire key_value ; // 按键值
wire key_flag ; //按键标志位
led_flash led_flash_i
(.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb));
//例化按键检测模块
key_debounce u_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_value (key_value),
.key_flag (key_flag)
);
//例化LED控制模块
led4_control u_led4_control(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_value (key_value),
.key_flag (key_flag),
.led4 (led4)
);
//例化呼吸灯模块
breath_led u_breath_led(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led3 (led3)
);
//例化跑马灯模块
led_twinkle u_led_twinkle(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led12 (led12)
);
endmodule
3、生成
的原理图会在原基础上增加ZYNQ最小系统,但这部分和原FPGA的其它部分没有关联。
4、导出硬件(包含比特流),加载SDK
5、在打开SDK后,新建应用工程,创建第一阶段BootLoader(FSBL),板级支持包BSP,BSP为PS侧的FSBL与PL侧的沟通桥梁。SDK软件好像是基于eclispe开发的。
6、创建BIN镜像文件,需要同时添加比特.bit文件和FSBL.elf文件,需要将Boot拨码开关拨到对应正确的位置,这个和ARM相似。
7、对应的原理图如下,MIO4和MIO5可以配置,其余Boot配置脚全部拉地处理了。烧录时配置为JTAG模式,即全0,烧录完后配置为QSPI启动,即为10。
8、选择烧写Flash。
烧录完成后的信息如下,将拨码开关拨到QSPI启动后按复位即可,后续再次上电程序也不会丢失。
SDK软件的program FPGA还可以烧录bit文件,这样就无需打开Vivado了。
bit文件默认在led_flash.runs-》impl_1文件夹中
6 固化到SD卡中
在IP核中双击打开配置菜单,勾选SD卡,这里特别注意SD卡的电平,需要和原理图一致,如果不一致会导致无法从SD卡启动。BANK0和BANK1均为PS端端口,也就是ZYNQ内核的配置和PL无关,不涉及FPGA部分端口的占用。
然后保存,此时前面的Wrapper.v文件会发生变化。需要重新添加IO口和例化模块。这里我有试过,增加QSPI和SD卡,最终的wrapper顶层文件是一样的。
生成.bin文件与QSPI方法相同,最后将生成的BIN文件拷贝到SD卡中(SD卡需要格式化为FAT32)。将BOOT跳到SD卡启动的模式。将SD卡插入后上电。单板即可从SD卡进行启动。
7 遇到的问题
SD卡方式启动时,如果频繁(几秒钟间隔)给开发板上电,存在较大的概率单板不能启动,更换SD卡后也不能解决,但是如果断电10分钟以上时,单板可以100%启动,查阅了相关资料,可能和单板上电时序设计有关,频繁上电会导致电容上的残压不能完全释放,导致上电初始时刻ZYNQ核心已经正常工作,但是SD卡尚未正常工作,导致启动时ZYNQ无法读到SD卡加载程序,启动失败。特别大容量的TF卡不能格式化为FAT32(比如256G),因此也无法被ZYNQ识别。
8 其它小技巧
配置IP核时点击右侧的summary report 可以生成IP配置报告
生成的报告(部分)
在写程序时要多用例化,这样层次和结构会非常好,也方便查找问题和移植。
9 程序仿真
9.1仿真文件
编写仿真所需的tb文件,这里的文件和top文件比较类似,将输入信号定义为reg信号,将输出信号定义为wire。(定义错误会导致软件报错)赋初值,这里定义0.1s翻转一次key。完成后,系统会自动将tb文件识别为仿真顶层文件。如果波形中出现蓝色的线条,说明对应的值是不定的,需要进行初始化。
`timescale 1ns / 1ps
module tb_key_led( );
//输入
reg sys_clk;
reg sys_rst_n;
reg key;
//输出
wire key_value;
wire key_flag;
wire[1:0] led12;
wire led3;
wire led4;
//信号初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
key=1'b1;
end
//生成时钟
always #10 sys_clk = ~sys_clk;
always #100000000 key=~key;//0.1s翻转一次KEY,模拟按键操作
//例化待测设计
key_debounce u_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_value (key_value),
.key_flag (key_flag)
);
//例化蜂鸣器控制模块
led4_control u_led4_control(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_value (key_value),
.key_flag (key_flag),
.led4 (led4)
);
//呼吸灯模块
breath_led u_breath_led(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led3 (led3)
);
//跑马灯模块
led_twinkle u_led_twinkle(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led12 (led12)
);
endmodule
9.2 仿真结果
如下图所示,0.1s按键key翻转一次,在20ms后key_value值发生改变,说明消抖完成,并产生了相应的key_flag信号。同时led4灯状态发生改变。仿真结果与程序代码相符合。
初始时刻,在200ns处退出复位,这和程序的设计也是一样的
10 LED实验
10.1 实验问题
后续我自行画了一块ZYNQ的扩展板,上面含数码管显示等电路。扩展板和ZYNQ主板通过40PIN排线进行连接。在开发过程中数码管显示不正常,当数字为非5和非6的其它数字时,始终会显示小数点,怎么改程序都没用。字符段选的部分代码如下。
//控制数码管段选信号,显示字符
always @ (posedge dri_clk or negedge rst_n) begin
if (!rst_n)
seg_led <= 8'hc0;
else begin
case (num_disp)
4'd0 : seg_led <= {8'b00111111}; //显示数字 0
4'd1 : seg_led <= {8'b00000110}; //显示数字 1
4'd2 : seg_led <= {8'b01011011}; //显示数字 2
4'd3 : seg_led <= {8'b01001111}; //显示数字 3
4'd4 : seg_led <= {8'b01100110}; //显示数字 4
4'd5 : seg_led <= {8'b01101101}; //显示数字 5
4'd6 : seg_led <= {8'b01111101}; //显示数字 6
4'd7 : seg_led <= {8'b00000111}; //显示数字 7
4'd8 : seg_led <= {8'b01111111}; //显示数字 8
4'd9 : seg_led <= {8'b01101111}; //显示数字 9
4'd10: seg_led <= 8'b00000000; //不显示任何字符
default:
seg_led <= 8'b00000000;
endcase
end
end
endmodule
我仔细思考了一下,数字5和数字6和其它数字的区别是,B那一竖杠不显示(位置如下图),难道是这个和小数点同亮同灭?
为了验证猜想,我将所有段码改为了8’b00000010,如果硬件正常,那么只会显示右上角那一竖杠。如果硬件不正常,那么同时会显示小数点。
8'b00000010
烧入程序后,显示如下,验证了我的想法。就是开通B时,同步会点亮DP。
电路是经过缓冲器再到达数码管的,于是我怀疑是74LVC244缓冲芯片的问题。
拔掉这个自己绘制打样的扩展板,给ZYNQ主板烧录上电,测量相应的引脚并无异常,B脚有电压而DP点没有电压。插上扩展板后,发现B脚和DP脚均有电压!确定是芯片存在问题。
于是将芯片拆下来,换一个新的芯片,没想到TSOP的芯片是如此的难焊接,我成功的把单板的铜皮给干废了。用半残的单板重新实验,程序正常烧录后,DP不再误显示(DP的铜皮没有被破坏),遗憾的是,铜皮废掉的段位也不显示了。
10.2 经验总结
1、自己制作的单板,千万不要用类似TSOP这种16脚的小封装,非常难焊接,且容易连锡;
2、尽量不要用烙铁焊接小封装器件,很容易把铜皮搞挂,用热风枪吹上去;
3、多分析实验现象,软件硬件都要考虑,不要再一颗树上吊死;
4、尽量用成熟的单板验证完方案后,再上自制的单板,软件和硬件至少要确认一个是好的。