硬知识
PWM(脉冲宽度调制)
摘自百度百科。

基本原理
控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等但宽度不一致的脉冲,用这些脉冲来代替正弦波或所需要的波形。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率。

(SPWM)例如,把正弦半波波形分成N等份,就可把正弦半波看成由N个彼此相连的脉冲所组成的波形。这些脉冲宽度相等,都等于 π/n ,但幅值不等,且脉冲顶部不是水平直线,而是曲线,各脉冲的幅值按正弦规律变化。如果把上述脉冲序列用同样数量的等幅而不等宽的矩形脉冲序列代替,使矩形脉冲的中点和相应正弦等分的中点重合,且使矩形脉冲和相应正弦部分面积(即冲量)相等,就得到一组脉冲序列,这就是PWM波形。可以看出,各脉冲宽度是按正弦规律变化的。根据冲量相等效果相同的原理,PWM波形和正弦半波是等效的。对于正弦的负半周,也可以用同样的方法得到PWM波形。

(SPWM)在PWM波形中,各脉冲的幅值是相等的,要改变等效输出正弦波的幅值时,只要按同一比例系数改变各脉冲的宽度即可,因此在交-直-交变频器中,PWM逆变电路输出的脉冲电压就是直流侧电压的幅值。

脉宽调制分类
从调制脉冲的极性看,PWM又可分为单极性与双极性控制模式两种。
产生单极性PWM模式的基本原理如图6.2所示。首先由同极性的三角波载波信号ut。与调制信号ur,比较 (图6.2中( a )),产生单极性的PWM脉冲 (图6.2中( b ));然后将单极性的PWM脉冲信号与图6.2中( c )所示的倒相信号UI相乘,从而得到正负半波对称的PWM脉冲信号Ud,如图6.2中(d)所示。

双极性PWM控制模式采用的是正负交变的双极性三角载波ut与调制波ur,如图6.3所示,可通过ut与ur,的比较直接得到双极性的PWM脉冲,而不需要倒相电路。

上机实战
呼吸灯

此处为最容易实现的三角波呼吸灯。
定时器的介绍和配置源码见【51单片机快速入门指南】3.2:定时器/计数器

main.c
配置定时器0为16位定时器模式,周期为100us,晶振频率为11.0592MHz,采用6T模式,故初始化时频率设为22118400L。

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"
#include "TIM.h"

void main(void)
{    
    Timer_Init(TIMER_0, TIMER_MODE_1, GATE_DISABLE, CLK_Internal, 22118400L, 100, STC_TIM_Priority_Lowest);
    while(1)
    {

    }
}

中断服务函数
翻转P2.0以供测试。

void TIM0_Callback() interrupt 1 //定时器0中断函数
{
    TL0 = TL0_Save;
    TH0 = TH0_Save;                //定时器赋初值
    P2_0 = !P2_0;                //翻转P2.0以供测试
}

如图逻辑分析仪测得P2.0脉宽为110.1us,误差为10.1%

修改TIM.c中的中断服务函数
(为方便计算,下面还是以周期是100us计算)
配置P2.0、P2.1以相反的方式输出,由于中断周期为100us,PWM的分辨率由TIM0_Counter控制,设为100,则PWM的周期为100us × 100 = 10 ms,即100Hz。
设呼吸频率为1s,则一次呼吸经历1s/100us=10000次中断,其中5000次为上升段,5000次为下降段,则可修改出如下程序,并令P2.0、P2.1产生相反的波形。

void TIM0_Callback() interrupt 1 //定时器0中断函数
{
    static uint16_t TIM0_Counter = 0;
    static int16_t PWM = 0;
    static int8_t k = 1;

    TL0 = TL0_Save;
    TH0 = TH0_Save;                //定时器赋初值

    PWM += k;
    if(PWM >= 5000 || PWM <= 0)
        k *= -1;
    if(TIM0_Counter < PWM / 50)
    {
        P2_0 = 0;
        P2_1 = 1;
    }
    else
    {
        P2_0 = 1;
        P2_1 = 0;
    }
    if(++TIM0_Counter == 100)    
        TIM0_Counter = 0;
}

效果
下载程序:

逻辑分析仪测得对应波形:




开发板电路分析
上图左LED DA1的电路分析:
DA1由P2.1控制:
当 P2.1 输出0V时,DAC1处自然为0V;
当 P2.1 输出5V时,由于运放的虚短特性,LM358的第2脚也为5V,又由于运放的虚断特性,流经第2脚的电流为0,故流经R28的电流 = 流经R29的电流,设LM358输出1脚电压为U1,LM358 2脚电压为U2=5V,则有(U1−U2)/R29=U2/R28,其中U2 = 5V,R28 = R29 = 470R解得U1=10V:

但由于LM358的供电为VCC仅为5V,则此时运放早已工作在非线性区,由手册查得LM358最大输出为Vcc−1.5V,开发板上Vcc=5V,则其最大输出应为Vcc−1.5V=3.5V

实测为3.358V,误差4.06%。

输出段,流经R30的电流 = 流经R33的电流 + 流经R32的电流,一般红色LED压降为1.8 V,若运放输出端为3.5V,则有 ( 3.5 V − V D A C 1 ) / R 30 = V D A C 1 / R 33 + ( V D A C 1 − 1.8 V ) / R 32解得V D A C 1 ≈ 2.69 V,则DA1上的电流为( V D A C 1 − 1.8 V ) / R 32 ≈ 2 m A

舵机控制
舵机控制方法

摘自舵机详解 —— 弘毅
舵机的伺服系统由可变宽度的脉冲来进行控制,控制线是用来传送脉冲的。脉冲的参数有最小值,最大值,和频率。一般而言,舵机的基准信号都是周期为20ms,宽度为1.5ms。这个基准信号定义的位置为中间位置。舵机有最大转动角度,中间位置的定义就是从这个位置到最大角度与最小角度的量完全一样。最重要的一点是,不同舵机的最大转动角度可能不相同,但是其中间位置的脉冲宽度是一定的,那就是1.5ms。如下图:

角度是由来自控制线的持续的脉冲所产生。这种控制方法叫做脉冲调制。脉冲的长短决定舵机转动多大角度。例如:1.5毫秒脉冲会到转动到中间位置(对于180°舵机来说,就是90°位置)。当控制系统发出指令,让舵机移动到某一位置,并让他保持这个角度,这时外力的影响不会让他角度产生变化,但是这个是由上限的,上限就是他的最大扭力。除非控制系统不停的发出脉冲稳定舵机的角度,舵机的角度不会一直不变。

当舵机接收到一个小于1.5ms的脉冲,输出轴会以中间位置为标准,逆时针旋转一定角度。接收到的脉冲大于1.5ms情况相反。不同品牌,甚至同一品牌的不同舵机,都会有不同的最大值和最小值。一般而言,最小脉冲为1ms,最大脉冲为2ms。如下图:



main.c
定时器的介绍和配置源码见【51单片机快速入门指南】3.2:定时器/计数器

设最终PWM的分辨率为200,则50Hz的PWM每次中断的周期为1 s / 50 / 200 = 100 u s 1s/50/200=100us1s/50/200=100us

配置定时器0为8位定时器自动重装载模式,周期为100us,晶振频率为11.0592MHz,采用6T模式,故初始化时频率设为22118400L。

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"
#include "TIM.h"

void main(void)
{    
    Timer_Init(TIMER_0, TIMER_MODE_2, GATE_DISABLE, CLK_Internal, 22118400L, 100, STC_TIM_Priority_Lowest);
    while(1)
    {

    }
}

中断服务函数
翻转P2.0以供测试:

void TIM0_Callback() interrupt 1 //定时器0中断函数
{
    P2_0 = !P2_0;
}

如图,脉宽为100.4us,误差0.4%

修改中断服务函数
PWM的分辨率为200,频率为50Hz,周期为1s/50=20ms,则1ms对应10次中断,2ms对应20次中断。

uint8_t Servo_PWM = 15;
void TIM0_Callback() interrupt 1 //定时器0中断函数
{
    static uint16_t TIM0_Counter = 0;
    if(TIM0_Counter < Servo_PWM)
        P2_0 = 1;
    else
        P2_0 = 0;
    if(++TIM0_Counter == 200)    
        TIM0_Counter = 0;
}

如图1.5ms的脉宽实测如下,误差0.13%

舵机测试程序
main.c

设置P2.0每0.5s切换一次占空比。

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"
#include "TIM.h"

void Delay500ms()        //@22.1184MHz
{
    unsigned char i, j, k;

    _nop_();
    i = 8;
    j = 1;
    k = 243;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

extern uint8_t Servo_PWM;
void main(void)
{    
    Timer_Init(TIMER_0, TIMER_MODE_2, GATE_DISABLE, CLK_Internal, 22118400L, 100, STC_TIM_Priority_Lowest);
    while(1)
    {
        Servo_PWM = 10;
        Delay500ms();
        Servo_PWM = 20;
        Delay500ms();
    }
}

效果