软件方式输出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<atime; 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的时间宽度设置为定时器, 直接做到定时器中断里面

这个代码中

  1. pwm_flag代表了输出的0和1, 每次定时器中断时进行切换, 并设置下一次中断的时间宽度
  2. 缺点: 用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
  • 定时器通过对变量tt做计数, 与scale做比较, 确定是否翻转电压
  • 这里scale分10个等级, scale=1时占比1/10个PWM周期(250us * 10 = 2.5ms), 在主循环里改变scale
  • 因为是低电平点亮LED, 所以tt<=scale的时间LED是暗的, scale增大时亮度变小, 这个可以根据自己电路的情况调整
  • 这样存在的问题是修改scale的值时, 可能正好在tt计数范围的中间, 导致输出出现毛刺, 可以通过增加一个中间变量来解决, 在tt计数时比较的是这个中间变量, 在周期结束时再用新值更新这个中间变量
#include<reg51.h>
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输出的思路

  1. 使用一个基础定时器, 定时器时间不能太大, 例如设置为100us, 可以用定时器模式2, 这样初始值能自动重置
  2. 设定一个PWM周期, 这个周期就是定时器间隔的整数倍, 例如10倍定时器周期, 就是1000us = 1ms
  3. 对于每个PWM通道
    • 设置一个计数, 计数在达到PWM周期时置零, 这是实现PWM周期的基础
    • 设置一个初始输出, 高电平或低电平
    • 设置一个输出宽度, 计数达到这个宽度值时翻转. 这个宽度决定了输出翻转的时间, 用于控制占空比
  4. 因为每个指令的执行时间需要1-2个CPU周期, 所以当通道数增加后, 误差会增大

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

#include <reg52.h>

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

51系列单片机的增强型版本, 有些带PCA(Programmable Counter Array 可编程计数序列)模块, 可以通过PCA实现PWM的输出.

PCA介绍

PCA其实就是一个增强型的计数器, 这个计数器中的一些元素是可以在代码中设置的, 例如

  • 可以设置的计数脉冲源, 可以来自于系统时钟, 系统时钟可以是不分频, 2分频, 4分频, 6分频, 8分频等; 来自计数器; 来自外部输入的时钟
  • 可以设置计数的触发条件, 上升沿还是下降沿, 或者都计数. 最后这个计数方式, 可以用来计算脉宽
  • 可以设置16位的比较值
  • 不占用CPU资源, 这点很重要, 可以使输出更加精确和稳定
  • 因为上一点, 有些型号可以做到在CPU处于IDLE状态时继续计数(输出)

可以用PCA实现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 <STC12C5A60S2.H>

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正常输出(默认)
#define PWM0_OUT_0() PCA_PWM0 |= 3 //PWM0一直输出0
#define PWM0_OUT_1() PCA_PWM0 &= ~3, CCAP0H = 0 //PWM0一直输出1 #define PWM1_NORMAL() PCA_PWM1 &= ~3 //PWM0正常输出(默认)
#define PWM1_OUT_0() PCA_PWM1 |= 3 //PWM0一直输出0
#define PWM1_OUT_1() PCA_PWM1 &= ~3, CCAP1H = 0 //PWM1一直输出1

另一个例子

void pwm() {
CMOD = 0x04; //用定时器0溢出做PCA脉冲
CL = 0x00; //PCA定时器低8位 地址:E9H
CH = 0x00; //PCA高8位 地址 F9H
CCON=0x00;
CCAP0L = 0x60; //PWM模式时他俩用来控制占空比
CCAP0H = 0x60; //0xff-0xc0=0x3f 64/256=25% 占空比(溢出)
CCAPM0 = 0x42; //0100,0010 Setup PCA module 0 in PWM mode
// ECOM0=1使能比较 PWM0=1 使能CEX0脚用作脉宽调节输出
/*********************
PCA 模块工作模式设置 (CCAPMn 寄存器 n= 0-3四种)
7 6 5 4 3 2 1 0
- ECOMn CAPPn CAPNn MATn TOGn PWMn ECCFn
选项: 0x00 无此操作
0x20 16位捕捉模式,由 CEXn上升沿触发
0x10 16位捕捉模式,由CEXn下降沿触发
0x30 16位捕捉模式,由CEXn的跳变触发
0x48 16位软件定时器
0x4c 16位高速输出
0x42 8位PWM输出
每个PCA模块另外还对应两个寄存器:CCAPnH 和 CCAPnL , 捕获或者比较时,它们用来
保存16位计数值,当工作于PWM模式时,用来控制占空比
*******************************/
TMOD=0x02;
TH0=0x06;
TL0=0x06;
CR=1; //Start PCA Timer.
TR0=1;
}

参考

STC MCU的软件和硬件PCA/PWM输出的更多相关文章

  1. 4、CC2541芯片中级教程-OSAL操作系统(简单AT指令实现+IIC软件和硬件实现驱动MPU6050)

    本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

  2. 热爱工作 发财机会大增(这里不是选择软件还是硬件的问题,是自己的性格和追求的问题)——当你的老板不如你懂行的时候,还赚的盆满钵满的时候,你就可以考虑独立了 good

    爱工作 发财机会大增 [ 油老板 ] 于:2011-02-09 06:39:41 复:1269077 认真回顾发主贴以来的三年半,俺觉得对于想发财的上班族来说,认真工作,刻苦钻研是发财的重要保证. 为 ...

  3. 查询软件和硬件列表清单[将文章里代码另存为 list.vbs,双击运行就会出现一个html页面]

    '==========================================================================' Name: 查询软件和硬件列表清单' 不支持W ...

  4. U8g2图形库与STM32移植(I2C,软件与硬件)

    U8g2图形库 简介 U8g2 是一个用于嵌入式设备的简易图形库,可以在多种 OLED 和 LCD 屏幕上,支持包括 SSD1306 等多种类型的底层驱动,并可以很方便地移植到 Arduino .树莓 ...

  5. 关于STM32CubeMX使用LL库设置PWM输出

    HAL和LL库 HAL是ST为了实现代码在ST家族的MCU上的移植性,推出的一个库,称为硬件抽象层,很明显,这样做将会牺牲存储资源,所以项目最后的代码比较冗余,且运行效率大大降低,运行速度受制于fla ...

  6. STM32F103ZET6 PWM输出

    1.通用定时器的PWM功能 STM32F103ZET6有4个通用定时器,分别是TIM2.TIM3.TIM4.TIM5. 通用定时器由一个可编程预分频器驱动的16位自动装载计数器构成. 通用定时器的很多 ...

  7. Arduino学习经验(一)之解决舵机库和pwm输出冲突

    一.前言 最近在公司学习Arduino uno ,用它实现小车超声波避障功能.实现的功能很简单,就是在小车前方挂一个超声波模块,当碰到障碍物时,会通过舵机进行摆头,判断两边的距离,进行左右转弯.但是碰 ...

  8. stm32 PWM输出学习

    STM32 的定时器除了 TIM6 和 7,其他的定时器都可以用来产生 PWM 输出.其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出.通用定时器也能同时产生多达 4路 ...

  9. 在现有的图像处理软件中融合dxf格式输出

    在现有的图像处理软件中融合dxf格式输出 dxf格式是autocade的支持格式.如果将现有图像识别的结果导出到dxf格式,就能够使用autocad的强大功能进行后续处理. dxf的格式比较复杂,开源 ...

  10. 关于普通定时器与高级定时器的 PWM输出的初始化的区别

    不管是普通定时器还是高级定时器,你用哪个通道,就在程序里用OC多少.比如CH3对应OC3 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  TIM_ ...

随机推荐

  1. 为R Markdown配置TinyTex编译环境

    技术背景 在前面一篇博客中,我们介绍了一些关于在Windows系统上安装R Studio来编写R Markdown,最后编译成Beamer的演示文档的过程.而在Windows系统的使用过程中发现,编译 ...

  2. [转帖]基于MySQL8.0存储过程实现myawr平台的top sql功能

    概述 众所周知,MySQL数据库中的performance_schema的事件统计表中的统计数据计算的是累计值,如果想要计算某段时间的TOP SQL是不行的,这里考虑用函数定期取值存进中间表定期将累计 ...

  3. [转帖]top指令如何查询指定用户的所有进程?

    有两种方法. 1.top指令与字符串查询指令通过通道配合查询. 指令:    "-d 5"指的是每五秒刷新一次进程列表,"grep"是字符串查询指令,它可以将含 ...

  4. [转帖]kingbase(人大金仓)的一些常用表操作语句

    包括 1)创建表 2)删除表 3)加字段 4)字段换名 5)字段改类型 6)字段添加注释 7)修改字段为自增类型 8)增加主键 9)查看模式下的表 一.创建和删除表 DROP TABLE IF EXI ...

  5. [转帖]《Linux性能优化实战》笔记(一)—— 平均负载

    最近在看极客时间的<Linux性能优化实战>课程,记录下学习内容. 一. 平均负载(Load Average) 1. 概念 我们都知道uptime命令的最后三列分别是过去 1 分钟.5 分 ...

  6. [转帖]Prometheus监控系统存储容量优化攻略,让你的数据安心保存!

    云原生监控领域不可撼动,Prometheus 是不是就没缺点?显然不是. 一个软件如果什么问题都想解决,就会导致什么问题都解决不好.所以Prometheus 也存在不足,广受诟病的问题就是 单机存储不 ...

  7. [转帖]漏洞预警|Apache Tomcat 信息泄露漏洞

    http://www.hackdig.com/03/hack-953615.htm 棱镜七彩安全预警 近日网上有关于开源项目Apache Tomcat 信息泄露漏洞,棱镜七彩威胁情报团队第一时间探测到 ...

  8. JDK内嵌指令的简单学习

    java 可以使用 java -jar的方式启动服务 日常工作中用到的比较少 javac 可以将.java 文件编译成 .class中间代码 这个工具开发编写代码中是经常需要使用的, jenkins ...

  9. scss常用语法

    在线编译 https://wow.techbrood.com/fiddle/11143 群组选择器的嵌套[编译前] .container { h1, h2, h3 {margin-bottom: .8 ...

  10. TienChin 渠道管理-字典原理分析

    在上一节当中,我们使用到了字典来进行翻译我们的渠道类型等等字段,那么这一节我们就来分析一下字典的原理. 从代码方面先开始分析,我们先来看一下字典的定义,我们是在如下图当中编写了我们的渠道类型,使用,p ...