前言
PWM是一种利用微处理器或FPGA的数字输出来控制模拟电路的有效技术,广泛应用于多个领域。它通过高分辨率计数器调整方波的占空比来编码模拟信号的电平,无需数模转换。PWM信号是数字的,通过开关直流供电来控制模拟负载的电压或电流。实验中使用PWM实现LED呼吸灯效果,通过调整PWM的周期和占空比(高电平占周期的百分比)来控制LED灯的亮暗程度,产生逐渐变亮再逐渐变弱的效果。一般建议PWM周期设置为10毫秒。
一、程序设计
本次实验使用到两个计数器,cnt_10ms 用于计数 10ms,这是 PWM 方波的周期,cnt_pwn用来计数PWM的占空比,当cnt_10ms的值小于cnt_pwn的时候,pwm输出为 0,当 cnt_10ms 大于 cnt_pwn 的时候,pwm 输出为 1。
通过inc_flag信号来决定cnt_pwm的值是累加还是累减步进值OFFSET; inc_flag
为 1 的时候累加 OFFSET,inc_flag 为 0 的时候累减 OFFSET。
1.Verilog代码如下
// 定义呼吸灯模块,模拟呼吸灯效果
module breath_led #
(
parameter CNT_10MS=10_000_000/20, // 定义10毫秒的计数值,用于产生周期性信号
parameter OFFSET=5000 // PWM调节的偏移量
)
(
input clk, // 时钟信号输入
input rst_n, // 复位信号输入,低电平有效
output led // LED输出,表示呼吸灯状态
);
// 内部寄存器和信号定义
reg[31:0]cnt_10ms; // 用于计数10毫秒的计数器
reg inc_flag; // 增加标志,用于控制PWM占空比的增减
reg[31:0]cnt_pwm; // PWM计数器,用于控制LED的亮度
wire pwm; // PWM信号,用于控制LED
// 计数器cnt_10ms,用于产生周期性信号
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_10ms<='d0; // 复位时清零计数器
else if(cnt_10ms==CNT_10MS-1)
cnt_10ms<='d0; // 达到10毫秒时清零计数器
else
cnt_10ms<=cnt_10ms+1'b1; // 否则计数器递增
end
// 控制inc_flag的逻辑,用于决定cnt_pwm是增加还是减少
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
inc_flag<=1'b1; // 复位时设置增加标志为1
else if(cnt_10ms==CNT_10MS-1) begin
if(inc_flag==1'b1) begin // 如果当前是增加阶段
if(cnt_pwm>=CNT_10MS-1) // 如果已经达到最大值,则切换到减少阶段
inc_flag<=1'b0;
else
inc_flag<=inc_flag; // 否则保持当前状态
end
else if(inc_flag==1'b0) begin // 如果当前是减少阶段
if(cnt_pwm==OFFSET) // 如果已经减少到最小值,则切换到增加阶段
inc_flag<=1'b1;
else
inc_flag<=inc_flag; // 否则保持当前状态
end
else
inc_flag<=inc_flag; // 如果inc_flag状态未知,则保持当前状态
end
else
inc_flag<=inc_flag; // 如果不是10毫秒计数器的触发时刻,则保持当前状态
end
// 控制PWM计数器cnt_pwm,实现LED亮度的调节
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_pwm<='d0; // 复位时清零PWM计数器
else if(cnt_10ms==CNT_10MS-1) begin
if(inc_flag) // 如果当前是增加阶段
cnt_pwm<=cnt_pwm+OFFSET; // PWM计数器增加
else // 如果当前是减少阶段
cnt_pwm<=cnt_pwm-OFFSET; // PWM计数器减少
end
else
cnt_pwm<=cnt_pwm; // 保持当前PWM计数器值
end
// 根据cnt_pwm和cnt_10ms的值,生成PWM信号
assign pwm=(cnt_10ms>cnt_pwm)?1'b1:1'b0; // 当cnt_10ms大于cnt_pwm时,pwm为高电平
// 将PWM信号赋值给led引脚,实现LED的呼吸效果
assign led=pwm;
endmodule
2.仿真代码如下
// 定义仿真时间单位和精度,1纳秒每单位,1皮秒的精度
`timescale 1ns / 1ps
// 定义测试模块 tb_breath_led,用于测试 breath_led 模块
module tb_breath_led();
// 定义测试模块中的信号
reg clk; // 定义时钟信号
reg rst_n; // 定义复位信号,低电平有效
wire led; // 定义LED信号,这里用作呼吸灯的输出
// 实例化 breath_led 模块,并传递参数 CNT_10MS 和 OFFSET
breath_led #(
.CNT_10MS(1000), // 设置呼吸周期为1000个时钟周期
.OFFSET(50) // 设置PWM偏移量为50
)
breath_led // 实例化模块,未命名
(
.clk(clk), // 连接时钟信号
.rst_n(rst_n), // 连接复位信号
.led(led) // 连接LED输出信号
);
// 第一个 initial 块,用于生成时钟信号
initial begin
clk = 1; // 初始化时钟信号为高电平
forever begin // 创建一个永久循环,以产生连续的时钟信号
#10 clk = ~clk; // 每10纳秒翻转一次时钟信号,产生50%占空比的方波
end
end
// 第二个 initial 块,用于初始化复位信号并触发复位序列
initial begin
rst_n = 0; // 初始化复位信号为低电平,启动复位
repeat(20) @(posedge clk); // 等待20个时钟周期,即200纳秒
rst_n = 1; // 将复位信号置为高电平,结束复位
end
endmodule
3.管脚分配
set_property PACKAGE_PIN J19 [get_ports clk]
set_property PACKAGE_PIN L18 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property PACKAGE_PIN N18 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports led]
##############将 FPGA 的其他没使用到的管脚设置为高电平################
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullup [current_design]