ちょっとまとめてみました。 (関連記事へのリンクも注目)
コメント歓迎!! ご意見、お待ちしています。
==========================
◆A/Dコンバータのスケーリング方法
Arduino-UNOの内蔵A/Dコンバータは10bit。
0x0000~0x3FF:0~1023の数値が出てくる。
この値から、
入力電圧値を求める時
温度センサーなどのセンサー情報を処理する時
AnalogWriteなどに値を渡す時
などの処理でのミスが目立つ。
●基本的な知識:A/D変換データの意味
10bitのA/D変換値(ADC)と入力電圧(Vin)、そして基準電圧(Vref)
は以下の関係がある。
ADC = (Vin / Vref) * 1024
Vin = (ADC * Vref) / 1024
つまり10bit ADCの最大値1023は、Vref電圧より「-1LSB」だけ
小さい入力電圧(以上)の時に発生する。
変換値1023はVrefではない。
「Vref - 1LSB」の入力電圧、つまり「Vref * 1023 / 1024」が
ADC=1023での入力電圧となる。
結論:ADCからVinへのスケーリングは「1/1024」で計算しなけ
ればならない。
「1/1023」は間違いである。
※関連
2020年1月8日:ミスが広まる 1/1023 vs 1/1024
●基本的な知識:Vref電圧の実値
A/D変換器のVrefはAVCC電源(5V)、内蔵基準電圧(1.1V)、外部
基準電圧入力(AREF)に切り替えできる。
A/D変換を使って、入力電圧や温度センサーの測定値を求める
ときなどは、Vref電圧の実値が重要である。
「電源電圧 = 5V、内蔵基準電圧 = 1.1V」と決め打ちするのは
危険である。
A/D変換後、電圧や温度といった数値に直した値を評価する
時は、実際の電圧を測定し、その値をVref値とすべきである。
例えば・・・内蔵基準電圧=1.1Vは1.0V(min)~1.2V(max)と
データシートで規定されていて、1.1Vと決め打ちできないのは
あきらかである。
5Vや1.1Vで決め打ちして計算した電圧や温度、これを評価する
ときは、Vref値の誤差を含んでいるとの注意書きが必須である。
これを記していない記事は・・・「ちょっとなぁ~」
※関連
2019年3月22日:Arduinoのアナログ基準電圧入力
●間違った知識:map関数
map関数を使ってスケーリングを行う処理でのミスが目立つ。
mapは2点間の数値を元に線形補間を行う。
例えば、10bitのA/D値を8bit値に変換する時:analogReadして
analogWriteするような時、(最大値1023を255に変換する)この
ような記述を見かける。
y = map(x, 0, 1023, 0, 255);
もともと、10bit→8bitの変換は、値を1/4するだけである。
(四捨五入の話は別問題として)
この場合の補間式は 「y = 0.25x + 0」で、傾き1/4の線上
で 「x → y」の変換が行われる。
先ほどの「0, 1023, 0, 255」だと傾きが「255/1023」となり
本来の「256/1024」の補正線から外れる。
256/1024 → 0.25
255/1023 → 0.249266…
map関数の定義で、
map(value, fromLow, fromHigh, toLow, toHigh)
value: 変換したい数値
fromLow: 現在の範囲の下限
fromHigh: 現在の範囲の上限
toLow: 変換後の範囲の下限
toHigh: 変換後の範囲の上限
上限、下限という表記があるのでanalogRead、analogWrite
の最小、最大値を記入していると推測できる。
しかし、10bit→8bitの変換の時にその最大値を用いるのは
正しくない。
例えば、
y = map(x, 0, 1023, 0, 255);
の時、10bitの半値である512を代入すると127が返ってくる。
正しい値は8bitの半値=128である。
また1LSB多い1024を代入しても256とならず255となってしまう。
※参考
・2019年4月3日:線形補間って「LERP」って言うんだ!
以下のように考えてもこれがミスであることがわかる。
この考えで8bit→10bitの変換をすると、
y = map(x, 0, 255, 0, 1023);
と書くことになる。
これで「x=255」を変換すると「y=1023」が得られる。
一見正しいようだが(式どおりの答え)、当たり前に考えて255の
4倍である「1020」が正しい値である。
正しくは、
10bit→8bit変換 y = map(x, 0, 1024, 0, 256);
8bit→10bit変換 y = map(x, 0, 256, 0, 1024);
である。
10bit→8bit変換では以下の書式でも正しい値が得られる。
y = map(x, 40, 1000, 10, 250);
「0,1024,0, 256」の直線の傾き=0.25と、オフセット=0は同じ
である。
※余談
本来のスケーリング処理は下限上限という意味ではなく、
2点の表示値(A/D値の読み)と、実際の測定値(外部電圧計など
での読み)というふうに、キャリブレーションをおこなった
結果の補正に用いる。
A/D変換回路に付随する回路、例えば基準電圧値の誤差や前置
アンプのゲイン誤差やオフセット誤差、これらを補正するた
めに線形補間を行う。
※以下の例題がおかしいのが根本原因か。
http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2743
https://www.arduino.cc/reference/en/language/functions/math/map/
※関連
2020年5月16日:Arduino 10bit A/D値をmap関数でスケーリングする例
※追記 2020-05-19
10bit→8bitの変換では数値が大きくて誤差が見えにくい。
そこで、極端な例として、3bit→2bitへの変換を考えてみる。
map(x, 0, 7, 0, 3) …<a>
map(x, 0, 8, 0, 4) …<b>
<a>がよく出ている例題「0,1023, 0,255」での方法。
変換前と変換後の下限値・上限値を指定している。
<b>が補間直線の傾きを考えて記したもの。
3bit→2bitなんで単純に0.5。 2で割れば良い。
結果は、
入力値 <a> <b>
0 0 0
1 0 0
2 0 1
3 1 1
4 1 2
5 2 2
6 2 3
7 3 3
----------------
8 3 4
9 3 4
10 4 5
11 4 5
: :
16 6 8
24 10 12
32 13 16
40 17 20
mapでは最大値を超えても変換結果が出てくる(線形補間だから)
ので、8以上の値を入れるとミスしているのが良く分かる。
また、四捨五入の問題でもないことが見える。
線形補間の式であるmap関数、「map(x, 0,1023, 0,255)」は
10bit→8bit変換におて根本的に間違った使い方をしていること
がわかっていただけたであろうか。
※さらに追記
英文のArduino Reference
https://www.arduino.cc/reference/en/language/functions/math/map/
を見てみると、
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notes & Warnings
As previously mentioned, the map() function uses integer math.
So fractions might get suppressed due to this. For example, fractions
like 3/2, 4/3, 5/4 will all be returned as 1 from the map() function,
despite their different actual values.
So if your project requires precise calculations (e.g. voltage accurate
to 3 decimal places), please consider avoiding map() and implementing
the calculations manually in your code yourself.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
なんて書かれている。
google翻訳にかけると・・・
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
前述のとおり、map()関数は整数演算を使用します。
そのため、これにより分数が抑制される可能性があります。
たとえば、3 / 2、4 / 3、5 / 4などの分数は、実際の値が異なっ
ていても、すべてmap()関数から1として返されます。
したがって、プロジェクトで正確な計算が必要な場合(小数点以下3桁
まで正確な電圧など)は、map()を避け、手動でコードに計算を実装
することを検討してください。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
文末の「・・・yourself.」:自分でなんとかしろ!
っと言っているよう・・・
mapは悪くない。
線形補間を32bitの整数で実行している。
mapに与える数値をミスっているのが問題。
日本語解説で、下限・上限と記されているが
英文ではthe lower boundとthe upper bound 。
下限・上限としか言いようがないか・・・
数学的な言い回しだとどうなるんだろうか。
================================
それと、もうちょい。
map(x, 0,1023, 0,255) ・・・これが正しい例を紹介
これが、例えばA/D変換器のキャリブレーション結果で、
A/Dの値が「1023」の時にその入力電圧をテスターで計っ
てみたら「255.0mV」だった。 (精度表現のためあえて.0を追記)
この場合の変換式はこれで合っている。
このA/Dコンバータが実は10bitではなくもっと桁数が大きくって、
例えば12bitの分解能だったとする。
電圧を4倍の「1020.0mV」に上げてみると、A/D値も4倍になり
「4092」という測定結果になるはずである。(4095ではなく)
この場合のスケーリング値は「255/1023」で正しい。
10bit→8bitの変換、つまり数値を単純に1/4するだけの計算で
この値を使っているからおかしいわけだ。
================================
以下はコメントに対するお答え
2020年5月17日 (日) 16時40分 タナカ さん
※map関数の中で二つの上限値を+1なんて処理をしたら、本来の
線形補間ができなくなっていまう。
mapのコードそのものは変更しなくてよい。
「下限・上限」というから間違うのか。
重要なのは変換直線の傾きとオフセット。
それを決める2点のX,Y位置。
この一次関数って、中学生の問題?
しっかりしろエンジニア!
================================
※追記 コメントに書いたが本文にも載せておく。
mapが浮動小数点を許すのなら・・・
map(x, 0, 1023.0, 0, 255.75)で解決だ。
この補正値は256.0/1024.0と同じ0.25。
================================
■関連記事ピックアップ:居酒屋ガレージ日記から
2020年5月2日:Arduino 放置したポートが及ぼす電源電流変化
2020年4月25日:Arduino やっぱり気になる放置ポート
2020年2月10日:ArduinoのanalogWrite 1/255なの?
2020年2月4日:Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
2013年04月25日:AVRマイコンのAREFピン
2013年05月02日:AVRマイコンのAREFピン #2
最近のコメント