Arduino

2020年5月21日 (木)

JIS C8708:2019充放電実験回路

バックアップがわりにJIS C8708:2019充放電実験回路とそのスケッチ(Arduino-UNOのブートローダーを焼いたATmega328Pで動かしている)をアップしておきます。

まず回路図。
Baycyc3b
これまでどおり、24V→5VのDC-DCコンバータを乗せています。
その理由:
・回路を置く作業ラックにDC24Vの電源が来ているから。
   FA用途の回路をテストしたりするんで、その絡み。
・0.5C充電の必要から、基板にDC5Vを直だと電流が大きくなり、
 電源配線でのドロップが気になるから。
ということです。
5V電源を基板そばに置き、電源配線を端子台などでしておけば
DC-DCコンバータは不要でしょう。

ICが6つ。抵抗が35本。コンデンサ27個。スイッチ3つ。
液晶が一つ。ヒートシンクに付けたパワーMOS-FETが二つ。
むちゃくちゃ大がかりな回路じゃありません。

そしてこれがスケッチ。 ダウンロード - batcyc3c1.zip
ファイルタイプを「ino」ではなく「c」にしています。
  inoだとArduinoのIDEが立ち上がってしまうんで。

設定パラメータ、前のよりちょい増やしています。
制御の操作で充放電の実行を中断した時、以前のバージョンでは
これを再開させる方法がありませんでした。
  (もういっぺん最初からになってしまう)
ちょっと不便なんで、「何サイクル目の充電あるいは放電から再開する」
という設定を設けておきました。
 "32 Brk cyc(50)"; 中断再実行時のサイクル (1~50)
 "33 Brk cnt(8)";  中断再実行時のサイクルカウント (1~8)
 "34 Brk dcg/chg"; 中断再実行時の放電(0),充電(1)区分
普通に最初から試験する場合はそれぞれに「0」を入れておきます。
cycとnct、どちらも0以外のときに試験再開の処理を行います。

| | コメント (0)

2020年5月17日 (日)

Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

ちょっとまとめてみました。  (関連記事へのリンクも注目)
コメント歓迎!!  ご意見、お待ちしています。

==========================

◆A/Dコンバータのスケーリング方法

Arduino-UNOの内蔵A/Dコンバータは10bit。
0x0000~0x3FF:0~1023の数値が出てくる。
この値から、
  入力電圧値を求める時
  温度センサーなどのセンサー情報を処理する時
  AnalogWriteなどに値を渡す時
などの処理でのミスが目立つ

●基本的な知識:A/D変換データの意味

10bitのA/D変換値(ADC)と入力電圧(Vin)、そして基準電圧(Vref)
は以下の関係がある。
   ADC = (Vin / Vref) * 1024
   Vin = (ADC * Vref) / 1024
つまり10bit ADCの最大値1023は、Vref電圧より「-1LSB」だけ
小さい入力電圧(以上)の時に発生する。
変換値1023はVrefではない。
「Vref - 1LSB」の入力電圧、つまり「Vref * 1023 / 1024」が
ADC=1023での入力電圧となる。

結論:ADCからVinへのスケーリングは「1/1024」で計算しなけ
   ればならない。
   「1/1023」は間違いである。

※関連
2020年1月8日:ミスが広まる 1/1023 vs 1/1024


●基本的な知識:Vref電圧の実値

A/D変換器のVrefはAVCC電源(5V)、内蔵基準電圧(1.1V)、外部
基準電圧入力(AREF)に切り替えできる。

A/D変換を使って、入力電圧や温度センサーの測定値を求める
ときなどは、Vref電圧の実値が重要である。
「電源電圧 = 5V、内蔵基準電圧 = 1.1V」と決め打ちするのは
危険である。
A/D変換後、電圧や温度といった数値に直した値を評価する
時は、実際の電圧を測定し、その値をVref値とすべきである。

例えば・・・内蔵基準電圧=1.1Vは1.0V(min)~1.2V(max)と
データシートで規定されていて、1.1Vと決め打ちできないのは
あきらかである。
5Vや1.1Vで決め打ちして計算した電圧や温度、これを評価する
ときは、Vref値の誤差を含んでいるとの注意書きが必須である。
これを記していない記事は・・・「ちょっとなぁ~」

※関連
2019年3月22日:Arduinoのアナログ基準電圧入力


●間違った知識:map関数

map関数を使ってスケーリングを行う処理でのミスが目立つ。
mapは2点間の数値を元に線形補間を行う。

例えば、10bitのA/D値を8bit値に変換する時:analogReadして
analogWriteするような時、(最大値1023を255に変換する)この
ような記述を見かける。
   y = map(x, 0, 1023, 0, 255);

もともと、10bit→8bitの変換は、値を1/4するだけである。
  (四捨五入の話は別問題として)
この場合の補間式は 「y = 0.25x + 0」で、傾き1/4の線上
で 「x → y」の変換が行われる。
先ほどの「0, 1023, 0, 255」だと傾きが「255/1023」となり
本来の「256/1024」の補正線から外れる。
  256/1024 → 0.25
  255/1023 → 0.249266…

map関数の定義で、
  map(value, fromLow, fromHigh, toLow, toHigh)
   value:   変換したい数値
   fromLow: 現在の範囲の下限
   fromHigh: 現在の範囲の上限
   toLow:   変換後の範囲の下限
   toHigh:  変換後の範囲の上限
上限、下限という表記があるのでanalogRead、analogWrite
の最小、最大値を記入していると推測できる。
しかし、10bit→8bitの変換の時にその最大値を用いるのは
正しくない。

例えば、
   y = map(x, 0, 1023, 0, 255);
の時、10bitの半値である512を代入すると127が返ってくる。
正しい値は8bitの半値=128である。
また1LSB多い1024を代入しても256とならず255となってしまう。

※参考
  ・2019年4月3日:線形補間って「LERP」って言うんだ!

以下のように考えてもこれがミスであることがわかる。
この考えで8bit→10bitの変換をすると、
   y = map(x, 0, 255, 0, 1023);
と書くことになる。
これで「x=255」を変換すると「y=1023」が得られる。
一見正しいようだが(式どおりの答え)、当たり前に考えて255の
4倍である「1020」が正しい値である。

正しくは、
 10bit→8bit変換  y = map(x, 0, 1024, 0, 256);
 8bit→10bit変換  y = map(x, 0, 256, 0, 1024);
である。

10bit→8bit変換では以下の書式でも正しい値が得られる。
  y = map(x, 40, 1000, 10, 250);
「0,1024,0, 256」の直線の傾き=0.25と、オフセット=0は同じ
である。

※余談
本来のスケーリング処理は下限上限という意味ではなく、
2点の表示値(A/D値の読み)と、実際の測定値(外部電圧計など
での読み)というふうに、キャリブレーションをおこなった
結果の補正に用いる。
A/D変換回路に付随する回路、例えば基準電圧値の誤差や前置
アンプのゲイン誤差やオフセット誤差、これらを補正するた
めに線形補間を行う。

※以下の例題がおかしいのが根本原因か
http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2743
https://www.arduino.cc/reference/en/language/functions/math/map/

※関連
2020年5月16日:Arduino 10bit A/D値をmap関数でスケーリングする例


※追記 2020-05-19
10bit→8bitの変換では数値が大きくて誤差が見えにくい。
そこで、極端な例として、3bit→2bitへの変換を考えてみる。
 map(x, 0, 7, 0, 3) …<a>
 map(x, 0, 8, 0, 4) …<b>

<a>がよく出ている例題「0,1023, 0,255」での方法。
変換前と変換後の下限値・上限値を指定している。

<b>が補間直線の傾きを考えて記したもの。
3bit→2bitなんで単純に0.5。 2で割れば良い。

結果は、
入力値 <a> <b>
 0   0  0
 1   0  0
 2   0  1
 3   1  1
 4   1  2
 5   2  2
 6   2  3
 7   3  3
----------------
 8   3  4
 9   3  4
 10   4  5
 11   4  5
 :    :
 16   6  8
 24   10  12
 32   13  16
 40   17  20

mapでは最大値を超えても変換結果が出てくる(線形補間だから)
ので、8以上の値を入れるとミスしているのが良く分かる。
また、四捨五入の問題でもないことが見える。

線形補間の式であるmap関数、「map(x, 0,1023, 0,255)」は
10bit→8bit変換におて根本的に間違った使い方をしていること
がわかっていただけたであろうか。


※さらに追記

英文のArduino Reference
https://www.arduino.cc/reference/en/language/functions/math/map/
を見てみると、
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notes & Warnings

As previously mentioned, the map() function uses integer math.
So fractions might get suppressed due to this. For example, fractions
like 3/2, 4/3, 5/4 will all be returned as 1 from the map() function,
despite their different actual values.
So if your project requires precise calculations (e.g. voltage accurate
to 3 decimal places), please consider avoiding map() and implementing
the calculations manually in your code yourself.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
なんて書かれている。
google翻訳にかけると・・・
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
前述のとおり、map()関数は整数演算を使用します。
そのため、これにより分数が抑制される可能性があります。
たとえば、3 / 2、4 / 3、5 / 4などの分数は、実際の値が異なっ
ていても、すべてmap()関数から1として返されます。
したがって、プロジェクトで正確な計算が必要な場合(小数点以下3桁
まで正確な電圧など)は、map()を避け、手動でコードに計算を実装
することを検討してください。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
文末の「・・・yourself.」:自分でなんとかしろ!
っと言っているよう・・・

mapは悪くない。
線形補間を32bitの整数で実行している。
mapに与える数値をミスっているのが問題。

日本語解説で、下限・上限と記されているが
英文ではthe lower boundとthe upper bound 。
下限・上限としか言いようがないか・・・
数学的な言い回しだとどうなるんだろうか。

================================
それと、もうちょい。
  map(x, 0,1023, 0,255)  ・・・これが正しい例を紹介

これが、例えばA/D変換器のキャリブレーション結果で、
A/Dの値が「1023」の時にその入力電圧をテスターで計っ
てみたら「255.0mV」だった。  (精度表現のためあえて.0を追記)
この場合の変換式はこれで合っている。

このA/Dコンバータが実は10bitではなくもっと桁数が大きくって、
例えば12bitの分解能だったとする。
電圧を4倍の「1020.0mV」に上げてみると、A/D値も4倍になり
「4092」という測定結果になるはずである。(4095ではなく)
この場合のスケーリング値は「255/1023」で正しい

10bit→8bitの変換、つまり数値を単純に1/4するだけの計算で
この値を使っているからおかしいわ
けだ。

================================
  以下はコメントに対するお答え
     2020年5月17日 (日) 16時40分 タナカ さん

※map関数の中で二つの上限値を+1なんて処理をしたら、本来の
線形補間ができなくなっていまう。
mapのコードそのものは変更しなくてよい。
「下限・上限」というから間違うのか。
重要なのは変換直線の傾きとオフセット。
それを決める2点のX,Y位置。
この一次関数って、中学生の問題?
しっかりしろエンジニア!

================================
※追記 コメントに書いたが本文にも載せておく。

mapが浮動小数点を許すのなら・・・
map(x, 0, 1023.0, 0, 255.75)で解決だ。

この補正値は256.0/1024.0と同じ0.25。

================================

■関連記事ピックアップ:居酒屋ガレージ日記から

2020年5月2日:Arduino 放置したポートが及ぼす電源電流変化
2020年4月25日:Arduino やっぱり気になる放置ポート
2020年2月10日:ArduinoのanalogWrite 1/255なの?
2020年2月4日:Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
2013年04月25日:AVRマイコンのAREFピン
2013年05月02日:AVRマイコンのAREFピン #2

 

| | コメント (16)

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」って言うんだ!

| | コメント (1)

2020年5月15日 (金)

Arduino-UNO + SDカードでシリアルデータロガー 完成形 今度こそ

2020年4月24日:Arduino-UNO + SDカードでシリアルデータロガー 完成形 の続き。
   (↑まだ完成じゃなかった)
回路図。
Sd1

2つのシリアル入出力のコネクタ部を変更。
・TX出力はパラ出し。 同じものを出力。
・RX入力はダイオードを入れてパラに。
  (同時入力はできないけれど)
・ハード的にエコーバック。

JPでエコーバック(制御ソフトでの)の有無を切り替えられるよ
うにしたけど、文字抜けの危険性があるので、RXD→TXDと
マイコンからのTXDをダイオードで合成。

バックアップ代わりのスケッチ:ダウンロード - rxbff_sd1a2.zip
   ※ファイルタイプを「.ino」ではなく「.c」にしてます。

 

| | コメント (0)

2020年5月 9日 (土)

SDカードの消費電流と受信割込バッファの増大

ハイサイド電流測定回路を使ってArduino-UNO + SDカードでシリアルデータロガー 完成形 の消費電流(SDカードアクセスの時、瞬間的に電流が流れる)を見てみました。

9600bpsで垂れ流しの受信電文をログしています。
一時バッファ(まとめてSDカードに書き込むためのバッファ。シリアル受信割込バッファとは別)は320バイト。
ですんで、連続受信していると330msくらいでこの一時バッファがいっぱいになり、SDカードへのアクセスが発生します。
そして、その書き込みバイト数を数えていて、まとまったらSDカードの「フラッシュ」を実行。
この時に電流が流れて、少々長い待ち時間が発生します。

オシロ波形のch1がSDカードへのアクセスが発生した時に出すパルス。
ch2がそのアクセスランプ(Lで点灯)で、シリアルデータを読み取った時(Serial.read→一時バッファへ)にも光らせています。
ch3が電流波形で1divが20mA。

01_20200509091001
↑では、ざっと100msの処理時間が発生。
この間の受信処理は割り込みが頼りです。


アクセスしているところをちょい拡大。
02
9600ボーでの受信処理パルスが判別できます。
時間軸1div 20msに20発ほど。

SDカード処理完了直後を拡大してみると・・・
03_20200509091101
カード処理完了直後、処理中に受信バッファに溜まったデータをまとめて読み出して一時バッファに保存している様子が見えます。

ここでの問題。
・SDカードアクセス処理中のシリアル受信データの確保は、
 受信割り込みに頼るしか無い。

・受信バッファのデフォルトが64バイト。
 1文字1msとしてざっと64msぶん。

・SDカード処理時間以上の受信割込バッファサイズの増大が必要。
 さもないと受信データを失ってしまう。

・SDカードの処理がどのくらいの時間か調べてみると・・・
  ・昔の1G、2GのSDカード 100~140ms
  ・x4速のSD HC  30~35mS
  ・x10速のSD HC  15~20mS (瞬間的な電流が増える)
    ※遅いカードは64msをはるかに越える。
     カードにあるフォルダやファイルの状態でも
     処理時間が変わる(長くなる方向で)

・受信割込バッファサイズの増大方法、ネットであれこれ調べたら・・・
   HardwareSerial.h の中の
   SERIAL_RX_BUFFER_SIZE の設定値を大きくすれば良い。
   どこにある HardwareSerial.h を触れば良いかが問題。
   私とこの現状環境では、
   C:\Program Files\Arduino\hardware\arduino\avr\cores\arduino
   だった。 ※これ、あれこれあるみたい
   うまく行ったかどうかは↓として確認出来る。
    Serial.println(SERIAL_RX_BUFFER_SIZE);

SDカードの処理区分ごと(busy待ち)に受信処理を走らせる、自前のSDカード処理を書けば良いのでしょうが・・・
受信割込バッファサイズを大きくするより方法がありません。
現在256バイトに設定しています。


◆ネットにあるArduinoのSDカードロガー製作例を見て感じる問題点を少々。

・SD.begin(chipSelect))をsetup()内で一回きり。
だもんで、
・運転中はカードは抜いてはいけない。
・カードを抜くのはいったん電源オフしてから。
・電源をオンしたままでのカード挿入もできない。
・カードの交換も無理。
・ログの書き込み動作を一旦停止する方法があるか?
・もしそれがないと、いつ電源を切ったらよいの?
   (いつ書き込みが行われるのかわからない)
・シリアルデータを記録するものなら、受信データ線のコネクタ
 を抜いて対処か。

こんなあんなで、作ってみたのが
Arduino-UNO + SDカードでシリアルデータロガー 完成形
  ↑からも、エコーバックの有無を設定するジャンパーを増やしたりと、ソフトは変えてます。
エコーバックすると、送信側にも受信と同じだけの割り込み処理バッファが必要なので。

| | コメント (0)

2020年5月 2日 (土)

Arduino 放置したポートが及ぼす電源電流変化

2020年4月25日:Arduino やっぱり気になる放置ポートで、未処理のI/Oポートに関して「やっぱりあかんやろ」っと意見しました。
入力回路のシュミットゲート、入力電圧がスレッショルドに近づいた時、具体的にどのくらいの電流が流れるのか電源電流の変化として計ってみました。

そのためのツールがこれ。

Ina1

11_20200502175601

2019年3月29日:TIの電流検出アンプ
2019年5月14日:TIの電流検出アンプ 入荷 置き換えてみた

で紹介しました「INA199」。
このゲイン50倍のICを使って電流検出回路を作りました。
電源のハイサイド側に入れて電流を測定します。
電流検出抵抗を1Ωにすれば、出力電圧0.1Vで2mAの電流が測定できます。

Arduino-UNOのATmega328Pにこんなスケッチを書きます。

void setup() {
  PORTB = 0b00000000;   // data off
  DDRB = 0b00111110;   // PB5~PB1出力 PB0入力
  PORTC = 0b00000000;   // data off
  DDRC = 0b00111111;   // PC5~PC0
  PORTD = 0b00000000;   // data off
  DDRD = 0b11111111;   // port指定
}
void loop() {
  cli();     // 割り込み禁止
  while(1){    // PB0の状態をPB1に出力
    if(PINB & (1 << PORTB0))  PORTB |= (1 << PB1);
    else            PORTB &= ~(1 << PB1);
  }
}

PB0だけを入力にして他のポートは全部出力。
そして、PB0を入力してその状態をPB1にコピー。
PB1をオシロで見ると、PB0のH/L状態(スレッショルドを越えるか)がわかります。
CPUはUNO基板から取り外して単独で実行。

PB0に発振器から三角波を入力し、その振幅とDCバイアスを変えて様子を見ます。
その結果・・・ ざっと1mA弱の電源電流変化が観察できました。

Cur000

Cur001

Cur002

Cur003

Cur004


※追加実験  入力ポートを増やす
↑のはPB0だけが入力ポート。

スケッチをこのようにしてPC0とPC1(A/D入力ポート)を入力に変更。
  PORTC = 0b00000000;   // data off
  DDRC = 0b00111100;   // PC5~PC2出力、 PC1,0入力

まず、PC0、PC1をGNDにつないだ状態でPB0に三角波(↑と同じ)。
Cur03
   オシロ、電流波形はACで観察

次に、PB0=PC0=PC1と3つをパラって三角波。
3ポートとも同じ信号を入れます。
すると・・・
Cur04
電流が増加。
それぞれのポートでシュミット回路が働いていると想像できます。

さらに、スケッチを変更。
DIDR0レジスタを操作して、PC0とPC1のデジタル入力を禁止します。
  PORTC = 0b00000000;   // data off
  DDRC = 0b00111100;   // PC5~PC2出力 PC1,0入力
  DIDR0 = 0b00000011;   // ★デジタル入力禁止
A/D変換する時、これは必須。
  これ、analogRead()が自動的にやってくれてるのか?

すると・・・
Cur05
ということに。


| | コメント (4)

Arduino ポートの初期化と通信設定

2020年4月25日:Arduino やっぱり気になる放置ポート に絡んで、ArduinoのI/Oポートの初期化、こんな方法(3ポートいっぺんに記述)を提案しました。
// I/Oイニシャル
  PORTB = 0b00011100;   // data/pull up
  DDRB = 0b00101111;   // port指定
  //     |||||+---- PB0 IO8  out 赤LED (Lでon)
  //     ||||+----- PB1 IO9  out 緑LED (Lでon)
  //     |||+------ PB2 IO10  out CS カードアクセス時Lに
  //     ||+------- PB3 IO11  out MOSI カードDI
  //     |+-------- PB4 IO12  in MISO カードDO
  //     +--------- PB5 IO13  out SCLK
  PORTC = 0b00000000;   // data/pull up
  DDRC = 0b00111111;   // port指定
  //     |||||+---- PC0 AD0  out -
  //     ||||+----- PC1 AD1  out -
  //     |||+------ PC2 AD2  out -
  //     ||+------- PC3 AD3  out -
  //     |+-------- PC4 AD4  out -
  //     +--------- PC5 AD5  out -
  PORTD = 0b10000011;   // data/pull up
  DDRD = 0b01111110;   // port指定
  //    |||||||+---- PD0 IO0  in RXD
  //    ||||||+----- PD1 IO1  out TXD
  //    |||||+------ PD2 IO2  out -
  //    ||||+------- PD3 IO3  out -
  //    |||+-------- PD4 IO4  out -
  //    ||+--------- PD5 IO5  out -
  //    |+---------- PD6 IO6  out -
  //    +----------- PD7 IO7  in SW入力 pull up

PortBとPortCがそれぞれ6ビット、PortDが8ビット。
「pinMode(1, OUTPUT);」などと一つずつ記述するのではなく、まとめてやってしまおうという方法です。
合計20ビットのポートの状態を6行で記述できます。

さて、そこで「どうしたら」というのがPortDの「RXD」と「TXD」の二つ。
PD0のRXDは受信なので入力、PD1のTXDは送信なので出力です。
ポートを初期化した後に「Serial.begin(9600);」と通信設定するのは納得できるでしょうが、通信設定した後にポートを初期化したらRXDとTXDのH/L状態がおかしくならないかという心配が残ります。

これ、心配ご無用。
シリアル通信を有効にしたら、
 ・PD0:RXD DDRDのDDD0の状態にかかわらず入力に。
 ・PD1:TXD DDRDのDDD1の状態にかかわらず出力に。
になります。
 ※この時PORTD0ビットでPD0のプルアップ制御が可能。

ですので、先にポートを初期化したい時はこんな具合に。
  PORTD = 0b00000011;  // TXD,RXD pull up
  DDRD = 0b11111110;  // RXDだけ入力 TXDはHを出力 他は出力
  Serial.begin(9600);   // 通信設定

あるいは、これでもok。
  PORTD = 0b00000011;  // TXD,RXD pull up
  DDRD = 0b11111100;  // TXDとRXDは入力 他は出力
  Serial.begin(9600);   // 通信設定

しかし、これを、
  PORTD = 0b00000000;  // portD全部Lに
  DDRD = 0b11111111;  // 出力指定
  Serial.begin(9600);   // 通信設定 TXD,RXD L→Hに
こうしちゃうと、TXDとRXDのラインに不要なLパルスが出てしまいます。
   (3uSくらいのLパルスが出現)

先に通信設定してもok。
  Serial.begin(9600);   // 通信設定
  PORTD = 0b00000000;  // TXD,RXD 0にしてもH/L変化しない
  DDRD = 0b11111111;  // 全部出力にしてもRXDは入力
この場合、後で設定するポートの初期化でTXDとRXDがLになりそうですが、ポートの出力回路がシリアル回路側に切り替わっているので、Lになってしまうことはありません。

 

| | コメント (0)

2020年4月25日 (土)

Arduino やっぱり気になる放置ポート

Arduino-UNOに搭載されているマイコンはATmega328P。
あれこれ公開されているArduinoの参考スケッチ、たいていの場合、使うポート以外はみんな放置したままなんです。
それが気になります。

例えば出力にするところは、
  pinMode(1, OUTPUT);
などとして、
  digitalWrite(1, HIGH);
と、出力をHに。

プルアップ付の入力なら
  pinMode(2, INPUT_PULLUP);
として、スイッチなどを対GNDにつなげば
  digitalRead(2);
で、スイッチのオン・オフが読み取れます。

ところが・・・スケッチで使わない入出力ポートは、何もしないのが普通になっています。
出力指定、あるいはプルアップ入力指定しないままだと、リセット後のそのポートは裸の入力のまま。
I/Oポートの構成図だとこんなふうになります。

A11_20200425151601
  ・ATmega328Pのデータシート(日本語訳)より

入力ピンに何もつながず放置しておくと、インピーダンスが非常に高いためH/Lが安定しません。
(オシロスコープで観察しようとしてプローブをつないだら、そのプローブの抵抗(たいてい10MΩ)でLレベルに安定してしまい、真の姿が見えない)

★マークのシュミットトリガ入力がその関門です。
入力ポートのシュミット、そのスレッショルド電圧は電源電圧のおよそ中央。
そしてヒステリシスが0.5Vほど。
つまり、何らかの原因(誘導ノイズなど)で入力レベルが変動してスレッショルド電圧付近に達したら・・・
C-MOSのシュミットゲート回路、入力がスレッショルド電圧付近になると電流が増えるという特性があります。
  ※昔々、いろんなシュミットIC(4584や4093、HC14、HC132、
   シュミット入力になったモノマルチなどその電流の増加を
   調べたことがあったんですが、資料行方不明。
   プロセッサ誌あたりに投稿した記憶がうっすらと)

ATmega328Pの入力でも同様のことが生じます。
何かの拍子、オープンになった入力ポートの電圧が電源電圧の半分近くになると・・・1つの入力につきざっと0.5mA~1mA電源電流が増加します。

USBから電源が供給されて動くArduino、こんなちょっとの電源電流増加は目立ちません。
Arduinoの環境ではなく、チップ単独で電池運用する時などにこの放置が問題になってくるでしょう。

未使用I/Oポート、その扱いの基本は、
・入力専用ポート
  抵抗でプルアップかプルダウン
  直接GNDあるいは電源につなぐ
・出力専用ポート
  放置
・入出力ポート(たいていのArduinoのポート)
  出力に指定して放置
  入力指定で(イニシャルせず)プルアップかプルダウン抵抗を付加
  入力にして内蔵プルアップ、プルダウン機能を有効に

リセット時の挙動、イニシャルプログラムが走る前のことも考えておかなくてはなりません。
ポートを出力にする。あるいは内蔵のプルアップやプルダウンを有効にする。これらはプログラムが正常に走りはじめてから設定できる機能です。
何らかの異常でリセット状態が続いた時、出力ポートにするはずのポートが入力のままで、これをC-MOSゲートで受けているとH/Lレベルが定まらず思いもよらない出力がオンしてしまうことがあります。
出力にもプルアップ、プルダウン抵抗が必要な場合があるのです。

もうひとつ。
ATmega328Pをスリープさせると入力ポートを外部から切り離してくれる機能があります。
  ※上の図、シュミットゲートの右側のスイッチ(Lへ落とすFETも)。
このおかげでスリープさせた時に入力が安定し(放置していた入力も)、スレッショルド付近の不安定な状態(シュミット回路の電流が増える)になりません。

未使用ポートの放置が気になって、私の書くプログラムはこんな具合に全ポートを入力か出力に区分けしています。
※例:JIS C8708:2019充放電実験回路のスケッチ (スペースがズレるかな)
// I/Oイニシャル
  PORTB = 0b00000000;   // data/pull up
  DDRB = 0b00111111;   // port指定
  //     |||||+---- PB0 IO8  out -
  //     ||||+----- PB1 IO9  out OC1A 充電PWM
  //     |||+------ PB2 IO10  out OC1B 放電PWM
  //     ||+------- PB3 IO11  out OC2A BZZ
  //     |+-------- PB4 IO12  out -
  //     +--------- PB5 IO13  out (LED)
  PORTC = 0b00001110;   // data/pull up
  DDRC = 0b00110000;   // port指定
  //     |||||+---- PC0 AD0  in 電池電圧 A/D入力
  //     ||||+----- PC1 AD1  in START SW
  //     |||+------ PC2 AD2  in ↑ TX SW
  //     ||+------- PC3 AD3  in ↓ MENU SW
  //     |+-------- PC4 AD4  out 充電オン
  //     +--------- PC5 AD5  out 放電オン
  PORTD = 0b00000011;   // data/pull up
  DDRD = 0b11111110;   // port指定
  //    |||||||+---- PD0 IO0  in RXD
  //    ||||||+----- PD1 IO1  out TXD
  //    |||||+------ PD2 IO2  out LCD RS
  //    ||||+------- PD3 IO3  out LCD E
  //    |||+-------- PD4 IO4  out LCD DB4
  //    ||+--------- PD5 IO5  out LCD DB5
  //    |+---------- PD6 IO6  out LCD DB6
  //    +----------- PD7 IO7  out LCD DB7

いっぱい書いてますが、実際の命令コードは6行だけ。

Arduino-UNO + SDカードでシリアルデータロガー 完成形 ではこんなの。
未使用ポートは出力にしています。
// I/Oイニシャル
  PORTB = 0b00011100;   // data/pull up
  DDRB = 0b00101111;   // port指定
  //     |||||+---- PB0 IO8  out 赤LED (Lでon)
  //     ||||+----- PB1 IO9  out 緑LED (Lでon)
  //     |||+------ PB2 IO10  out CS カードアクセス時Lに
  //     ||+------- PB3 IO11  out MOSI カードDI
  //     |+-------- PB4 IO12  in MISO カードDO
  //     +--------- PB5 IO13  out SCLK
  PORTC = 0b00000000;   // data/pull up
  DDRC = 0b00111111;   // port指定
  //     |||||+---- PC0 AD0  out -
  //     ||||+----- PC1 AD1  out -
  //     |||+------ PC2 AD2  out -
  //     ||+------- PC3 AD3  out -
  //     |+-------- PC4 AD4  out -
  //     +--------- PC5 AD5  out -
  PORTD = 0b10000011;   // data/pull up
  DDRD = 0b01111110;   // port指定
  //    |||||||+---- PD0 IO0  in RXD
  //    ||||||+----- PD1 IO1  out TXD
  //    |||||+------ PD2 IO2  out -
  //    ||||+------- PD3 IO3  out -
  //    |||+-------- PD4 IO4  out -
  //    ||+--------- PD5 IO5  out -
  //    |+---------- PD6 IO6  out -
  //    +----------- PD7 IO7  in SW入力 pull up

 

| | コメント (6)

2020年4月24日 (金)

Arduino-UNO + SDカードでシリアルデータロガー 完成形

※完成形とタイトルにしたけど、まだ完成じゃありませんです。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020年4月22日:遅ればせながらArduinoでSDカードの完成形。
こんな回路です。

Rxbff_sd1

OpenLog を真似て、16MHzクロックのATmega328Pを3.3Vで動かしています。
ケースはタカチのSW-120を使いました。
電池電圧チェッカーで使っているケースなんで、在庫があったから。

11_20200424165601

12_20200424165601

使い方を忘れたらいかんので、シールをペタペタ。
13_20200424165601

14_20200424165601

前記事からの変更点。
・通信待機時の記録までの時間を10秒に。
・フラッシュ実行を3分に。
・フラッシュの書き込みカウントを4096バイトに。
3分にしたのは、1分ごとのデータ取得が多いから。
頻繁にフラッシュするとSDカードにやさしくない(かもしれない)から。

バックアップがわりに、スケッチの最新版。
    ダウンロード - rxbff_sd1a.zip


※オシロで見たSDカードをアクセスする様子

まず、「begin」して「DIR」確認、
そして、目的ファイルの検索とオープン。
RX0000.TXTがある状態で、追加書き込みオープンするまで。
Bb000

ファイルRX0000.TXT~RX0010.TXTが存在していて、
RX0010.TXTをオープンするまで。
Bb001

9600bpsで連続受信している時、512バイト(受信バッファ)ごと
に行われる書き込み処理。
Bb002
たまにちょいと長いパルスが出てくる。


※ログファイル名、RX0000.TXT~RX9999.TXTまでを
想定しているわけですが、サーチし始めるのはRX0000が
いつも最初になります。
わずか10ファイルで0.2秒ほどかかっているので、ファイルの
番号が大きくなると起動がどんくさくなってしまいます。
だもんで、ある程度ファイル番号が進んだら、
・記録ディレクトリをWin環境でリネームしてしまう。
 ディレクトリLOGの下にファイルを作りますんで、
 LOGをLOG_A123などとリネームしてしまうと、
 新しいLOGが作られてRX0000.TXTから再スタート
 できます。
・この時、ついでにWin環境でLOGディレクトリ(フォルダ)
 を作っておけば、LOGに作成日付が記録されます。
   勝手に作る場合は2000年1月1日と年月日固定
・LOG下にファイルを残したい場合は、
 RX0000.TXT~をABC_0000.TXT~と一括リネーム。
 するとLOG下に新しいRX0000.TXTが作られます。
   コマンドプロンプトを起動して
     REN RX*.TXT ABC_*.TXT


| | コメント (1)

2020年4月22日 (水)

遅ればせながらArduinoでSDカード

2020年3月20日:256kBシリアルデータ記録回路とりあえず完成
↑は基板に乗せたEEPROMへのデータ保存でしたんで、容量が限られています。
  (フロッピディスクにも満たない)
やっぱSDカードへの記録だろうと、ネットをあれこれ探し回っておりました。

で、まずこんなのがあるのを発見。
OpenLog - スイッチサイエンス

シリアルデータのログ、「これでエエやん」なんですが、あれこれ手を加えるとなると(例えばスイッチ付加)チップから配線を直出ししなければなりません。
それにソフトも。
可能ならArduinoの標準的環境で手作りしたい。
  ※OpenLogのスケッチ、読んでみましたが
   SDカードのエラー処理、リセット・電源再投入でしか
   回復できない。

※参考
オープンソースロガーOpenLog V4互換モジュールの製作 - アキバ通いと旅

こんなあんなで、
ラジオペンチさん: Arduinoで作るSDカードを使ったデーターロガー
などを読みながら、どうしたものか思案中です。

●悩みどころ
・SDカードを抜き差ししたい。 SW操作でログのon/off。
 抜く時はログ書き込みを停止。 挿してSW onで再開。
 この時、別のSDカードを入れても処理できるように。
 SDカードのエラーが起きた時に止めたらダメ。

・文字受信のたびにファイルのオープン・クローズを
 繰り返してたらその回数が気になる。
 FAT上、連続したデータを書き込む位置は変化するけど、
 ファイル名といっしょにファイルサイズを記憶している
 場所、頻繁にクローズ処理していると、この特定部分の
 書き込み回数が多くなってしまう。
 だもんで、ログ中はオープンしっぱなしで書き込み実行。
 クローズするんじゃなく、たまにフラッシュさせるような
 処理かしらと。
 書き込み回数を見て、ファイル名を変えるか。

 ※昔々、RXマイコンで作った装置(仕事)はこんな具合に処理。
    運転には人が関わるんだけど、保存データの
    確認なんて、システム替えやトラブルが有っ
    た時くらい・・・という装置。

   ・SDカードの他にEEPROM、それにバックアップRAM
    エリア付のRTC ICを搭載。
      (日時データが必要だったんでRTCは必須)

   ・計測データ数など常に変化する値は、RTCの中の
    バックアップRAMを使って保持。
       (何度でも書き込み出来る)

   ・計測データそのものはEEPROMに一次保存。
     (特定場所だけ書き込み回数が多くなるという
      ことは無く、全域同じように書き込み回数が
      増える)

   ・EEPROMが一杯になる、あるいは定時起動や手動操作
    でEEPROMの保存データをSDカードに転送。
    この書き込みは一瞬だし、何度も行わない。
    普通の運転中はSDカードを抜いていてもOK。

   ・EEPROMへの一次保存データはバイナリ。
    SDカードへ書き込む時に文字に変えCSVファイルで。


こんなのでした。

SDカードに作ったファイルの頻繁なオープン・クローズ、
書き込み回数の面でちょいと気になります。

仮に書き込み可能回数10万回としたら・・・
1分に1回の書き込みで、1日あたり1440回。
すると、70日ほどで10万回を突破。

とりあえず作ってみたのが・・・
・ディレクトリ名「LOG」を作る。
・その下に「RX0000.TXT」というファイル名でログファイルを
 作り記録開始。
・スイッチ長押しで、次番号のファイル「RX0001.TXT」を作成。
・スイッチ短押しでファイル名の数字のいちばん大きいファイル
 にログデータを追加書き込み。
・いったんファイルをオープンしたらスイッチ操作で記録を止める
 までクローズしない。
  ※これだとあんまりなんで、タイマーでフラッシュ操作を
   入れるつもり。


※実験中  ブレッドボードじゃない
11_20200422102301
Arduino-UNOとSDカード間(5V~3.3V)は、レベル変換IC
TXS0104(仕事試作の残り)でつなぎ。
SDカードの基板はサンハヤト。 これも試作での残り物。


OpenLogのATmega328P、3.3V電源を使って16MHzで動かしてるのが面白い。
データシートだと、2.7Vで10MHz。 4.5V以上で20MHz。
この間、直線的に電源電圧と最高周波数が関連するとのこと。
4.5V - 2.7V = 1.8V。 この間が10MHzだから、
ということは、1.0Vあたり5.55MHz。
3.3Vだと、(3.3V-2.7V)*5.55 + 10MHzで13.33MHz。

※バックアップがわりにスケッチをアップロード
  ・ダウンロード - rxbff_sd1.zip

 

| | コメント (4)

より以前の記事一覧