软件方式输出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 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正常输出(默认)
设计资源 培训 开发板 精华推荐
- LT6656ACS6-5、5V 低功率精密高压电源监视器的典型应用
- 使用 Omron 的 S8VS-48024B 的参考设计
- DC1082A-D、LTC1403A 演示板、14 位 1CH 2.8 Msps SAR ADC
- 充电宝面板
- DER-270 - 125W LLC DC-DC谐振转换器
- 使用 Diodes Incorporated 的 ZLPM8014JB20 的参考设计
- 4 至 100W,一个 LED 通用 LED 驱动器,用于 LED 照明
- LTC2430、具有差分输入和差分基准的 20 位 Delta Sigma ADC 的典型应用
- 使用 Analog Devices 的 ADR3425 的参考设计
- R_109_V10基于IPS2电机换向传感器的设计