AVRマイコン

2022年11月15日 (火)

FLUKE 87IV デジタルテスターのデータをIrDAで吸い上げる

2022年7月6日:デジタルテスター「FLUKE 87IV」の赤外線通信ユニット完成
これでいちおう完成してるんですが、IrDAのインターフェース・チップを
テキサス・インスツルメンツの「TIR1000」 に換えて試してみました。

マイクロチップの「MCP2122」 と何が違うのか・・・

図示すると・・・ピンは同じ配列。
Tt11

(1) MCP2122は8pin DIPのがある。 そして安価。
 TIR1000はSOPとTSSOP。

(2) リセット入力の論理レベルが異なる。
 MCP2122はLでリセット。 TIR1000はHでリセット。

(3) 赤外線の自己反射光の処理が違う。
 こちらから出した赤外線、どこかに当たって返ってきて
 受信光としてRXIR信号に入ってきます。
 MCP2122では送信中のRXoutは禁止してくれるので、
 これを考えなくて良いのです。
 ところがTIR1000では筒抜けに。
 自分で処理しなくちゃなりません。

この(3)せいで、単純なターミナルでの実験だとローカルエコーが
返ってくる感じになります。

マイコンでの処理だと、IrDAへのシリアル送信処理が始める前に、
IrDAからの受信を禁止しちゃって無視するように。
そして、送信完了で受信を再開。
こんな手順を制御プログラムに入れなくてはなりません。

前に紹介したATtiny841のプログラムでは、
「送信データレジスタ空き割り込み」で次データを送り込んで
いたのですが、受信を再開させるには送信完了(全フレームの
送出完了)を知らなければなりません。
送信割り込みの方法を変えなければなりませんでした。

TIR1000を使うメリットって無いぞ、なんですが、
試しに回路を作っちゃったんで、仕上げてしまいました。
ジャンパーを追加してMCP2122かTIR1000かを切り替えられ
るようにしました。

| | コメント (0)

2022年10月 8日 (土)

数値をBCD出力(表示)するルーチン

ROM、RAM容量に余裕のあるマイコンなら
「printf」系の「sprintf」で文字列に
変換してシリアル出力、あるいは液晶表示
という手法が使えます。

しかし・・・
  ROM、RAMに余裕がないなぁ
  BCDにして出力(表示)するだけなのに
てな時に役立つルーチンを紹介しておきます。

・longデータ ということは整数だけ。
・ゼロサプレス付で数値に変換。
・マイナスは最も左側の数字の頭に「-」を付加。
・数値「-12345」の最小桁が0.001を意味する時の
 ため(例えば-12.345Vと表示したい)、整数部桁数
 と小数部桁数を指定できるようにした。
・値が指定桁数より大きくなっても、左にはみ
 出さない。
   液晶表示などでは表示がズレないけれど
   越えた部分の数字は見えない。
・1文字シリアル出力ルーチンの名をtx()として
 記している。
   液晶表示なら1文字書き込みに変える。
・「 -12.345」と出力したいのなら
   (先頭にスペース1コ付けている)
      txbcd(d, 4, 3);
 これで、-999.999 ~ 9999.999 の範囲を
 出力できる。
 -32768 ~ 32767のint値なら(longにキャストして)
   「 -32.678」~ 「  32.767」
  頭にスペース1つ  頭にスペース2つ
 wordなら
   「   0.000」 ~ 「  65.535」
  頭にスペース3つ  頭にスペース2つ

/*****  桁指定BCD出力       *****/
// n:整数部文字数 p:小数部文字数
// 数値は整数を入力 浮動小数点ではない
// 小数部は整数の基数が0.01などの時に用いる
// 12345が123.56を意味する時 (d,3,2)と指定
void txbcd(int32_t d, byte n, byte p)
{
char bff[20]; // 20文字(null入れず)
byte i, m;
byte sgn = 0; // 符号フラグ
memset(bff, ' ', sizeof(bff)); // スペースで埋める
if(d < 0){ // マイナス?
d = -d;
sgn = 1;
}
m = n + p; // 整数+小数 文字数
for(i = 0; i < (sizeof(bff) - 2); i++){ // longで12文字とマイナス
bff[i] = (d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
if((d == 0) && // 0になったら終わり
(i >= p)) break; // 小数桁ok?
}
if(sgn){ // 元値がマイナス?
bff[i + 1] = '-';
}
for(i = 0; i < m; i++){
tx(bff[m - i - 1]); // バッファを逆順で
if(p){
if(i == (m - p - 1)) tx('.'); // 小数点
}
}
}

例に記した「12345が123.56を意味する時 (d,3,2)と指定」
だと、整数部が3桁だけですのでマイナスを考えると正常に
出力できる(落ち無く)数値の範囲は「-99.99 ~ 999.99」
となります。

プログラムそのものは30行もありませんが、実プログラム
ではこれでも300バイトを越えてしまいます。
もっと小さくしたいときは、
 ・long:int32_t → int16_tに
 ・bffをローカル変数(スタックの処理が入る)で
  はなく関数の外に持ってくる。
てなところでしょうか。

sprintfだと、数値の単位や区切り文字を含めて1行
に表記できて便利ですが、これは数字だけ。

longの剰余と除算を使っています。
アセンブラで書くとなると、ここらから始めなければ
なりません。

Arduinoの「print」は数値の桁数を指定できない
ので不便。
数値とゼロサプレスの数を合わせて桁を揃えて
出力したい時は面倒でも「sprintf」。

 

| | コメント (4)

2022年10月 6日 (木)

5chサーミスタ温度計のA/D入力、map関数を使って補正

スカタンな応用例ばかり出てくるArduinoのmap関数
サーミスタ103JTで計った温度をシリアル出力 でのA/D変換値補正
で使ってみました。

まず、A/D入力電圧実測値と基準電圧値から真のA/D値を計算します。
  ツールはデジタルテスター。
  それと電圧発生器、あるいは抵抗を組み合わせて
  安定した入力電圧を得ます。
マイコンにはその時のA/D値をシリアル出力させます。
0に近いある値と1023に近いある値の2点をチェック。
どちらも同じなら補正の必要はありません。
ちょっとのズレなら「直線補間」の出番です。

今回の回路、サーミスタをつなぐch1~ch4はポートAでしたが
ch5だけがポートB。
この間でちょっとズレが出ていました。
まず0Vに近い方。
正値20に対してch1~4が17と-3。
ch5が16と-4と低めの値が出ていました。
フルスケールに近い方は、正値991に対して
ch1~4が992にと+1。ch5は誤差なし。

パラメータのテーブルにはこれらの値を10倍して設定します。
  ※浮動小数点にするとメモリーを食うので。
読み込んだA/D値も10倍してmap関数に食わせて補正を
行い、出てきた値の1の位を見て四捨五入。
10倍することで四捨五入の処理ができます。

これで、ずいぶんと温度測定の調子が良くなりました。
サーミスタの抵抗が低くなる高温域ではA/D値が小さくなります。
A/D入力電圧の低いところで誤差が生じていたのでこの補正が
うまく働いたのでしょう。
80℃付近ではA/D値の1bitが0.3~0.4℃の差になります。

map関数の応用例ということで、ご覧下さい。

//  A/D補正パラメータ
// 四捨五入のためA/D値を10倍にして
// x0とx1に実測値を設定する
// Rs = 10kΩで
// Lo側 200Ωでy0=20  Hi側 300kΩでy1=991
const struct{
const word x0; // in min (Lo)
const word x1; // in max (Hi)
const word y0; // out min
const word y1; // out max
}adj_tbl[] PROGMEM = {
{ 170, 9920, 200, 9910 }, // ch1 ADC1 PA1
{ 170, 9920, 200, 9910 }, // ch2 ADC2 PA2
{ 170, 9920, 200, 9910 }, // ch3 ADC3 PA3
{ 170, 9920, 200, 9910 }, // ch4 ADC7 PA7
{ 160, 9910, 200, 9910 }, // ch5 ADC8 PB0
};
/***** 線形補間 *****/
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/***** A/D値補正 *****/
// in ad : 0~1023 A/D値
// ch : 0~4 ch1~ch5
// 10bitの範囲でリターン
word adadj(word ad, byte ch)
{
long a, d;
a = map((long)ad * 10L, // 線形補間
(long)pgm_read_word(&adj_tbl[ch].x0), // in_min
(long)pgm_read_word(&adj_tbl[ch].x1), // in_max
(long)pgm_read_word(&adj_tbl[ch].y0), // out_min
(long)pgm_read_word(&adj_tbl[ch].y1)); // out_max
d = a / 10; // 1の位で四捨五入 10の位を+0か+1,-1
if(a >= 0){ // プラス
if((a % 10) >= 5) d++;
}
else{ // マイナス
if(((-a) % 10) >= 5) d--; // 正にして1の位で判定
}
if(d < 0) d = 0; // マイナスは0に
else if(d > 1023) d = 1023; // 最大は1023
return (word)d; // wordにしてリターン
}


5chの温度測定で重要なのが「Rs」=「神様抵抗」。
今回の製作では「0.1%」精度のを使ったので、
補正テーブルの「y0、y1」(out_min、out_max)は5chとも
同じになっています。
Rsの誤差は、この値の変更で吸収できます。

使っているAVRマイコンは14ピンのATtiny841。
ROMが8kバイトにRAMが512バイト。
現在のプログラムサイズが3826バイト。
このうち2048バイトが10bit ADC→0.1℃温度データ
の変換テーブル。
プログラムは実質1778バイト。BSSエリアが58バイト。

浮動小数点使わず。sprintf使わず。
この結果だと。

ROM、RAM容量半分のATtiny441にも入るかしら。

| | コメント (0)

2022年10月 5日 (水)

サーミスタ103JTで計った温度をシリアル出力

サーミスタ103JTで計った温度をアナログ出力
この製作で使ったマイコンは8ピンのATtiny85
内蔵タイマーが8ビットで、PWM出力でD/Aするには
分解能不足。
そこで、DACを使ってアナログ電圧を出力しました。

今回の「ダイソー ミニケース 5個組」を使った工作
やはりサーミスタ103JTを使った温度計なのですが、
シリアル出力でデータを得ようと目論見ました。

14ピンのATtiny841を使って、5つのサーミスタを
つなげられるようにしています。

ブロックダイヤグラム
C51_20221005170901
1秒間隔で測定値を出力します。
先頭が6桁数字が連番。
  データ抜けをチェックできます。
スペース区切りで5つの温度を出力。

セミテックの103JT温度・抵抗値表が元データ。
-30.0℃~125.0℃をテーブルにしています。

  ※10bit A/D値から温度データテーブルを
   直に呼び出すことで、浮動小数点関数計算
   を使わなくて済みます。
   ATtiny841のROMサイズは8kバイトしかあり
   ません。
   浮動小数点や「sprintf」による数値→文字列
   変換関数を使うと、ROM領域が足らなくなって
   しまいます。
   可能ならROM半分のATtiny441でも動くようにと。

ところが・・・ここでも失敗に遭遇。
  ATmega328Pを使ったピピちゃん温度計
では、外に取り出した内蔵基準電圧1.1Vを使っての
サーミスタ抵抗測定がうまく行ったので、ATtiny841でも
大丈夫だろうと考えたのです。

  ※ATtiny841と同じ14ピンのATtiny84は
   内蔵基準電圧を外に取り出せない。
   そして、外部からの基準電圧も2.0Vが
   最低というスペックで断念。
   ・回路設計はデータシートの熟読から。 スペックをちゃんと調べろ!

こんな接続をしたかった。 (ATmega328Pでは実績あり)
C52_20221005171201
ATtiny841のAREFピンからちゃんと1.1Vは出てきました。
これを低オフセット電圧のオペアンプでバッファします。
しかし、この出力を「神様抵抗」のプラス側としてA/D変換
すると・・・オープンにしても(10kΩでAREF電圧につながる)
フルスケールの「1023」が出てこないのです。
5つのチャンネルとも、計算値より低い値が出るのです。
フルスケールに近い電圧の高い所だと2~3。
電圧が低い所だと、3~4、出てくるA/D値が小さいのです。

全体に下へシフトしてる感じです。
  ※ATmega328Pではこんなことには
   ならなかった・・・

そこで、外部から基準電圧を供給してみることに
しました。
  ※基準電圧を出力するAREF端子。
   ノイズ低減のためにパスコンを入れる
   ためのもの。この電圧出力を利用
   できるとは、データシートには記され
   てない。
  ※ATtiny85や84では2.0Vが最低電圧
   という制限あり。
C53
そうすると大丈夫に。
  ※完璧に誤差がないというわけではく
   そこそこエエ感じ。

ということで、
 「同じシリーズのチップでうまいこと行った
  経験があっても、始めて使うチップは、何が
  あるかわからへんでぇ」
と、あたりまえの結果に。

ダイソーのプラケースに入れるとこんな感じ。
Cc1_20221005171601

  ※L型ダブルのピンヘッダーが無かったんで、
   オムロンのツメ付ヘッダーを切断改造。

| | コメント (0)

2022年10月 3日 (月)

サーミスタ103JTで計った温度をアナログ出力

失敗談、2022年9月28日:回路設計はデータシートの熟読から。 スペックをちゃんと調べろ!
この発端の製作物です。

「温度→電圧」変換はLM35やTMP35あたりのセンサーが
便利なんですが、ブツに接触させて温度を測るときにちょいと
問題があるんです。
 ・形状のせいか応答が遅い。(TO-92パッケージ)
 ・足から熱が逃げるのか、低い目の温度が出る

  ※型番、LM35は、昔はナショセミ。今はTIに。
   そしてTMP35はアナログ・デバイセズに。
   ややこしいぞ。

応答が早くて間違いなくブツに接触できる温度センサーという
ことで、「セミテックのサーミスタ103JT」を使って、
 ・センサーの抵抗値から温度を算出。
 ・温度をD/A出力。
という方法で、アナログ値を得るようにしました。
  ※単純なことをするのに、むちゃたいそうな回路。

Tn85_thm1

ATtiny85を選んだんがそもそも失敗。
でも、作りかけたんで、完成形に仕上げてみました。

測定温度範囲は0~80℃。
10bit分解能のA/Dで温度を得てますんで、分解能は
ざっと0.1℃。
これを0.0V~4.0Vの電圧に変換します。
1℃あたり0.05Vのスケールです。

AVR studioでプログラムを組んでます。
フォルダーごと圧縮。
   ・ダウンロード - thm_85_da1.zip

10bitのA/D値から2kバイトのテーブルで温度に変換
という手抜き手法。
複雑な算術関数は使っていません。

回路を組み込んだプラケースはダイソーの「ミニケース5個組」
M31
M32

この回路の電源は、
Arduino-UNO 12bit×4chアナログ SDカードデータロガー完成形
から供給します。

さて、温度センサーIC:LM35(TMP35)とサーミスタ103JTの
違い、どんなもんかと調べますと・・・

15Ωのホーロー抵抗の側面にセンサーをくっつけて、
抵抗を通電して発熱させます。
その時の温度変化を電圧として記録しました。

M11_20221003101301

M12_20221003101301

まずは、センサーを軽く貼り付けした状態から。
M13_20221003101301
すると・・・
Cap004_20221003101401
センサーICとサーミスタの出力に差が生じます。
時間が経過してもそれが埋まりません。

M14_20221003101301
テープでぐるぐる巻きにして、温度が逃げないように
したつもりでも・・・
Cap005_20221003101401
途中で段ボール箱をかぶせましたが、時間が経っても
2℃くらいの差が埋まりません。

そこで、2017年11月6日:液晶表示器「焼き鈍し」
で使った「保温箱」で試してみました。
この2種以外にも、センサーICとサーミスタを加えます。
M21
2と4が今回のセンサー。
1はセンサーICの外周を熱収縮チューブで覆っています。
4は同じ103JTですが、先端近くまで熱収縮チューブが来ています。

センサー部分はむき出しのまま、電線のところでマスキングテープを
巻いてまとめました。

65℃で保温された箱の中に投入しますと、数分で4つとも温度が
安定、収束します。
  ※電線部分、およそ20cmは保温箱の中に入っていて、
   先端のセンサー部と同じ温度になっているかと。
Cap006_20221003103401

また、4つのセンサーを箱に入れたまま、箱を室温
から加熱した場合も20分ほどで65℃に到達し、
その後の温度差はそれほど感じません。

Cap007_20221003103401
  赤:保温箱温度制御センサー
  緑:サーミスタ 4
  青:サーミスタ 3
  マゼンタ:センサIC 2
  シアン: センサIC 1

発熱体に直接接触させて温度を測る場合と、周囲の空気ごと
温度を測るということで差が生じるのでしょう。

このあたりをもうちょい勉強しようとすれば、何を
調べれば良いでしょうか?

| | コメント (6)

2022年9月29日 (木)

103JTサーミスタの自己発熱。 5Vで10kΩはちょっとなぁ

前記事2022年9月28日:回路設計はデータシートの熟読から。 スペックをちゃんと調べろ!
は、サーミスタを使った温度測定に絡みます。

Arduinoにサーミスタをつないで温度測定・・・
複雑な計算式も入るし、サーミスタを指で触れば温度変化が
出てくるし。
マイコンを活用してる気分になれます。

しかし・・・ちょっと待てぇ
  そのやり方は正しくない。
  サーミスタが持つ性能をつぶしているぞ。
という例を見かけるのです。
  ネットでも初心者向けの本でも。

安価で入手しやすいからとよく出てくるのが
セミテックの「103JT」
  応答性が良いので私も愛用しています。

このサーミスタの特性を分かって「こんなもんだ」と
使っているのなら仕方ないのですが、きっと分かって
ないのだろうなぁというスペックが「熱放散定数」。

サーミスタに電流を流した時、その消費電力で自分が
発熱してしまい、測定温度に誤差が生じます。
その影響度合いで、単位が「mW/℃」。
「ンmWの測定電力で1℃上がる」という指標です。
103JTだと「約0.7mW/℃」というスペックが出ています。

  ※形状の大きな103AT-11だと3mW/℃、
   103AT-2だと2mW/℃。
   大きくなると頑丈になりますが、応答が
   遅くなります。

103JTの約0.7mW/℃、これ、なかなかキツいスペックなんです。
よく出てくる例がこの図のような接続。
【図1】
T1_20220929114401
サーミスタに直列に10kΩの抵抗を入れて5V電源に接続。
ArduinoのADCは、基準電圧=5Vで変換。
   ※
サーミスタを使って温度測定という解説記事、
    たいていはこれ。

これだとどうなるか。
サーミスタの抵抗値とRsが同じ値になったときが
最大の消費電力になります。
   ※103JTだったら10kΩの25℃
どれだけの発熱があるかをグラフに。
Cap036
各温度での抵抗値からサーミスタの消費電力を算出し、
熱放散定数で温度上昇を計算しました。
  ※5Vだけでなく、3.3V、2.5V、2.0V、1.25Vの
   電圧の時も示しています。

測定電圧=5V、Rs=10kΩだと10~40℃の温度域で
およそ0.8℃の温度上昇が生まれると。
Arduino UNOの10bit ADCだと、0.1~0.2℃が
分解能です。
それに0.8℃が積み重なるのは・・・ちょっとなぁ。です。
  ※これをどうにかしたいと思うか、
   こんなもんでエエやんと置いとくか・・・

この温度測定方法でサーミスタの消費電力=発熱を
減らすには・・・
 ・測定するときだけ通電する
 ・測定電圧を下げる
 ・Rsを大きくする
この3つです。

Rsを変えた時の様子がこのグラフ。
15kΩと20kΩにした時の発熱です。
Cap037
Cap038
Rsを大きくするより、サーミスタへの供給電圧を
下げる方が効果があります。
   (電力の計算式 ExE/R、IxIxR)

※Rsを4.7kΩにしたら・・・
Cap001_20220930145401
40~50℃あたりで、自己発熱が1.9℃ほど積み
上がってしまいます。

~~~~~~~~~~~~~~~~~~~~~~~~
サーミスタへの供給電圧を下げての測定方法、
どんなのが・・・
例えば、こんな回路。
【図2】
T2_20220929114901

サーミスタへの供給電圧とA/Dコンバータの基準電圧
を分けてやると、測定温度範囲が自由に決められます。
ところが、この回路だとやっかいな問題が出てくるの
です。
VthmとVzが変動したら、それが測定系の誤差に
つながります。
サーミスタの抵抗値を正しく算出するにはVthmとVzの
絶対値が必要なのです。
どちらかの電圧が動くとアウト。誤差が生じます。
  ※動くなら二つが比例して。
  ※VthmをそのADC測定系で計るのも方法。

それを改善するのがこの方法。
【図3】
T3

【図1】は5V電源でしたが、ADCの基準電圧と
サーミスタへの供給電圧を両方とも下げて発熱を
防ぎます。
二つを同じ電圧にというのが重要で、サーミスタの
抵抗値はRsを基準に算出でき、Vzは計算式から除け
ます。
このRsは「神様抵抗」(俺が正しい!)となります。

Arduino UNOで使っているATmega328Pマイコンのように
Vref電圧が外に取り出せるなら、こんな方法も可能です。
【図4】
T4
Arduino UNOだと「1.1V」が取り出せます。
  ※測定電圧が低い方が自己発熱が小さくできる。


サーミスタを使った測定で、もう一つのグラフを示しておきます。
ADCの分解能を変えた時、測定温度の最小値がどう変化するかを
描いています。
神様抵抗Rsを10k、15k、20kにした時を計算してみました。
Cap039_20220929115501
Cap040_20220929115501
Cap041_20220929115601

Arduino UNOの10bit A/Dだと、常温域で0.1~0.2℃の
分解能がギリギリになることが見えます。
発熱の影響を小さくしようと、Rsを大きくすると、
サーミスタの抵抗値が小さくなる高温域での分解能が
落ちてしまいます。
  ※Vz=ADCの基準電圧=Vthmとしてのグラフです。
   Vz=Vthmが変わっても分解能は変わりません。

 

※関連
2021年8月11日:サーミスタ温度計、何ビットのA/Dコンバータがいるか?
2021年7月31日:16bit A/Dコンバータ LTC2460 サーミスタ103JTを使った温度測定で・・・
2018年2月13日:ピピちゃん温度計・ヒータ制御

※ネット検索
ラジオペンチ:Arduiono を使ってサーミスタで温度を測る(2019-11-08)
ラジオペンチ:ペルチェ温度コントローラーの製作 (温度センサーをサーミスタに変更)(2019-11-11)
セッピーナの趣味の天文計算:サーミスタで正確な温度を測るコツ - 基準抵抗(R0)、B定数、熱拡散係数(2015年2月18日)
マカロニペンギンの健忘録:#arduino で温度測定-自己発熱対応版(2011年11月17日)

あれこれ見てますと・・・
サーミスタの抵抗測定の手順で、A/D電圧を求める時、
件の「1/1023」 が多く出現しています。
例えば、
Decent dress:Arduino Nano と NTCサーミスタで温度計測 (2018/11/29)

本来、Rsを神様抵抗としての測定系では、A/D変換の
基準電圧値は数式から除外されます。
しかし、電圧計算の1/1024を1/1023にしてしまうと
「‰」単位の誤差が生じます。   ※10bitのADCで

jh4vaj:Arduinoでサーミスタを使って温度計を作るのに、電圧を求める必要はない(2021/3/9)
jh4vaj:ArduinoのADコンバータ、1023?1024?( )

| | コメント (0)

2022年9月28日 (水)

回路設計はデータシートの熟読から。 スペックをちゃんと調べろ!

8ピンのATtiny85、14ピンのATtiny84のA/D入力絡みで
ちょいと失敗。

◆状況
・テスト用ジグ作り。
・内蔵のADCを使ってアナログ電圧を測定。
   サーミスタで温度測定
・基準電圧はできるだけ低くしたい。
   サーミスタの自己発熱の防止
・内蔵基準電圧の1.1Vを外に引っ張り出せれれば
 良いんだが、tiny85もtiny84もそれはできない。
   Arduino UNOのATmega328Pは可能
・基準電圧を外部に持たせて、Arefピンに供給か。

こんな考えで回路を組んだのです。

◆参考:基準電圧の切換
ATtiny85の場合
VccかArefか、内部の1.1Vあるいは2.56V。
85_0

ATtiny84の場合
VccかArefか、内部の1.1V
84_0

この基準電圧選択で
 「内部の1.1Vが使えるんだから、Arefピンに外部の
  基準電圧として1.1Vを加えてもかまわないだろう」
と、勝手に推測。

これが間違い。
A/D変換のスペック表を見ますと・・・
ATtiny85
85_1

ATtiny84
84_1

いずれのチップもAref:外部基準電圧の最小値は「2.0V」

外部から1.1Vを入れると・・・
  A/Dから全ビットHのデータが出てきます。

Arefに入れてる電圧が低いんやぁ」の原因に到達するのに
えらく手間取りました。
I/Oの初期化ルーチンで、A/Dの基準電圧をVccや内部電圧に
切り替えると正しく変換。
おもわく通りのデータが出てきます。
ところがArefにして外部からの1.1Vだとアウト。

その後、データシートを熟読したら「最小2.0V」という
記述を発見
したという次第です。
  Aref電圧も徐々にアップすると、1.5Vくらいから
  データが出始め、2.0Vのちょい手前から安定になる
  という挙動でした。

 

| | コメント (0)

2022年8月 7日 (日)

パルスジェネレータを作ってみた:16MHzクロックを追加

2022年8月 5日:Arduino-UNOでパルスジェネレータを作ってみた
この続きです。
クロック源、もう一つ「速いの」を追加しました。
これまでは、
 1us  1MHz
 10us 100kHz
 0.1ms 10kHz
 1ms  1kHz
の4種類でしたが、ハード的に得られる最高クロック
16MHzを追加しました。

クロック周期が「1/16us」で、「0.0625us」が最小単位と
なります。
小数以下の表示桁が4桁に増えちゃうわけです。
1クリックで1つ増減するのではなく「0.0625」増減します。
  ※同じ4桁でも、「0/16」「1/16」・・「15/16」と
   1/16で示す方が増減が分かりやすいかと思ったん
   ですが、どうでしょう?

整数部の最大が「4095」になるので、小数点を入れても
「9桁」になり、なんとか表示できました。

こんな具合。
Ta2

タイマー1のカウント値16bitの最大65535で、
「4095.9375us」となります。
  ※1MHzクロックだと1usピッチで最大が「65.535ms」
   でした。単純に周波数16倍。

クロックを変えるとこんな感じで表示が変わります。
Ta1

16MHzでの最下位カーソル位置が0.1桁のところになり
ここに合わせたときだけ0.0625ピッチで変化します。
しかし、10^0桁~10^3桁に持ってくると、ちゃんと
10進で変化するようになっていて、操作上の違和感は
ありません。

※その後:
2022年8月24日:パルスジェネレータを作ってみた:箱に入れた
をどうぞ。

| | コメント (3)

2022年8月 5日 (金)

Arduino-UNOでパルスジェネレータを作ってみた

ラジオペンチさんとvabenecosiさんのパルスジェネレータを
真似してみました。
・ラジオペンチさん
   http://radiopench.blog96.fc2.com/blog-entry-808.html
・vabenecosiさん
   http://vabenecosi.blog.fc2.com/blog-entry-37.html

そのままでは、面白くないので、
  ・トラ技 2010年7月号 ATtiny2313を使ったハンディ方形波発生器
の方式、
 ・16bitタイマー1のクロック入力T1に
 ・タイマー2で作った方形波を入れる
ということにしてみました。

クロック源となる方形波の周波数は、
1MHz 100kHz 10kHz 1kHzの4種類。

周期だと1us 10us 0.1ms 1msとなり、
これをクロック源として16ビットで設定できるので、
最大の周期は、
  1us : 65.535ms
  10us : 655.35ms
 0.1ms : 6.5535秒
  1ms : 65.535秒
となります。
  ※ATtiny2313では3桁のデジタルスイッチだったので
   もの足りなかった。

とりあえずArduino-UNOをベースに試してみました。
こんな回路。

Pg11

操作スイッチを4つにして、ロータリーエンコーダで
設定できる入力桁位置を設定できるようにしています。
   ※←(SW2) →(SW3)がカーソルスイッチ
カーソルより右の下位桁の値をそのままにして
左側(上位桁)の数値だけを±できるようにしました。

「12.345ms」で、2の位置にカーソルがある時CCW回しすると
→11.345ms
→10.345ms
→ 9.345ms
→ 8.345ms
→ 7.345ms
と、下位3桁はそのままにして上位の数値を-1します。

  ※左右のカーソルを独立させたので、操作スイッチは
   4つになっています。

また、スイッチの拡張機能操作は「二つ同時押し」
ではなく「長押し」にしています。

こんな操作画面です。
Ga001
右上がタイマー1の供給クロック周期。 (4種類)
右下が出力パルスの極性、PosとNegで表示。
周期設定の時は周期から周波数を計算して表示。

SW1の操作で周期設定とパルス幅設定を変えます。
Ga002
パルス幅設定の時は、デューティ比を表示します。

Ga003

操作する桁を固定できるので、大きく数値を変えることが
できます。
パルスの周期を0.2秒にしてパルス幅を変えてみました。
エンコーダーのつまみをあわてずに回すと、
デューティが「100% → 0% → 100%」と変化する
様子が見えます。

Pg21
デューティ100%と0%でH、Lに張り付かせる処理も
面白いかと。

・エンコーダの回転チェック
・操作スイッチの入力
この二つを割り込みで処理しています。

エンコーダをクルクル操作すると、周期やパルス幅が変わ
るので、その値の表示だけでなく、周波数とデューティー比
を計算して表示します。

その処理時間が「7~8ms」。
  ※液晶表示に時間がかかる

エンコーダのクルクルを速くすると・・・処理が間に合わ
なくなってきます。
でも、エンコーダのカウント処理は割り込みで動いているので、
up/down値の抜けはありません。

メイン側の計算・表示処理がつまってもカウント抜けは
しないというしかけになっています。

その様子です。
エンコーダを速く回した時、表示処理がつまっている
様子が見えています。
  ※loopの頭で出力ポートをトグルしているので、
   処理が間に合わなくなると、トグルが見えるように
   なります。
   描画中はトグルが止まります。
Pg22

テスト出力しているパルスで、表示処理中も、エンコーダ
の回転を読んでいる様子が見えています。

割り込みを使わずにエンコーダを読んでいたら・・・
   カウント抜けの可能性大です。

ラジオペンチさん、vabenecosiさんの回路そのままでは
動きません。
  ・OC2A 方形波出力 → T1クロック入力のつなぎ
  ・エンコーダをINT0、INT1に。
     (INT1は割り込みでは使っていない)
ということで、液晶やスイッチのポートが変わっています。

・回路図(BSch3Vファイル) 圧縮
      ダウンロード - pgen16a.zip

・スケッチ (ファイルタイプをtxtに)
      ダウンロード - p_gen16a.txt


さて、ケース入れを考えなければなりません。
電源は電池かな。


※追記 : 参考、関連あれこれ
     (勉強のために読んでみて)
2020年2月10日:ArduinoのanalogWrite 1/255なの?
2020年1月28日:Arduinoから「タイマー0」を取り上げる(ユーザーが使う)
2022年7月25日:Atmega328P タイマー/カウンタ1の高速PWM動作
2020年8月13日:Arduino、analogWriteは捨てちゃえ。ちゃんとしたPWMを使おう
2020年8月16日:Arduino UNOのPWM出力を(ちょっと精密に)確かめる
2020年9月19日:クリック有りのロータリーエンコーダーで
2019年4月1日:ArduinoのEEPROMアクセス、「EEMEM」について
2020年2月4日:Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
2022年3月11日:Arduino-UNO 割り込み処理のミスあれこれ:ロータリーエンコーダー
2018年10月11日:魔法の言葉「volatile」


| | コメント (2)

2022年7月26日 (火)

割り込みと絡むクリチカルな制御 変数にはVolatileを!

魔法の言葉「Volatile」、これ大事です。
先日の Atmega328P タイマー/カウンタ1の高速PWM動作
このスケッチ、「IR_FRQ_SCAN3.ino」。
  ※たくさん空いている出力ポートにパルスを出して、
   動きをオシロでチェックできるようにしています。

大まかな処理の流れは、

・5msごとに、LED駆動周波数であるPWM周波数と
 デューティ比を計算して、変数に保存。
・タイマー1割り込み内(PWM周波数と同じ)でその値を
 タイマー1レジスタに書き込み。

これを繰り返し、22kHz~54kHzの周波数で赤外線LEDを
光らせています。

スケッチから関連部分をプックアップ。

【関連する変数】
メインルーチンでセットして割り込みでハードウェアを制御

volatile byte f_frqset; // 周波数設定フラグ
volatile word led_div; // LED駆動 分周比 ICR1に設定
volatile word led_duty; // 駆動デューティ PWM設定値 OCR1Aに設定

【関連する処理:loop内】

// 5msサイクルloop
while(1){
if(f_5ms){ // 5ms経過
f_5ms = 0;
// LED PWM周波数とデューティ
PD4_H; // (!!!)
if(led_frq == FRQ_LO){ // LO周波数のとき
PD2_H; // (!!!) パルスH/L
nop(); nop(); nop(); nop();
PD2_L; // (!!!)
}
a = (word)(160000L / (long)led_frq); // 分周比 (1)
b = (2 * a) / 3; // デューティ 1/3で (2)
cli(); // いったん割り込み禁止にして (3)
led_div = a; // LED駆動周分周比
led_duty = b; // デューティ比
f_frqset = 1; // 設定on タイマー1割り込みで処理
sei(); // 割込再開
// 周波数スキャン D/A PWM出力 220~540を0~320にして1/2
OCR2A = 255 - ((led_frq - FRQ_LO) / 2); // PWM 0~
PD4_L; // (!!!)
}

【割り込み処理】

ISR(TIMER1_OVF_vect) // タイマー1割り込み TOP値で
{
static byte cyc = 0; // LEDonサイクル
PD7_H; // (!!!)
switch(f_frqset){ // 実行区分
case 1: // PWM 設定指令
PD3_H; // (!!!)
ICR1 = led_div - 1; // LED周波数分周比 ★
OCR1A = led_duty - 1; // LED駆動デューティ 1/3 ★
cyc = 8; // 8サイクルだけLEDをon
f_frqset = 2;
PD3_L; // (!!!)
break;
case 2: // サイクル数をチェックしてオフに
if(cyc) cyc--;
if(cyc == 0){ // ダウンカウントしてゼロになった
PD3_H; // (!!!)
if(INP_SENS){ // センサー入力 H/L ?
SENS_H; // ポート出力でH保持
f_senshl = 1;
}
else{
SENS_L; // ポート出力でL保持
f_senshl = 0;
}
OCR1A = 0xFFFF; // LED駆動オフに
f_sensok = 1; // センサー状態確定
f_frqset = 0; // 次の起動設定を待つ
PD3_L; // (!!!)
}
break;
}
PD7_L; // (!!!)
}

(1) 5msごとに分周比=PWMの周波数とデューティ比を計算。
(2) 結果をいったん a と b に入れておいて。
(3) 割り込み禁止状態で変数に保存。

この部分の処理が、変数に付けた「volatileの有無」で変わって
しまうのです。

まずvolatileを付けたとき。 オシロでタイミングを見ます。
Vl09

PWMを処理しているタイマー1割り込み、この場合は
一定の周期で繰り返しています。

ところがvolatileを外すと・・・

Vl07

ch1波形、約65uほどのHレベル区間が「(1)(2)(3)」を
処理している時間です。
  ※ch2の1ms割り込みが5ms周期を知らせている。

ch3のタイマー1割り込み、新周期と新デューティ値を
変数に書いているのが「★★★」のタイミングです。
どういうわけか、割り込みが待たされてしまい、
処理の開始が遅れています。

これ、volatileを取ってしまったため、コンパイラが
  「いちいち仮変数の a と b に入れんでもエエやん」
  「直接 led_div と led_dutyに書いたろ」
  「ちょっとは早なるで
  「いちおう書き込み前に cli() はしとこ」
  「書き終わったら sei() ね」
と判断したせいです。

コンパイラの「ちょっとでも速よう実行できるほうがエエやろ」
というおせっかい。 (最適化の指示)
ただ、計算の前から割り込み禁止としたんで、時間のかかる除算
割り込み禁止の中に入ってしまい、処理が長くなってタイマー1割り込み
が待たされてしまいました。
  ※(2)の除算が割り込み禁止の中に入ってました。
   (1)は禁止前の実行でした。
割り込みが待たされるということは・・・
ICR1でPWM周波数を決めている方法では、ちょっと怖いこと
(PWM処理の一周抜け)が起こるかもしれません。

※関連
2018年10月11日:魔法の言葉「volatile」
2016年02月19日:Arduinoのタイマー処理

※追記
コンパイルされたソースファイルで比較してみると。

★volatile有

7b4: 20 91 21 01 lds r18, 0x0121 ▲led_frq
7b8: 30 91 22 01 lds r19, 0x0122
7bc: 40 e0 ldi r20, 0x00 ; 0
7be: 50 e0 ldi r21, 0x00 ; 0
7c0: c5 01 movw r24, r10
7c2: b4 01 movw r22, r8
7c4: 0e 94 94 07 call 0xf28 ; 0xf28 <__divmodsi4>
7c8: 29 01 movw r4, r18
7ca: 3a 01 movw r6, r20
7cc: c9 01 movw r24, r18
7ce: 88 0f add r24, r24
7d0: 99 1f adc r25, r25
7d2: b6 01 movw r22, r12
7d4: 0e 94 80 07 call 0xf00 ; 0xf00 <__udivmodhi4>
7d8: f8 94 cli
7da: 50 92 20 01 sts 0x0120, r5 ▲led_div
7de: 40 92 1f 01 sts 0x011F, r4
7e2: 70 93 1e 01 sts 0x011E, r23 ▲led_duty
7e6: 60 93 1d 01 sts 0x011D, r22
7ea: 30 92 1c 01 sts 0x011C, r3 ▲f_frq_set
7ee: 78 94 sei
★volatile無

7b2: 20 91 21 01 lds r18, 0x0121 ▲led_frq
7b6: 30 91 22 01 lds r19, 0x0122
7ba: 40 e0 ldi r20, 0x00 ; 0
7bc: 50 e0 ldi r21, 0x00 ; 0
7be: c5 01 movw r24, r10
7c0: b4 01 movw r22, r8
7c2: 0e 94 92 07 call 0xf24 ; 0xf24 <__divmodsi4>
7c6: f8 94 cli
7c8: 30 93 20 01 sts 0x0120, r19 ▲led_div
7cc: 20 93 1f 01 sts 0x011F, r18
7d0: c9 01 movw r24, r18
7d2: 88 0f add r24, r24
7d4: 99 1f adc r25, r25
7d6: b6 01 movw r22, r12
7d8: 0e 94 7e 07 call 0xefc ; 0xefc <__udivmodhi4> ←★
7dc: 70 93 1e 01 sts 0x011E, r23 ▲led_duty
7e0: 60 93 1d 01 sts 0x011D, r22
7e4: 70 92 1c 01 sts 0x011C, r7 ▲f_frq_set
7e8: 78 94 sei

「★volatile無」だと、「cli()割り込み禁止~sei()割り込み許可」内に
割り算が入っています。
「★volatile有」だと割り算は外に。
これが割り込み処理の遅れにつながっています。

| | コメント (0)