51单片机(STC89C52)的多路PWM脉冲宽度调制输出

发布者:SparklingRiver最新更新时间:2022-08-08 来源: csdn关键字:51单片机  STC89C52  多路PWM 手机看文章 扫描二维码
随时随地手机看文章

软件方式输出PWM

PWM用于输出强度的控制, 例如灯的亮度, 轮子速度等, STC89/90系列没有硬件PWM, 需要使用代码模拟


使用纯循环的方式实现PWM

非中断的实现(SDCC环境编译)


#include <8052.h>

 

#define Led10 P0_7

typedef unsigned int u16;

 

int atime = 64;

 

// 仅作为延时, pms取值区间为 0 - 64

void delay(u16 pms) {

  u16 x, y;

  for (x=pms; x>0; x--) {

    for (y=11; y>0; y--);

  }

}

 

// 这里控制占空比, i取值区间为 0 - 64, 

// i越大脉冲宽度越低, 因为输出是低位点亮, 所以i越大LED越亮

void ledfade(u16 i) {

  Led10 = 0;

  delay(i);

  Led10 = 1;

  delay(atime-i);

}

 

int main(void) {

  u16 a, b;

  // 每个循环, 小灯

  while(1) {

    // a增大, 脉冲宽度降, 亮度增

    for (a=0; a      for (b=0; b < (atime - a)/4; b++) {

        ledfade(a);

      }

    }

    // a减小, 脉冲宽度增, 亮度降

    for (a=atime; a>0; a--) {

      for (b=0; b < (atime - a)/4; b++) {

        ledfade(a);

      }

    }

  }

}


使用中断的方式

因为需要PWM输出的场景, 一般都不会仅仅有PWM输出, 所以通常会做到定时器中断中, 由中断来实现

将1和0的时间宽度设置为定时器, 直接做到定时器中断里面


这个代码中


pwm_flag代表了输出的0和1, 每次定时器中断时进行切换, 并设置下一次中断的时间宽度

用TR0做开关, 但是这种停止方式, 停止后输出可能还是1

/* Global variables and definition */

#define PWMPIN P1_0

 

unsigned char pwm_width;

bit pwm_flag = 0;

 

void pwm_setup()

{

  TMOD = 0; // Timer mode 0, 13bit

  pwm_width = 160;

  EA = 1;

  ET0 = 1;

  TR0 = 1;

}

 

/* Timer 0 Interrupt service routine */

void timer0() interrupt 1

{

  if (!pwm_flag) {  /* Start of High level */

    pwm_flag = 1; /* Set flag */

    PWMPIN = 1;   /* Set PWM o/p pin */

    TH0 = pwm_width;  /* Load timer */

    TF0 = 0;    /* Clear interrupt flag */

  } else {      /* Start of Low level */

    pwm_flag = 0; /* Clear flag */

    PWMPIN = 0;   /* Clear PWM o/p pin */

    TH0 = 255 - pwm_width;  /* Load timer */

    TF0 = 0;    /* Clear Interrupt flag */

  }

}

 

void pwm_stop()

{

  TR0 = 0;      /* Disable timer to disable PWM */

}


使用定时器模式2和中断实现的PWM输出

使用定时器工作模式2. 这里scale分10个等级, scale=1时占比1/10个PWM周期(250us * 10 = 2.5ms), 因为是低电平点亮LED, 所以tt<=scale的时间LED是暗的, scale增大时亮度变小


#include

sbit P10 = P1^0;

sbit P11 = P1^1;

unsigned int scale;   //占空比控制变量

void main(void) {

  unsigned int n;     //延时循环变量

  TMOD = 0x02;        //定时器0,工作模式2, 8位定时, TL0溢出时自动重载TH0中的值

  TH0 = 0x06;         //定时, 250us一个中断 (12M晶振, 12分频后1MHz, 单次1us)

  TL0 = 0x06;         //初始值

  TR0 = 1;            //启动定时器0

  ET0 = 1;            //启动定时器0中断

  EA  = 1;            //开启总中断

  while(1) {

    for(n = 0; n < 50000; n++); //延时50ms

    scale++;                    //占空比控制, 自增

    if(scale == 10) scale = 0;  //使占空比从0-10循环变化

  }

}

 

timer0() interrupt 1 {

  static unsigned int tt;      //tt用来保存当前时间在一个时钟周期的位置

  tt++;                        //每中断一次,即每经过250us,tt的值自加1

  if (tt == 10) {              //中断10次定时2.5ms

    tt = 0;                    //使tt=0,开始新的周期,达到循环的效果

    P10 = 0;                   //点亮LED

  }

  if (tt <= scale) {           //如果占空比与中断次数相同时,此时输出高电平

    P10 = 1;                   //熄灭LED灯

  }

}


使用定时器模式2和中断实现的多路PWM输出

实现多路PWM输出的思路


使用一个基础定时器, 定时器时间不能太大, 例如设置为100us, 可以用定时器模式2, 这样初始值能自动重置

设定一个PWM周期, 这个周期就是定时器间隔的整数倍, 例如10倍定时器周期, 就是1000us = 1ms

对于每个PWM通道

设置一个计数, 计数在达到PWM周期时置零, 这是实现PWM周期的基础

设置一个初始输出, 高电平或低电平

设置一个输出宽度, 计数达到这个宽度值时翻转. 这个宽度决定了输出翻转的时间, 用于控制占空比

因为每个指令的执行时间需要1-2个CPU周期, 所以当通道数增加后, 误差会增大

代码例子: 这里用8个位指定4个轮子的PWM输出, 每个轮子两位是为了控制轮子的正反向


#include

 

typedef unsigned int u16;

typedef unsigned char u8;

 

// Wheel 0

sbit  P1_0 = P1^0;

sbit  P1_1 = P1^1;

// Wheel 1

sbit  P1_2 = P1^2;

sbit  P1_3 = P1^3;

// Wheel 2

sbit  P1_4 = P1^4;

sbit  P1_5 = P1^5;

// Wheel 3

sbit  P1_6 = P1^6;

sbit  P1_7 = P1^7;

 

/*

Duty Cycle =  Toogle_P1_x / PWM_Period;

*/

u8 PWM_Period = 128; // PWM Period = N * Timer delay(100us), between 10 - 254

u8 Toggle_W0 = 0; // Toggle of Wheel 0

u8 Dir_W0 = 0;    // Direction, 0:P1_0=0,P1_1=PWM, 1:P1_1=0,P1_0=PWM

u8 Toggle_W1 = 0; // Toggle of Wheel 1

u8 Dir_W1 = 0;    // Direction, 0:P1_2=0,P1_3=PWM, 1:P1_3=0,P1_2=PWM

 

u8 Count_W0, Count_W1;

 

void Time0_Init(void)

{

  TMOD = 0x02; // Mode 2, 8-bit and auto-reload

  TH0 = 0x9C;  // 0x9c = 156, timer of 100us (12MHz OSC)

  TL0 = 0x9C;

  ET0 = 1;

  EA  = 1;

  TR0 = 1;

 

  EX0 = 1; EX1 = 1; // Enable external interrupt 0 and 1

  IT0 = 1; IT1 = 1; // Toggle = jump

}

 

void main()

{

  Time0_Init();

  while(1);

}

 

void Timer0_IT() interrupt 1

{

  // W0

  if(Count_W0 == Toggle_W0) {

    if (Dir_W0 == 0) { // P1_1=PWM

      P1_1 = 0;

    } else { // P1_0=PWM

      P1_0 = 0;

    }

  }

  if(Count_W0 == PWM_Period - 1) {

    Count_W0 = 0;

    if (Dir_W0 == 0) {

      P1_0 = 0;

      P1_1 = 1;

    } else {

      P1_0 = 1;

      P1_1 = 0;

    }

  } else {

    Count_W0++;

  }

 

  // W1

  if(Count_W1 == Toggle_W1) {

    if (Dir_W1 == 0) { // P1_3=PWM

      P1_3 = 0;

    } else { // P1_2=PWM

      P1_2 = 0;

    }

  }

  if(Count_W1 == PWM_Period - 1) {

    Count_W1 = 0;

    if (Dir_W1 == 0) {

      P1_2 = 0;

      P1_3 = 1;

    } else {

      P1_2 = 1;

      P1_3 = 0;

    }

  } else {

    Count_W1++;

  }

 

}

 

 

// W0 dir0->max

void W0_dir0(void)

{

  if (Dir_W0 == 0) {

    Toggle_W0++;

    if(Toggle_W0 > PWM_Period) {

      Toggle_W0 = PWM_Period;

    }

  } else {

    Toggle_W0--;

    if(Toggle_W0 == 0) {

      Dir_W0 = 0;

    }

  }

}

 

// W0 dir1->max

void W0_dir1(void)

{

  if (Dir_W0 == 0) {

    Toggle_W0--;

    if(Toggle_W0 == 0) {

      Dir_W0 = 1;

    }

  } else {

    Toggle_W0++;

    if(Toggle_W0 > PWM_Period) {

      Toggle_W0 = PWM_Period;

    }

  }

}

 

// W1 dir0->max

void W1_dir0(void)

{

  if (Dir_W1 == 0) {

    Toggle_W1++;

    if(Toggle_W1 > PWM_Period) {

      Toggle_W1 = PWM_Period;

    }

  } else {

    Toggle_W1--;

    if(Toggle_W1 == 0) {

      Dir_W1 = 0;

    }

  }

}

// W1 dir1->max

void W1_dir1(void)

{

  if (Dir_W1 == 0) {

    Toggle_W1--;

    if(Toggle_W1 == 0) {

      Dir_W1 = 1;

    }

  } else {

    Toggle_W1++;

    if(Toggle_W1 > PWM_Period) {

      Toggle_W1 = PWM_Period;

    }

  }

}

 

 

void IT0_INT() interrupt 0

{

  W1_dir0();

}

 

void IT1_INT() interrupt 2

{

  W1_dir1();

}


硬件PWM

STC12C5A60S2系列PCA实现的PWM

参考STC12C5A60S2的手册


有两路输出, 默认PWM0:P1.3, PWM1:P1.4, 可以换到P4口: PWM0:P4.2, PWM1:P4.3

这个在AUXR1里面控制

两路共用PCA定时器, 定时器的频率由CMOD控制

因为PWM输出是8位的, 所以定时器的频率/256就是PWM频率

两路输出的占空比是独立变化的, 与当前的[EPCnL, CCAPnL]的值有关

前者的值在 PCA_PWM0 PCA_PWM1 里控制

后者的值在 CCAP0L,CCAP0H 和 CCAP1L,CCAP1H 里控制

先输出低, 当CL的值大于等于[EPCnL, CCAPnL]时, 输出为高

当CL由FF变为00时, 输出变低, 同时自动将[EPCnH, CCAPnH]的值装载到[EPCnL, CCAPnL], 实现无干扰更新PWM占空比

下面的代码中, CCAP1H 控制的就是装载值, CCAP1L 控制的是比较值, PCA_PWM1 控制的是EPCnH 和 EPCnL


如果 EPCnL = 0, 那么正常输出

如果 EPCnL = 1, 那么会一直输出低电平

#include

 

void main() {

  CCON = 0;            // Initial PCA control register

                       // PCA timer stop running

                       // Clear CF flag

                       // Clear all module interrupt flag

  CL = 0;              // Reset PCA base timer

  CH = 0;

  CMOD = 0x02;         // Set PCA timer clock source as Fosc/2

                       // Disable PCA timer overflow interrupt

  CCAP0H = CCAP0L = 0x80; // PWM0 port output 50% duty cycle suquare wave

  CCAPM0 = 0x42;       // PCA module-0 as 8-bit PWM, no PAC interrupt

  

  CCAP1H = CCAP1L = 0xFF; // PWM1port output 0% duty cycle square wave

  PCA_PWM1 = 0x03;     // PWM will keep low level

  CCAPM1 = 0x42;       // PCA module-0 as 8-bit PWM, no PAC interrupt

  

  CR = 1;              // PCA timer start run

  

  while(1);

  

}


对PCA_PWM1的说明


;PCA_PWMn:    7       6     5   4   3   2     1       0

;        EBSn_1 EBSn_0  -   -   -   -  EPCnH  EPCnL

 

;B5-B2: 保留

;B1(EPCnH): 在PWM模式下,与CCAPnH组成9位数。

;B0(EPCnL): 在PWM模式下,与CCAPnL组成9位数。

 

#define PWM0_NORMAL() PCA_PWM0 &= ~3 //PWM0正常输出(默认)

[1] [2]
关键字:51单片机  STC89C52  多路PWM 引用地址:51单片机(STC89C52)的多路PWM脉冲宽度调制输出

上一篇:51单片机(STC89C52)在Ubuntu下的开发
下一篇:STC系列51单片机在Windows下的开发

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved