本帖最后由 dabing89 于 2018-10-12 10:08 编辑
用定时器来模拟实现PWM实现呼吸灯的效果-----20181012
众所周知,PWM的应用是及其广泛的,现在很多高速的单片机内部都集成了硬件PWM,使用起来也很简单,配置好频率和装入计数值就可以工作了,但是在一些低成本的场合,我们选择的单片机没有硬件PWM功能模块,但是我们还存在这个需求怎么办呢?这个时候,我们需要用PWM来模拟实现他,但实现PWM必须要了解PWM的原理,这里我们先了解下。
PWM全称是脉冲宽度调制解调,比如1个小灯,按照500MS亮一次,500MS灭一次,周期是1S,频率是1HZ,在这里,1个周期说明白了就是2个方波,有高电平和低电平组成,在周期固定的情况下,我们通过不断的调整高电平所占的整个周期比例,即所谓的占空比,就可以实现小灯”不是那么亮“的效果,如果连续起来,就可以实现呼吸灯的效果了,先来用定时器0实现小灯500MS闪烁的效果,通过DEBUG来看下波形。我们把下面的代码拷贝进去,看下现象
/********************************************************
*描述:工程模板,点亮led 500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"
/*******************************************************************************
* 文件名:位定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
bit flag500ms = 0;//500ms标志位
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//
/*******************************************************************************
* 文件名:数据类型定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
/*******************************************************************************
* 文件名:函数前置声明
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void);
void Timer0Init(void);
/*******************************************************************************
* 文件名:主循环入口
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
Bsp_Power_Init();//LED端口初始化
Timer0Init();
LED0 = 1;
while(1)
{
if(flag500ms)
{
flag500ms = 0;
DATA0 = ~DATA0;//
}
}
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
P0M1 = 0xFC;
P0M0 = 0X03;
P0 = 0X00;
P1M1 = 0xE0;
P1M0 = 0X1F;
P1 = 0X00;
//P2口开漏输出
P2M1 = 0XFF;
P2M0 = 0XFF;
P2 = 0Xff;
// //P54,P55口为推挽输出
P5M1 = 0X00;
P5M0 = 0X00;
P5 = 0xFF;
//P37,P36,3.2,P3.3 P3.4口为推挽输出
P3M1 = 0X00;
P3M0 = 0XFC;
P3 = 0X23;
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void) //1毫秒@12MHZ
{
AUXR &= 0x7f; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = (65535 - 1000) % 256; //设置定时初值
TH0 = (65535 - 1000) / 256; //设置定时初值
ET0 = 1;
TR0 = 1; //定时器0开始计时
EA = 1;
}
/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
static uint16 tmr500ms = 0;
TL0 = (65535 - 1000) % 256; //设置定时初值1ms
TH0 = (65535 - 1000) / 256; //设置定时初值
tmr500ms++;
if(tmr500ms >= 500)
{
tmr500ms = 0;
flag500ms = 1;
}
}
从DEBUG可以看到,是500MS变化一次,说明我们的设置是对的,但是在这里还是说明一点,我们用的而是STC15W系列的芯片,但是定时器我配置成了12T模式,和STC89C52是一样使用的。既然我们实现了这个500MS高电平,500MS低电平的效果,我们再来实现下200MS亮,800MS灭的效果吧,程序代码如下:
/********************************************************
*描述:工程模板,点亮led 500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"
/*******************************************************************************
* 文件名:位定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//
/*******************************************************************************
* 文件名:数据类型定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
/*******************************************************************************
* 文件名:函数前置声明
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void);
void Timer0Init(void);
/*******************************************************************************
* 文件名:主循环入口
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
Bsp_Power_Init();//LED端口初始化
Timer0Init();
LED0 = 1;
while(1);
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
P0M1 = 0xFC;
P0M0 = 0X03;
P0 = 0X00;
P1M1 = 0xE0;
P1M0 = 0X1F;
P1 = 0X00;
//P2口开漏输出
P2M1 = 0XFF;
P2M0 = 0XFF;
P2 = 0Xff;
// //P54,P55口为推挽输出
P5M1 = 0X00;
P5M0 = 0X00;
P5 = 0xFF;
//P37,P36,3.2,P3.3 P3.4口为推挽输出
P3M1 = 0X00;
P3M0 = 0XFC;
P3 = 0X23;
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void) //1毫秒@12MHZ
{
AUXR &= 0x7f; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = (65535 - 1000) % 256; //设置定时初值
TH0 = (65535 - 1000) / 256; //设置定时初值
ET0 = 1;
TR0 = 1; //定时器0开始计时
EA = 1;
}
/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
static uint16 tmr200ms = 0;
static bit a = 0;//翻转状态变量
if(a)
{
TL0 = (65535 - 1000) % 256; //设置定时初值1ms
TH0 = (65535 - 1000) / 256; //设置定时初值
DATA0 = 0;//小灯亮
tmr200ms++;
if(tmr200ms >= 200)
{
tmr200ms = 0;
a = 0;
}
}
else
{
TL0 = (65535 - 1000) % 256; //设置定时初值1ms
TH0 = (65535 - 1000) / 256; //设置定时初值
DATA0 = 1;//小灯灭
tmr200ms++;
if(tmr200ms >= 800)
{
tmr200ms = 0;
a = 1;
}
}
}
仿真效果如下图所示:
我们可以看到高电平占到了80,低电平占到了20,但是把代码下载进单片机,怎么不是我们想要的那种状态呢?这里普及一个知识点,前面帖子说过的,人类的眼睛不能分辨这种刷新速度低于10MS的物体,如果物体的刷新速度高于10MS,我们的眼睛就会感觉到明显的闪烁了,所以我们看到了下载进开发板的现象就是亮200MS,灭800MS的效果,但是我们想实现我们想要的那种不是太亮的效果怎么办呢?其实只要把刷新频率高于100HZ就OK了,也就是周期要控制在10MS之内,改变高低电平所占的比例即可实现这样的效果,我们写一个让小灯2MS亮8MS灭的程序,看看啥效果,程序如下:
/********************************************************
*描述:工程模板,点亮led 500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"
/*******************************************************************************
* 文件名:位定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//
/*******************************************************************************
* 文件名:数据类型定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
/*******************************************************************************
* 文件名:函数前置声明
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void);
void Timer0Init(void);
/*******************************************************************************
* 文件名:主循环入口
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
Bsp_Power_Init();//LED端口初始化
Timer0Init();
LED0 = 1;
while(1);
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
P0M1 = 0xFC;
P0M0 = 0X03;
P0 = 0X00;
P1M1 = 0xE0;
P1M0 = 0X1F;
P1 = 0X00;
//P2口开漏输出
P2M1 = 0XFF;
P2M0 = 0XFF;
P2 = 0Xff;
// //P54,P55口为推挽输出
P5M1 = 0X00;
P5M0 = 0X00;
P5 = 0xFF;
//P37,P36,3.2,P3.3 P3.4口为推挽输出
P3M1 = 0X00;
P3M0 = 0XFC;
P3 = 0X23;
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void) //1毫秒@12MHZ
{
AUXR &= 0x7f; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = (65535 - 1000) % 256; //设置定时初值
TH0 = (65535 - 1000) / 256; //设置定时初值
ET0 = 1;
TR0 = 1; //定时器0开始计时
EA = 1;
}
/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
static uint16 tmr200ms = 0;
static bit a = 0;//翻转状态变量
if(a)
{
TL0 = (65535 - 1000) % 256; //设置定时初值1ms
TH0 = (65535 - 1000) / 256; //设置定时初值
DATA0 = 0;//小灯亮
tmr200ms++;
if(tmr200ms >= 2)
{
tmr200ms = 0;
a = 0;
}
}
else
{
TL0 = (65535 - 1000) % 256; //设置定时初值1ms
TH0 = (65535 - 1000) / 256; //设置定时初值
DATA0 = 1;//小灯灭
tmr200ms++;
if(tmr200ms >= 8)
{
tmr200ms = 0;
a = 1;
}
}
}
将程序下载进板子上,可以很明显的看到小灯变的不是那么亮了,用逻辑分析仪看下,我们的周期是10MS,实现了我们想要的变的不是那么亮的效果,可是距离我们想要的呼吸灯还是没有实现啊?怎么办呢?答案很简单,只要在在定时器中装入不同的初值即可实现这样的效果,不过要实现呼吸灯的效果,一个定时器是不够的,还要再用一个定时器1才可以,写好的程序如下:
/********************************************************
*描述:工程模板 PWM呼吸灯代码
********************************************************/
#include "stc15w.h"
/*******************************************************************************
* 文件名:位定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
bit flag200ms = 0;
bit flag800ms = 0;
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//
void Bsp_Power_Init(void);
void TIM0_Init(void);
void TIM1_Init(void);
/*******************************************************************************
* 文件名:数据类型定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
uint8 i = 0;
code uint16 PWM_H[] = {
100,300,500,700,1000,1300,1500,1700,2000,2300,2500,2700,
3000,3300,3500,3700,4000,4300,4500,4700,5000,5300,5500,
5700,6000,6300,6500,6700,7000,7300,7500,7700,8000,8300,
8500,8700,9000,9300,9500,9700,9900 //高电平重装值
};
code uint16 PWM_L[] = {
9900,9700,9500,9300,9000,8700,8500,8300,8000,7700,7500,
7300,7000,6700,6500,6300,6000,5700,5500,5300,5000,4700,
4500,4300,4000,3700,3500,3300,3000,2700,2500,2300,2000,
1700,1500,1300,1000,700,500,300,100//低电平重装值
};
/*******************************************************************************
* 文件名:主循环入口
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
Bsp_Power_Init();//LED端口初始化
TIM0_Init();
TIM1_Init();
LED0 = 1;
while(1)
{
if(flag200ms)
{
flag200ms = 0;
DATA0 = 0;
}
if(flag800ms)
{
flag800ms = 0;
DATA0 = 1;
}
}
}
/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描 述: 数码管上电显示
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
P0M1 = 0xFC;
P0M0 = 0X03;
P0 = 0X00;
P1M1 = 0xE0;
P1M0 = 0X1F;
P1 = 0X00;
//P2口开漏输出
P2M1 = 0XFF;
P2M0 = 0XFF;
P2 = 0Xff;
// //P54,P55口为推挽输出
P5M1 = 0X00;
P5M0 = 0X00;
P5 = 0xFF;
//P37,P36,3.2,P3.3 P3.4口为推挽输出
P3M1 = 0X00;
P3M0 = 0XFC;
P3 = 0X23;
}
/*******************************************************************************
* 文件名:void Timer0Init(void)
* 描 述: 数定时器0初始化
* 功 能:编程模块化
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIM0_Init(void)
{
AUXR &= 0x7F;//定时器时钟12T模式
TMOD &= 0XF0;//配置定时器0为工作模式1
TMOD |= 0X01;
TH0 = (65535 - 1000) / 256;//高八位重载值溢出1000次定时1ms
TL0 = (65535 - 1000) % 256;//低八位重载值
ET0 = 1;//打开定时器0中断使能位
TR0 = 1;//打开定时器,使之工作
EA = 1;//打开总中断
}
/*******************************************************************************
* 文件名:void TIM1_Init(void)
* 描 述:定时器1初始化配置
* 功 能:初始化
* 作 者:大核桃
* 版本号:1.0.1(2016.07.23)
*******************************************************************************/
void TIM1_Init(void)
{
AUXR &= 0xBF;//定时器时钟12T模式
TMOD &= 0X0F;//配置定时器1为工作模式1
TMOD |= 0X10;
TH1 = (65535 - 10000) / 256;//高八位重载值溢出1000次定时1ms
TL1 = (65535 - 10000) % 256;//低八位重载值
ET1 = 1;//打开定时器1中断使能位
TR1 = 1;//打开定时器,使之工作
EA = 1;//打开总中断
}
/*******************************************************************************
* 文件名:中断服务函数
* 描 述:定时器1中断服务函数
* 功 能: 中断标号对应 参考数据手册560页
* 中断名称
* 作 者:大核桃
* 版本号:1.0.1(2016.11.15)
*******************************************************************************/
void TIM1_IRQ_Handler(void) interrupt 3
{
static uint16 tmr50ms = 0;
static bit a = 0;
TH1 = (65535 - 10000) / 256;
TL1 = (65535 - 10000) % 256;//10ms溢出一次
tmr50ms++;
if(tmr50ms >= 5)//50ms改变一次PWM重装值
{
tmr50ms = 0;
if(a)
{
i--;
if(i == 0)
{
a = 0;
}
}
else
{
i++;
if(i >= 40)
{
a = 1;
}
}
}
}
/*******************************************************************************
* 文件名:void TIMER0_INTER(void) interrupt 1
* 描 述: 中断处理程序
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIM0_IRQ_Handler(void) interrupt 1
{
static bit index = 0;
if(index)
{
TH0 = (65536 - PWM_H) / 256;
TL0 = (65536 - PWM_H) % 256;//12MHZ下溢出1000次定时1ms
flag200ms = 1;
index = 0;
}
else
{
TH0 = (65536 - PWM_L) / 256;
TL0 = (65536 - PWM_L) % 256;//12MHZ下溢出1000次定时1ms
flag800ms = 1;
index = 1;
}
}
我们用了定时器1每隔50MS改变1次定时器的初值,做了2个数组,分别存放PWM的高电平计数初值和低电平计数初值,在12MHZ下计数10000个,恰好是10MS,这样我们就实现了呼吸灯的效果,如果你想让呼吸灯变的更平滑更均匀,可以将定时器的初值更加细化就可以了,如果你对这个程序有啥疑问,可以留言,好了,就介绍到这里吧,代码奉献上。
009 实用PWM的使用.rar
(35.08 KB)
|