1023 vs 1024

2022年11月17日 (木)

1023 vs 1024 、255 vs 256 なんでみなさん「2^n-1」が好きなの?

Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
この直前の名なしさんから頂戴したコメント(2022年11月17日  09時28分)。
1/255や1/1023がおかしいと分かってもらうのに、こんな絵を描いてみました。
いかがでしょう。
Ss11_20221117180001
1cmを8bitにしたとき、上位桁がどういうふうに見えるかを
表現してみました。

※定規はほんまもんをスキャン

1cm、2cm・・・とcm単位での区切りのところが、
0x0100、0x0200・・・と8bit単位での桁上がりに
なります。

そこで「分解能」を示すのなら、1cmピッチの定規だと「1/10」。
「1/9」じゃありません。
それを8bitにすると・・・
「1/255」だとアカンのが見えてくるかと。

※コメントした所をコピペしておきます。
~~~~~~~~~~~~~~~~~~~~
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」の入力電圧が最大値です。
~~~~~~~~~~~~~~~~~~~~


※追記 4cmを1023にしたら (2022-11-19)
Ss12
1mm目盛の定規、4cmでの分解能は「1/40」。
「1/39」ではありません。


※さらに追記 1cmフルスケールで
Ss2_20221126092001
定規の読みでも「-1LSB」が値が得られる最大値です。
これで、ADCによる測定が基準電圧を越えられない
ことがわかるでしょう。

| | コメント (4)

2022年11月12日 (土)

『1/1023 vs 1/1024』問題 analogReadを2bitにして確かめてみる

未だ、Arduino環境での「1/1023」が蔓延っています。
10bitでデータを出すanalogRead()の値から、実際の
入力電圧を算出するのは・・・
何度も書いてますが正しくは「1/1024」
「1/1023」じゃありません

2020年1月8日:ミスが広まる 1/1023 vs 1/1024
2020年5月17日:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数
2022年10月 6日:ArduinoのA/D:1/1023 vs 1/1024 その後

このA/D入力を10bitじゃなく2bitにして変化の様子
見られるようにしてみました。
オシロとシリアル・モニターで観察します。

使うのはArduino-UNOと抵抗、コンデンサ。
  LEDを光らせるのはオマケ。

Aa1_20221112132201
いったんA0を出力にして、コンデンサを放電します。
0V近くまでの放電を確かめたら、A0を入力に戻して
A/D変換を続行。
読み出した10bitA/D値の上位2bitを使って、2bitの
A/Dコンバータとして扱います。
その結果をD8(LSB)とD9ポート(MSB)に出力します。

こんな波形が得られます。
Aa2_20221112132601

100kΩの抵抗で1uFを充電するので、
0Vから上昇する指数関数カーブになります。
時定数は「100ms」。 (63%位置 3.15V)
  5V近くになるとなかなか上がらないので
  1000を越えると再放電しています。

2bitですので0V~5Vが4分割されます。
  5V÷4=1.25Vになるので、オシロのY軸を
  1.25V/divにしたかったのですが、
  1.24V/divしかできませんでした。

1.25Vピッチで4分割されてデータが変化して
いる様子が分かるかと思います。

シリアルモニターには、2bit値変化点での
10bit値(0~1023)が出てきます。
  :
1 256 512 768
0 257 512 768
1 256 512 768
0 256 513 768
0 256 512 768
  :

1/1024」の式にならえば2bitだと「1/4」。
1/1023」だと「1/3」。
「1/3」しちゃうと、半値の2.50Vは出ませんし、
実値入力が3.75Vを越えると、いきなり5.00Vに
なっちゃうしで、「1/3だとなんかおかしい」を
感じていただけるかと。

こんなスケッチです。

//  『1/1023 vs 1/1024』問題
// analogRead()を2bitにして動作を確かめてみる
// 10bitデータを1/256して上位2bitだけの値を見る
// A0 A/D入力 100k抵抗で5Vにプルアップ
// 1uFコンデンサでGNDに
// Lレベルを出力し放電してからスタート
// 0Vから始まるエクスポネンシャルカーブが得られる
// 2bitでの変化点をチェック
// D8 LED bit0 LSB Hで点灯
// D9 LED bit1 MSB

/***** STEUP *****/
void setup() {
pinMode(8, OUTPUT); // D8 出力に設定 HでLED点灯
pinMode(9, OUTPUT); // D9 (bit 1のLED)
analogReference(DEFAULT); // 5Vを基準電圧に
Serial.begin(9600); // 9600BPSでシリアル出力
}

/***** LOOP *****/
void loop() {
word ad10; // 10bit A/D値 0~1023
short ad2; // 1/256して2bitにした値(比較するので+/-値に)
short ad2x; // 変化チェックデータ (-1からスタート)
byte exc = 0; // 実行区分
while(1){
// A/D入力
ad10 = analogRead(A0); // 10bit A/D値読み出し (0~1023)
ad2 = ad10 / 256; // 2bitに (0,1,2,3) プラスの値
// LED出力
if(ad2 & 0b01) digitalWrite(8, HIGH); // bit 0
else digitalWrite(8, LOW);
if(ad2 & 0b10) digitalWrite(9, HIGH); // bit 1
else digitalWrite(9, LOW);
// 実行区分で処理
switch(exc){
case 0: // 放電開始
pinMode(A0, OUTPUT); // A/D入力をいったん出力に
digitalWrite(A0, LOW); // Lにしてコンデンサを放電
Serial.println(); // 改行
exc++; // 次処理
break;
case 1: // 0V近くになるまで待つ
if(ad10 < 1){ // 10bit A/D値がゼロに近づいた
pinMode(A0, INPUT); // A/D入力を入力に戻す
ad2x = -1; // 変化チェックデータをセット
exc++; // 次処理
}
break;
case 2: // 5V近くになるまで待つ
if(ad2 > ad2x){ // 2bit A/D値が大きくなった
ad2x = ad2; // チェックデータ更新
Serial.print(ad10); // 10bit A/D値をシリアル出力
Serial.print(" "); // space
}
if(ad10 > 1000){ // フルスケール近くになった
exc = 0; // 放電から繰り返し
}
break;
}
}
}
/*===== end of "test_ad_2bit.ino" =====*/

直線で変化するノコギリ波が良いのですが、
ノコギリ波を作ろうとすると、定電流回路用に
オペアンプがいります。
エクスポネンシャル・カーブだと抵抗+コンデンサ
だけなんで簡単。

※データシート(ATmega328P)の記載
・英文
Aa4_20221112150401
・和訳
Aa3_20221112150401

出てくるのはあたりまえに「1024」です。
最大値 「1023 = 0x3FF」になる電圧は「Vref - 1LSB」と。

※参
ルネサス:A/D,D/Aの量子化の単位は、2^n ? 2^n-1?
  量子化誤差というと「1/2LSB」の変動を問題にします。
  Vref=5Vで2bit ADCだと、1/2LSBは0.625V。
  さすがここまで大きくはフラフラしませんので、
  ADCの「原理的」な変換結果がオシロ波形に現れま
  した。

| | コメント (1)

2022年10月 6日 (木)

ArduinoのA/D:1/1023 vs 1/1024 その後

ちょっとネットを検索。
単純に1/1023や1/1024しているスケッチを見つけるの
じゃなく、いろんな考えを探してみました。

(1)ADCの出力値をアナログ値に換算する考え方
 http://kumikomi.k.asablo.jp/blog/2017/09/25/8683672

(2)DL03_ADCの細かい話:2021年06月17日
 https://kousukeno.seesaa.net/article/482057043.html

(3)A/D,D/Aの量子化の単位は、 2^n ? 2^n-1?
 https://community-ja.renesas.com/cafe_rene/forums-groups/analog/f/analog/1988/a-d-d-a-2-n-2-n-1

(4)analogReadの値の変換を誤解していた話(1023、1024問題)
 https://qiita.com/c_in_g/items/a87e5632aacbc79ba5d0

(5)10 bit ADC - divide by 1024 or 1023:Using ArduinoProgramming Questions
 https://forum.arduino.cc/t/10-bit-adc-divide-by-1024-or-1023/657174/9

(6)ADC: dividing by 1024 or 1023?
 https://forums.adafruit.com/viewtopic.php?f=25&t=25830


(3)と(4)で「2bitでの A/D」を示されています。
この考えで10bitまで有効桁を増やすとどうだ、でエエでしょう。

あと、半値や1/4値で考えるのも良。

それと、「D/Aだとどう考える」です。
A/Dだと電圧変換の結果が小数になるので、四捨五入がどうの
量子化がどうのが出てきます。
しかし、D/Aの入力値は1bit単位の整数。
そして、最大出力電圧は「Vref - 1LSB」。
どう頑張ってもVref電圧そのものは出せません。
出力電圧の変化量は「基準電圧÷分解能」。
正しくD/Aをするには・・・


※ArduinoでのD/Aというと
 Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
map関数 の問題とともに、これ↑が絡んできます。

| | コメント (1)

2021年8月 5日 (木)

PICでも「1/1023」。 トラ技2021年9月号別冊付録

今月発売のトランジスタ技術(2021年9月)には、
PIC開発マニュアル 2021」という別冊がオマケに付いています。

Tr09a
著者は後閑 哲也さん
ぱらぱらっとページをめくりますと、A/Dコンバータの解説のところ
(55p)に「1/1023」が出現。
10bit  A/Dの変換結果、0~1023から電圧値を算出しようという
目論見です。

T11a

やっぱ、「1/1023」を使いたくなるんですなぁ。

※参
ミスが広まる 1/1023 vs 1/1024
Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

| | コメント (4)

2020年9月 9日 (水)

書籍でもやってる「1/1023」と「map(x, 0, 1023, 0, 255)」

ちょっと気になってたんで、Arduino関連の書籍を図書館にリクエストしてました。
とりあえずこの2冊。

(1)Arduino電子工作実践講座

A11_20200909091401

(2)Arduinoをはじめよう

A21_20200909091401

(1)からはこれ。
analogReadで読んだA/D値を電圧値へ変換する処理。
1/1023」と「Vref値の決め打ち」が蔓延っています。

A12_20200909091501


(2)では。
10bit→8bitへのスケーリングで「map」関数。
  map(val, 0, 1023, 0, 255);

A22_20200909091501

※関連
Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数
ミスが広まる 1/1023 vs 1/1024
Arduino 10bit A/D値をmap関数でスケーリングする例
線形補間って「LERP」って言うんだ!


| | コメント (0)

2020年5月17日 (日)

Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

ちょっとまとめてみました。  (関連記事へのリンクも注目)
コメント歓迎!!  ご意見、お待ちしています。

==========================

◆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

 

| | コメント (18)

2020年1月 8日 (水)

ミスが広まる 1/1023 vs 1/1024

定番回路、「これはあかんやろ」を指摘したんが
液晶表示モジュールを4ビットモードで使ったときの空きピン処理
誰かが始めた間違ったつなぎ方(この場合はチップメーカーのアプリケーション
ノートか)がずっと踏襲されて広まってしまったというのを発見しました。
これをトラ技の記事にしたのが2009年5月号
以後、空きピンをGNDにつないでしまう例、だいぶ少なくなってきたように思います。

で、新年早々こんなのを発見しました。
 ・ラジオペンチ Arduinoを使ったバッテリー放電器
A/D変換データを電圧値にスケーリングする処理がスケッチに書かれています。
ArduinoのanalogRead() は10bit。 0~1023(10進)の値が得られます。
これの処理が間違っていたのです。
ここ↓
//  battV = analogRead(0) * Vcc / 1023.0;       // バッテリー電圧測定(これは間違いで、)
  battV = analogRead(0) * Vcc / 1024.0;       // バッテリー電圧測定(正しくはこっち

10bit値で1/1023と1/1024の違いですので、差は0.1%ほど
わずかですが、原理的に間違っていますんでこれはまずいです。

  ※A/Dコンバータが8bitだとミスによる実際の誤差
   が目につくでしょう。
   1/255 vs 1/256 で差が0.4%ほどになり、
   実値との差に気がつく
   テスターで読んだ値と「微妙に違うなぁ」と。
   しかし、10bitになると気が付きにくい。

  ※もっと極端に2bitだと分かりやすいかと。
   ATmega328PのA/Dは10bit。
   プログラムの処理としては2bit + 8bitに分割して
   レジスターから読み出します。
   この上位2bitのデータを考えてみましょう。

   出てくる数値は 「0 1 2 3」の4種類
   AREFを5Vとすると、1bitが1.25V
     0は0.0V。 そこから電圧を上げて、
     1になると1.25V。
     中間の2が2.50V。
     最大の3が出るのは入力電圧3.75V以上で

   「1/3」して電圧を求めるのは明らかに間違っていて、
   「1/4」が正しいことがわかるでしょう。

   A/Dの結果が2進だから勘違いするのかな。
   BCD出力だと・・・
    100カウント、0~99基準電圧5Vだと 0~4.95Vを表示。
    基準電圧に読み出しデータを乗じて1/100します。
    「1/99」する人はいないかと。
    

※理屈の詳細はラジオペンチさんの記事のコメントをご覧ください。

この「1/1023と1/1024」、調べてみると「1/1023」を使っているプログラム例があちこち
に出てきます。
どこが発端なのかは調べ切れてませんが、ArduinoのanalogRead処理で多いような気がします。

ArduinoではArduinoのアナログ基準電圧入力 も勘違いの処理が広まっているかもです。
正しく使えば、「外部基準電圧とAREFピンの間に抵抗を入れておく」なんてことはしなくてもokですから。

| | コメント (15)