1023 vs 1024

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)

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)