51单片机输出频率可调脉宽可调的PWM

文章目录


前言

前段时间有个师弟问我关于51单片机输出PWM的问题,我看了网上的资料,各有千秋,但有一些的思路对新手不是很友好;还有一些输出的脉宽和频率不是可调的。因此写下这篇关于51单片机如何输出频率可调脉宽可调的PWM的文章


一、PWM是什么?

关于PWM网上的介绍已经很多了,我这里就简单的介绍一下。

这个是方波,Tp是脉冲宽度,简称脉宽,PWM就是这个脉宽可以调节大小的方波,所以叫脉宽调制(Pulse Width Modulation)

PWM频率就是在一秒内一个完整的PWM发出的次数,比如PWM的频率为1HZ,即一秒发送1次,那T=1000ms;若PWM的频率为50HZ,即一秒发送50次,那T=20ms。

二、输出PWM

如何输出PWM

其实要输出一个PWM很简单,我们可以假设PWM输出引脚为P2^0。

while(1)

{

P2^0 = 1;

delay(100);//这里相当于Tp

P2^0 = 0;

delay(200);

}

让引脚输出高电平,延迟一段时间后再输出低电平,通过改变延迟的时间就可以调节PWM的脉宽。

但是这样输出的频率是不可调的,也是不准确的;而且一旦有其它程序加入,波形就会发生变化。

使用定时器控制输出频率

PWM输出频率我们可以借助定时器来实现,我们可以让定时器固定一段时间输出高电平,一段时间输出低电平。输出高电平和低电平时间之和即为PWM的周期(周期为频率的倒数),我们只要通过改变输出高电平和低电平时间之和就可以改变PWM的频率了。

c 复制代码
sbit led = P0 ^ 0; // 定义P20口是led
sbit Pwm = P2 ^ 0;

//定时器0初始化,每1ms进入一次中断
void Timer0Init()
{
    TMOD |= 0X01; // 选择为定时器0模式,工作方式1,仅用TR0打开启动。
    TH0 = 0XFC; // 给定时器赋初值,定时1ms
    TL0 = 0X18;
    ET0 = 1; // 打开定时器0中断允许
    EA = 1;  // 打开总中断
    TR0 = 1; // 打开定时器
}

void main()
{
    Timer0Init(); // 定时器0初始化
    Pwm = 1;// 先让PWM引脚输出高电平
    while (1)
    {
    }
}

//定时器0的中断服务函数,即每1ms就执行这段程序
void Timer0() interrupt 1
{
    static u16 i;
    TH0 = 0XFC; // 给定时器赋重新初值,定时1ms
    TL0 = 0X18;
    i++;
    if (i >= 20)
    {
    	i = 0;
        Pwm = ~PWM;
    }
    led = ~led;
}

我们让PMW输出引脚每20ms电平反转一次,led输出引脚每1ms电平反转一次进行比较。

这是PWM输出的仿真图,蓝色是led引脚输出波形,周期是2ms,红色是PWM引脚输出波形,周期是40ms。(因为每隔20ms反转一次电平,高电平和低电平持续时间之和才是PWM的一个周期的时间)

这样,我们就可以通过改变电平反转的时间来控制PWM的输出频率了。

使用定时器控制输出占空比

上面我们已经可以控制PWM的频率了,那我们如何控制高电平在整个周期中所占的比例(即占空比)呢?

我们可以在一个固定的时间内,改变高电平所占的比例,即可以改变占空比。

c 复制代码
sbit led = P0 ^ 0; // 定义P20口是led
sbit Pwm = P2 ^ 0;

u8 Pwm_Duty = 0;
u16 Pwm_fre = 50;

/*******************************************************************************
 * 函 数 名         : Timer0Init
 * 函数功能		   : 定时器0初始化
 * 输    入         : 无
 * 输    出         : 无
 *******************************************************************************/
void Timer0Init()
{
    TMOD |= 0X01; // 选择为定时器0模式,工作方式1,仅用TR0打开启动。
    TH0 = 0XFC; // 给定时器赋初值,定时1ms
    TL0 = 0X18;
    ET0 = 1; // 打开定时器0中断允许
    EA = 1;  // 打开总中断
    TR0 = 1; // 打开定时器
}

void main()
{
    Timer0Init(); // 定时器0初始化
    Pwm = 1;
    led = 1;
    Pwm_Duty = 40;
    while (1)
    {

    }
}
/*******************************************************************************
 * 函 数 名         : void Timer0() interrupt 1
 * 函数功能		   : 定时器0中断函数
 * 输    入         : 无
 * 输    出         : 无
 *******************************************************************************/
void Timer0() interrupt 1
{
    static u16 i;
    TH0 = 0XFC; // 给定时器赋初值,定时1ms
    TL0 = 0X18;
    i++;
    if (i >= (Pwm_Duty*20)/100)
    {
        Pwm = 0;
    }
    if(i>=10)
    {
        led = 0;
    }
    if (i >= 20)
    {
        i = 0;
        Pwm = 1;
        led = 1;
    }
}

Pwm_Duty 为高电平比例系数,比如Pwm_Duty =0.4,当i>=0.4*20=8时,PWM反转为低电平,当i>20时,电平再反转为高电平,这样高电平就占了整个周期的四成,所以PWM=40%。

不过应该注意的是,这里PWM在8ms已经反转一次,20ms时再反转一次,一个周期已经结束,即这里的PWM周期为20ms;即频率为50HZ。由于51单片机是8位整形单片机,没有浮点型数据,不能直接让Pwm_Duty =0.4,不然就可能取整为0,这里必须先让Pwm_Duty = 40*20=800再除以100才能得到8ms

这是PWM输出的仿真图,蓝色是led引脚输出波形,频率是50HZ,占空比是50%;红色是PWM引脚输出波形,频率是50HZ,占空比是40%。

这样,我们就可以控制Pwm_Duty来控制PWM的占空比了。

我们再把程序给优化一下,如下

c 复制代码
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器

typedef unsigned int u16; // 对数据类型进行声明定义
typedef unsigned char u8;

sbit led = P0 ^ 0; // 定义P20口是led
sbit Pwm = P2 ^ 0;
sbit k1 = P1 ^ 1; // 定义P20口是led
sbit k2 = P1 ^ 2;

u8 Pwm_Duty = 40;//pwm的占空比(%)
u16 Pwm_fre = 50;//PWM的频率(HZ)

/*******************************************************************************
 * 函 数 名         : Timer0Init
 * 函数功能		   : 定时器0初始化
 * 输    入         : 无
 * 输    出         : 无
 *******************************************************************************/
void Timer0Init()
{
    TMOD |= 0X01; // 选择为定时器0模式,工作方式1,仅用TR0打开启动。
    TH0 = 0XFC;   // 给定时器赋初值,定时1ms
    TL0 = 0X18;
    ET0 = 1; // 打开定时器0中断允许
    EA = 1;  // 打开总中断
    TR0 = 1; // 打开定时器
}

/*******************************************************************************
 * 函 数 名       : main
 * 函数功能		 : 主函数
 * 输    入       : 无
 * 输    出    	 : 无
 *******************************************************************************/
void main()
{
    Timer0Init(); // 定时器0初始化
    Pwm = 1;
    led = 1;
    while (1)
    {
    }
}

/*******************************************************************************
 * 函 数 名         : void Timer0() interrupt 1
 * 函数功能		   : 定时器0中断函数
 * 输    入         : 无
 * 输    出         : 无
 *******************************************************************************/
void Timer0() interrupt 1
{
    static u16 i;
    TH0 = 0XFC; // 给定时器赋初值,定时1ms
    TL0 = 0X18;
    i++;
    if (i >= (Pwm_Duty * (1000 / Pwm_fre)) / 100)
    {
        Pwm = 0;
    }
    if (i >= (50 * (1000 / Pwm_fre)) / 100)
    {
        led = 0;
    }
    if (i >= 1000 / Pwm_fre)
    {
        i = 0;
        led = 1;
        Pwm = 1;
    }
}

把频率和占空比作为变量,即可以在程序运行中,通过改变Pwm_fre 改变PWM信号的频率,通过改变Pwm_Duty 改变PWM信号的占空比。

补充 : (Pwm_Duty * (1000 / Pwm_fre)) / 100这段可能有人看不懂,我解释一下:Pwm_fre是PWM的频率,频率和周期互为倒数,所以PWM周期 = 1/ Pwm_fre。由于程序是每1ms进入一次中断,频率是一秒内一个完整信号发送的次数,所以这里要乘1000,又因为51单片机不支持浮点型数据,所以需要写成(1000 / Pwm_fre);若信号频率为50hz,则这里周期为20ms。

相关推荐
美式小田2 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
兰_博2 小时前
51单片机-独立按键与数码管联动
单片机·嵌入式硬件·51单片机
时光の尘3 小时前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
-一杯为品-3 小时前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
嵌入式大圣5 小时前
单片机结合OpenCV
单片机·嵌入式硬件·opencv
日晨难再6 小时前
嵌入式:STM32的启动(Startup)文件解析
stm32·单片机·嵌入式硬件
yufengxinpian7 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
__基本操作__8 小时前
历遍单片机下的IIC设备[ESP--0]
单片机·嵌入式硬件
网易独家音乐人Mike Zhou14 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵14 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程