您现在的位置是:网站首页> 硬件

Ardunio PWM频率库

  • 硬件
  • 2021-03-28
  • 792人已阅读
摘要

 因之前使用过飞思卡尔及瑞萨16位单片机,在PWM模块上使用很顺手,也没有产生任何疑惑,反倒是Arduino输出的PWM固定频率让人很是恼火。试想你的电机在490hz的PWM控制下捏着鼻子嗡嗡叫,这是一件多么叫人忧伤的事呀。
     Arduino很好,但PWM很鸡肋。当我想要查datasheet来对底层操作时,无意发现了PWM frequency 库,感觉很好用,很省时间。那么,就翻出来造福大家喽。这是我第一个认真写并写完整的技术文档翻译,欢迎交流指正。

翻译自http://arduino.cc/forum/index.php?topic=117425.0

   在目前与Arduino微控制器相关的项目中,我发现没有一种方法能不直接操作底层寄存器,而改变PWM频率的。就目前我所google到的,没有一种通用的库能改变arduino微控制器的PWM频率。网上有各种关于改变PWM频率的代码段,但是最终我还是决定参考400多页的ARV-Mega系列单片机的datasheet来实现这些功能。
       据我推测,Arduino的编程人员没有发行任何关于改变PWM频率的方法是因为很难编写一个简单的,直观的硬件定时器封装程序,而不至使初学者困惑。硬件本身就有一些特殊的局限性,它们以一些奇怪的方式表现出来。
请允许我与大家分享一些:
  • PWM的行为是由叫作定时器的集成元件决定的。每个定时器有2个或4个通道。每个通道连到一个控制器引脚上。改变一个引脚的频率需要改变它所连接的定时器的频率,这样反过来也会改变其它连到同一个定时器的引脚的频率。

  • Timer0通常被用作实现Arduino的计时功能(例如,millis()函数)。改变timer0的频率将会破坏你工程里使用计时函数的其它程序段部分。

  • Arduino中有两种定时器,8位定时器和16位定时器。简单来说,就是它们所存在的细微差别使得不限制一方或另一方的代码实现变得困难。

  • 使用一个8位的定时器来设定定制的频率(使用预分频产生不了的频率)需要牺牲一个通道。换句话说,每个设定定制频率的8位定时器会失去在一个引脚产生pwm的能力(连接A通道的引脚会更准确些)。除了Leonardo的所有Arduino都有两个8位定时器,这意味着如果你把所有定时器设定特殊的频率,上述所说的Arduino控制器总共将会牺牲两个引脚。

         先不管这些,我依然觉得对硬件定时器做一个库或封装是非常值得的,这样我或者任何其他选择使用这个库的人不必花那么多时间去深挖那些容易产生bug的逐位操作的模块和初始化部分。
这个库有五个全局函数:
InitTimers()                                                                        初始化所有定时器。需要在改变定时器频率或设定一个引脚的占空比之前用。
InitTimersSafe()                                                                除了为了保留计时功能函数而不初始化timer0外,其它都与InitTimers()一样。
pwmWrite(uint8_tpin,uint8_t val)                                与‘analogWrite()’一样,但是只有在相应定时器初始化后才工作
SetPinFrequency(int8_tpin,int32_t frequency)          设定引脚的频率(hz),返回一个设定成功与否的布尔值
SetPinFrequencySafe(int8_tpin,int32_tfrequency)    除了不影响timer0外,其它都与SetPinFrequency函数一样
         这个库针对每个定时器还有五个函数。我没有把代码大小减小到一个我认为合理的程度,而是编写了C++类,并做了大量细致的封装工作。这里的每一个函数在都用明显专门的宏定义在编译前调出那些库的头文件里隐晦的函数。
对timer1而言,这些函数是:
Timer1_GetFrequency()                                读取定时器的频率(hz)
Timer1_SetFrequency(intfrequency)          设定定时器的频率
Timer1_GetPrescaler()                                  读取预分频因子
Timer1_SetPrescaler(enumvalue)               设定预分频因子
Timer1_GetTop()                                            读取定时器寄存器最大值
Timer1_SetTop(inttop)                                 设定定时器寄存器最大值
Timer1_Initialize()                                          初始化定时器

         预分频因子在不同的定时器中是不一致的。我认为使用枚举数是最好的解决方法,因为大多数无效的类型输入将会在编译的时候被发现。例如一个普通的定时器,使用下面其中一个作为参数:ps_1,ps_8,ps_64,ps_256,ps_1024.如果这些都报类型错误,那么你所使用的定时器就是与其它定时器不一样的一个例子,你应该用Psalt_1,psalt_8,psalt_32,psalt_64,psalt_128,psalt_256,或者psalt_1024代替。如果你想使用不同的定时器,只要改变序号即可(例如,Timer2_GetFrequency()读取定时器2的频率)。这取决于你是否要使用定时器的特殊功能。全局函数对大多数情况下已经够用了。使用这个库,16位的定时器的频率可以从1hz变化到8Mhz,8位定时器的频率可以从31hz变化到2Mhz。但是随着频率的越来越大,占空比可变化的范围就越小。将频率设定为8Mhz是可以实现的,但是这样占空比的变化范围将变得非常小。请确定定时器的频率正确的设置了,并检查这个函数的返回值。如果你想要牺牲任何8位的PWM引脚,就不要调用定时器对应的初始化函数,试着改变预分频因子来改变它的频率。有许多教程解释预分频因子怎么影响定时器的,这个库包函这些方法,并能使其变得更简单,在处理过程中更不容易产生bug。到目前为止,我已经在UNO和Mega上测试过了。这个库能兼容除了Leonardo和Due的任何arduino控制器。如果你拥有的arduino控制器不是Mega或者Uno,请在上面测试并把运行状况告诉我。如果你们手上有示波器,试着变换它生成的频率,显示正确,则说明这个库是可以用的。

现在,这个库正在测试中。这个库的发展将在后面的帖子里描述。
下面是目前这个库的一些特征:

1.使用函数封装了定时器的特殊功能。(例如定时器寄存器的最大值和预分频因子)
2.基于引脚(与定时器无关)的函数。
3.拥有在定时器级或引脚级读取和设定频率的函数。
4.拥有定时器级和引脚级的定时器分辨率测量工具。

最新库是的05版本:
链接:http://code.google.com/p/arduino-pwm-frequency-library/downloads/list

下面是跟帖中比较有用的部分总结:
硬件上,Mega系统控制板,11引脚连着timer1,引脚9连接timer2, 引脚7连接timer4。这是软件改变不了的。

关于分辨率的问题:
8位的定时器兼容8位的分辨率,16位的定时器兼容16位的分辨率。为了与analogWrite()保持一致性,pwmWrite()函数都使用8位分辨率。如何需要更高的分辨率时,使用pwmWriteHR()代替。

Void pwmWrite(uint8_t pin, uint8_t duty) 8-bit, 0-255
Void pwmWriteHR(uint8_t pin, uint16_t duty) 16-bit 0-65535

不幸的是,一旦使用了定制的频率,分辨率控制将不会那么简单。如果你修改定时器的频率,分辨将跟着变化。经验法则是频率越高,分辨率越低。有一些变量在SetFrequency函数里封装了。函数知道他们,并在给定的频率下精确的使用可能的最高的分辨率。尽管pwmWriteHR()接受16位的整数,它将自动映射到定时器任一分辨率下(也就是说它的分辨率不一定是16位的)。为了知道在特定频率下分辨率是否小于界限值,我添加了两个函数。

Float  GetPinResolution(uint8_t pin)
Float  TimerX_GetResolution() (用定时器号来替换X)

这些函数能读出分辨率,以二进制的位数来表示分辨率。请注意这两个函数都返回浮点数,而不是整数,那是我故意这么做的。如果你更喜欢用最大可能值而非二进制数来表示的话,使用TimerX_GetTop()然后加1(其实质是一样的,只不过是十进制的)。
我在工程中添加了一个叫做PWM_lib_resolution_example的例程,来演示这些函数和定时器频率、分辨率之间的关系。

#include <PWM.h>

//use pin 11 on the Mega instead, otherwise there is a frequency cap at 31 Hz
int led = 7;                // the pin that the LED is attached to
int brightness = 0;         // how bright the LED is
int fadeAmount = 5;         // how many points to fade the LED by
int32_t frequency = 20000; //frequency (in Hz)

void setup()
{
  //initialize all timers except for 0, to save time keeping functions
  InitTimersSafe();

  //sets the frequency for the specified pin
  bool success = SetPinFrequencySafe(led, frequency);
 
  //if the pin frequency was set successfully, pin 13 turn on
  if(success) {
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);   
  }
}

void loop()
{
  //use this functions instead of analogWrite on 'initialized' pins
  pwmWrite(led, brightness);

  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
  }     
 
  delay(30);     
}

Top