mianchaodahai 发表于 2012-2-3 16:21:29

STC单片机的一个定时器怎么在两个IO口同时输出2个可调的PWM波

我用的STC15L204EA单片机,想用一个定时器控制2个舵机,设计要求是:一个舵机在转动的时候,另外一个舵机的信号也在一直保持,也就是给2个舵机一直发送信号,并且保持住这个信号,想了好几天也不知道怎么解决,各位大侠能不能给个思路,最好说的详细些,谢谢

mianchaodahai 发表于 2012-2-3 16:50:59

再补充一下,不是通过设置,而是C语言纯编程,我查过网上一个简单的示意程序:
void TIMER_HANDLE(void)
{
//判断是否达到TC值,如果达到则复位(COUNT清零),否则执行匹配中断
//在原有基础上设置匹配值中断,匹配值 +=300;
COUNT++;
if((COUNT == IO1_H_TIME)) //现在正好是需要高电平的时刻,因此值置高
{
//持续时间开启(自减),是否为0?
//不是0,那么继续输出高电平
//是0,关闭输出,同时清除高电平时刻
}
}
但是讲的不是很清楚,希望各位高手能说一下思路。先谢谢了!

BXAK 发表于 2012-2-3 19:38:59

不知你需要多少位多少频率的PWM?

// STC 1T @ 12MHz

#include <STC15F204EA.H>

sbit    PWM0 = P1^0;        //PWM0输出
sbit    PWM1 = P1^1;        //PWM1输出

uint8   num0;                 //PWM0占空比
uint8   num1;                 //PWM1占空比
uint8   cnt;               //PWM计数器

/*┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
函数:模拟PWM初始化(用定时器0模拟)
说明:PWM时基30us,256级,周期7680us,频率约130Hz
注意:PWM时基 >> 中断程序执行时间
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈*/
void PWM_Init_T0()
{
        TMOD &= 0xF0;             //清定时器0
        TMOD |= 0x02;             //定时器0方式2,8位自动重装模式
        TH0 = TL0 = 256-30;      //30us
        EA= 1;                           //CPU中断 开
        ET0 = 1;                           //T0中断       开
        TR0 = 1;
}

void main()
{
        PWM_Init_T0();

        num0=1;                      //PWM0默认占空比1/256
         num1=1;                      //PWM1默认占空比1/256
                                                  
        while(1)
        {                       
          }

void time0(void) interrupt 1
{
   cnt++;
   if(cnt < num0)PWM0=1;   else PWM0=0;
   if(cnt < num1)PWM1=1;   else PWM1=0;
}

f5911 发表于 2012-2-3 20:25:34

二楼的意思就是用一个定时器产生基准定时(30uS)中断,然后根据需要两路输出PWM。如果加入N个标志位以及N个计数单元,就能够控制N路的PWM输出。我是专门写汇编的,我就这么处理。呵呵

mianchaodahai 发表于 2012-2-4 09:45:36

回复【2楼】BXAK
-----------------------------------------------------------------------

多谢指教.我用的晶振33MHz,是用来驱动舵机的,总周期是20ms,高电平持续时间是0.5ms,1ms,1.5ms,2ms,2.5ms,占空比是一直变化的,具体项目是:四个按键控制2个舵机左右上下转动,当控制左右的一个按键按下时,左右方向的舵机转动,上下方向的舵机虽然不动,但是其控制信号也要一直发送,当按键松开时,左右和上下的舵机都要保持它们的状态,也就是信号还是一直发送。

mianchaodahai 发表于 2012-2-4 10:02:21

回复【2楼】BXAK
-----------------------------------------------------------------------

这个程序好像有点问题,cnt不可能小于num0吧,每次进入中断cnt++,也就是cnt至少得等于1。

BXAK 发表于 2012-2-4 13:55:10

回复【4楼】mianchaodahai
-----------------------------------------------------------------------
“总周期是20ms,高电平持续时间是0.5ms,1ms,1.5ms,2ms,2.5ms,占空比是一直变化的……”

那程序是LED调光的,只供参考,不适合你用,但原理差不多,晶振33MHz的1T STC 很容易办到的。



回复【5楼】mianchaodahai
-----------------------------------------------------------------------
uint8   num0;

cnt++到255,再加1就溢出等于0了,0至255反复循环

BXAK 发表于 2012-2-4 13:59:28

回复【4楼】mianchaodahai
-----------------------------------------------------------------------
“总周期是20ms,高电平持续时间是0.5ms,1ms,1.5ms,2ms,2.5ms,占空比是一直变化的……”

高电平持续时间每次递增都是0.5ms吗?最大只到2.5ms吗?

如果是,你每0.5ms中断就够了

gao_hailong 发表于 2012-2-4 15:37:35

以前看过这方面的资料,不过现在手里没有了。一个定时器可以控制八个舵机,具体方法就是,以2.5ms为一个周期,每个周期控制一个舵机。定时器开始前,令第一个舵机控制引脚保持高电平,其他为低电平,定时周期为第一个舵机的高电平时间,第一次进入中断后,控制引脚变为低电平,定时周期变为5ms-高电平时间,第二次进入中断后第二个舵机变为高电平,定时周期改为第二个舵机高电平时间,以此类推。可以控制八路舵机。

sszzeettee 发表于 2012-2-4 21:40:17

高手

wshtyr 发表于 2012-2-4 22:48:04

2楼的程序需要频繁进中断,但只要IO口够用,CPU够快,可以挂很多舵机,而且由于占空比可以在0~1之间变化,挂的不局限于舵机。LED,电机都可以。

8楼的方式效率高,进中断的次数少,但舵机数有限。而且占空比只能在有限的范围内变化,不适合LED和电机。LZ舵机少,用8楼的方式比较适合。

最方便的还是硬件PWM。LZ可以试试ATMEGA48,ATMEGA48的16位PWM可以将20毫秒20000细分,精确到0.001毫秒,还不用进中断。

以下是4MHz晶体下将20毫秒10000细分的程序:

初始化:
void Init_PWM(void)
{
TCCR1A=_BV(COM1A1)|_BV(COM1B1)|_BV(WGM11);
TCCR1B=_BV(WGM13)|_BV(WGM12)|_BV(CS11);//8分频
ICR1=9999;//20ms分成10000份
OCR1A=750;//中间位置
OCR1B=750;
}

设置角度,如Rot(0,1600)表示将通道0的高电平时间设成1.6ms:
void Rot(uint8_t way,uint16_t ms)
{
ms>>=1;
if(way)OCR1A=ms;
else OCR1B=ms;
}

LXM_0922 发表于 2012-2-4 23:59:55

static void Timer0Init(void);
static void MotorProcess(void);
static void KeyProcess(void);


sbit M1 = P1^1;
sbit M2 = P1^2;
sbit M3 = P1^3;
sbit M4 = P1^4;

static unsigned char Motor1;
static unsigned char Motor2;
static unsigned char Motor3;
static unsigned char Motor4;

volatile unsigned char Count;

void main()
{
        Timer0Init();
        Motor1 = 1;
        Motor2 = 2;
        Motor3 = 3;
        Motor4 = 4;
        while(1)
        {
                MotorProcess();
                KeyProcess();
        }
}

static void Timer0Init(void)
{
        //6M 晶振
        //定时器设为周期为0.5ms,自重载方式
        TMOD = 0x2;
        TH0 = 5;
        TL0 = 5;
        TCON = 0x50; //TR0=1,TR1=1
        IE = 0x82;
}


static void MotorProcess(void)
{
        if(Count < Motor1)
                M1 = 1;
        else
                M1 = 0;
               
        if(Count < Motor2)
                M2 = 1;
        else
                M2 = 0;
               
        if(Count < Motor3)
                M3 = 1;
        else
                M3 = 0;
               
        if(Count < Motor4)
                M4 = 1;
        else
                M4 = 0;
}

static void KeyProcess(void)
{
        //按键处理,改变Motor1-Motor4的数值,从而改变电机1-电机4的导通角
        //0:恒为低电平,>=4:恒为高电平
}


void Timer0Isr(void) interrupt 1
{
        Count++;
        if(Count > 4)
                Count = 0;
}

//刚写的程序,支持4个电机,简单调试了一下,希望能帮到你。

tonyone 发表于 2012-2-5 20:53:19

mark

mianchaodahai 发表于 2012-2-6 09:43:08

回复【7楼】BXAK
回复【4楼】mianchaodahai   
-----------------------------------------------------------------------
“总周期是20ms,高电平持续时间是0.5ms,1ms,1.5ms,2ms,2.5ms,占空比是一直变化的……”
高电平持续时间每次递增都是0.5ms吗?最大只到2.5ms吗?
如果是,你每0.5ms中断就够了
-----------------------------------------------------------------------

不是0.5ms中断,和步进电机差不多吧,每次都减一个阶梯,比如100,每次都减3,你说的这种中断好像不行,因为这种中断操作都是整数倍,而设计要求实际是100-3,最后不是大于就是小于,说的不是很清楚,就是占空比在0.5ms到1ms之间也是变化的

mianchaodahai 发表于 2012-2-6 10:17:16

回复【11楼】LXM_0922
-----------------------------------------------------------------------

谢谢!
你写的这个程序好像是固定的时间中断,而设计要求是:不是0.5ms,1ms,1.5ms,2ms,2.5ms这5个值,而是0.5ms和2.5ms之间的任何值,那样舵机运行的比较平稳,就是一步一步的,比如步长为20,0.5ms+20,(0.5ms+20)+20,。。。一直加到2.5ms左右,而且2个舵机的信号也要一直保持。

mianchaodahai 发表于 2012-2-6 10:26:29

回复【11楼】LXM_0922
-----------------------------------------------------------------------

例如:这个程序是错误的,但是能给一个我要表示的大致的思路
          按键处理函数:case 4:        //左勾头 +
                  {
                        change_time_bit=1;//启动长按时间标志
                        th1_bak-=STEP;
                        if(th1_bak<MOTO_RIGHT)//MOTO_RIGHT为2.5ms
                        {th1_bak=MOTO_RIGHT;gt_max_bit=0;}//gt_max_bit=0时,说明舵机已到极限值,禁止发送舵机信号

                        else
                          gt_max_bit=1;//gt_max_bit=1时,允许发送舵机信号。
                        change_duoji_bit=1;//切换另一个舵机                                                               
                          break;
                  }
          void int_t1(void) interrupt 3//0.36us
        {                     //t1 1.5MS

                if(moto_pwm_bit)
                        {
                                TH1=(TIME_20MS-th1_bak)>>8;
                                TL1=(TIME_20MS-th1_bak)>>&0XFF;
                                moto_en=gt_max_bit;        //gt_max_bit=1时,允许发送舵机信号。
                                                        //gt_max_bit=0时,说明舵机已到极限值,禁止发送舵机信号
                                led_en=gt_max_bit1;//moto_en是一个舵机控制端,led_en是另一个舵机控制端
                        }
                else
                        {
                                TH1=th1_bak>>8;
                                TL1=th1_bak&0xff;
                                moto_en=0;
                                led_en=0;
                        }
                        moto_pwm_bit=~moto_pwm_bit;       
}

madswan 发表于 2012-2-6 11:02:48

mark

qinhya 发表于 2012-2-6 12:56:46

mark

wobushi66 发表于 2012-2-6 13:13:52

定时器1控制20ms的中断,定时0控制高电平(需要的脉宽)的时间。每次定时器1中断后打开定时器0,等定时器0定时完后的在中断内关闭定时器0。因为舵机需要的脉宽为0.5ms-1.5ms,在20ms内分四份可以控制5个舵机,可以的http://cache.amobbs.com/bbs_upload782111/files_51/ourdev_716362N2XT1A.JPG
(原文件名:PWM.JPG)

BXAK 发表于 2012-2-6 14:45:37

没用过舵机,抽空了解了一下。
10楼分析得很不错,用8楼的方案不错(和18楼的原理一样),你只控制2路,20ms内分2等分就够了,进入中断少提高CPU的效率,又可以实现周期20ms,0.5ms~2.5ms任意脉宽

LXM_0922 发表于 2012-2-6 22:40:39

回复【11楼】lxm_0922
-----------------------------------------------------------------------
谢谢!
你写的这个程序好像是固定的时间中断,而设计要求是:不是0.5ms,1ms,1.5ms,2ms,2.5ms这5个值,而是0.5ms和2.5ms之间的任何值,那样舵机运行的比较平稳,就是一步一步的,比如步长为20,0.5ms+20,(0.5ms+20)+20,。。。一直加到2.5ms左右,而且2个舵机的信号也要一直保持。
-----------------------------------------------------------------------

你要程序是不是一个PWM宽度是从0到2.5ms可调的?还是说,机舵有5个角度,然后在每个角度切换时,需要缓慢的变动,即细分式。如0.5ms到1ms时是0.5ms->0.6ms->0.7ms->0.8ms->0.9ms->1ms,是这样的吗?

mianchaodahai 发表于 2012-2-7 08:49:41

回复【20楼】LXM_0922
-----------------------------------------------------------------------

对,是可以任意角度旋转的,就是需要缓慢的变动,而且当按键松开时,舵机停止转动,保持住信号。

mianchaodahai 发表于 2012-2-7 08:56:50

回复【19楼】BXAK
-----------------------------------------------------------------------

非常感谢你的提示,我想一下先

LXM_0922 发表于 2012-2-7 12:17:49

回复【21楼】mianchaodahai
回复【20楼】lxm_0922
-----------------------------------------------------------------------
对,是可以任意角度旋转的,就是需要缓慢的变动,而且当按键松开时,舵机停止转动,保持住信号。
-----------------------------------------------------------------------

你这个跟步进电机细分很相似,你只要把我之前那段程序的定时器的定时间设小一点,然后Count的值范围变大就可以了。


点击此处下载 ourdev_716544TG8NIF.rar(文件大小:692字节) (原文件名:main.rar)

BXAK 发表于 2012-2-7 12:31:37

回复【22楼】mianchaodahai
-----------------------------------------------------------------------

仅供参考(已通过仿真)

// 晶振:12MHz

#include <STC15F204EA.H>

sbit PWM0 = P1^0;
sbit PWM1 = P1^1;

uint16 PWM0_K,PWM1_K;        //脉宽500us ~ 2500us
uint8TL_K,TH_K;
uint8num;

/*┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
函数:PWM初始化
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈*/
void PWM_Init_T0()
{
        TMOD &= 0xF0;                //清定时器0
        TMOD |= 0x01;                //定时器0方式1,16位模式
        TL0 = (65536-PWM0_K);          //T0初始值(PWM0脉宽时间)
        TH0 = (65536-PWM0_K)>>8;
        EA= 1;                              //允许CPU中断
        ET0 = 1;                              //允许T0中断

        num= 0;
        TL_K = (55536+PWM0_K);          //预装定时值(65536-(10000-PWM0_K))
        TH_K = (55536+PWM0_K)>>8;
        PWM0 = 1;                                  //初始化PWM0
        PWM1 = 0;                                  //初始化PWM1
        TR0 = 1;
}

void main()
{                                    
    PWM0_K = 1000;                          //默认1ms
        PWM1_K = 1000;                          //默认1ms
       
        PWM_Init_T0();

        while(1);
}

/*
2路PWM周期20ms,0.5ms~2.5ms任意脉宽原理:20ms分2等份,每等份10ms完成一路PWM脉宽。
*/
void time0() interrupt 1
{
   TL0 = TL_K;           //装载预装值
   TH0 = TH_K;
   
   switch(num)
   {
          case 0:PWM0 = 0;
                        TL_K = (65536-PWM1_K);          //预装PWM1脉宽时间
                TH_K = (65536-PWM1_K)>>8;
                        break;

       case 1:PWM1 = 1;
                        TL_K = (55536+PWM1_K);          //预装(10ms-PWM1脉宽时间)
                TH_K = (55536+PWM1_K)>>8;
                        break;

       case 2:PWM1 = 0;
                        TL_K = (65536-PWM0_K);          //预装PWM0脉宽时间
                TH_K = (65536-PWM0_K)>>8;
                        break;

       case 3:PWM0 = 1;
                        TL_K = (55536+PWM0_K);          //预装(10ms-PWM0脉宽时间)
                TH_K = (55536+PWM0_K)>>8;
                        break;                               
   }
   if(++num > 3)num=0;
}


(上面的程序比下面的好)
下面这个比较直观,但对12T的51要加一定修正值,不过1T的STC 33MHz速度够快,应该不需要

//12MHz
void time0() interrupt 1
{
   switch(num)
   {
          case 0:PWM0 = 1;
                        TL0 = (65536-PWM0_K);          
                TH0 = (65536-PWM0_K)>>8;
                        break;

       case 1:PWM0 = 0;
                        TL0 = (55536+PWM0_K);          
                TH0 = (55536+PWM0_K)>>8;
                        break;

       case 2:PWM1 = 1;
                        TL0 = (65536-PWM1_K);          
                TH0 = (65536-PWM1_K)>>8;
                        break;

       case 3:PWM1 = 0;
                        TL0 = (55536+PWM1_K);          
                TH0 = (55536+PWM1_K)>>8;
                        break;                               
   }
   if(++num > 3)num=0;
}

mianchaodahai 发表于 2012-2-9 10:43:24

非常感谢各位高手的指导,我再好好想想
页: [1]
查看完整版本: STC单片机的一个定时器怎么在两个IO口同时输出2个可调的PWM波