Arduino: High resolution PWM (more than 8bits),Arduino提高PWM resolution

Arduino PWM(analogWrite)

Arduino PWM是使用analogWrite,但是其有一個限制,也就是PWM resolution只有256,PWM duty cycle的值只能介於0~255,對於一般的應用來說,這個值還可以應付,但是對於高精度的Servo或是馬達,這個值實在是太小了。(請參考analogWrite)。雖然在有些板子Arduino提供analogWriteResolution這個函式(Zero, Due & MKR Family、參閱:analogWriteResolution)可以將PWM resolution提高至12,更高的值內部會做mapping到比較低解析度。至於其他板子(例如:Arduino Mega 2560),則需要第三方library支援(或是自己寫)。

Arduino的硬體PWM極限(Arduino Mega 2560)

以Arduino Mega 2560為例,在其datasheet(link)中提到其"Six/Twelve PWM Channels with Programmable Resolution from 2 to 16 Bits",ATmega2560有12個PWM channel,resolution可為2~16bits。所以其實Arduino Mega 2560硬體可以比Arduino Framework設計的resolution高很多。至於為什麼Arduino官方不用16bits呢,我猜是因為為了保證0~255的限制是可以達成。Arduino Framework的PWM frequency是固定的(16 MHz / 64 / 255 / 2 = 490.196Hz,見Secrets Of Arduino PWM)。

而PWM resolution並不是你想要16bits就可以有16bits的resolution,PWM resolution跟PWM frequency息息相關。在ATmega2560中,Frequecy與系統頻率、timer prescaler(除頻器),還有最常使用的TOP值(限定Timer counter的最大上限)有關。TOP值的大小即為PWM resolution。依據Atmel PWM resolution公式:

PWM resolution = log(TOP + 1) / log 2

 通常要把PWM resolution拉到最高,PWM frequency會很小,這主要看設計時的選擇。其他細部詳情請見ATmega640/V-1280/V-1281/V-2560/V-2561/V [DATASHEET]第17章。

 arduino-pwm-frequency-library

這裡介紹一個第三方library “arduino-pwm-frequency-library”,可以在這個網址找到:https://code.google.com/archive/p/arduino-pwm-frequency-library/downloads

,這個libaray的範例中可以提供你在給定的frequency下,PWM resolution的上限。而程式用起來十分簡單如下:

// Arduino Mega,pins 2 - 13 and 44 - 46.  
  
#include <pwm.h>  
  
unsigned int duty = 1;   
  
//use pin 11 on the mega for this example to work  
  
int led = 11; // the pin that the LED is attached to  
  
void setup() {  
  
  // put your setup code here, to run once:  
  
  Serial.begin(57600);  
  
  InitTimersSafe(); //initialize all timers except for 0, to save time keeping functions  
  
  pinMode( led, OUTPUT);  
  
  SetPinFrequency(led, 10);  
  
    
  
}  
  
  
  
void loop() {  
  
  // put your main code here, to run repeatedly:  
  
    
  
  while(1){  
  
    pwmWriteHR(led,duty);  
  
    duty+=10;  
  
    delay(100);  
  
    Serial.print("duty = ");  
  
    Serial.println(duty);  
  
  }  
  
}  
  

然而這個library的function會有一些副作用,由於前面提過要改resolution和frequency需要更改Timer的一些設定,其中Arduino使用Timer0作為delay() 這個函是的時序。所以可能會有影響,另外她同時會更改所有Timer的設定,若有使用其他Timer的話要小心。

Arduino Framework裡的digitalPinToTimer Macro

在Trace arduino-pwm-frequency-library時,我發現其呼叫了這個Marco digitalPinToTimer,其實在Arduino Core[github]中還有多類似的Macro像是 digitalPinToInterrupt等(https://github.com/arduino/ArduinoCore-avr/blob/3f63f2975e7183c3254b6794bfcc8f19ca0301c9/variants/standard/pins_arduino.h)這些Macro實做方式為,在同一個header中使用PROGMEM(program space)建表,可知Arduino framework其實裡面可能使用了些program space存放這些表,即便你沒用到這些函式或marco。若是哪天你不小心寫滿了program space可能就是這個問題。(補充:大部分Arduino cpu如ATmega 2560為哈佛架構的RISC cpu,其有獨立的program memory存放code指令和一些const,還有獨立的data memory存放變數。而我們一般的桌機為拆分快取的哈佛架構,到L1或是L2 cache變為哈佛架構。而8051為Von Neumann,data space和program space共用memory (bus))。

comments powered by Disqus