PWM也即脉冲宽度调试技术,是电子领域的重要技术之一(广泛应用于开关电源,电机速度控制,LED灯亮度控制等领域),类似DSP,单片机,STM32等微处理器中都带有硬件PWM,本文将介绍如何使用VERILOG语言来写一个PWM,并通过呼吸灯的方式来进行效果的演示。
本文在 vivado2018.3版本上 演示, 其他版本请自行研究
PWM调整LED亮度的简单原理分析:
我们先来看下PWM的标准波形,如下图所示, 其中上中下三个波形的周期T和频率F是相同的,第一个波形高电平占整个周期的T的时间是25%,第二个波形占50%,第三个占75%。
因为三个波形的周期T和频率F是相同的,比方说 三个PWM的波形都是1KHZ,那么1秒钟内,三个PWM波形都会出现1000个周期为T的方波。如果此时拿这3个波形分别去点亮3个LED灯,那3个LED都会闪烁1000次(只是因为人眼的视觉残留因素,所以感觉不到这个闪烁),但是因为3者的占空比不同(高电平占整个周期的百分比不同)导致最上面25%占空比的波形单位时间内亮的时间最少,75%占空比的波形亮的时间最久。反应到视觉上的结果就是 第三个波形点亮的LED灯最亮,第一个波形对应的LED灯最暗,这也就是PWM调光的简单原理。同样的借助这个方法同样可以分析电机等场景。
同样当占空比为0%时,LED灯完全熄灭,当占空比为100%时LED灯完全点亮,借助这个原理,我们只要将占空比从0%逐渐增加到100%,再从100%逐渐减少到0%不断变化,就可以实现呼吸灯的效果了。
代码编写
首先我们要设计一个周期性的计数器来来进行波形周期的计数,我们定义占空比的频率为1KHZ,那波形的一个周期就是1ms,在50mhz时钟下,1ms相当于振荡了50000次(对应二进制1100001101010000,即16位),所以这里定义一个16位的寄存器作计数用
reg [15:0] period_cnt;
计数的代码如下
reg [15:0] time_count; //从0-50000的计数器,即1ms计数器 always @(posedge clk or negedge rst_n) begin if(!rst_n)begin time_count<=16'd0; end else if(time_count == 16'd50_000) time_count <= 16'd0; else time_count <= time_count + 1'b1; end
其中 if(!rst_n)begin time_count <=16’d0; end 这句是负责复位用,外部rst_n信号接入到按键上
仅仅有计数器不能实现波形的输出功能,接下来我们简单写个 输出50%方波的程序,仅仅一句话就可以了
wire led=(time_count <16’d25_000)?1:0;
这句话相当于,在time_count由0计数到50_000的过程中,当小于25000时,输出1,当大于25000时候输出0,又因为整个周期是50000,而25000刚好是一半,那样就产生了一半的高电平,一半的低电平,即标准的50%方波。
我们的目标是设计呼吸灯,那只要把25000变成一个由小到大又从大变到小的寄存器pwm_perid就可以了。
代码如下
reg [15:0] pwm_perid=16'd0; reg mode=0;//mode为0则自增,mode为1为自减 always @(posedge clk or negedge rst_n) begin if(!rst_n)begin pwm_perid<=16'd0; end else if(time_count== 16'd50_000)begin if(mode==1'b0)begin if(pwm_perid<16'd50_000) pwm_perid<=pwm_perid+50; else mode<=1'b1; end else if(mode==1'b1)begin if(pwm_perid>16'd0) pwm_perid<=pwm_perid-50; else mode<=1'b0; end end end
增减程序的改变周期为1ms,即1秒钟改变1000次,这里偷个懒,直接用上面的time_count计数器来作这边的改变周期用if(time_count== 16’d50_000),当然也可以自己重新写一个,都一样的
程序里有一个reg mode,这个是用来记录工作模式的,mode为0则pwm_perid自增,mode为1pwm_perid为自减
而mode切换的条件就是 占空比达到最大,或者占空比被减少到0%
代码很简单,直接编译综合并且下载进FPGA观测结果
增加约束文件如下
set_property IOSTANDARD LVCMOS33 [get_ports led] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property PACKAGE_PIN K18 [get_ports clk] set_property PACKAGE_PIN D18 [get_ports led] set_property PACKAGE_PIN G15 [get_ports rst_n] set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {clk_IBUF}
下载完后就能看到 LED1在不停的变量变暗变量变暗了
完整工程如下: