1023 vs 1024

2025年3月22日 (土)

Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMの例

analogReadでの電圧値変換問題(1023 vs 1024)だけじゃなく
2020年8月13日:Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
analogWriteにも噛み付いています。

で、まっとうなPWM出力制御の例を出していなかったかと
  (アプリケーション・スケッチの中では
   いっぱい使っているんで探せば出てくるけど)
いうことで、OC2AとOC2Bの2chをPWM出力にして
まっとうなPWMの方法(analogWriteと大きい声で
言っても恥ずかしくないような)を示しておきます。

/*****  タイマー2でまっとうなPWMを *****/
// タイマー2 PWM出力 0~255,256を許容
// 0:duty=0, 128:duty=128/256, 255:duty=255/256
// 256:duty 100%(Hレベル)
/***** PWM出力 OC2A *****/
void pwm2a(short d)
{
if(d < 0) d = 0; // 0未満は0に
if(d < 256){ // 255まで
OCR2A = (byte)(255 - d); // duty 0~255/256 PWM
TCCR2A |= 0b11000011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B (PWM反転動作)
// ++--------- OC2A PWM反転動作
}
else{ // 256以上はHレベル出力
PORTB |= (1 << PB3); // PB3をHに
TCCR2A &= 0b00110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B (PWM反転動作)
// ++--------- OC2A OC2A切断
}
}

/***** PWM出力 OC2B *****/
void pwm2b(short d)
{
if(d < 0) d = 0; // 0未満は0に
if(d < 256){ // 255まで
OCR2B = (byte)(255 - d); // duty 0~255/256 PWM
TCCR2A |= 0b00110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OCR2B PWM反転動作
// ++--------- OCR2A (PWM反転動作)
}
else{ // 256以上はHレベル出力
PORTD |= (1 << PD3); // PD3をHに
TCCR2A &= 0b11000011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OCR2B OC2B切断
// ++--------- OCR2A (PWM反転動作)
}
}

/***** SETUP *****/
void setup() {
// タイマー2 PWM出力に初期化
// OC2A:PB3 , OC2B:PD3出力ポートに
DDRB |= (1 << PB3); // OC2A : PB3
DDRD |= (1 << PD3); // OC2B : PD3
// タイマー2 980Hz PWM出力
TCCR2A = 0b11110011;
// |||| ++--- 8bit 高速PWM動作
// ||++------- OC2B PWM反転動作
// ++--------- OC2A PWM反転動作
TCCR2B = 0b00000100;
// || |+++--- CS 1/64 250kHz → 1/256 976.6Hz(1.024ms)
// || +------ WGM22
// ++--------- FOC2
OCR2A = 255 - 0; // duty 0に
OCR2B = 255 - 0; // duty 0
TIMSK2 = 0b00000000;
// ||+--- TOIE2 割込オフ
// |+---- OCIE2A
// +----- OCIE2B
// タイマー0,1と2の前置分周器をリセット
GTCCR = 0b10000000;
// | |+--- PSRSYNC タイマ0,1
// | +---- PSRASY タイマ2
// +---------- TSM タイマ同期動作
GTCCR = 0b10000011; // リセット操作
TCNT0 = 0;
TCNT2 = 0;
GTCCR = 0b00000000; // 解除
}

// 出力データ
struct st_pwm{
short p0a; // OC0A PD6 (6)
short p0b; // OC0B PD5 (5)
short p2a; // OC2A PB3 (11)
short p2b; // OC2B PD3 (3)
};
// 順に出力
st_pwm pwm_tbl[]={
{ 1, 0, 0, 254 }, // <0>
{ 1, 1, 1, 255 }, // <1>
{ 1, 2, 2, 256 }, // <2>
{ 1, 254, 254, 0 }, // <3>
{ 1, 255, 255, 1 }, // <4>
{ 1, 256, 256, 2 }, // <5>
}; // | | | +--- OC2B PD3 (3)
// | | +-------- OC2A PB3 (11)
// | +------------- OC0B PD5 (5)
// +------------------- OC0A PD6 (6)

/***** LOOP *****/
void loop() {
byte i;
while(1){
for(i = 0; i < 6; i++){
analogWrite(6, pwm_tbl[i].p0a); // OC0A (6)
analogWrite(5, pwm_tbl[i].p0b); // OC0B (5)
pwm2a(pwm_tbl[i].p2a); // OC2A (11)
pwm2b(pwm_tbl[i].p2b); // OC2B (3)
delay(2000); // 2秒待ち
}
}
}
// 2025-03-22 JH3DBO

元のanalogWrite、これの何がおかしいのか気付いたのが
  ・2020年2月10日:ArduinoのanalogWrite 1/255なの?
このあたり。

~~~~~~~~~~~~~~~~~~~~~~~~~~~
タイマー0のanalogWriteに対して、0~255の数値を順に
与えてみると・・・周期1.024msのパルスに対して、
  0ならずっとLOW
  1だと8usのHIGHパルス  ▼1
  2だと12usのHIGHパルス
   :
  253だと8usのLOWパルス
  254だと4usのLOWパルス
  255だとずっとHIGH
が観察されます。
タイマー0に関して、値0と1のところ▼1で、ほんとなら4usステップ
になって欲しいのが8usとなっていて、値1~255とは異なるピッチ
なっていることがわかります。

デューティ50%が欲しい時は127。
普通の8bit D/Aコンバータだと128で半値がでてきます。

8bit D/Aコンバータの代わりになるようなPWMだと、どんな信号に
なるかというと。
  0 → デューティ0%で 全区間L
  1 → デューティ 1/256%
  2 → デューティ 2/256%
  :
 128 → デューティ 128/256% = 50% 方形波
  :
 254 → デューティ 254/256%
 255 → デューティ 255/256%
  ここでおしまい。 全区間Hはなし
~~~~~~~~~~~~~~~~~~~~~~~~~~

「半値」や「1/4値」でチェックすると、「何かおかしい」
と気付くはずなんですが・・・

上に上げた「まっとうなPWM」では、0~255で
0/256~255/256を出力。
そして8bitを越えるけど256でduty 100%となるような
処理を入れ込みました。

オシロで波形を観察するとこんな様子になります。
OC0Aがトリガー用。
OC0BとOC2Aに同じ値を入れてます。
I01b

2020年8月16日:Arduino UNOのPWM出力を(ちょっと精密に)確かめる

analogWriteも
2022年11月12日:『1/1023 vs 1/1024』問題 analogReadを2bitにして確かめてみる
と同じように2bitにしたら分かるかな。
  0はゼロ。
  1で1/4の1.25V。
  2で半値の2.50V。
  3で3.75Vが出てきます。
5.00Vは2bitのD/Aコンバータでは出せません。

  Vout = (Vref * data) / (2^n)
という式で出力が出てきます。

フルスケールの3で5.00Vを出したいとなると、
  0はゼロ。
  1で1.67V。
  2で3.33V。
  3で5.00V。
アナログ的なゲイン調整でこういったことはできますが、
半値がどこかへ行ってしまいます。

| | コメント (0)

2025年3月19日 (水)

1023 vs 1024:AVRマイコンとPICマイコンのデータシートより

10bit A/D変換データから電圧を計算するときに使われている
「1/1023」が気になってしかたありません。
「1/1024でっせ」っと何度も言ってるんですが・・・

AVRマイコンとPICマイコンのデーターシートから「1/1024」である
根拠をピックアップしてきました。

Analogread1023

AVRマイコンはA/Dデータが出てくる「式」。
PICマイコンはアナログ入力に対する出力データのグラフ。

「フルスケールが出てくる点はVref - 1LSBだ」っと
どちらも明確に記されています。

※関連する過去記事
2024年2月13日:トラ技のArduino Uno R4特集でも1/1023
2024年2月7日:トランジスタ技術2024年3月号に記事が載りました
  トラ技Jr.コーナ『実はワナだらけ…確実に動かすArduino Uno R3』
2022年11月17日:1023 vs 1024 、255 vs 256 なんでみなさん「2^n-1」が好きなの?
2022年11月12日:『1/1023 vs 1/1024』問題 analogReadを2bitにして確かめてみる
2021年8月5日:PICでも「1/1023」。 トラ技2021年9月号別冊付録
2020年5月17日:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

振り返ってみますと、もう5年ほどウダウダと言っております
トラ技にも記事を載せてもらったし。
皆さん、見てないのでしょうなぁ。

| | コメント (0)

2025年3月10日 (月)

1/1023監視団 活動中!

CQ出版 トランジスタ技術2025年4月号 (最新号)。
特集の
  フレッシャーズに贈る部品・回路設計・測定の基礎
   オームの法則の現実から!電子回路入門&検定
「エンジャー」さんが記事を書かれています。
その第2部・第3章
  現実を「計る」からはじまる!
   センサ測定回路
128頁の【図12】【リスト1】に、
Arduino UNO R3でのanalogReadが出ていて
やってます。
  voltage = ad * 5.0 / 1023

10131

「電圧を計る」のじゃないのなら、なんでも
良いのですが、得るのが電圧ですんで「1/1023」は
間違い。

ここの他、129頁、131頁、133頁、139頁に
1/1023が出現。

131頁の【図17】の説明では、
 「referenceVolatgeの値を修正したら
  マルチメータとほぼ一致」
とありますので、
  「5.0V決め打ちはダメよ」
の解説はちゃんとされています。

また、152頁には
  「map(ad, 0, 1023, 0, 3)」
とmap命令が使われてますが、これは
  「10bit→8bit変換」
のような使われ方じゃないのでOKでしょう。

トランジスタ技術2024年3月号のトラ技Jr.コーナ
  『実はワナだらけ…確実に動かすArduino Uno R3
を読んでおいてもらいたいでっす。


| | コメント (0)

2024年10月 6日 (日)

おっと。map関数の計算桁に注意

Arduinoのmap関数に関して恨みを持っている
がごとくウダウダ言っております。
 ・2020年5月17日:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

トラ技にも載りました。
 ・トランジスタ技術2024年3月号『実はワナだらけ…確実に動かすArduino Uno R3

で、今回の
 ・2024年10月5日:DDS方式の2相パルス発生回路、周波数スキャン機能を付ける

ここで、「おっと危ない」だったのが「map」関数の数値範囲。

周波数sweepでは、
  周波数の値は0.01Hz単位で9999.99Hzが最大。
  BCDで8桁。999999=0xF423F 20bit

  経過時間が10ms単位で600.00秒が最大。
  BCDで5桁。60000=0xEA60 16bit

sweep処理では、こんな値を使います。
  Lo側周波数 1~9999Hz   f1
  Hi側周波数 1~9999Hz   f2
  Rise,fall時間 0.0~600.0秒 t1

sweep周波数の設定は1Hz単位ですが、内部では0.01Hz
単位に直します。
sweep時間も0.1秒を0.01秒にして60000が最大。

そして、「map関数」はこのように定義されています。

long map(long x,         入力
  long in_min, long in_max,  X1,X2 変換元
  long out_min, long out_max) Y1,Y2 変換先
{
 return (x - in_min) * (out_max - out_min) ★1
   /  (in_max - in_min)
   + out_min;
}

sweep処理で経過時間tmからsweep周波数fmを計算するとき、
map関数をこのように使いました。

  fm = map (tm , 0, t1, f1, f2);

このとき、周波数と経過時間が設定の最大値に近づくと、
★1の乗算に失敗するのです。

周波数(20bit)と時間(16bit)を掛け算すると、
値によっては「36bit」の数字が出てきます。

map関数の入力と出力、単独で見るとそれぞれは
32bitの数値範囲に入っていますが、内部計算を考えると
32bitではアウトなのです。

内部は「int64_t」で計算しなくちゃなりません。

 return ((int64_t)(x - in_min) *
     (int64_t)(out_max - out_min))
     / ((int64_t)(in_max - in_min))
     + out_min;

乗算部、除算部をint64_tにキャストして64bitで
計算するようにしました。

LOG sweepではfloatにしたmapも使っています。

float mapf(float x,
  float in_min, float in_max,
  float out_min, float out_max)

2022年11月6日:Arduino map関数をfloatに

もうひとつ。
t1がゼロだとゼロ除算してしまうので、mapに
食わす前に判定して除外しておかなくちゃなりません。
t1がゼロなら fm=f1でリターンてな処理です。

| | コメント (0)

2024年5月13日 (月)

サーミスタでの温度測定、「inf」の出現に耐えられるか?

2023年3月21日:A/Dコンバータでサーミスタの抵抗値を読む サーミスタをつなぐ場所は?
で問題にしましたが、A/D入力するサーミスタをつなぐ位置が重要です。
A02_20230321164901
サーミスタを電源側につなぐと、
  温度の上昇でサーミスタの抵抗値が減少
  それに連れてA/D値が上昇
となり、温度が上がるとA/D値も上がり、感覚的に
合うのでしょう。

・A/D値からサーミスタの抵抗値の計算式
A3_20230321165401

このサンプルとして
  ・おもろ家さんのArduino 入門 Lesson 18 【サーミスタ編】
のスケッチ使わせてもらいます。
おもろ家さんの「つなぎ」でもサーミスタは電源側。
基準抵抗がGND側です。

この時の問題点をお復習い。
  (a)サーミスタの接続線が短絡したら
  (b)サーミスタが外れてオープンになったら

まず(a)。
A/D値はフルスケールの「1023」に。
その結果、サーミスタの抵抗値は
  (1024÷1023 - 1) ×10kΩ
となり、約9.8Ω
B定数による温度計算に進むと352℃という
値が出てきます。

次に(b)
A/D値は「ゼロ」になり、サーミスタの抵抗値計算で
ゼロ除算」が生じます。
その結果、抵抗値は「無限大」。
続く、温度計算では「ケルビン温度」の「-273.15℃」が
出てきます。
  ※Arduino UNOの環境ではゼロ除算では
   止まらず、とりあえず計算は進みます。

実際の液晶表示を見てみましょう。

(a)のサーミスタ短絡
T10_20240513111101

(b)のサーミスタ断線
T11_20240513111201

抵抗値の箇所に「inf」という見慣れない「値」が出現します。
  ※inf=無限大
   print()やdtostrf()に食わせると「inf」
   という「文字」が出てきます。
   lround()で整数変換すると0x80000000
   (long値のマイナス目一杯)になって
   しまいます。

サーミスタで温度制御していたら、
  (1)いつまでたってもオンしない (a)のとき
  (2)いつまでたってもオフしない (b)のとき
状態が出現します。

(1)は、放っておいてもとりあえず周囲温度に馴染む
でしょうが(2)の状態が加熱しっぱなしとなり危険です。

何らかの警報的処置(表示だけでも)が必要に
なるのがわかっていただけるかと。

トランジスタ技術2024年6月号トラ技Jr.コーナ
掲載してもらった4チャンネル温度計の製作 では、
温度・抵抗値テーブルから最高値(温度低)と最低値
(温度高)を拾ってきて、値として規制するように
しました。
  ※プログラムエリアがあんましないので
   手抜き。
オープンだと「-20.0℃」、短絡だと「120.0℃」を
出力します。

| | コメント (0)

2024年4月 7日 (日)

トラ技2024年5月号に「3.3/65535」

トランジスタ技術の最新号、2024年5月号を
パラパラめくりしていたら、p.70のリスト1に
怪しい記述を発見。
A/D値から電圧値を計算するための定数を出す
のに「3.3/65535」と。

Pp21
65536と65535の差はほんの僅か。

でも、リストの7行目のPID演算のところで、
6桁の数値が出ているんで、5桁数値の最小桁で
の1違いは大きくないかいなとちょいと心配。

電源電圧「3.3V」も、「ほんまかいな」だし。

元データはA/D値のまま使い、「CAL(校正)したらこうなった」
にして、「浮動小数点化したmap関数」を使って線形補間して
答えを出すてなほうが「理にかなっている」ような気がします。

ラズピコだと1/65535が出現:トラ技2022年5月

※検索
ラズパイマガジン2022年12月12日の訂正
   ・・・本文中の「1023」は「1024」、図2下の式の
  「4095」は「4096」の誤りです。
   ・・・Raspberry Piのところで分割数が「4095」と
  あるのは「4096」の誤り、Raspberry Pi Picoでは
  「65535」が「65536」、micro:bitとArduinoでは
  「1023」が「1024」の誤りでした。
訂正が出ています。

 

| | コメント (2)

2024年3月30日 (土)

ラズピコだと1/65535が出現

Arduinoでぐだぐだ言ったのが1023 vs 1024問題

トラ技のラズピコ特集、2022年5月号を見ていたら・・・
Pc12
A/D変換の値から電圧を計算するのに「1/65535」が
出ていました。

ただし言語は「python」。
analogioを検索してみると、Aanaloginは
16bitの値になるそうで、
https://learn.adafruit.com/circuitpython-essentials/circuitpython-analog-in
ここでは1/65536して電圧に変換しています。

https://www.denshi.club/pc/python/circuitpython/circuitpython-10-3.html
ここは、「65535は間違いなので」と注記があって
1/65536になっています。

https://logikara.blog/raspi-pico-basic/
ここは1/65536。

もう一つ、気になったのが
  v / (ref - v)
のところ。
Pc11

vが電圧値でrefが基準電圧つまり3.3Vなら、
adc.valueがフルスケールの65535になったときは
  v = 65535 / 65535 * 3.3 で
   = 3.3 となります。
すると、
  3.3 / (3.3 - 3.3)
で、ゼロ除算が発生。

図15のCDSが外れて、A/D値がフルスケールになると
ゼロ除算してしまうわけです。
1/65536にしていれば、v = 3.29995となり、
ゼロ除算は避けられます。

pythonでの数値型がよく分かってないので、
これでどんな挙動になるのかは知りません。
記事をぱっと見して、
  1/65535が出てきたぞ
  1023 vs 1024と同じ匂いか
っと、感じた次第です。

 

| | コメント (0)

2024年2月19日 (月)

Arduino UNO R4のDACサンプルに出てくるmap関数

Arduino UNO R4 Minima Digital-to-Analog Converter (DAC)

この中のサンプルプログラムに
  freq = map(analogRead(A5), 0, 1024, 0, 10000);
というma関数を使った変換式が出てきます。

トラ技の著者でもあるjh4vajさんが、ブログで
1024じゃなくて1023だと思うが」と書かれています。
これにちょっと反論を。
  ※jh4vajさんのブログにうまくコメントできないので
   ここに記しておきます。
Arduino UNO R4 MinimaのDAC ~ analogWaveクラス編

mapの1023・1024問題、これは
  10bit→8bitの変換にmap(a, 0, 1023, 0, 255)
とするからおかしいのであって、(本来は1/4するだけ)
  map(a, 0, 1024, 0, 10000)
「そういう変換」だからこれでかまいません。

1024の時に10000になるよ」あるいは
なったよ」を表現しているわけです。
10bitのADCだとフルスケールが1023で
規制されるのでおかしな感じになりますが、
  半値の512だと5000だし、
  2倍の2048なら20000になり、
ADCの分解能を上げ下げしても線形補間の傾きは同じです。
  ※a=1023だと9990.234に

ですので、当然
  map(a, 0, 1023, 0, 10000)
も、okなわけです。
1023の時は10000でっせ」の変換式です。

10bit→8bit変換での1023・1024問題とは関係なくて、
これは正しい使い方です。

※関連
トランジスタ技術2024年3月号に記事が載りました
Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数
   map(x, 0,1023, 0,255)  ・・・これが正しい例を紹介




| | コメント (4)

2024年2月13日 (火)

トラ技のArduino Uno R4特集でも1/1023

Arduino Uno R4を知っておこうと
トランジスタ技術2024年1月号
パラパラめくりしていたら・・・
1/1023」を発見。

場所はp.106。
R42
記事のタイトルは
  Uno R4低消費電力の実力!
   家庭菜園用バッテリ動作2ch温度ロガー

その中の「R3からR4への乗り換え時の注意!
という章。
P.107の【図5】にはこんなものさしの絵も。
R43
1023と10進じゃなく0x03FFと16進表記にして
半値やら1/4値、3/4値を入れ込むと目盛がはっき
りするかと。

こちらでの「ものさし」表記。
Ss12

・1cmを1mm分解能で
Ss2_20221126092001
※こんな解説かな
・絵の縮尺の関係で、1cm位置が実際は5.0mやった。
・mmでの測定値a (0~9の範囲)から実際の長さを
 求める式は・・・
   a × (5.0 ÷ 10) が答え (単位はm)
          [ ÷9ではない ]

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

| | コメント (0)

2024年2月 7日 (水)

トランジスタ技術2024年3月号に記事が載りました

トランジスタ技術2024年3月号 、年間予約されてたら
そろそろ手元に届く頃でしょうか。

トラ技Jr.コーナ」に
  『実はワナだらけ…確実に動かすArduino Uno R3
というタイトルで、重箱の隅をほじくった記事を
載せてもらいました。

間違っていても動いちゃう危険…ひとこと言わせて!
がサブタイトル。

  ※校正原稿の段階では
   『教科書では教えてくれない…
    確実に動かすArduino Uno R3使いこなし術』
   というタイトルでした。
「1023 vs 1024」でウダウダ言っていたのを投稿した
のです。
  ※ページ数の関係で、トラ技に掲載されたのは
   面白いところだけになってしまいました。


本誌ではこんな章タイトルになっています。
~~~~~~~~~~~~~~~~~~~~~~~
便利だけれどもミスが潜んでいるのがArduino

A-D変換のワナ
  analogReadの基準電圧値と1/1023問題
線形補間のワナ
  map関数の使い方問題
PWM出力のワナ
  analogWriteでのPWM出力問題
やっぱり気になる放置ポート
  ポートほったらかしで電流増加問題
時間待ちのワナ
  delayMicrosecondsの最大値問題
サーミスタを使った温度測定のワナ
  ゼロ除算問題
wordデータのワナ
  割り込み処理問題
~~~~~~~~~~~~~~~~~~~~~~~

こちらから出した提出原稿の章タイトルはこん
なのでした。
◆サンプルスケッチを信じて良いのか
 1. analogReadでA-D値を読む
 1-1. 極端に2bitのADCで考えると
 2. analogReadしたA-D値をPWM出力
 2-1. 3bit→2bit変換にmapを使うと
◆回路図を見ておこう
 3. D13(PB5ポート)に注意
 3-1. 起動時のD13
 3-2. D13の衝突を避ける
◆データシートを読もう
 4. analogWriteのデューティー比が微妙にズレる
 4-1. タイマー0のPWM出力
 4-2. タイマー1,2のPWM出力
 4-3. analogWriteは誤差を持つ
 4-4. D-Aコンバータとして使えるPWM出力
 5. 方形波を出力したけれど周波数に誤差が出る
 5-1. このミスに気付かない原因の推測
 6. やっぱり気になる放置ポート
 6-1. 入力電圧が変化した時の電源電流の変化
 6-2. アナログ入力ポートでは
 7. AnalogReadとAREFピン、衝突でチップを壊してしまう?!
 7-1. 壊れるという話が出た原因
◆こんな落とし穴も
 8 delayMicrosecondsで待てるのは16383μ秒まで
 8-1. 50ms待ちを試すと
 9. 温湿度センサ用ライブラリで氷点下の温度をミスる
 10. サーミスタを使った温度測定にも落とし穴
 11. 割り込みで処理させるwordデータの扱い
 11-1. 1バイトデータでも割り込みと競合する
 12. 小さいことだけど不満を解消するヒント

タイトル「やっぱり気になる放置ポート」だけが、そのまま
残ったようです。

※関連
delayMicroseconds(50000)をオシロで確認

| | コメント (1)