1.简介
PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写。它是通过对一系列脉冲的宽度进行调制,等效输出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占空比的变化来调节信号、能量等的变化。
占空比就是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比,例如方波的占空比就是50%。
简单的说:假设在1毫秒,0.5毫秒开,0.5毫秒灭,那么灯的闪烁频率就很高。闪烁频率超过一定值,人眼就会感觉不到。所以只能看到灯的亮度只有原来的一半。这时PWM占空比为50%,PWM周期为1ms,PWM频率F=1/1ms=1KHZ。同理,如果在1毫秒内,0.1毫秒开,0.9毫秒灭,那么,灯的亮度就只有原来的10分之一。
2.实现方式
(1)芯片自带PWM模块,设置寄存器就可以使用PWM。比如晟矽微芯片型号MC30P6280,根据数据手册就可以快速上手。
(2)利用定时器模拟PWM输出,本章重点。
3.代码分析
3.1普通版
PWM占空比为50%,PWM周期为1ms,PWM频率F=1/1ms=1KHZ。
为什么定时器要设置成70us中断一次?因为多次试验后,1/(70us*12)的实际频率最接近1Khz。根据网上设置的1/(10us*100)根本达不到1K频率。可以自己多尝试设置参数。
控制PWM开关代码最好放在定时中断函数里面。
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
//--定义使用的IO口--//
sbit PWM=P2^1;
//--定义一个全局变量--//
u16 count=12; //中断次数,即决定PWM周期
u16 pwm_disp=6; //占空比显示单元,初始化为50%
/*******************************************************************************
* 函 数 名 : Timer1Init
* 函数功能 : 定时器1初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1Init()
{
TMOD|=0X10;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
TH1 = (65536-70)/256;
TL1 = (65536-70)%256;
EA=1;//打开总中断
ET1=1;//打开定时器1中断允许
TR1=1;//打开定时器
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
Timer1Init(); //定时器1初始化
while(1)
{
}
}
/*******************************************************************************
* 函 数 名 : Time1
* 函数功能 : 定时器1的中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Time1(void) interrupt 3 //3 为定时器1的中断号
{
TH1 = (65536-70)/256; //70us
TL1 = (65536-70)%256;
count--;
if(pwm_disp==count)
{
PWM=1;
}
if(count==0)
{
PWM=0;
count=12;
}
}
3.2汇编版
PWMH1 DATA 30H
PWM DATA 33H ;PWM周期
COUNTER DATA 35H ;高电平脉冲的个数
TEMP DATA 36H
ORG 0000H
AJMP MAIN
ORG 000BH
AJMP INTT0
ORG 0100H
MAIN:
MOV TMOD,#0X01 //配置T0为模式1
MOV TH0,#HIGH(65536-100)
MOV TL0,#LOW(65536-100)
SETB ET0 ;使能定时器0中断
SETB EA ;使能总中断
SETB TR0 ;开始计时
MOV PWMH1,#10 ; PWM 1 占空比 可以修改
MOV COUNTER,#0
MOV PWM,#10 ;
JMP $
INTT0:
PUSH PSW ; 现场保护
PUSH ACC
MOV TH0,#HIGH(65536-100)
MOV TL0,#LOW(65536-100)
INC COUNTER ; 计数值加1
MOV A,COUNTER
CJNE A,PWMH1,INTT01 ; 如果等于高电平脉冲数
SETB P1.0 ; P1.0变为高电平 PWM1
INTT01:
CJNE A,PWM,INTT05 ;如果等于周期数
MOV COUNTER,#0 ;计数器复位
CLR P1.0 ;P1.0为低电平
INTT05:
POP ACC ;出栈
POP PSW
RETI
END
3.3进阶版(参考网上的)
这版代码值得学习的地方比较多,可以自己调整PWM频率和占空比,精度也较高
注意:(1)整体代码功能------用5挡开关控制PWM占空比和频率,自己可以根据情况增删代码。
(2)注释代码功能-------是可以用定时器T1动态调整PWM的占空比和频率。
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
//--定义使用的IO口--//
sbit PWMOUT = P0^0; //pwm输出
sbit K1=P1^0; //5挡开关引脚
sbit K2=P1^1;
sbit K3=P1^2;
sbit K4=P1^3;
//定义5挡开关状态
#define FLAG_F1 ((K1==1)&&(K2==1)&&(K3==1)&&(K4==1)) //第1档
#define FLAG_F2 ((K1==0)&&(K2==1)&&(K3==1)&&(K4==1)) //第2档
#define FLAG_F3 ((K1==1)&&(K2==0)&&(K3==1)&&(K4==1)) //第3档
#define FLAG_F4 ((K1==1)&&(K2==1)&&(K3==0)&&(K4==1)) //第4档
#define FLAG_F5 ((K1==1)&&(K2==1)&&(K3==1)&&(K4==0)) //第5档
unsigned long PeriodCnt = 0; //PWM周期计数值
unsigned char HighRH = 0; //高电平重载bai值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
unsigned char T1RH = 0; //T1重载值的高字节
unsigned char T1RL = 0; //T1重载值的低字节
void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);
void AdjustDutyCycle(unsigned char dc);
void main()
{
u8 num=1; //定义开关档数
EA = 1; //开总中断
ConfigPWM(1000, 0); //配置并启动PWM
while(1)
{
if(FLAG_F1)
{
num=1;
}
else if(FLAG_F2)
{
num=2;
}
else if(FLAG_F3)
{
num=3;
}
else if(FLAG_F4)
{
num=4;
}
else if(FLAG_F5)
{
num=5;
}
switch(num)
{
case 1: AdjustDutyCycle(100);break; //频率一定,调整占空比
case 2: AdjustDutyCycle(17);break;
case 3: AdjustDutyCycle(50);break;
case 4: AdjustDutyCycle(74);break;
case 5: AdjustDutyCycle(0);break;
}
}
// ConfigTimer1(50); //用T1定时调整占空比
// while (1);
}
/* 配置并启动T1,ms-定时时间 */
//void ConfigTimer1(unsigned int ms)
//{
// unsigned long tmp; //临时变量
//
// tmp = 12000000 / 12; //定时器计数频率
// tmp = (tmp * ms) / 1000; //计算所需的计数值
// tmp = 65536 - tmp; //计算定时器重载值
// tmp = tmp + 12; //补偿中断响应延时造成的误差
// T1RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
// T1RL = (unsigned char)tmp;
// TMOD &= 0x0F; //清零T1的控制位
// TMOD |= 0x10; //配置T1为模式1
// TH1 = T1RH; //加载T1重载值
// TL1 = T1RL;
// ET1 = 1; //使能T1中断
// TR1 = 1; //启动T1
//}
/* 配置并启动PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc)
{
unsigned int high, low;
PeriodCnt = (12000000/12) / fr; //计算一个周期所需的计数值
high = (PeriodCnt*dc) / 100; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的定时器重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = HighRH; //加载T0重载值
TL0 = HighRL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
PWMOUT = 1; //输出高电平
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc)
{
unsigned int high, low;
high = (PeriodCnt*dc) / 100; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的定时器重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
}
/* T0中断服务函数,产生PWM输出 */
void InterruptTimer0() interrupt 1
{
if (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平
{
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}
else //当前输出为低电平时,装载高电平值并输出高电平
{
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
/* T1中断服务函数,定时动态调整占空比 */
//void InterruptTimer1() interrupt 3
//{
//static bit dir = 0;
//static unsigned char index = 0;
//unsigned char code table[13] = { //占空比调整表
//5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
//};
//
//TH1 = T1RH; //重新加载T1重载值
//TL1 = T1RL;
//AdjustDutyCycle(table[index]); //调整PWM的占空比
//if (dir == 0) //逐步增大占空比
//{
//index++;
//if (index >= 12)
//{
//dir = 1;
//}
//}
//else //逐步减小占空比
//{
//index--;
//if (index == 0)
//{
//dir = 0;
//}
//}
//}
//