1023 vs 1024

2023年3月23日 (木)

Arduino サーミスタを使った温度測定で 【ゼロ除算問題】

2023年3月21日:A/Dコンバータでサーミスタの抵抗値を読む サーミスタをつなぐ場所は?
この続き。 ゼロ除算問題が見えてきました。

あれこれ検索。

★電源側にサーミスタ

VasteeLab:Arduinoで火災報知機をつくってみた
 int val = analogRead(PinTemp);     // get analog value
 resistance=(float)(1023-val)*10000/val; // get resistance
   val値が0ならゼロ除算。

★サーミスタをGND側に持ってきた例

今日から始める電子工作 【初めてのArduino】6.サーミスタ|ハンズオンで学ぶ初心者向け入門コース
 サーミスタはGND側。
 しかし、Vref値を1023としているため、抵抗値算出の時、
 A/D値がフルスケールの1023ならゼロ除算エラー発生。
 正しくは「/ (1024 - readValue)」。
  //アナログ値を読む
   float readValue = analogRead(analogPin);
  //Rtを計算する
   float Rt = Rd * readValue / (1023 - readValue);

  ※1023が出るのはサーミスタが外れた時。
   0が短絡なんで、回路の異常報知案件。

みのや電子工作所:なんちゃって自作体温計の製作
  電圧計算に1023を使っているので、A/Dの
  フルスケール値を読み出したときはV0が5.0Vと
  なり、抵抗値算出の/(5.0-V0)でゼロ除算エラー。
  val*5.0/1024.0にしておくのが正しい計算で、
  これで、ゼロ除算エラーを回避できる。
   val = analogRead(AN0); //アナログ値読込み
   V0 = val*5.0/1023.0; //アナログ値から電圧換算
   THR = 10000.0*V0/(5.0-V0); //電圧値からサーミスタ抵抗値換算

adafruit.com:Using a Thermistor
  な、なんなんだ、この式は!
  A/D値が0でも1023でもアウト。
   float reading;
   reading = analogRead(THERMISTORPIN);
   reading = (1023 / reading) - 1; // (1023/ADC - 1)
   reading = SERIESRESISTOR / reading; // 10K / (1023/ADC - 1)


◆正しく計算 (コメントしたところも)

アイデアノート:Arduinoとサーミスタで温度測定

jh4vaj:Arduinoでサーミスタを使って温度計を作るのに、電圧を求める必要はない

プチモンテ:サーミスタ(NTC)の使い方 [Arduino]

arduino.stackexchange.com:How to include Vref in thermistor temperature calculation?
  あれこれ議論。


◆基準電圧で誤差が出るかも

ラジオペンチ:Arduiono を使ってサーミスタで温度を測る
   (電圧算出に1/1023を使っているのも気になる←修正済)

◆イイこと書いてある

Qiita @c_in_g:analogReadの値の変換を誤解していた話(1023、1024問題)
 引用させて頂きます。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  長々と書いたが、ぶっちゃけ、255/1023と256/1024の
  違いなんて微々たる差(1%にも満たない)で考慮する必要ない
  だろと思う。  こういうことは言ってはいけない
   :
  初心者はこんなクソ細かいことを気にするより色々作ってみた
  ほうがいい。  こういうことも言ってはいけない
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

★エラいことにつながるかも
『1023 vs 1024 問題』が【ゼロ除算問題】にまで膨らんじゃ
いました。
1023 vs 1024は「ちょっとした誤差やん」で見逃せた
んですが、ゼロ除算は笑って済ませられないかもしれ
ません。

『ミサイル巡洋艦・ヨークタウン ゼロ除算』を検索すると、
ゼロ除算エラーでシステムがダウン。航行不能に」なんて
記事が見つかります。

コンピュータのトラブルで漂流したアメリカのイージス巡洋艦 の考察

◆ゼロ除算エラーの話
PLCが突然停止、原因は割り算の演算エラー
【中級編】GX Works3 除算演算エラー回避方法 0で割らない

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
※追記

サーミスタでの温度計算とゼロ除算問題、式との相性が良い(!)
のかあれこれ見つかります。
「1023 vs 1024」と絡むのも面白いかと。

理系男子の電子工作:【PIC】ADCの使い方 サーミスタで温度測定
 サーミスタはGND側。
 エクセルでA/D値→温度テーブルを作っておくという
 手法です。
 このように↓言い切られています。
  『A = 0, 1023 の時にゼロ除算のエラーが
   発生しますが、測定値がそのような値を
   とることは考えられないので気にする
   必要はありません。』
 A/D値(A)からサーミスタ抵抗(R)の計算で
  「R = (r × A) ÷ (1023 - A)
 としているのを、1023→1024にするだけで
 フルスケールでのゼロ除算が回避できます。
 1024だと半値もちゃんと出るし。
   (log(0)エラーの問題は置いておいて)
 前もってデータテーブルを作るときのエラーですんで、
 実行時のエラーとは違う話になります。
 しかし、「いつも気にしておく」のが設計という
 ものです。

Digi-Key:正確なサーミスタベースの温度検出回路を迅速に作成
 デジキーの技術解説。
 サーミスタは電源側。
    Vadco A/D入力電圧
    Dout  A/Dデータ
    2^N  A/D分解能
    Rth  サーミスタ抵抗
    R25  GND側抵抗
  Vadco = Vref × (R25 ÷ (R25 + Rth))
  Dout = 2^N × (Vadco / Vref)
  Rth  = R25 × ((2^N ÷ Dout) - 1)
 
   (2^N ÷ Dout)でゼロ除算エラー発生(の可能性)。

 

| | コメント (2)

2023年3月21日 (火)

A/Dコンバータでサーミスタの抵抗値を読む サーミスタをつなぐ場所は?

私の場合、サーミスタ読み取りのための接続は、
基準抵抗をVref側に持ってきます。
こんな具合。

A01_20230321164901
こうすると、サーミスタの片方をGNDにできるので
シールド線が使えます。
この場合、温度が上昇するとサーミスタの抵抗値が
下がり、それに連れてA/D値も下がってしまいます。

温度が上がるとA/D値が下がってしまうので、直感的に
これを嫌う方が居られるのでしょうか、こんな具合に
基準抵抗をGND側につないでいる回路例を見かけます。

A02_20230321164901
こうすると、
 温度が上がる
   ↓
 サーミスタの抵抗値が下がる
   ↓
 A/D値が上がる
と、温度上昇でA/D値が上昇と、まぁ見た目の感覚に
合うような気がします。
しかしちょいと問題が・・・

RaあるいはRbの値を固定して、その時のA/D値から
反対側の抵抗値を計算する方法を見てみましょう。

VrefとVin、そしてRaとRbの関係式です。
A2_20230321165001
VrefとVinは何ボルトという実値でなくてもかまいません。

Vinは「ゼロ~フルスケール」。
8bitのADCなら「0~255」、10bitなら「0~1023」と
いう範囲の値になります。
そして、Vrefは「フルスケール値 + 1」

まず、サーミスタをGND側につないだ時
Raが基準抵抗。 Rbがサーミスタ。
VinからRb値を計算します。
A4_20230321165201

分母の「Vref - Vin」に注目。
サーミスタがGNDに短絡して0ΩになってVin=0になると
分子がゼロでRb=0が出てきます。
そして、分母はVrefそのもので計算可能。

サーミスタ入力がオープンになってVinがフルスケールに
なっても、分母の「Vref - Vin」は「1」になって計算可能
です。
  ※Vref=フルスケール値 + 1 が重要!

問題がサーミスタを電源側につないだ時の計算。
基準抵抗がGND側。
Raがサーミスタ。 Rbが基準抵抗。 
A3_20230321165401
サーミスタ入力がオープンになるとVinは
RbでGNDに落ちて、Vinはゼロ
この時、「Vref ÷ Vin」の分母がゼロになってしまって
ゼロ除算エラーが発生して、正しく計算できません。

このようなつなぎ方の時、A/D値「Vin = 0」をチェック
して、ゼロ除算エラーを回避する処置が必要です。
例えば、「0なら1に」するような。

Arduino UNOだとint値をゼロで割っても答えを「0xFFFF」
(intだと-1)にしているようですし、floatだと「inf」
無限大を出して、「ゼロで割ったから停止!」とはして
いません。

ネットに上がっているいろんなサンプル、ゼロ除算エラー
を無視しているのが多いようです。
マイコンを使っての計算でこれは「ちょっとなぁ」です。

~~~~~~~~~~~~~~~~~~~~~~~~~~~
※追記
サーミスタによる温度計測で、ゼロ除算エラーが
発生する可能性のある回路やスケッチの例。
  ※ネットを検索

Arduino 入門 Lesson 18 【サーミスタ編】:おもろ家
 サーミスタは電源側。
 「Rth = ((Vin/Vout) - 1) × R1」として
 サーミスタ抵抗値Rthを計算。
 VoutがA/D入力値で、「0」だとアウト。

https://asukiaaa.blogspot.com/2021試行錯誤な日々:Arduino(ESP32)でサーミスタを使い温度を取得
 サーミスタは電源側。
 「(double)(analogMax - analog) / analog * resistorPullDown;」
 でサーミスタ抵抗値を算出。
 analogがA/D値。 「0」だとアウト。
 12bit ADCだが、「#define ANALOG_MAX 4095」として
 「フルスケール+1」を使っていない。
 考え方として、これもダメ。

NOBのArduino日記!サーミスタの使い方! その2( 実測編!)(103JT-050)
 サーミスタは電源側。
 「R1 = ((Vcc × R2) / Vout) - R2 」で
 サーミスタ抵抗値R1を算出。
 VoutがA/D入力値で、やはり「0」だとアウト。

初めてのロボット組立:Arduinoにサーミスタを接続して温度を測定する
 サーミスタは電源側。
 「10000.0 * ((1024.0 / tempReading - 1))」でサーミスタの
 抵抗値を算出。
 tempReadingがA/D入力値で「0」だとアウト。

   ※LCDのコントラスト調整の半固定抵抗記号に
      んっ? ボリュームの記号が!
    で紹介した表記が使われている。
      電子回路エンジニアの皆さん、
      ほんとにこれ、どうにかして!

まったりYO$HI日記 気の向くまま(・∀・)【Arduino】サーミスタで温度測定
 サーミスタは電源側。
 「R = R1 × ((V / Vt) - 1)」でサーミスタの
 抵抗値Rを計算。 R1はGND側の抵抗100kΩ。
 VtがA-D入力値。 これが0ならアウト。

基礎からの IoT 入門Arduino IoT とは? 温度を測る ~ サーミスタの利用
 サーミスタは電源側。
 「R = ((Vin / Vout) - 1) × R1」で計算。
 VoutがA/D値。 0ならアウト。

~~~~~~~~~~~~~~~~~~~~~~~~~~~
Arduino UNOのような単純な8bitマイコンだと
機械語として割り算命令は持っていませんので
ゼロ除算によるトラップ(例外処理)は考慮しなくて
かまいません。(勝手には止まらない)
しかし、高機能なマイコンだとトラップに引っかかり
制御がそこで止まってしまうかもしれません。
  (そんな仕掛けをしていたら)

int値のゼロ除算なら結果はおそらく「-1」。
  (符号無しなら最大値に)
ゼロ除算を無視するのなら、この「-1」がその後の
計算にどんな影響を与えるのかを考えておかなくて
はなりません。

単純な表示でも、想定する桁数を越えちゃうと、表示
が「グチャ」っとなるかもしれません。
何かの制御に使っていたら・・・ちょっと怖い。

浮動小数点なら「NaN:Not a Number」や
inf:infinity」てなところでしょうか。

今回はサーミスタの抵抗値計算での話ですんで、
「マイナスの抵抗値」が出現てなことに
なっちゃうかも、です。

やはり、どこかで数値のエラーチェックが必要でしょう。
0での割り算をしちゃう大もとの、A/D値のゼロを
排除というのが簡単かと。

  これ、あれこれ書いてきましたが、
  サーミスタをGND側につないだ時は
  大丈夫なんですよ。
  10bitのADCなら0~1023の範囲で
  ちゃんと答えが得られます。

うだうだ言ってますが、結局はサーミスタの接続が
外れなければ大丈夫。
しかし・・・
  ・接触不良がおきたら?
  ・コネクタやプラグを使って抜き差しできる
   構造なら、抜いた時は?
  ・サーミスタへの配線が切れたら?
と、Vinが0Vになる可能性があるのなら、
ゼロ除算エラーが生じる可能性もあるわけです。
このつなぎ方をするのなら、ソフト的な対策は必要か
と思うのです。


★続き
 ↓
Arduino サーミスタを使った温度測定で 【ゼロ除算問題】

  サーミスタを使って温度計測の手順から、
  【1023 vs 1024 問題】だけじゃなく【ゼロ除算問題】が
  見えてきました。

| | コメント (0)

2023年3月 8日 (水)

こんなところに「256-1」が

トランジスタ技術2012年1月号
特集が「エレクトロニクス格言集」
その中の第3章 アナログ2:計測&センサ

・3-6 抵抗分圧比をA-D変換するときの
    基準電圧ICは無駄使い

Ad12

サーミスタの抵抗値を直列に入れたRpの値から
求めようという手法の解説です。
抵抗の算出式から基準電圧値は不要で、
ADCの分解能が分かれば良いと。

この「?」と記したところに「2^N - 1」が出現!
8ビットA-Dなら255に、10ビットなら1023にという
ことなんですが・・・これは間違い。
フルスケール値 + 1、つまり分解能で計算しなくては
なりません。

これだとA-D値が1/2(半値)になるとき、「Rx = Rp」
となりません。

で、仮にADCが8ビットとすると
  Nx = 255 * Rx/(Rx+Rp) これを変形していくと
  Rx = Rp * (Nx / (255 - Nx))

Rx 未接続のとき、つまり∞の時。
この時のA-D値はフルスケールになってNx=255
すると、分母の (255 - Nx) がゼロになってしまい、
DIV0エラーに!

255じゃなく、256だと 256 - 255 = 1で
 Rx = Rp * 255。
Rp値の255倍以上の時(無限もOK)に
A-D値255が出てきます。

ということで 「2^N - 1」は大間違い。

半値や1/4値、3/4値で確かめれば「なんかおかしい」
っと思うはずなんですが・・・。

| | コメント (0)

2023年2月22日 (水)

ラズピコのPWM。やっぱしanalogWriteは捨てちゃえ #2

2023年2月21日:ラズピコのPWM。 やっぱしanalogWriteは捨てちゃえ
この続き。

ラジオペンチさんのコメント
  「せっかくだから analogWritePure なんて関数・・・」と
ありましたので、難しいことはせずにサクッとまとめてみました。
   ※サクッとで、エラーチェックや
    数値範囲チェックはしてません。

関数は2つ。

 int pwmStart(pin_size_t pin, int val, int range, int frq)
まず、これで
 分解能 range と 周波数 frq から、
val値に対する乗数を計算します。
 pinはPWM出力ピン番号。 valはPWM幅。

というのはピコのPWM、クロックが125MHzで分周器の最大が
8bitの256未満 (m・n/16の浮動小数点で設定できる)という
制限があるのです。

PWM周波数500Hzで8bit分解能だと分周比が976.56になって
しまい、256.0を越えてしまいます。
そこで、PWM幅を得るval値にmlt値を乗じるようにして、
分周比が256.0を越えないようにします。

PWM周波数1kHzで分解能が100だと分周比が1/250で、
PWM幅valへの乗数が5となります。
val値0でL、100でH。
1~99でデューティ1%~99%が出てきます。

pwmStartの返り値である乗数(mlt)を得たあとは、
この関数↓でPWM幅を出力。
 void pwmVal(pin_size_t pin, int val, int mlt)
浮動小数点計算が不要な分、処理が早くなります。

テストプログラムを走らせるとこんな感じ。
  USBで通信 出力ピンをオシロで観察

# Pico PWM Test #5 (2023-02-22) Entでタイトル出力
# PWM Port >22      出力ピン番号
# PWM Freq (500Hz) >1000 PWM周波数
# PWM Range (256) >100   分解能 0~100で
PWM:22 m:5 1/250.000   mltとclkdiv値

# PWM Data >10  10だとデューティ10%
10/100 10.000%
# PWM Data >99
99/100 99.000%
# PWM Data >45
45/100 45.000%
# PWM Data >33
33/100 33.000%

# PWM Data >  Entだけ入力で最初から
# PWM Port >
# PWM Port >19  出力を19ピンに
# PWM Freq (500Hz) >800  周波数を800Hzに
# PWM Range (256) >1000  分解能を1000に
PWM:19 m:1 1/156.250  mltとclkdivが確定

# PWM Data >120
120/1000 12.000%
# PWM Data >990
990/1000 99.000%
# PWM Data >10
10/1000 1.000%

# PWM Port >19
# PWM Freq (500Hz) >1200  1200Hz
# PWM Range (256) >4096  分解能12bitで
PWM:19 m:1 1/25.431  mltとclkdivが確定
# PWM Data >100
100/4096 2.441%
# PWM Data >4000
4000/4096 97.656%
# PWM Data >4095
4095/4096 99.976%
# PWM Data >4090
4090/4096 99.854%

1Hz単位でPWM周波数が設定できます。
PWM分解能も「2^n」だけでなく、100とか1000に
できるので、10進でPWMデューティを設定する時に
便利かと。
分解能が偶数だと、半値で1/2デューティが得られます。

※テストプログラム
  ・ダウンロード - pwm_serial_in5.txt

Arduino IDEの環境によってはコンパイルエラーが出るかも。

※ pwmStart() 実行時間
Pp1_20230222141401

※ pwmVal() 実行時間
Pp2_20230222141401



| | コメント (2)

2023年2月21日 (火)

ラズピコのPWM。 やっぱしanalogWriteは捨てちゃえ

2023年2月20日:ラズピコのPWM。 なんかおかしいような #2
この続き。

ここ↓で、PWMを使ったタイマー割り込みを試していました。
2022年4月12日:Arduino IDEでラズパイ・ピコ:1msタイマー割り込み
それの応用で500HzのPWMを確認してみます。
  ※analogWriteを捨てるための実験です

/****************************/
/* PWM出力 */
/****************************/
#include "pwm.h" // PWM処理に必要
// PWMデータ
int pwm_port; // PWMポート番号
int pwm_range = 256; // PWM レンジ 初期値は256
int pwm_data; // PWM設定値データ
/***** PWM出力 *****/
// pinに出力 valがPWM H区間 最大がrange
// val=0ならL出力 val=rangeならH出力
// ピコのクロックは125MHz
// val,rangeを10倍して処理
// set_clkdivは256.0未満 m・n/16で分周
// set_wrap,set_chan_levelはuint16_tなので65535がmax
void pwmWrite(pin_size_t pin, int val, int range)
{
uint sn; // slice number
gpio_set_function(pwm_port, GPIO_FUNC_PWM); // ポートはPWM出力で
sn = pwm_gpio_to_slice_num(pwm_port); // スライス番号を得る
pwm_set_clkdiv(sn, 97.66); // 1.28MHzに
pwm_set_wrap(sn, (10 * range) - 1); // 256で500Hz
pwm_set_chan_level(sn, (pwm_port & 1), 10 * val); // ch-A,ch-B
pwm_set_enabled(sn, true); // PWMスタート
}

グラフにすると、よく分かるかと。
   ※前のと同じスケールで
Cap110
   ※拡大
Cap111
へんな周期性は見えません。

「半値」のあたりはこんな具合。

・range = 255で
PWM発生  実測値   n/255で  実測値   差分
n 1/255 H H+L
126 255 15744 31864 49.412% 49.410% -0.002%
127 255 15870 31865 49.804% 49.804% -0.000%
128 255 15995 31865 50.196% 50.196% 0.000%
129 255 16120 31865 50.588% 50.588% 0.000%
130 255 16245 31865 50.980% 50.981% 0.000%

・range = 256で
PWM発生  実測値   n/255で  実測値   差分
n 1/256 H H+L
126 256 15745 31990 49.219% 49.219% -0.000%
127 256 15870 31990 49.609% 49.609% -0.000%
128 256 15995 31990 50.000% 50.000% 0.000%
129 256 16120 31990 50.391% 50.391% 0.000%
130 256 16245 31990 50.781% 50.781% 0.000%

実測値は16MHzのクロックでサンプリングしたHとLのパルス幅です。
128/255も128/256も期待値が出ています。

◆テストプログラム
  ・ダウンロード - pwm_serial_in4.txt

(.inoではなく.txtにしてます。 UTF-8Nエンコード)

| | コメント (3)

2023年2月20日 (月)

ラズピコのPWM。 なんかおかしいような #2

ラズピコのPWM。 なんかおかしいような
これの続きです。

analogWrite()を処理しているソースを探し出すと
どうやらこれのようです。

void analogWrite(pin_size_t pin, int val)
{
if (pin >= PINS_COUNT) {
return;
}
float percent = (float)val/(float)((1 << write_resolution)-1); // ★1
mbed::PwmOut* pwm = digitalPinToPwm(pin);
if (pwm == NULL) {
pwm = new mbed::PwmOut(digitalPinToPinName(pin));
digitalPinToPwm(pin) = pwm;
pwm->period_ms(2); //500Hz
}
if (percent < 0) {
delete pwm;
digitalPinToPwm(pin) = NULL;
} else {
pwm->write(percent);
}
}

★1のところで、write_resolutionが
  8bitなら255に、
  10bitにしたら1023に。
これがデューティ100%での浮動小数点除算の
除数になります。
つまりPWMの出力が出力がHに固定される値です。

得られた「percent」値がデューティ比でそれを
  pwm->write(percent);
でPWM出力しています。
  ※「pwm->write」の中味はまだ追いかけてません
このanalogWriteをこんなふうに書き換えました。
  (1 << m) - 1
をやめて、「write_resolution」を任意の値に設定でき
るようにします。

/***** PWM出力     *****/
// analogWiteの代替
// フルスケールはpwm_range -1
// 元々は (1 << write_resolution)-1 で8bitなら255に
void pwmWrite(pin_size_t pin, int val, int range)
{
float percent; // デューティサイクル
if(pin >= PINS_COUNT){
return;
}
percent = (float)val / (float)range; // デューティ比を計算
mbed::PwmOut* pwm = digitalPinToPwm(pin);
if(pwm == NULL){
pwm = new mbed::PwmOut(digitalPinToPinName(pin));
digitalPinToPwm(pin) = pwm;
pwm->period_ms(2); // 500Hz PWM周波数
}
if(percent < 0){
delete pwm;
digitalPinToPwm(pin) = NULL;
}
else{
pwm->write(percent); // デューティ指定でPWM出力
}
}

これで、フルスケール値(デューティ100%)を任意に書き換え
できます。

で、試してみました。
デューティ計算値と実測値の差をグラフにします。
まず、n/255 と n/256
Cap108_20230220112701

微妙に変動の周期が異なります。
n/256にしても半値の128で差が生じました。
128/256で50.000%になるはずが、実測値49.950%
で、-0.050%の差です。

そして、試したのがn/100、n/105、n/200、n/250の4つ。

Cap109
10^0桁が0の100、200、250は右下がりの直線になりましたが、
n/105が「なんじゃこれは」に。

デューティ比の設定がfloatなのが問題なのかなぁ。
「pwm->write」の中味を調べなくっちゃというところ
でしょうね。

| | コメント (3)

2023年2月16日 (木)

ラズピコのPWM。 なんかおかしいような

ありゃま。ラズピコがおかしくなった。 PWMを調べたかったのに
これの続き。
  書き込み出来なかったのはUSB回りのゴソゴソ。
    よく分からんけど。
  2022年4月13日:Arduino IDEでRaspberry Pi Pico:Win7でのUSB問題解決です
    これで使ったZagig.exeを触ってたら
    書き込みできるようになりました。

さて、ラズピコのPWM。
なんかおかしい。
単純な整数値でPWMしてたら誤差なんて出ない(1clkの変動
はあるだろけど)はず。
  analogWriteと名乗るからにはちゃんとしてほしい。

まず、テストで使ったスケッチ
   ・ダウンロード - pwm_serial_in1.txt
     (ファイルタイプを.inoではなく.txtにしてます)

・USBでシリアル通信。
・スケッチを書き込み後ターミナルを起動。
・最初の「CR」入力でタイトルを出力。
・次に使うPWMポート番号を入力。
   # PWM Port >
  そのポートに対しデューティ50%の
  (つもりの)波形を出力
・その次からPWMデータ入力を繰り返し。
   # PWM Data (0...255) >
  0~255の入力で設定したポートに
  analogWriteでPWM値を設定
・「CR」だけだとPWMポート番号入力に戻る。
・データを「-1」で設定したらPWM値を順番に
 増加させるモードに
 1~254まで2秒ごとに+1。
 254で停止。
 設定したPWM値はSerial1にも出力。
   これをデューティチェッカの出力とともに記録。
   デューティチェッカの出力サイクルが1秒なので
   ピコは2秒ごとにPWM値を変更。

設定したデューティ「n/255」値とデューティチェッカで
拾ったH/Lクロック数から計算した実デューティの差を
グラフにしたら、こうなりました。
Cap105

デューティ比の差が「-0.1~0.0」を周期的に変動。
なんなんだこれは。

まず、測定した生データ。
設定したPWM値とデューティチェッカで拾った値です。
  ・ダウンロード - pico_pwm2a.txt

そこからそれぞれのデューティ比を計算。
  ・ダウンロード - pico_pwm3a.txt

その2つのデューティの差をグラフにしたのが↑。

| | コメント (6)

2023年2月14日 (火)

ありゃま。ラズピコがおかしくなった。 PWMを調べたかったのに

ラジオペンチさんからのコメント(2023年2月12日13時09分)
件の記事(2023年3月号p.155)を読んで、図8の
ラズパイpicoの疑似DAC特性」が気になったので
ごそごそしていました。

で、昨夕は手持ちのラズピコにちゃんとスケッチを書き込
めたんですが(Arduino IDE環境)、今朝、ゴソゴソしたら
エラーが出てしまって書き込めません。
昨夕、最後に書いた、シリアル入力値でPWMを設定するという
スケッチは動いていてUSBを通してのシリアル送受はできてい
るのです。
ところが、スケッチのアップロードは失敗。
BOOT SWを押しながらUSBコネクタを挿しても、普通なら現れる
はずのドライブが出てきません。
どこか何かがおかしくなったようです。

Arduino UNOのPWMは、
Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
で記してますように、「途中で1/256狂って」います。

で、ピコのPWMにも違和感。
記事には最大1.4mV未満と測定結果が記されますが、
PWMによるDAC、少々リップルが乗ってもこんな
誤差は生じないはずです。
Bb10_20230214160301

3.3V/255で1bit変化で12.9mV。 (あえて1/255で表記)
10%ほどがプラスに積み重なって、128あたりで最大に
なってます。
8bitのPWMそのものに何かがあるんじゃないかと感じました。

2020年11月12日:Arduino UNOでデューティー比測定回路 ケースに入れて完成

Bb11_20230214160301
    PWM=128にした時のデューティ、周波数と
   H区間クロック数、L区間、計測クロック(MHz)

これで、ピコが出すPWM波のディーティを調べてみようとした
のです。
とりあえず手入力した0~255の値でPWMを設定という
スケッチ、これは昨夕にピコに書けたのですが、
今朝になってこれを更新しようとしたらアウトっとい
う状態になってしまったのです。

で、1~254の値を手入力してちょっと調べてみました。
ピコもUNOと同じように、設定値0で出力L固定、255でH固定
となっています。

ピコでの1~4までのH区間とL区間を見てみます。
デューティチェッカーのクロックは16MHz。
PWM H  L
 1  96 31937
 2 224 31809
 3 352 31681
 4 480 31553

1と2、2と3、3と4の差は128クロック。
つまり125kHz=8μs。
しかし、1のH区間が96で6μsしかありません。
  オシロでも確認したんで、デューティ比チェッカの
  ミスじゃありません。

L区間がちょこっと出る253と254はこんな値。
PWM  H   L
 253 31777 256
 254 31905 128

これで、1~254の端っこを見たわけですが、1の時の
96を考慮したら、253の時のはH区間は「32352」に
なって欲しいところ。
  252 * 128 + 96=32352
ところが31777。 なんか小さい。
254も「32480」が妥当じゃないのかと。

  ※ピコのスケッチを、手入力じゃなく自動で順にPWM値を
   変化させて、デューティ比をチェックできればと
   考えたのですが、アップロードできなくなってし
   まったのです。

どこかでおかしくなっているようなのですが、手での
入力と目で見ての確認はちょっとつらい。
気を取り直して、まず1に近いところから順におかしく
なっている場所を探してみました。

すると12~16でこんな変化が。
PWM 期待値 実値H 実値L
 12 1504 1504  30529
 13 1632 1632  30400
 14 1760 1728  30305
 15 1888 1856  30177

実値Hの13と14の間が「96」になってます。
そして、14と15の間は「128」に復帰。

ピコのPWM、何かが潜んでいそうですぞ。
   ※単調性がどこかで狂ってる?

しかし、ピコへのスケッチ書き込み失敗、困りました。
なんとかしなくちゃ。

デューティ比チェッカ、自動記録用にシリアル出力が
いりますなぁ。

| | コメント (3)

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による測定が基準電圧を越えられない
ことがわかるでしょう。

| | コメント (6)

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)