« 14pinのAVRマイコン、ATtiny24が動かん! | トップページ | Arduino UNOのPWM出力を(ちょっと精密に)確かめる »

2020年8月13日 (木)

Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう

※検索「arduino analogWrite」。

ArduinoのanalogWrite、「ちょっとおかしい」のではと書いたことがあります。
2020年2月10日:ArduinoのanalogWrite 1/255なの?

内容は、
・設定値に対し、デューティーサイクルの変化がおかしい
 場所がある。
 (これではアナログ値を出力する関数とは名乗れないぞ)

今回は、このあたりを掘り下げて自前の処理を目指します。
  ※とりあえずタイマー0を触りますが、このタイマー、
   システムが使っているんであれこれ利用できない
   関数が出てきます。
   そのあたりは、
   2020年1月28日:Arduinoから「タイマー0」を取り上げる(ユーザーが使う)
   を参考に。

現状のanalogWriteのおかしい所・・・
  0~255の8bit値でデューティ0%(全区間L)~100%(全区間H)を
  制御している。
  8bitのD/A値なら、1/256が分解能。
  5V電源で0Vからスタートして0~255まで値を変化させた時、
  「5V * (255 / 256)」が最大値。
  5Vより5/256V低い電圧(約20mV)が値255の時に出てくるはず
  でそこで打ち止めなんだけれど、これ(255の時)を無理やり
  5Vとしている。
  どこかで1/256狂ってる
  これをanalogWriteというのはちょっと。

※どんな処理がされてるかは前記事をご覧ください。

その根本なのが「TOP値255の高速PWM動作」(モード3)にイニシャル
されたタイマー0と「非反転動作での出力制御」かと。

8bit D/Aコンバータの代わりになるようなPWMだと、どんな信号に
なるかというと。
  0 → デューティ0%で 全区間L
  1 → デューティ 1/256%
  2 → デューティ 2/256%
  :
 128 → デューティ 128/256% = 50% 方形波
  :
 254 → デューティ 254/256%
 255 → デューティ 255/256%
  ここでおしまい。 全区間Hはなし

ところが現状のanalogWriteは「1が2/256」「2が3/256」になっ
ていて、そのまま続いて「255で全区間H」となっています。
  ※わずか「1/256」の違いと言うなかれ。
   「0.4%」の「誤差」ですぞ!

この「1が2/256」になってしまう原因、これが「高速PWMモード」
の「非反転動作での出力制御」なんです。

このモードで「0」をタイマーのOCR(output compare register)に
書いても「全区間L」の出力は得られず、「0でも1/256」、
「1で2/256」になってしまうのです。

そして「255」なら強制的な全区間Hの処理は必要なく、タイマー
のOCRを255にするだけで、出力は256/256になって全区間Hの信号
が出てくるのです。

その対策。(本来、AVRマイコンの定石のはずなんだけど)
「非反転動作」ではなく「反転動作」の出力制御を使います。
すると・・・
  0 → 255/256
  1 → 254/256
  :
 254 → 1/256
 255 → 0/256 全区間L

反転動作をさせて、8bitの入力値val(0~255)をTOP値(255)から
減算した値をOCRに書くのです。

val=0は255となって全区間Lに。
1は254で1/256に、254は254/256、255は255/256となり
欲していた8bit D/Aの動作が得られます。

残るのは「全区間H」。
これは簡単です。
analogWriteの入力valの型は「int」。
だもんで、8bit内の0~255は0/256~255/256のデューティーで出力。
8bitを越える値は全区間H出力(ポート操作で)。
入力valの値、「0~255だけじゃなく、256も許容しちゃえ」という
全区間Hを得るための処理。
不正確さに目をつむるより、これもありでしょう。

「反転動作の出力制御」とこの数値判断だけで、正確なPWMによる
アナログ出力と出力H固定の処理ができます。

・・・言葉で言ってもよく分からないんで、データシートのこの図
を見てもらいながら、実際に動かしてみます。

A2_20200813132901

※テストプログラム : ダウンロード - test_pwm0_2.c
  (ファイルタイプ、.inoじゃなく.cにしています)

Arduino UNOはこんな回路に。

A1_20200813132901

・0~9を設定できるロータリーDIP SWをつなぐ
・8bitのタイマー0とタイマー2を使い、タイマー0を
 非反転出力動作で、タイマー2を反転出力動作で。
・TOP値が255だとわかりにくいので、波形生成モードを
 「7」のTOP値をOCR0Aで設定する高速PWM動作に。
トップ値は0~9をカウントする「9」に
・PWM出力はOCR0BとOCR2Bに出てくるのでこれを観察。
・OCR0AとOCR2Aはオシロのトリガー源に。トップ値でトグル。

ロータリーDIP SWを回すとこんな波形が得られます。

A3_20200813133001
上側の非反転動作が現状の処理。
下側の反転動作が今回の信号処理。
TOP-valの値(右側の(9-n)の値)で、正しいD/Aの動きが得ら
れるのがわかるかと。
半値5で、ちゃんと50%デューティになっています。

実際の信号をオシロで見るとこんな具合。
信号は上からOCR0B、OCR0A、OCR2B、OCR2A。
  (OCR0AとOCR2Aはトリガー源で方形波)

非反転と反転動作の違いは一番上と3番目に注目。

まずスイッチ=0。 デューティ10%と90%に。
B000

スイッチ1になると、デューティ20%と80%。
B001

スイッチ4で両方ともデューティ50%。
B004

スイッチ8で90%と10%。
B008

スイッチ9で全Hと全Lに。
B009

いかがでしょうか。
反転動作を使ってTOP値からの減算データをOCRに与えれば
まっとうなD/Aになるということが。

analogWriteの処理のように非反転動作のままでということなら、
 ・0の時は強制的L出力。
 ・1~255は「-1」して処理。これで1/256~255/256に。
 ・全H出力したいのなら、提示したval=256を許容する
  処理で。
というふうになるでしょうか。

0~255でまっとうな8bit D/A出力をということなら、
反転動作で「255-val」をOCRにというのがシンプルな
考え方で正解かと。
  ※分解能の高い↓のタイマー1の処理にも絡みます。

さて、示したのは8bitのタイマー0とタイマー2でしたが、
タイマー1は16bitです。
レジスタを直接操作すれば、ちょいと高性能な12bitや14bitの
D/Aコンバータが簡単に構成できます。
ここではなおさら「反転動作」でのD/A処理が正解かと。

2020年5月21日:JIS C8708:2019充放電実験回路
の制御プログラムを見てもらえれば、タイマー2のPWMで
12bit D/Aコンバータを作り、充電電流と放電電流を制御している
様子が分かるかと。
精度が上がると基準電圧も大事で、どうやってPWMから得ているか
回路図も参考にどうぞ。

これも参考に

2020年8月11日:14pinのAVRマイコン、ATtiny24が動かん!
 タイマー0をTOP値99のPWMで使ってます。

2020年8月10日:オリンパス OM-D E-M1mk2の充電器の充電表示ランプを判別その2
 R/2RのD/AをやめてPWMに変更。

|

« 14pinのAVRマイコン、ATtiny24が動かん! | トップページ | Arduino UNOのPWM出力を(ちょっと精密に)確かめる »

Arduino」カテゴリの記事

コメント

もし・・・analogWriteが全H出力のために「0~255,256」を
許容するのなら・・・
きっと、map関数の誤用問題↓
http://igarage.cocolog-nifty.com/blog/2020/05/post-115911.html
が変わっていたのじゃないかしら。
10bit→8bitにスケーリングした結果・・・
『「256」をどうするねん』っと誰かが気づいてくれそう(笑)。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2020年8月13日 (木) 18時01分

Arduino 日本語リファレンス:analogWrite(pin, value)
http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2153
の最後のほう、
~~~~~~~~~~~~~~~~~~~~~~~~~~
【注意】
ピン5と6のPWM出力はデューティ比が高めになります。
これはPWM出力に使う内蔵タイマを、millis()やdelay()と
いった関数でも利用していることが原因です。
注意が必要なのはディーティ比を低くして使うときで
(例えば0~10)、仮にパラメータを0に設定しても、
ピン5と6の出力は完全にはオフにならないかもしれません。
~~~~~~~~~~~~~~~~~~~~~~~~~~
このように記されてますが、ちょいと違うような・・・

「ArduinoのanalogWrite 1/255なの?」
http://igarage.cocolog-nifty.com/blog/2020/02/post-d4c821.html
に書いた「wiring_analog.c」でのanalogWriteの処理、
見直しますと★1、★2の所に追加しなければならない手順
がありますわ。

初めてanalogWriteが呼ばれたときにvalが0あるいは255なら
出力がLあるいはHに張り付きます。

ところが、一度でも0と255以外のPWMを出しちゃうと、
TCCR0Aの制御により、元はI/Oポートだったのがタイマー回路の
出力に切り替わります。

★1と★2の処理を有効にするには、「TCCR0A」を操作して、
タイマー回路出力からI/Oポート出力に戻さなくてはなりません。

現在のままだとPWM数値をあれこれ可変して操作したとき、
255で全Hは出ますが、0では全Lになりません。

つまり、0~255のval値で1/256~256/256となります。
  ※255で256/256はたまたまの非反転動作の性質で。

Arduinoリファレンスでの誤差原因追及、間違っています。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2020年8月14日 (金) 08時39分

↑の書き込み、
★1と★2のところで抜けているぞという処理、
digitalWrite()の中で『turnOffPWM(timer)』という
手順で、ちゃんとI/Oに戻されていました。

だもんで、この書き込みは無かったことで
よろしくです。

それでも、PWMのズレは発生しますんで。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2020年8月14日 (金) 10時09分

※関連
・2020年8月16日:Arduino UNOのPWM出力を(ちょっと精密に)確かめる
http://igarage.cocolog-nifty.com/blog/2020/08/post-ba853b.html

・2020年2月10日:ArduinoのanalogWrite 1/255なの?
http://igarage.cocolog-nifty.com/blog/2020/02/post-d4c821.html

・2020年2月 4日:Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
http://igarage.cocolog-nifty.com/blog/2020/02/post-c1f98a.html

・2019年12月20日:Arduino UNOで周波数カウンタ
http://igarage.cocolog-nifty.com/blog/2019/12/post-8ded09.html

投稿: 居酒屋ガレージ店主(JH3DBO) | 2020年8月16日 (日) 17時17分

コメントを書く



(ウェブ上には掲載しません)




« 14pinのAVRマイコン、ATtiny24が動かん! | トップページ | Arduino UNOのPWM出力を(ちょっと精密に)確かめる »