打印
[51单片机]

用定时器来模拟实现PWM实现呼吸灯的效果

[复制链接]
4257|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dabing89|  楼主 | 2018-10-12 08:51 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 dabing89 于 2018-10-12 09:12 编辑

                                                                            用定时器来模拟实现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[i]) / 256;
                TL0 = (65536 - PWM_H[i]) % 256;//12MHZ下溢出1000次定时1ms

                flag200ms = 1;
                index = 0;
        }
        else
        {
                TH0 = (65536 - PWM_L[i]) / 256;
                TL0 = (65536 - PWM_L[i]) % 256;//12MHZ下溢出1000次定时1ms
                flag800ms = 1;

                index = 1;        
        }        
                        
}
我们用了定时器1每隔50MS改变1次定时器的初值,做了2个数组,分别存放PWM的高电平计数初值和低电平计数初值,在12MHZ下计数10000个,恰好是10MS,这样我们就实现了呼吸灯的效果,如果你想让呼吸灯变的更平滑更均匀,可以将定时器的初值更加细化就可以了,如果你对这个程序有啥疑问,可以留言,好了,就介绍到这里吧,代码奉献上。
009 实用PWM的使用.rar (35.08 KB)




相关帖子

沙发
冷画| | 2018-10-12 10:10 | 只看该作者
留爪

使用特权

评论回复
板凳
布丁奶茶| | 2018-12-18 21:08 | 只看该作者
楼主你好,最近在做模拟呼吸灯,请问如果我只有一个定时器,该如何实现呼吸灯的效果呢?

使用特权

评论回复
地板
dabing89|  楼主 | 2018-12-19 07:33 | 只看该作者
布丁奶茶 发表于 2018-12-18 21:08
楼主你好,最近在做模拟呼吸灯,请问如果我只有一个定时器,该如何实现呼吸灯的效果呢? ...

这个可以参考STC的那个程序,他就是只用一个定时器做的

使用特权

评论回复
5
xiaobush| | 2018-12-23 21:20 | 只看该作者
参考学习一下

使用特权

评论回复
6
一路向北lm| | 2018-12-23 21:38 | 只看该作者
感谢分享

使用特权

评论回复
7
yzq13246068880| | 2020-6-23 17:34 | 只看该作者
值得学习

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:车到山前必有路

4

主题

28

帖子

7

粉丝