Hi, Robots(二):基于软件的PWM

昨天上午研究完了基于硬件的PWM,由于懒得去找电机,所以干脆下午就开始研究基于软件的PWM。动机很简单,因为PIC16F877A只支持2通道的PWM,要是多一点,那就麻烦了,岂不是还得用专门的芯片?所以还是自己研究一下基于软件的PWM。

基于软件的PWM,注定要占用CPU许多时间,所以频率不能很高。比如100Hz,一个周期就是1s/100Hz1000=10ms,一个周期实现10档占空比调整(10%,20%,…,100%),那么一档的时间就是:10ms/101000=1000μs。1000μs能干嘛呢?对于4MHz的晶振来说,就是1000条语句,所以时间很紧。

不过实现的原理很简单,用TMR2精确定时(如果选用TMR0需要自己冗余误差),每一档进入一次中断。在中断里面判断是否到达时间,到达的话就修改引脚的输出。还是以上面100Hz,分10档,晶振4MHz,控制6路I/O口说起:
  1. 要实现100Hz,也就是一档1000μs,那么TMR2的预分频设为1:4,PR=249,这样每次溢出的时间就是(249+1)*4=1000μs,刚刚好。(TMR2的优势在这里高度体现o(╯□╰)o)
  2. 软件方面,设置一个_pwm数组记录每一个通道的占空比,sta数组记录每一个通道目前的时间是多少。则当pwm[i]=_sta[i]时,开始输出低电平;当_sta[i]>_maxpwm时,一个周期结束,_sta[i]清零,同时开始输出高电平。
  3. 为了方便以后修改以及自定义方便,设置了_maxpwm规定最大的占空比,_maxpwm越大,实现的效果就越精细。
  4. 通过void PWM_Init(const char t2con, const char pr2)来开启PWM模块,t2con为TMR2的参数,pr2就是PR2。
  5. 通过void PWM_SetPWM(const char index, const char value)来设置每一路的占空比。
  6. 而void PWM_interrupt()则要加进中断里面,使单片机能够处理TMR2的中断。void PWM_interrupt()里面就是关于PWM的详细实现。
  7. 至于_T变量,可以用来实现多任务系统,具体不多说。下面的示例因为太简单,所以没用到_T,而是用老土的delay。_T变量参考第1个通道,把第一个通道变成0当作一个周期结束。
  8. 为了项目方便,可以建立一个头文件,将这些函数、变量在里面extern。

再次强调:做PWM这种事情是很耗CPU的!比如控制8路I/O,需要时间就要:612μs,请问还有多少时间留给其它事情?所以我建议要实现软件PWM,要么晶振加快,要么就单独搞个芯片出来,要么就是限制I/O口数量。

下面就详细看看代码是怎么实现的(不加注释,根据上面自己应该可以理解,不懂再回复):

PWM.C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
 * Software PWM by abcdabcd987 2011-1-2
 * There is something to write into your header:
extern unsigned int _T;
extern const char _maxpwm;
extern char _pwm[_tol];
extern char _sta[_tol];
extern void PWM_Init(const char, const char);
extern void PWM_interrupt();
extern void PWM_SetPWM(const char, const char);
extern void PWM_Stop();
 * Use PWM_init(t2con, pr2) to start the software PWM.
 * Use PWM_SetPWM(index, value) to set the value of each PWM channel.
 * Add a usage of PWM_interrupt() to your interrupt.
 * You can modify _tol, _prot, _tris(header.h), _maxpwm. Others DO NOT modify.
 */
#include "header.h"
const char _maxpwm = 10;
const char _out[8]={0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80};
const char _trisseting[8]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x0};
unsigned int  _T;
char _pwm[_tol];
char _sta[_tol];
void PWM_Init(const char, const char);
void PWM_SetPWM(const char, const char);
void PWM_interrupt();
void PWM_Stop();

void PWM_Init(const char t2con, const char pr2)
{
  for (char _i = 0; _i < _tol; ++_i) _sta[_i] = 0;
  _tris &= _trisseting[_tol-1];
  _port = 0;
  T2CON = t2con;
  PR2 = pr2;
  TMR2IE = 1;
  PEIE = 1;
  GIE = 1;
  _T = 0;
}
void /*interrupt */PWM_interrupt()
{
  if (TMR2IF)
  {
      TMR2IF = 0;
      char _pt = _port;
      for (char i = 0; i < _tol; ++i)
      {
          _sta[i] = _sta[i] + 1;
          if (_sta[i] >= _maxpwm)
          {
              _pt = _pt | _out[i];
              _sta[i] = 0;
          }
          if (_sta[i]==_pwm[i] && _pwm[i]!=_maxpwm)
          {
              _pt = _pt & (~_out[i]);
          }
      }
      if (_sta[0] == 0)
          if (_T == 50000)
              _T = 0;
          else
              _T = _T+1;
      _port = _pt;
  }
}
void PWM_SetPWM(const char index, const char value)
{
  _pwm[index] = value;
}
void PWM_Stop()
{
  T2CON = 0;
  TMR2IE = 0;
}

header.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef HEADER_H
#define HEADER_H
#include <pic.h>
#define _tol 8
#define _port PORTC
#define _tris TRISC
/*PWM start*/
extern unsigned int _T;
extern const char _maxpwm;
extern char _pwm[_tol];
extern char _sta[_tol];
extern void PWM_Init(const char, const char);
extern void PWM_interrupt();
extern void PWM_SetPWM(const char, const char);
extern void PWM_Stop();
/*PWM   end*/
#endif

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "header.h"
__CONFIG(0x2739);
char Up[6]  ={1,1,1,1,1,1};

int main();
void delay(int x);

int main()
{
  PWM_Init(0b00000101, 249);
  PWM_SetPWM(0, 0);
  PWM_SetPWM(1, 2);
  PWM_SetPWM(2, 4);
  PWM_SetPWM(3, 6);
  PWM_SetPWM(4, 8);
  PWM_SetPWM(5, 10);
  while(1)
  {
      delay(0x5fff);
      for (char i = 0; i < 6; ++i)
      {
          if (Up[i])
          {
              ++_pwm[i];
              if (_pwm[i]==10)
                  Up[i] = 0;
          }
          else
          {
              --_pwm[i];
              if (_pwm[i]==0)
                  Up[i] = 1;
          }
      }
  }
}
void delay(int x)
{
  while (--x);
}
void interrupt breakdown()
{
  PWM_interrupt();
}

最后再上几张图片,实验板上控制6个LED灯,示波器上显示的是头两个灯的波形(从中间隔开),可以看到它们的占空比差了一拍。至于直观的效果,本应是几个亮度不一样的,手机拍的不明显(当然为了明显,我只留下三盏灯)

Comments