单片机PWM输出原理与实践

一、什么是PWM?

  PWM(Pulse Width Modulation)脉冲宽度调制,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码。   
查看源图像
  通俗的说,就是一个周期内,控制高电平多长时间,低电平多长时间(高电平:5/3.3V,低电平:0V)。通过调节高低电平时间的变化来调节信号、能量等的变化。
图为周期4毫秒的PWM波形
  图为周期4毫秒的PWM波形、占空比为0.25

  • 两个重要的概念,频率、占空比

  频率:是指每秒钟信号从高电平到低电平再回到高电平的次数,为一个PWM波周期的倒数。上图中频率=1/(0.003+0.001)=250 HZ
  占空比:是指高电平持续时间比一个周期持续的时间。上图中占空比=1/(1+3)=25%,所以可以通过控制占空比,来控制输出的等效电压。
  所以对于方波的话,频率和占空比就确定了一个波。

二、怎么能产生一个PWM波?

  方法1利用芯片内部模块输出PWM信号,STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样, STM32 最多可以同时产生 30 路 PWM 输出! 但是!!!同一个定时器TIM只能产生一个频率的PWM波,你只能改变占空比。 具体例程可查看STM32数据手册
  方法2利用IO口高低电平转变输出PWM信号,比如上图中先把电平置1,维持1ms,然后将电平拉低,维持3ms,再将电平置高,如此循环往复下去,就可以产生一个周期4毫秒占空比为25%的PWM波了。具体方法就是给IO口加一个定时器,用定时器中断来实现及时切换高低电平。 看后面51单片机代码部分。

  • 定时器

  要想使用51单片机来产生一路PWM,根据上述的方法2,首先你应该知道什么是定时器?定时器是怎么工作的?

  定时器:和计数器说的是一个东西,因为它既能计时也能计数。定时器的实质是,由机器频率向一个16位寄存器累加,累加满溢出时触发中断。为了产生一个我们想要的时间间隔。比如说1s,所以我们要在这个寄存器里设定一个初值,以至于让它在这个初值上累加可以产生一个1s的倍数。这样我们就得到了稳定的时间间隔。
  这个寄存器分为TH(高八位)和TL(低八位)。所以我们需要把计算好的初值分成两部分分别放入TH和TL。

  过程
  首先,我们通过单片机的晶振频率得知其时钟周期,时钟周期*12=机器周期。每一个机器周期在寄存器内+1,直到加满溢出产生中断。

  举例说明
  若单片机外部时钟频率为12MHz,其时钟周期就是1/12μs,机器周期为1μs,也就是每1μs寄存器+1。16位的寄存器加到溢出最多需要(2^16)-1=65535μs,溢出也需要一个机器周期,所以总共要65536μs。但这个值太别扭,和我们要的1s没什么关系。我们最好让它记50000μs产生一次中断,所以其初值就设为65536-50000=15536。但我们还要将这个值分别放在高八位和低八位,所以要将这个十进制数,转换为4位十六进制数再分开赋值。十进制计算法:TH = 15536/256; TL = 15536%256;进制计算问题这里不细讨论。这样的话,每50ms就会产生一次中断。我们只要用程序判断其中断20次就记1s。
   

三、PWM的应用

1、输出模拟电压(通过电压的高低来控制如LED的亮度,直流电机的速度,蜂鸣器的音调等)
  PWM对模拟信号电平进行数字编码的方法,计算机只能输出0或5V的数字电压值而不能输出模拟电压,而我们如果想获得一个模拟电压值(介于0 - 5V的电压值),则需通过使用高分辨率计数器,改变方波的占空比来对一个模拟信号的电平进行编码。电压是以一种连接(1)或断开(0)的重复脉冲序列被夹到模拟负载上去的,连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,只要带宽足够,可以输出任意不大于最大电压值的模拟电压。

  输出电压=(接通时间/脉冲时间)*最大电压值

PWM输出等效电压
   PWM输出等效电压
2、控制步进电机
  做项目的过程中,一般涉及到精确控制位移的时候,会经常用到步进电机。
  对于四线双极性步进电机把电脉冲信号变换成角位移以控制转子转动的电机。在自动控制装置中作为执行元件。每输入一个脉冲信号,步进电动机前进一步,故又称脉冲电动机。 !!!这里注意一点,直接控制单片机的话是脉冲控制,就是进来一个脉冲信号,步进电机转动一个步进角(一般为0.9°)。所以控制步进电机速度的方式就是通过控制 频率 (占空比一般都是50%)但是!!!现在可以通过接入步进电机驱动板的方式细分步进角。比如细分为2,一个脉冲步进电机就转动半个脉冲(0.45°)


四、51单片机例程

  这里使用51实现呼吸灯的功能同样原理也可以控制直流电机;而步进电机是通过控制频率的方式来控制速度、蜂鸣器通过控制频率控制音调。

接线:P1.0接任意一个LDE灯!

这里写图片描述
有两种实现方法:不用定时器(延时)、定时器
延时和用定时器的区别在于:延时时执行的是delay函数中变量的加减,延时过程中,单片机只能执行delay函数
                                              定时器的话,是TH和TL寄存器不断的累加,直到溢出时执行一次中断处理程序,期间单片机还可以执行其他程序!


1.不用定时器(单纯使用延时)

/***
*源代码来来自于 https://www.bilibili.com/read/cv4779664
*pwm LED呼吸灯
*现象:亮-灭-亮 循环
*LED接口:P1.0
*思路:改变延时时间实现亮度不一从而实现呼吸的效果
******/
#include <reg52.h> //引用头文件

sbit led=P1^0;     //LED接口

bit ledsign=0;     //定义一个标志位ledsign==0 

void delay(int);   //声明延时函数
   
void main()
{
  int i=0;         //i:延时控制变量
  while(1)         
  {
   if(ledsign==0)   //如果b=0(初始0)(灯由亮到灭)
		 {   
				led=1;         //灭
				delay(i);      //i秒后(i从小到大)
				led=0;         //亮
				delay(1000-i); //延时1000(可自行定义)-i秒后
				i+=2;          //增加i(开灯的时间)
        if(i>=1000) 
        {	
            ledsign=1;     //i到最大值(>1000)改变标志位下一阶段(灯由灭到亮)
        }
   }
   else if(ledsign==1)         //判断是否进入到下一阶段(灯由灭到亮)
   {       
      led=1;    //灭
      delay(i);   //i秒后(i在上一阶段已到最大值(>1000))
      led=0;    //亮
      delay(1000-i);//延时1000(随手定义)-i秒后
      i-=2;     //减少i(开灯的时间)
      if(i<=0) 
      {
        ledsign=0;//判断是否回到上一阶段
      }
   }
   
  }
}

void delay(int a)      //定义延时函数
{
  int i;
  for(i=0;i<a;i++);
}

 2.用定时器

#include<reg52.h>
/*************************

**************************/

sbit LED = P1^0;

unsigned char PWM_COUNT;  	 //计数
unsigned int  HUXI_COUNT;    //占空比更新时间
unsigned char PWM_VLAUE;     //占空比比对值
bit direc_flag;             //占空比更新方向

void timer0_init(void);
	
void main()
{
    HUXI_COUNT = 0;
    PWM_COUNT = 0;
    PWM_VLAUE = 5;
    direc_flag = 0;
    LED = 1;            //默认LED熄灭   
    timer0_init();      //定时器0初始化
    while(1);
}

void timer0_init()
{
    TMOD=0x02;          		//  工作于模式2(M1=1,M0=0)
    TH0=0x38;               //定时器溢出值设置,每隔200us发起一次中断。12M晶振
    TL0=0X38;
    ET0=1;                  //开定时器0中断
    EA=1;                       //开总中断
    PWM_COUNT =0;
	  TR0=1;                  //定时器0开始计时
}

void time0() interrupt 1
{   
    PWM_COUNT++;
    HUXI_COUNT++;
    if(PWM_COUNT == PWM_VLAUE)      //判断是否到了点亮LED的时候
        LED = 0;                    //点亮LED
    if(PWM_COUNT == 10)             //当前周期结束
    {
        LED = 1;                    //熄灭LED
        PWM_COUNT = 0;              //重新计时
    }

    if((HUXI_COUNT == 600) && (direc_flag == 0))
    {                               //占空比增加10%
        HUXI_COUNT = 0;
        PWM_VLAUE++;
        if(PWM_VLAUE == 9)          //占空比更改方向
            direc_flag = 1; 
    }

    if((HUXI_COUNT == 600) && (direc_flag == 1))
    {                               //占空比减少10%
        HUXI_COUNT = 0;
        PWM_VLAUE--;
        if(PWM_VLAUE == 1)          //占空比更改方向
            direc_flag = 0; 
    }   
}

猜你喜欢

转载自blog.csdn.net/qq_27148893/article/details/110194229