Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMの例
analogReadでの電圧値変換問題(1023 vs 1024)だけじゃなく
・2020年8月13日:Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
analogWriteにも噛み付いています。
で、まっとうなPWM出力制御の例を出していなかったかと
(アプリケーション・スケッチの中では
いっぱい使っているんで探せば出てくるけど)
いうことで、OC2AとOC2Bの2chをPWM出力にして
まっとうなPWMの方法(analogWriteと大きい声で
言っても恥ずかしくないような)を示しておきます。
/***** タイマー2でまっとうなPWMを *****/
// タイマー2 PWM出力 0~255,256を許容
// 0:duty=0, 128:duty=128/256, 255:duty=255/256
// 256:duty 100%(Hレベル)
/***** PWM出力 OC2A *****/
void pwm2a(short d)
{
if(d < 0) d = 0; // 0未満は0に
if(d < 256){ // 255まで
OCR2A = (byte)(255 - d); // duty 0~255/256 PWM
TCCR2A |= 0b11000011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B (PWM反転動作)
// ++--------- OC2A PWM反転動作
}
else{ // 256以上はHレベル出力
PORTB |= (1 << PB3); // PB3をHに
TCCR2A &= 0b00110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B (PWM反転動作)
// ++--------- OC2A OC2A切断
}
}
/***** PWM出力 OC2B *****/
void pwm2b(short d)
{
if(d < 0) d = 0; // 0未満は0に
if(d < 256){ // 255まで
OCR2B = (byte)(255 - d); // duty 0~255/256 PWM
TCCR2A |= 0b00110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OCR2B PWM反転動作
// ++--------- OCR2A (PWM反転動作)
}
else{ // 256以上はHレベル出力
PORTD |= (1 << PD3); // PD3をHに
TCCR2A &= 0b11000011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OCR2B OC2B切断
// ++--------- OCR2A (PWM反転動作)
}
}
/***** SETUP *****/
void setup() {
// タイマー2 PWM出力に初期化
// OC2A:PB3 , OC2B:PD3出力ポートに
DDRB |= (1 << PB3); // OC2A : PB3
DDRD |= (1 << PD3); // OC2B : PD3
// タイマー2 980Hz PWM出力
TCCR2A = 0b11110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B PWM反転動作
// ++--------- OC2A PWM反転動作
TCCR2B = 0b00000100;
// || |+++--- CS 1/64 250kHz → 1/256 976.6Hz(1.024ms)
// || +------ WGM22
// ++--------- FOC2
OCR2A = 255 - 0; // duty 0に
OCR2B = 255 - 0; // duty 0
TIMSK2 = 0b00000000;
// ||+--- TOIE2 割込オフ
// |+---- OCIE2A
// +----- OCIE2B
// タイマー0,1と2の前置分周器をリセット
GTCCR = 0b10000000;
// | |+--- PSRSYNC タイマ0,1
// | +---- PSRASY タイマ2
// +---------- TSM タイマ同期動作
GTCCR = 0b10000011; // リセット操作
TCNT0 = 0;
TCNT2 = 0;
GTCCR = 0b00000000; // 解除
}
// 出力データ
struct st_pwm{
short p0a; // OC0A PD6 (6)
short p0b; // OC0B PD5 (5)
short p2a; // OC2A PB3 (11)
short p2b; // OC2B PD3 (3)
};
// 順に出力
st_pwm pwm_tbl[]={
{ 1, 0, 0, 254 }, // <0>
{ 1, 1, 1, 255 }, // <1>
{ 1, 2, 2, 256 }, // <2>
{ 1, 254, 254, 0 }, // <3>
{ 1, 255, 255, 1 }, // <4>
{ 1, 256, 256, 2 }, // <5>
}; // | | | +--- OC2B PD3 (3)
// | | +-------- OC2A PB3 (11)
// | +------------- OC0B PD5 (5)
// +------------------- OC0A PD6 (6)
/***** LOOP *****/
void loop() {
byte i;
while(1){
for(i = 0; i < 6; i++){
analogWrite(6, pwm_tbl[i].p0a); // OC0A (6)
analogWrite(5, pwm_tbl[i].p0b); // OC0B (5)
pwm2a(pwm_tbl[i].p2a); // OC2A (11)
pwm2b(pwm_tbl[i].p2b); // OC2B (3)
delay(2000); // 2秒待ち
}
}
}
// 2025-03-22 JH3DBO
元のanalogWrite、これの何がおかしいのか気付いたのが
・2020年2月10日:ArduinoのanalogWrite 1/255なの?
このあたり。
~~~~~~~~~~~~~~~~~~~~~~~~~~~
タイマー0のanalogWriteに対して、0~255の数値を順に
与えてみると・・・周期1.024msのパルスに対して、
0ならずっとLOW
1だと8usのHIGHパルス ▼1
2だと12usのHIGHパルス
:
253だと8usのLOWパルス
254だと4usのLOWパルス
255だとずっとHIGH
が観察されます。
タイマー0に関して、値0と1のところ▼1で、ほんとなら4usステップ
になって欲しいのが8usとなっていて、値1~255とは異なるピッチに
なっていることがわかります。
デューティ50%が欲しい時は127。
普通の8bit D/Aコンバータだと128で半値がでてきます。
8bit D/Aコンバータの代わりになるようなPWMだと、どんな信号に
なるかというと。
0 → デューティ0%で 全区間L
1 → デューティ 1/256%
2 → デューティ 2/256%
:
128 → デューティ 128/256% = 50% 方形波
:
254 → デューティ 254/256%
255 → デューティ 255/256%
ここでおしまい。 全区間Hはなし。
~~~~~~~~~~~~~~~~~~~~~~~~~~
「半値」や「1/4値」でチェックすると、「何かおかしい」
と気付くはずなんですが・・・
上に上げた「まっとうなPWM」では、0~255で
0/256~255/256を出力。
そして8bitを越えるけど256でduty 100%となるような
処理を入れ込みました。
オシロで波形を観察するとこんな様子になります。
OC0Aがトリガー用。
OC0BとOC2Aに同じ値を入れてます。
・2020年8月16日:Arduino UNOのPWM出力を(ちょっと精密に)確かめる
analogWriteも
・2022年11月12日:『1/1023 vs 1/1024』問題 analogReadを2bitにして確かめてみる
と同じように2bitにしたら分かるかな。
0はゼロ。
1で1/4の1.25V。
2で半値の2.50V。
3で3.75Vが出てきます。
5.00Vは2bitのD/Aコンバータでは出せません。
Vout = (Vref * data) / (2^n)
という式で出力が出てきます。
フルスケールの3で5.00Vを出したいとなると、
0はゼロ。
1で1.67V。
2で3.33V。
3で5.00V。
アナログ的なゲイン調整でこういったことはできますが、
半値がどこかへ行ってしまいます。
最近のコメント