文章目录
- 原理简介
- 实验代码
- 软件仿真
- 板上验证
原理简介
呼吸灯的实现过程就是把不同占空比的脉冲输出后加在LED上,LED灯就会显示不同的亮度,通过不断地调节方波的占空比,LED灯的亮度也会跟着变化,看起来就像是“呼吸”一样。
要得到不同占空比的脉冲,就要采用脉宽调制(Pulse Width Modulation, PWM)的方法,脉宽调制是比较常用的模块,实际应用中比如电机转速的控制,电灯亮暗的调节等,脉宽调制的示意图如下。
用一个N比特的计数器,其最大值可以表示为2的N次方,最小值0,计数器以一个给定的值为步进值进行累加,加到最大值后会溢出,然后进入下一个累加周期。当计数器的值大于某一门限时(注意,这里的门限值不是固定不变的,而是变化的),脉冲输出高,否则输出低,这样就可以完成上面图中红色线所示的脉冲占空比可调的脉冲输出。
计数器的步进值可以根据输出频率计算:step value =
输出频率
×
2
n
系统时钟频率
\frac{输出频率×2^n}{系统时钟频率}
系统时钟频率输出频率×2n。
实验代码
本次实验中用到的代码有两个模块,首先第一个模块是脉宽调制模块,其代码如下。
`timescale 1ns / 1ps
module pwm
# (parameter N = 16) //端口使用的参数在这里定义
(
input clk,
input rst,
input[N-1:0] period, //步进值,根据想要的输出频率计算得到
input[N-1:0] threshold, //与count进行比较的门限值(可变的)
output pwm_out
);
reg[N-1:0] period_reg; //接收输入值对应的寄存器
reg[N-1:0] threshold_reg;
reg[N-1:0] count; //以period为步进值的计数器
reg pwm_reg;
assign pwm_out = pwm_reg;
always@(posedge clk or negedge rst)
begin
if(rst == 0)
begin
period_reg <= {N{1'b0}}; //将N位二进制的0拼接
threshold_reg <= {N{1'b0}};
end
else
begin
period_reg <= period; //将输入值存放在各自的寄存器中
threshold_reg <= threshold;
end
end
always@(posedge clk or negedge rst)
begin
if(rst == 0)
count <= {N{1'b0}};
else
count <= count + period_reg; //计数器累加
end
always@(posedge clk or negedge rst)
begin
if(rst == 0)
pwm_reg <= 1'b0;
else
begin
if(count >= threshold_reg) //将count值与输入的门限值比较,大于门限则将脉冲输出置为1,小于则置为0
pwm_reg <= 1'b1;
else
pwm_reg <= 1'b0;
end
end
第二个模块是用来设置输入参数的,其代码如下。
`timescale 1ns / 1ps
module led_breathing(
input clk,
input rst,
output led
);
localparam STEP = 32'd100000; //threshold对应的步进值
localparam MIN_VALUE = 32'h6fffffff; //threshold最小值,减到比此值更小时就进行加操作
localparam MAX_VALUE = 32'hffffffff; //threshold最大值,加到比此值更大时就进行减操作
localparam IDLE = 0; //空闲
localparam PWM_PLUS = 1; //加操作
localparam PWM_MINUS = 2; //减操作
localparam PWM_GAP = 3; //间隔,判断下次是加操作是减操作
wire pwm_out; //脉冲输出
reg pwm_flag; //加减操作的标志位,加操作设置为0,减操作设置为1
reg [1:0] state; //四个状态
reg [31:0] period; //count的步进值
reg [31:0] threshold;
reg [31:0] timer; //PWM_GAP状态下使用
assign led = ~pwm_out; //pwm_out输出高电平时,led亮,led是低电平有效的
always@(posedge clk or negedge rst)
begin
if(rst == 1'b0)
begin
pwm_flag <= 1'b0;
period <= 32'd0;
threshold <= 32'd0;
timer <= 32'd0;
state <= IDLE;
end
else
case(state)
IDLE:
begin
period <= 32'd17179; //period = 200*2^32/50000000,pwm=200Hz
state <= PWM_PLUS; //状态设置为加
threshold <= MIN_VALUE; //threshold的初始值设置为最小值
end
PWM_PLUS:
begin
if(threshold > MAX_VALUE - STEP) //判断threshold的值是否超过了最大值,超过了就将加减操作状态标志设为减操作状态
begin
pwm_flag <= 1'b1;
threshold <= threshold - STEP;
end
else
begin
pwm_flag <= 1'b0;
threshold <= threshold + STEP;
end
state <= PWM_GAP; //完成一次加操作后进入PWM_GAP状态
end
PWM_MINUS:
begin
if(threshold < MAX_VALUE + STEP) //判断threshold的值是否低于了最小值,小于了就将加减操作状态标志设为加操作状态
begin
pwm_flag <= 1'b0;
threshold <= threshold + STEP;
end
else
begin
pwm_flag <= 1'b1;
threshold <= threshold - STEP;
end
state <= PWM_GAP; //完成一次减操作后进入PWM_GAP状态
end
PWM_GAP:
begin
if(timer >= 5000) //100us的计数次数,做仿真时,这里设置的小一点
begin
if(pwm_flag) //根据加减操作标志决定下一次执行的操作
state <= PWM_MINUS;
else
state <= PWM_PLUS;
timer <= 32'd0;
end
else
timer <= timer + 32'd1; //计数到达规定的时长后再执行下次加或减操作
end
default: state <= IDLE;
endcase
end
pwm //将第一个模块在这里进行例化
# (.N(32))
pwm_inst(
.clk(clk),
.rst(rst),
.period(period),
.threshold(threshold),
.pwm_out(pwm_out)
);
endmodule
软件仿真
本实验用到的仿真测试代码如下。
`timescale 1ns / 1ps
module led_breathing_sim();
reg clk;
reg rst;
wire led;
initial
begin
clk = 0;
rst = 0;
#100
rst = 1;
end
always #10 clk = ~clk;
led_breathing uut_led_breathing(
.clk(clk),
.rst(rst),
.led(led)
);
endmodule
仿真的结果如下图所示。
上图中,pwm_out就是脉宽调制之后的输出波形,而led就是pwm_out信号的翻转,可以看到,波形的占空比各不相同。
代码中设置的计数器的步进值period是200Hz对应的值,200Hz对应的周期就是5ms,将上面的仿真图放大可以看到,尽管每个方波的占空比不同,但是其周期都是5ms。
当时看代码的时候挺头晕的,搞不太懂为啥输出的方波波形的占空比各不相同。我将代码中的几个十六进制数转化为十进制数看,首先,threshold对应的最小值是1879048191(十六进制的6fffffff),最大值是4294967295(十六进制的ffffffff),而其步进值是100000,也就是说,从1879048191到4294967295,每次加100000,需要24159次;count对应的最小值是0,最大值就是自动溢出后,也就是4294967295,count的步进值是period=17179,从0到溢出需要250013次。两者差不多是十倍的关系,而且有一点不太一样的是threshold加到最大值后不是直接掉到最小值,而是一步步减下来,而count则是加满溢出,相当于从0开始新一轮的计数。
根据上面的分析,我简单的画了一个示意图,如下图所示。
按理说,count从0到溢出应该对应大概10个这样的波形,但是为了绘图清楚,这里绘制了简单的示意图,阐明其中的原理即可。而且这里我用直线替代了阶梯上升和阶梯下降,因为这些小的阶梯在宏观上来看可以抽象成一个点。上图中,蓝色线(count)高于黑色线(threshold)的部分,输出的脉冲为高;蓝色线低于黑色线的部分,输出脉冲为低。通过画一幅这样的示意图就能清楚地反映代码中是怎么生成占空比不同的波形了。
板上验证
本实验中的引脚分配如下图所示。
通过上面的仿真,只看输出信号led的波形,其波形的占空比在不断地变化,表现在开发板的LED亮灭特性上就像“呼吸”一般,呼吸灯实现的动图如下图所示。
以上就是ZYNQ——脉宽调制之呼吸灯实现的全部内容了!
参考资料:
ZYNQ 开发平台 FPGA 教程 AX7020