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固定の処理ができます。
・・・言葉で言ってもよく分からないんで、データシートのこの図
を見てもらいながら、実際に動かしてみます。
※テストプログラム : ダウンロード - test_pwm0_2.c
(ファイルタイプ、.inoじゃなく.cにしています)
・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を回すとこんな波形が得られます。
上側の非反転動作が現状の処理。
下側の反転動作が今回の信号処理。
TOP-valの値(右側の(9-n)の値)で、正しいD/Aの動きが得ら
れるのがわかるかと。
半値5で、ちゃんと50%デューティになっています。
実際の信号をオシロで見るとこんな具合。
信号は上からOCR0B、OCR0A、OCR2B、OCR2A。
(OCR0AとOCR2Aはトリガー源で方形波)
非反転と反転動作の違いは一番上と3番目に注目。
まずスイッチ=0。 デューティ10%と90%に。
スイッチ1になると、デューティ20%と80%。
スイッチ4で両方ともデューティ50%。
スイッチ8で90%と10%。
スイッチ9で全Hと全Lに。
いかがでしょうか。
反転動作を使って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に変更。
| 固定リンク
「Arduino」カテゴリの記事
- 1/nカウント方式とDDS方式の2相パルス発生回路(2024.10.13)
- おっと。map関数の計算桁に注意(2024.10.06)
- DDS方式の2相パルス発生回路、周波数スキャン機能を付ける(2024.10.05)
- 1クロックでも速くしたい 割込を「ISR_NAKED」で(2024.09.30)
- 1クロックでも速くしたい DDS方式の2相パルス発生器(2024.09.27)
「AVRマイコン」カテゴリの記事
- AVRマイコンAT90S1200を使ったデジタル時計(2024.06.02)
- 数値をBCD出力(表示)するルーチン #3(2024.05.03)
- 数値をBCD出力(表示)するルーチン #2(2024.04.28)
- ATmega4809のシリアルポート(2023.01.18)
- PWMでD/A変換:アナログマルチプレクサの応用で(2023.01.02)
コメント
もし・・・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分