tim4146 发表于 2014-5-17 23:33:15

51单片机IO口模拟PWM,问题看了一晚上找不到原因

PWM周期:20ms,也就是50Hz频率
由于用于控制舵机,高电平是0.5ms~2.5ms,所以打算把20ms分为200份,没份时间就是20ms/200=0.1ms,这样5份高电平就代表0.5ms了
单片机是常用的STC89C52RC,烧录程序后从示波器看波形却傻眼了....波形上面频率是170Hz左右,看了一晚上的程序居然没看出问题。所谓旁观者清,各位来指点指点吧。

程序如下,一看就懂
/*********************************************************************************
====舵机驱动程序=========
编写:小刘
晶振:12MHz
舵机型号:MG996
舵机周期驱动信号周期:20ms
电压:4.8-7.2V
**********************************************************************************/

#include<reg52.h> //包含特殊功能寄存器的定义
sbit p11=P1^1;//输出pwm脉冲

#define uchar unsigned char
#define uint unsigned int
/* 函数&变量申明 -----------------------------------------------*/
void Init_Timer0(void);        //定时器定时20ms,分为200份,每份时间0.1ms=100us
void Init_Uart() ;
uchar receive;
uchar pwm=25;//阀值,25就是2.5ms 的高电平时间
uchar period=200;//舵机信号周期20ms,分成200份
uchar i=0;//用于计数
bit temp=0;
/*
********************************************************************************
** 函数名称 : main(void)
** 函数功能 : 主函数
********************************************************************************
*/
main()
{
        Init_Timer0();
    Init_Uart() ;
        while(1);
}


/*
********************************************************************************
** 函数名称 : Init_Timer0(void)
** 函数功能 : 定时器初始化子程序
********************************************************************************
*/
void Init_Timer0(void)                  //定时器初始化子程序
{
        TMOD |= 0x01;          //使用模式1,16位定时器,使用"|"符号可以在使用多个定时器时不受影响                     
        //给定初值,每份定时时间0.1ms,一个周期200份=20ms
        TH0=(65535-100)/256;//0XFF             
        TL0=(65535-100)%256;//0X9C
        EA=1;            //总中断打开
        ET0=1;         //定时器中断打开
        TR0=1;         //定时器开关打开
}
/*
********************************************************************************
** 函数名称 : void Init_Uart()
** 函数功能 : 串口初始化子程序
********************************************************************************
*/
void Init_Uart()
{
        TMOD=0x20;                   //用定时器设置串口波特率
        TH1=0xfd;
        TL1=0xfd;
        TR1=1;
        REN=1;          //串口初始化
        SM0=0;
        SM1=1;
        EA=1;         //开启总中断
        ES=1; //开串口中断
}
/*
********************************************************************************
** 函数名称 : Timer0_isr(void) interrupt 1 using 1
** 函数功能 : 定时器中断程序
********************************************************************************
*/
void Timer0_isr(void) interrupt 1
{
        TH0=(65535-100)/256;             
        TL0=(65535-100)%256;
        i++;
        if (i>=pwm)p11=0; //如果超过了阀值就是电平

        if (i==200){i=0;p11=1;}        //0.1ms*200=20ms 周期时间到,默认给输出高电平


}
/*
********************************************************************************
** 函数名称 : void Uart()
** 函数功能 : 串口接收中断程序
********************************************************************************
*/
void Uart() interrupt 4                //串口中断响应程序
{
        ES=0;//关闭串口中断
        if(RI)
        {
        RI=0;//清除接收中断标志位
        receive=SBUF;//读取SBUF
        }
        ES=1;//打开串口中断

}

hithms 发表于 2014-5-18 00:19:32

你可以用示波器看看定时器的中断周期是不是和你所设置的一样,假如没问题可以看看串口中断有没有干扰到定时器中断了。

qqliyunpeng 发表于 2014-5-18 00:33:26

你的程序是2.5ms的高电平,17.5ms的低电平?周期50hz?

tim4146 发表于 2014-5-18 00:48:08

qqliyunpeng 发表于 2014-5-18 00:33
你的程序是2.5ms的高电平,17.5ms的低电平?周期50hz?

对啊,周期就是17.5+2.5=20ms,频率就是50Hz了啊,光是这样的都没法达到预期...

tim4146 发表于 2014-5-18 00:48:50

hithms 发表于 2014-5-18 00:19
你可以用示波器看看定时器的中断周期是不是和你所设置的一样,假如没问题可以看看串口中断有没有干扰到定时 ...

嗯嗯,这个得明天再去实验室看看了。光从程序上您能看出明显的问题吗?

tim4146 发表于 2014-5-18 15:20:48

hithms 发表于 2014-5-18 00:19
你可以用示波器看看定时器的中断周期是不是和你所设置的一样,假如没问题可以看看串口中断有没有干扰到定时 ...

问题已找到,我的TMOD在初始化串口定时器1的时候用了“=”而不是“|=“,结果可想而知。但是新的问题出现,我现在的频率是40Hz左右...用        TH0=(65535-100)/256;TL0=(65535-100)%256; 误差如此之大?

tim4146 发表于 2014-5-18 15:25:17

万分惊奇,程序中使用        TH0=(65535-78)/256;             
        TL0=(65535-78)%256;
并且循环200次,居然是20ms,和理论差太多了,求大神降临啊

tim4146 发表于 2014-5-18 15:25:41

万分惊奇,程序中使用        TH0=(65535-78)/256;             
        TL0=(65535-78)%256;
并且循环200次,居然是20ms,和理论差太多了,求大神降临啊

qq开始学单片机 发表于 2014-5-19 01:05:19

tim4146 发表于 2014-5-18 15:25
万分惊奇,程序中使用
并且循环200次,居然是20ms,和理论差太多了,求大神降临啊 ...

是65535还是65536?

tim4146 发表于 2014-5-19 12:44:19

qq开始学单片机 发表于 2014-5-19 01:05
是65535还是65536?

都是(65535-X)/256这种计算啊,即使相差1 也不至于100设定成78才达到效果吧...

takashiki 发表于 2014-5-19 13:22:25

本帖最后由 takashiki 于 2014-5-19 13:28 编辑

tim4146 发表于 2014-5-18 15:25
万分惊奇,程序中使用
并且循环200次,居然是20ms,和理论差太多了,求大神降临啊 ...必须的,你要知道,51很慢,尤其对于你这个应用来说,100个周期就要计时一次,进出中断的的时间无可忽略,而且赋值也是需要时间的,你需要进行补偿。
最精确的解决方案:将T0或T1工作于8位自动重载模式,中断中无需重新赋值,精确多了。

补充一下说明:
另外,你的UART中断写得过于复杂,浪费了很多CPU,这样就行了,嘿嘿:void Uart() interrupt 4 {      //串口中断响应程序
    if(_testbit_(RI))            //清除接收中断标志位
      receive=SBUF;            //读取SBUF
}

waothom 发表于 2014-5-19 14:38:19

不是自装载的,在定时器里更新定时器计数值,要考虑指令消耗

wkman 发表于 2014-5-19 14:58:54

换 stc12c 有记录硬件pwm输出,这样方便点{:shocked:}

mobile02 发表于 2014-5-24 20:54:03

不知道 STC PWM 死区怎么设定?

tim4146 发表于 2014-5-25 23:10:48

wkman 发表于 2014-5-19 14:58
换 stc12c 有记录硬件pwm输出,这样方便点

够用的情况下我一直用STC89C52的,比较简单,后来试着用了STM32. 现在的STC12C价格怎么样?

millwood0 发表于 2014-5-25 23:44:03

You are very challenged.


      TH0 = -100 >> 8;//TH0=(65535-100)/256;//0XFF            
      TL0 = -100;      //TL0=(65535-100)%256;//0X9C



void Timer0_isr(void) interrupt 1
{
      TH0=(65535-100)/256;            
      //TL0=(65535-100)%256;
      TL0 += -100;



void Uart() interrupt 4                //串口中断响应程序
{
      ES=0;//关闭串口中断


Speechless.

Just think about it.

tim4146 发表于 2014-5-27 18:57:58

millwood0 发表于 2014-5-25 23:44
You are very challenged.




It seems to be a good idea,use" TL0 += -100;" to offset the time spent before.
Thank u ,u r so helpful~
页: [1]
查看完整版本: 51单片机IO口模拟PWM,问题看了一晚上找不到原因