« 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」カテゴリの記事

AVRマイコン」カテゴリの記事

コメント

もし・・・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分

Facebookでコメントした・・・
Lチカを超えて電子工作をちゃんと知るための「n講」第7回:ソースコードを覗くanalogWrite編 >

https://deviceplus.jp/mc-general/learning-electronics-07/?fbclid=IwAR1PDCH2RkkLzjQkLhAC82xGjbAst1jocXqd0F1IvheNIFpEVTe46Eo30uM

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年6月22日 (水) 11時45分

これでFasebookの書き込みが見れるかな?
https://www.facebook.com/noriyuki.shimotsuma/posts/2183219355161843

コメント歓迎!

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年6月22日 (水) 21時06分

※関連
・2022年7月25日:Atmega328P タイマー/カウンタ1の高速PWM動作
http://igarage.cocolog-nifty.com/blog/2022/07/post-d75779.html

・2022年7月26日:割り込みと絡むクリチカルな制御 変数にはVolatileを!
http://igarage.cocolog-nifty.com/blog/2022/07/post-d0b7fe.html

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年7月26日 (火) 14時35分

8ビットADCの表現値は0~255までの「0」を含めた256レベルなので分解能は1/256ではなく1/255です。
(1/256分解能が必要なら0~256を表現する必要がありますが、当然8ビットでは不可能です。)

投稿: | 2022年11月17日 (木) 09時28分

『1/1023 vs 1/1024』問題 analogReadを2bitにして確かめてみる
http://igarage.cocolog-nifty.com/blog/2022/11/post-bed4a2.html
をお読みください。

A/Dが2bitなら出てくる数字は「0,1,2,3」の4つ。
「1/256ではなく1/255です」だと分解能1/3になってしまいますが、1/4ですよね。
最大値の3は「基準電圧 - 1LSB」したポイントです。

1mmごとに目盛が刻まれた定規。
この定規の分解能は1mmで、(基点0mmにも目盛を入れるとして)11本目が10mm = 1cm。
目盛0~9の10本、1桁の数字で表現できるのは0~9mm。
0/10cm~9/10cmが計測範囲。
分解能は1/10cm = 1mm。
  1/9cmではありません。
10mm=11本目になると桁が増えます。
ADCだとこの10mmが基準電圧。
「基準電圧 - 1LSB」の入力電圧が最大値です。

DACも同じ。
2bit DACなら3で「基準電圧 - 1LSB」の電圧が出ます。
ArduinoのAnalaogWriteだと「255で全H」が出ますので「こりゃなんだ?!」っとなったわけです。
ほんとなら「255/256」にならなくちゃならない。
255で1/256のLパルスの出現がPWMを使った正しいDAC。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年11月17日 (木) 10時59分

分解能1/255なら「半値」が出せない。
  127/255? 128/255? どちらにする?
やはり128/256でしょう。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年11月17日 (木) 11時17分

2022年11月17日 09時28分書き込みの名無しさんにうかがいたい。

(1)「分解能は1/256ではなく1/255です。」はどこからのお知恵でしょうか?
  ネットから? 本から? 自分で考えて?

(2) エンジニアさん?
  マイコンのソフトを自分で書かれる?
  アセンブラの経験は?

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年11月17日 (木) 12時18分

1023 vs 1024 、255 vs 256 なんでみなさん「2^n-1」が好きなの?
http://igarage.cocolog-nifty.com/blog/2022/11/post-0339ae.html

をご覧ください。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年11月17日 (木) 19時25分

コメントを書く



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




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