« Arduino-UNO + SDカードでシリアルデータロガー 完成形 今度こそ | トップページ | Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数 »

2020年5月16日 (土)

Arduino 10bit A/D値をmap関数でスケーリングする例

Arduino-UNOのA/Dは10bit 。 0~1023の値が出てきます。
この値のスケーリング例題に、しばしば「map関数」が使われてます。
こんな具合・・・

  int val = analogRead(0);
val = map(val, 0, 1023, 0, 255);
analogWrite(9, val);

0~1023の値を0~255の値に置き換えようというもくろみです。 
本来は、単純に「1/4」すれば良いだけ。
   ※10bitの上位8bitを使う。 2bit右シフトで。

しかし、このmap()の値だと10bitの半値512がうまく変換できないのですよね。
128になりません。
mapは整数計算ですんで、127になってしまいます。
  ※四捨五入の話とは別問題

そこで、「map関数」を浮動小数点にしてみました。
(val, 0, 1023, 0, 255) と (val, 0, 1024, 0, 256)の違いを見てもらいましょう。

※シリアルモニターを起動していろんな数値を入力できるようにしてあります。

//  「map」を浮動小数点にしてみる

/***** mapをfloatに *****/
float mapf(float x,
float in_min, float in_max,
float out_min, float out_max)
{
return (x - in_min) *
(out_max - out_min) / (in_max - in_min)
+ out_min;
}

/***** シリアル1行入力 *****/
#define RXBF_SIZ 16 // 文字バッファ文字数
char rx_bff[RXBF_SIZ+1]; // 受信文字バッファ (+null)
byte f_rxok; // 受信データありフラグ
/***** シリアル1行受信 *****/
// CRでターミネート f_rxokを1に
// BSで1文字戻す
// 1行文字入力に使えるSerialEventというのがあるらしいが・・・
void rxbff(void)
{
static byte cnt = 0; // 受信文字数
char c;
if(Serial.available()){ // 受信データあり
if(f_rxok == 0){ // 前データ受信処理した
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR?
rx_bff[cnt] = '\0'; // nullを最後に
Serial.println(); // 改行
f_rxok = 1; // 受信成功
cnt = 0; // 最初から
}
else if(c == '\x08'){ // BS?
if(cnt > 0){
cnt--; // 1文字戻す
Serial.print("\b \b"); // BS,space,BS
}
}
else{ // 文字
if((cnt < RXBF_SIZ) && // バッファサイズ内
(isprint(c))){ // 表示可能文字0x20~0x7E
Serial.write(c); // エコーバック
rx_bff[cnt] = c; // バッファに入れる
cnt++; // 1文字進める
}
}
}
}
}

/***** SET UP *****/
void setup()
{
Serial.begin(9600);
Serial.println("Test float-map");
}
/***** LOOP *****/
void loop()
{
float d;
while(1){
rxbff(); // 1行受信処理
if(f_rxok){
d = atof(rx_bff);
Serial.print("float-map:");
Serial.print(mapf(d, 0,1023, 0,255), 4); // ★1
Serial.print(" ");
Serial.println(mapf(d, 0,1024, 0,256), 4); // ★2
f_rxok = 0;
}
}
}

/***** 結果 *****
10bit→8bitの変換なんで単純に1/4したら良いだけ
それを0~1023→0~255で変換するものだから・・・
Test float-map
0 0はゼロ
float-map:0.0000 0.0000
1023 1023は255
float-map:255.0000 255.7500
512 半値の512は違いが出る
float-map:127.6246 128.0000
4 4/1024も
float-map:0.9971 1.0000
8 8/1024も
float-map:1.9941 2.0000
12 12/1024も
float-map:2.9912 3.0000
1020 1020/1024も
float-map:254.2522 255.0000
*****/

いかがでしょうか?

この10bit A/D値を元にした勘違い、むちゃ広まっていますよ。
入力の最小・最大を0~1023にするから端数が発生。
たとえば、map(val,  40, 1000,  10, 250) っと中間値の
ポイントを指定してもミスなく計算してくれます。 →線形補間
map関数は、入力値に対するリミットを処理していませんので、
mapの中で指定するin_min、in_max(最小・最大)を越えても
正しく計算してくれるのに0~1023が最小・最大だと思ってしま
うからややこしい。
困ったことです。

本来、10bit→8bitのスケーリングでの定数は「256/1024 = 1/4」。
傾き「1/4」の直線に乗っています。
  「y = ax + b」   a=0.25b=0

それが map(val, 0, 1023, 0, 255)だと補正直線の傾きが
255/1023」。
  a=0.249267  ・・・補正直線の傾きが異なります
これ、数学というより算数。

この間違い、誰か、どないかしてほしい!!!

※関連
2020年1月8日:ミスが広まる 1/1023 vs 1/1024
2020年2月10日:ArduinoのanalogWrite 1/255なの?
2019年4月3日:線形補間って「LERP」って言うんだ!

|

« Arduino-UNO + SDカードでシリアルデータロガー 完成形 今度こそ | トップページ | Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数 »

Arduino」カテゴリの記事

コメント

まとめ:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数
http://igarage.cocolog-nifty.com/blog/2020/05/post-115911.html

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

★書籍でもやってる「1/1023」と「map(x, 0, 1023, 0, 255)」
http://igarage.cocolog-nifty.com/blog/2020/09/post-e67167.html

投稿: 居酒屋ガレージ店主(JH3DBO) | 2020年9月 9日 (水) 09時25分

コメントを書く



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




« Arduino-UNO + SDカードでシリアルデータロガー 完成形 今度こそ | トップページ | Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数 »