STC89C52输出14Hz~1KHz的PWM,频率可调,占空比1%~99%可调

/*****************************************************************
* 功能描述: 输出PWM,可调频率范围14Hz~1KHz,占空比1%~99%
* 修改日期: 2021年5月18日
* 硬件说明: 11.0592M
*****************************************************************/
/*-----------------------包含头文件区域-------------------------*/
#include<reg52.h>           //单片机头文件 
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
/*-----------------------数据类型定义区域-----------------------*/
typedef unsigned char u8;   //定义类型unsigned char别名为u8
typedef unsigned int u16;   //定义类型unsigned int别名为u16
typedef unsigned long u32;  //定义类型unsigned int别名为u16
/*-----------------------端口/引脚定义区域----------------------*/
sbit LED = P2^7;            //定义P2.7引脚名为LED
sbit KEY1 = P3^2;           //定义P3.2引脚名为KEY1
sbit KEY2 = P3^3;           //定义P3.3引脚名为KEY2
sbit KEY3 = P3^4;           //定义P3.4引脚名为KEY3
sbit KEY4 = P3^5;           //定义P3.5引脚名为KEY4
/*-----------------------用户自定义数据区域---------------------*/
#define FOSC 11059200L          //系统时钟
#define UART_BAUD 9600          //串口波特率
#define TIMER_CLOCK FOSC / 12   //定时器时钟(系统时钟/12T模式)    
//定时器值 =(-(定时器时钟/32)/波特率)32是波特率因子,代表移动一位需要多少个时钟脉冲
#define Timer1_value (-(TIMER_CLOCK/32)/UART_BAUD)
#define T1_10us(x) (65536-(((float)TIMER_CLOCK / 100000.0) * x) + 20 ) //定时器值计算,以10us为单位
#define LOG my_printf
bit Timerflag = 0;      //重载定时器值标志位
bit TX_busy;            //发送忙碌标志
u8 idata Log_buf[100];  //打印最大数量
u16 T0_val;
u16 PWM_Fre;
u16 PWM_High;
u16 PWM_Low;
u16 PWMH_count;
u16 PWML_count;
u8 duty_cycle;          //占空比
#define FRE_MAX 7000    //周期最大时间,70ms
#define FRE_MIN 100     //周期最小时间,1ms
#define PWM_MAX 0.99    //最大占空比99%, 因为速度问题,1KHZ左右不能设置占空比大于95%与小于5%
#define PWM_MIN 0.01    //最小占空比1%
#define KEY_STATE_SHORT 0
#define KEY_STATE_LONG  1
#define KEY_STATE_NULL  2
enum key
{
    vKEY_NULL = 0,
    vKEY1,
    vKEY2,
    vKEY3,
    vKEY4,
};
u16 KEY_Data[3];
/*-----------------------函数声明区域---------------------------*/
void my_printf(char* fmt,...);
void delay(u16 ms);
void Timer0Init(void);
void UART_Init(void);
u8 Get_KeyValue(void);
void Get_KEY_Data(u8 KEY,u16 KEY_buf[]);
void KEY_dispose(void);
void Freq_update(void);
/*-----------------------主函数区域-----------------------------*/
void main()
{
    u16 sys_count = 0, count = 0;
    u8 key;
    LED = 0;
    Timer0Init();   //配置定时器
    UART_Init();    //配置串口
    EA = 1;         //打开总中断
    LOG("System Init OK\r\n");
    //配置PWM周期与占空比
    PWM_Fre = 100;          //1ms周期,1K
    PWM_High = PWM_Fre / 2; //占空比50%
    Freq_update();          //更新频率数据
    while(1)
    {
        delay(5);               //延时5ms
        key = Get_KeyValue();   //获取键值
        if(key == vKEY_NULL)    //当系统空闲时
        {
            if(++sys_count >= 1000/5)//1秒串口打印一次系统运行时间
            {
                sys_count = 0;
                if(++count >= 1000)
                {
                    count = 0;
                }
                LOG("System Run %3dS\r\n", count);
            }
        }
        Get_KEY_Data(key, KEY_Data);//获取按键参数
        KEY_dispose();//按键处理程序
    }
}
/*-----------------------定时器0中断函数区域--------------------*/
void Timer0()interrupt 1
{
    if(Timerflag)
    {
        LED = 1;
        T0_val = PWMH_count;//输出高电平脉冲时间
        Timerflag = 0;
    }
    else
    {
        LED = 0;
        T0_val = PWML_count;//输出低电平脉冲时间
        Timerflag = 1;
    }
    TH0 = T0_val >> 8;
    TL0 = T0_val;
}
/*-------------------------串口中断函数区域----------------------*/
void Uart1() interrupt 4
{
    if(RI)                              //接收中断
    {
        RI = 0;                         //清标志
    }
    else                                //发送中断
    {
        TI = 0;                         //清标志
        TX_busy=0;                      //非忙
    }
}
/*-------------------------更新频率数据函数-------------------------*/
void Freq_update(void)
{
    PWM_Low = PWM_Fre - PWM_High;
    PWMH_count = (u16)T1_10us(PWM_High);
    PWML_count = (u16)T1_10us(PWM_Low);
    duty_cycle = (u8)((float)((float)PWM_High/PWM_Fre)*100);//计算占空比
    LOG("Freq: %.1fHZ, cycl: %3dms, ", (float)(1000.0 / (float)(PWM_Fre/100.0)), PWM_Fre/100);
    LOG("PWM_High: %.1fms, Duty: %3d%%\r\n", PWM_High/100.0, (u16)duty_cycle);
}
/*-------------------------获取按键值函数-------------------------*/
u8 Get_KeyValue(void)
{
    if(!KEY1)
    {
        return vKEY1;
    }
    if(!KEY2)
    {
        return vKEY2;
    }
    if(!KEY3)
    {
        return vKEY3;
    }
    if(!KEY4)
    {
        return vKEY4;
    }
    return vKEY_NULL;
}
/********************************************************************************************
按键长短按检测函数
此函数适合1~10ms执行一次
KEY为按键扫描函数得到的键值
KEY_buf[0]为按键值
KEY_buf[1]为按键当前按下时间,单位为ms
KEY_buf[2]为按键按下至松开的时间,单位为ms
********************************************************************************************/
void Get_KEY_Data(u8 KEY,u16 KEY_buf[])
{
#define ScanTime_ms 5           //按键扫描时间(1~10ms)
#define KEY_MaxTime 5000        //按键长按最大时间  5000ms
    static u8 KEY_state=1,KEY_value;    //初始化进入按键检测状态
    static u8 KEY_flag;
    KEY_value=KEY;//读取键值
    if(KEY_state&&KEY_value != vKEY_NULL)//无键状态有键按下则消抖
    {
        KEY_buf[2]=0;               //按键按下则清0按键松开的时间
        KEY_buf[1]+=ScanTime_ms;        //计时按键按下时间
        if(ScanTime_ms <= 10)
        {
            if(KEY_buf[1]>(10/ScanTime_ms))//超过按键消抖时间
            {
                KEY_buf[0]=KEY_value;   //获取键值
                KEY_flag=KEY_value;
                KEY_state=0;              //进入有键状态
            }
        }
        else
        {
            KEY_buf[0]=KEY_value;   //获取键值
            KEY_flag=KEY_value;
            KEY_state=0;              //进入有键状态
        }
    }
    else if(!KEY_state)//有键状态
    {
        KEY_buf[1] += ScanTime_ms;  //计时按键按下时间
        if(KEY_buf[1] > KEY_MaxTime)//限制按键长按最大时间
        {
            KEY_buf[1] = KEY_MaxTime;
        }
        if(KEY_value == vKEY_NULL)//无键则进入读键状态
        {
            KEY_state = 1;              //进入无键状态
            KEY_buf[2] = KEY_buf[1];    //获取按键按下至松开的时间
        }
    }
    else if(KEY_state)//无键状态
    {
        KEY_buf[0] = 0;
        KEY_buf[1] = 0;
    }
}
/*----------------------按键长短按处理的任务函数-----------------------*/
void KEY_LongShort_Task(u8 flag, u8 key)
{
    switch(key)
    {
    case vKEY1://周期增加1ms
        PWM_Fre += 100;
        if(PWM_Fre > FRE_MAX)
        {
            PWM_Fre = FRE_MAX;
        }
        PWM_High = (u16)((float)PWM_Fre * (duty_cycle / 100.0));
        break;
    case vKEY2://周期减少1ms
        PWM_Fre -= 100;
        if(PWM_Fre < FRE_MIN)
        {
            PWM_Fre = FRE_MIN;
        }
        PWM_High = (u16)((float)PWM_Fre * (duty_cycle / 100.0));
        break;
    case vKEY3://占空比增加 1%
        PWM_High += PWM_Fre / 100;
        if(PWM_High > (float)PWM_Fre * PWM_MAX )//限制最大占空比
        {
            PWM_High = (float)PWM_Fre * PWM_MAX;
        }
        break;
    case vKEY4://占空比减少 1%
        PWM_High -= PWM_Fre / 100;
        if(PWM_High < (float)PWM_Fre * PWM_MIN)//限制最小占空比
        {
            PWM_High = (float)PWM_Fre * PWM_MIN;
        }
        break;
    case vKEY_NULL:
        break;
    }
    if(key != vKEY_NULL) //频率和占空比有变化即计算低脉宽,并串口打印出来
    {
        Freq_update();
    }
}
void KEY_dispose(void)
{
#define ScanTime_ms 5//扫描时间
#define EventTime (200 / ScanTime_ms) //事件间隔时间ms,为扫描时间的倍数
    static u8 key;
    static u8 i=0;          //用于长按时触发的时间间隔
    if(KEY_Data[0] != vKEY_NULL)
    {
        key = KEY_Data[0];
        /*按键长按连续触发事件*/
        if(KEY_Data[1] > 500)
        {
            i += ScanTime_ms;
            if(i >= EventTime)//事件间隔时间
            {
                i = 0;
                /**********按键长按事件区域***********/
                //LOG("K%d长按%d\r\n", (u16)key, KEY_Data[1]/1000);
                KEY_LongShort_Task(KEY_STATE_LONG, key);
            }
        }
    }
    //按键松开触发事件
    if(KEY_Data[2] > 0 && KEY_Data[2] < 500)
    {
        KEY_Data[0]=0;
        KEY_Data[1]=0;
        KEY_Data[2]=0;
        /**********按键短按事件区域***********/
        //LOG("K%d短按\r\n", (u16)key);
        KEY_LongShort_Task(KEY_STATE_SHORT, key);
    }
    else if(KEY_Data[2] >= 500)
    {
        KEY_LongShort_Task(KEY_STATE_NULL, vKEY_NULL);
    }
}
/*----------------------------------------------------------------
    函数名称:my_printf()
    函数功能:串口打印
    函数形参:形参数量无限制
    返 回 值:无
----------------------------------------------------------------*/
void my_printf(char* fmt,...)
{
    unsigned char i,len;
    va_list ap;
    va_start(ap,fmt);
    len=vsprintf((char*)Log_buf,fmt,ap);
    va_end(ap);
    for(i=0; i<len; i++)        //把缓存内的字符发送出去
    {
        while(TX_busy)
        {
            ;    //等待前面的数据发送完成
        }
        TX_busy = 1;
        SBUF = Log_buf[i];      //发送一个字节
    }
    memset(Log_buf,0,sizeof(Log_buf));  //清空缓存
}
/*----------------------------------------------------------------
    函数名称:delay()
    函数功能:延时
    函数形参:ms为延时时间,延时范围0~65535
    返 回 值:无
----------------------------------------------------------------*/
void delay(u16 ms)
{
    u8 i;
    while(ms--)  //循环延时1ms的次数
    {
        for(i = 115; i > 0; i--)
        {
            ;    //延时1ms
        }
    }
}
/*----------------------------------------------------------------
    函数名称:Timer0_Init()
    函数功能:定时器0初始化
    函数形参:无
    返 回 值:无
----------------------------------------------------------------*/
void Timer0Init(void)
{
    TMOD &= 0xF0;           //清零T0的控制位
    TMOD |= 0x01;           //设置定时器模式
    T0_val = T1_10us(63);
    TH0 = T0_val >> 8;
    TL0 = T0_val;
    TF0 = 0;                //清除TF0标志
    TR0 = 1;                //定时器0开始计时
    ET0 = 1;                //使能定时器0中断
}
/*----------------------------------------------------------------
    函数名称:UART_Config
    函数功能:串口配置
    函数形参:无
    返 回 值:无
----------------------------------------------------------------*/
void UART_Init(void)
{
    SCON = 0x50;            //8位数据,可变波特率
    TMOD &= 0x0F;           //清零T1的控制位
    TMOD |= 0x20;           //配置T1为模式2
    TH1 = Timer1_value;     //计算T1重载值
    TL1 = TH1;
    TR1 = 1;                //启动定时器1
    ES = 1;
}