Hi, Robots(一):基于PIC硬件的PWM

其实我很早就有自己做一个机器人的想法了,受到两部《Iron Man》的刺激,曾热血沸腾,不过苦于没有经验,不知道从何入手。2010年第1期的《无线电》来了,我欣喜地发现上面有个关于机器人入门的专题。读了这次专题,我才知道,其实机器人也很好做。当然,敢说这句话,只是因为我有单片机基础,至于电子方面的问题,可以和老爸合作。我的想法是做一个最简单的机器人:避撞机器人,说白了就是一只车。

虽说是避撞,但是第一步还是要撞的。我的初步构想是,前面弄一根杆,杆上弄一排微动开关,撞上东西了微动就被触发,电平变化,单片机做出指示:倒退、转弯。等这个作品完成了,再来真正的避撞,大概用红外线来搞吧。

本以为撞只是简单的事情,但看到后面才想起来:电机是模拟的,而单片机输出的信号确是数字的(即要么高电平,要么低电平),无法控制电机的快慢。这才注意到应该用PWM来实现。根据维基百科上面的解释
脉冲宽度调制(Pulse Width Modulation, 缩写为 PWM),简称脉宽调制,是将模拟信号 转换为脉波的一种技术,一般转换后脉波的周期固定,但脉波的占空比会依模拟信号的大小而改变。

占空比说白了就是在一个周期内,高电位时间占总时间的多少,例如下面,占空比就是T1/T。

也就是利用占空比,可以调整输出的“电压”,从而来控制模拟器件的运行。要控制直流电机,就要学会PWM!PIC16F877A单片机中带有两路的CCP模块,也就是支持两路PWM。不过这两路共用TMR2计时器,所以它们频率相同,但是可以设置独立的占空比。经过一个早上的奋斗,终于弄懂了基于PIC硬件的PWM。

PIC16F877A的CCP模块通过两个寄存器控制:CCP1CON、CCP2CON。因为功能相同,所以下面直接将(1,2)写为x,即CCPxCON。单片机具有10位精度的占空比控制,用到的寄存器是CCPRxLCCPxCON<5:4>(其中CCPRxL是高8位,CCPxCON<6:5>是低2位)。同时,PWM功能需要结合TMR2实现,所以又要用到PR2T2CON两个寄存器。

PWM 周期是通过写Timer2 的PR2 寄存器来指定的。可以使用如下公式来计算:PWM周期=(PR2+1)4TOSC(TMR2预分频值)(TOSC=1/FOSC。例如使用的是4MHz的晶振,它的TOSC=0.25μs,TMR2预分频值为1:1,要产生5kHz的方波,也就是PWM周期为200μs,PR2=200μs/(40.25μs*1)-1=199。

占空比也可以通过公式来计算:占空比=(CCPRxL:CCPxCON<5:4>)/4(PR2+1)。例如PR=199,要获得60%的占空比,那么CCPRxL:CCPxCON<5:4>就要设为560,即CCPRxL=140、CCPxCON<5:4>=0。

从上面可以看出,PR2不仅用来控制TMR2,而且可以被用来控制PWM。下面再来介绍一下CCPxCON:

它的高2位未定义,<5:4>是对TMR2补充的2位,<3:0>模式设置,为了开启PWM,可以设为1100。

理论的基础就是这样,在TMR2为PWM计数同时,也可以设置中断,这样就可以利用TMR2中断来做其他的事情,包括修改占空比,实现SPWM等。由于控制直流电机只需要输出方波即可,所以这里不做深入研究。

根据PIC16F877A的引脚图,CCP1模块控制RC2,CCP2控制RC1,启用PWM时硬件会自动控制高低电平。需要注意的是,要把RC1和RC2设为输出。

下面来看看软件方面的实现。

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
77
78
79
80
81
82
83
/*
2011-1-1
Auther:abcdabcd987
通过两个按键,实现动态改变两路PWM的占空比。硬件方面,在RC1和RC2接上10k电阻和LED。
*/
#include <pic.h>
#include "ButtonPress.h";//用来做按键扫描,内容略去。
__CONFIG(0x2739);
bit Up1,Up2;
int Light1,Light2;
void delay(int);
void SetLight1(int);
void SetLight2(int);
int main();
int main()
{
  TRISB = 0xff;
  TRISC = 0;
  PORTC = 0;
  PR2 = 199; //200us/(4*0.25us)-1
  T2CON = 0b00000100;//1:1,1:1
  CCP1CON = 0b00001100; //PWM Mode
  CCP2CON = 0b00001100; //PWM Mode
  Up1 = 1;
  Light1 = 256;
  Up2 = 0;
  Light2 = 480;
  SetLight1(Light1);
  while(1)
  {
      if (IsPressedBT1())
      {
          if (Up1)
          {
              ++Light1;
              if (Light1 == 800)
                  Up1 = 0;
          }
          else
          {
              --Light1;
              if (Light1 == 1)
                  Up1 = 1;
          }
      }
      if (IsPressedBT2())
      {
          if (Up2)
          {
              ++Light2;
              if (Light2 == 800)
                  Up2 = 0;
          }
          else
          {
              --Light2;
              if (Light2 == 1)
                  Up2 = 1;
          }
      }
      SetLight1(Light1);
      SetLight2(Light2);
      delay(0xff);
  }
}
void SetLight1(int Light)
{
  char low = (Light&3)<<2|0b00001100;
  char high = Light>>2;
  CCP1CON = low;
  CCPR1L = high;
}
void SetLight2(int Light)
{
  char low = (Light&3)<<2|0b00001100;
  char high = Light>>2;
  CCP2CON = low;
  CCPR2L = high;
}
void delay(int x)
{
  while(--x);
}

这是最简单的程序。效果不错,在示波器上可以看到波形(悲剧的是用Proteus模拟的时候竟然不变,模拟和现实往往存在差距啊╮(╯▽╰)╭),频率计上也显示5000Hz。

学会PWM,就离Robots近了一步。

Comments