Arduino

2020年3月20日 (金)

256kBシリアルデータ記録回路とりあえず完成

この続き・・・
・シリアルデータ記録装置
・ICの2段重ねで容量アップ

・JIS C8708:2019充放電試験回路 をもう一台作るための前準備。

回路図↓
A11_20200320115701

Arduino-UNOのチップを外して制御。
プログラムツールはArduinoで。

ケースと基板の様子。

11_20200320115701
12_20200320115701
14_20200320115701
13_20200320115701
  ※もっと小さく作れます。

在庫してあったケースから適当なの見繕って使ったんですが、
「Made in CANADA」のものでした。
  ・Enclosure HAMMOND 1599B

なにをしようとしてこのケースを買ってあったのか・・・
もう覚えていません。

制御プログラム・・・ダウンロード - rxbff2.zip

◎製作目的
何ヵ月にもわたって続く実験。
最終的な実験結果は実験装置が出力してくれるが、
その途中の経過データも記録しておきたい。
実験装置が2~3時間に1回、あるいは1日1回くらい
のペースで出すシリアルデータを記録。
この記録のためにPCをずっと通電しておくのはもったいない。
SDカードに書くのも方法だけど、それほどの量でもないか。
で、Arduinoのチップに外付けEEPROMという構成で製作。

制御についてうだうだ言っていた内容。
・リングバッファはやめ。
 256kバイト受信(メモリーいっぱい)で停止に。
 ボーレートは9600のまま。
 setupの 「Serial.begin(9600);」 を変えればok。
 EEPROMのページ書き込みを使っているんで、もっと早く
 しても追いつくはず。
・受信割込のバッファ数はシステムのままの64バイトで触って
 いない。
 だけど、EEPROMの書き込み時間待ちの時、内部に設けた
 512バイトの受信バッファにデータを溜めるようにしている。 
・受信バイト数のセーブタイミング。
 シリアル受信が途絶えてから3分後に内蔵EEPROMに保存。
    ※頻繁に書くと書き込み回数制限が気になる
・スイッチ操作
  MEM短押し 記録開始と中止をトグル
  MEM長押し メモリーの最初から記録開始
  TX短押し  記録データの送出
  TX長押し  メモリー全部の送出
        これ↑、追加機能。
      停電などで記録したデータ数を内蔵EEPROM
      に保存しないまま電源が落ちた時の救済。
      受信したシリアルデータは受信の都度
      外付けEEPROMに書き込んでるんで、停電でも
      なんとか残ってる可能性が大。
      記録データ数を残せていなくてもメモリーを
      全部吸い出し。

・記録中LEDを使ったメモリー容量の表示

  H_______H________H____ // <20%
  H_H_______H_H_________ // <40%
  H_H_H_______H_H_H_____ // <60%
  H_H_H_H_______H_H_H_H_ // <80%
  H_H_H_H_H_______H_H_H_ // <100%
  H_H_H_H_H_H_H_H_H_H_H_ // =100%

 音に出すモールスなら少々早くても聞き分けできるけど
 光りの点滅回数判断ってなかなか難しい。
 モールスで「E I S H 5」の判断になるんだけど、光る
 周期を早くすると読めない。
   今はこんな速度に
    100%の時 オン20ms、オフ180msの0.2秒サイクルで
    20%~   オン50ms、オフ250msの0.3秒で「短点」
         を繰り返す。
 この点滅デューティー50%にして早くすると、3つ4つ5つの
 連続点滅回数を数えられなくなる。
 音でのモールスのように訓練で分かるようになるのかな?

| | コメント (0)

2020年3月17日 (火)

ICの2段重ねで容量アップ

シリアルデータ記録装置をの受信データを記録するEEPROM、
今回は2つにして256kバイトにする予定です。

10_20200317174001

回路のU1とU2。 8ピンのSOP。
両方とも同じICで違うのは1ピンの接続だけ。
普通なら2つ並べて配線なんですが、手抜きでこんな
ふうにしました。
垂直に2段重ね。

11_20200317174001

昔はRAMで2段重ねをよくやってました。
チップセレクトピンだけ足を曲げ別ラインに接続。
他のピン(27本)は全部ハンダ付け。
12_20200317174001

これは昔に作ったROMエミュレータ。
昔に作ったROMエミュレータ:プロセッサ誌1989年6月号に掲載してもらった製作記事をまとめてみました
32kバイトの28ピンRAMを2つ重ねて64kバイトにと。

※ROMエミュレータの回路
Romem1
「JP1」を使って拡張してます。
1989年に基板化。



検索すると・・・
ヘッドホンアンプ(改造) - なんぎな日記
で発見!。
HCU04を重ねてパワーアップっと。


| | コメント (0)

2020年3月16日 (月)

シリアルデータ記録装置を

JIS C8708:2019充放電試験回路製作中
これをもう一台作ろう!っと思ってるんですが、その前に
「シリアルデータ記録装置」を作っておかなくてはなりません。

今回のJIS C8708充放電試験回路、50サイクルごとの放電データ
をATmega328P(Arduino UNOチップ)の内蔵EEPROMに8コ分
(400サイクル実行)貯められるようにしています。
  1kバイトの内蔵EEPROMに1.0Vまでの放電経過
  時間(分)を記録。60ワードx8で960バイト。
しかし、50サイクルだけでなく1~49サイクルでの充放電の
様子と充放電待機期間中の様子(電圧変化)も残しておきた
いのです。
これは内蔵EEPROMには入りません。
そこで、試験回路は毎サイクルの充放電データをシリアル
(9600ボー)で出力しています。

ダイソーReVOLTES単3 充放電実験 1~49サイクルのデータをグラフ化
は、残していたこのデータを用いてグラフにしたのです。

試験回路が出すシリアルデータをシリアルモニターで記録。
電池の劣化具合がわかるデータとしてこれも残しておきたい
のです。
PCを使ってモニターしても良いのですが、ずっと通電して
おくには電気がもったいない。
そこでこんなシリアルバッファ回路を作っていたのです。
  (日付は2015年)
S11

記録するのは128kバイトのI2CインターフェースのEEPROM。
ひたすら記録して、送信スイッチを押したら記録してあった
データを出力するという仕掛けです。

Arduino-UNOからチップを抜いて作っています。
当時はArduinoから必要なポートの信号線を全部
引っ張ってデバッグしてました。

デバッグ中。
11_20200316171101

部品面。
12_20200316171101

ハンダ面。 LEDとスイッチはこちらに。
13_20200316171101

こんなケースに入れてあります。
2012年09月07日:ステップ・モータのチェッカー MCH-1
これのケースだけを利用してます。

14_20200316171101

今回、充放電試験回路を作る前に、このシリアルデータ記録
装置を先に作っておかなくてはなりません。

※改善したいな~という点
・現在はリングバッファにしていない。
 128kバイトのEEPROMが目一杯になったらそこで
 ストップ。
 リングバッファにもできるように。

・どれだけ保存してるか分からないので、
 LEDの点滅周期を変えて、20%ステップごとに
 およその量がわかるように。
   今は、目一杯になったらLEDを高速点滅。

・停電しても再通電で記録を再開できるように
 しているのだが、内蔵EEPROMに残している
 文字数カウンタ、これの更新頻度が気になるので
 ちょい改善か。
 今の処理:受信データが1分間途切れたら、文字数
 カウンタを内蔵EEPROMに記録。
 電文周期が短いと1分ごとはちょいと問題か。
   いつまでたっても記録されない。 これと、
   EEPROMの書き込み回数制限。

・EEPROMをパラって、記録容量を倍に。

今回の製作で改善かと。

※現在の制御プログラム→ダウンロード - rxbff1.zip

※今回製作の回路案
A11_20200317090701  


| | コメント (0)

2020年2月22日 (土)

JIS C8708:2019充放電試験回路での「-ΔV」検出の様子

試運転中のJIS C8708:2019充放電試験回路
その「-ΔV」検出の様子をご覧ください。
※テストですんで新品の電池ではなく、
「ダイソーReVOLTES」JIS C8708:2019充放電試験 50サイクル目
で、ソフトのバグでデータ取得をミスった時に使ったダイソーの
ReVOLTES単3。
すでに200回の充放電試験を経ています。

新JISでの充電条件が(サイクル1~49)
  「0.5Cで-ΔV又は132分のタイマー制御」
  「-ΔVは5~10mV」
と記されています。

このテストでは-ΔV値をとりあえず「10mV」に設定。
1分サイクルでシリアル出力する経過時間と電圧値
を観察してみます。
  (7サイクル目の途中まで)

~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 1cyc 1/5 1h39m 1.670V
#C 0.5C 1cyc 1/5 1h40m 1.670V
#C 0.5C 1cyc 1/5 1h41m 1.670V ★Peak #1
#C 0.5C 1cyc 1/5 1h42m 1.668V
#C 0.5C 1cyc 1/5 1h43m 1.668V
#C 0.5C 1cyc 1/5 1h44m 1.667V
#C 0.5C 1cyc 1/5 1h45m 1.666V
#C 0.5C 1cyc 1/5 1h46m 1.665V
#C 0.5C 1cyc 1/5 1h47m 1.663V
#C 0.5C 1cyc 1/5 1h48m 1.662V
#C 0.5C 1cyc 1/5 1h49m 1.661V
#C 0.5C 1cyc 1/5 1h49m 1.660V ★Stop
#C-Wait 1cyc 1/5 0h01m 1.520V

#D 0.5C 1cyc 1/5 1h07m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 2cyc 1/5 1h09m 1.689V
#C 0.5C 2cyc 1/5 1h10m 1.689V
#C 0.5C 2cyc 1/5 1h11m 1.689V ★Peak #2
#C 0.5C 2cyc 1/5 1h12m 1.688V
#C 0.5C 2cyc 1/5 1h13m 1.687V
#C 0.5C 2cyc 1/5 1h14m 1.685V
#C 0.5C 2cyc 1/5 1h15m 1.684V
#C 0.5C 2cyc 1/5 1h16m 1.682V
#C 0.5C 2cyc 1/5 1h17m 1.680V
#C 0.5C 2cyc 1/5 1h17m 1.679V ★Stop
#C-Wait 2cyc 1/5 0h01m 1.529V

#D 0.5C 2cyc 1/5 1h05m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 3cyc 1/5 1h06m 1.698V
#C 0.5C 3cyc 1/5 1h07m 1.698V
#C 0.5C 3cyc 1/5 1h08m 1.698V ★Peak #3
#C 0.5C 3cyc 1/5 1h09m 1.696V
#C 0.5C 3cyc 1/5 1h10m 1.695V
#C 0.5C 3cyc 1/5 1h11m 1.694V
#C 0.5C 3cyc 1/5 1h12m 1.691V
#C 0.5C 3cyc 1/5 1h13m 1.690V
#C 0.5C 3cyc 1/5 1h13m 1.688V ★Stop
#C-Wait 3cyc 1/5 0h01m 1.533V

#D 0.5C 3cyc 1/5 1h03m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 4cyc 1/5 1h03m 1.704V
#C 0.5C 4cyc 1/5 1h04m 1.705V
#C 0.5C 4cyc 1/5 1h05m 1.705V ★Peak #4
#C 0.5C 4cyc 1/5 1h06m 1.704V
#C 0.5C 4cyc 1/5 1h07m 1.702V
#C 0.5C 4cyc 1/5 1h08m 1.701V
#C 0.5C 4cyc 1/5 1h09m 1.700V
#C 0.5C 4cyc 1/5 1h10m 1.698V
#C 0.5C 4cyc 1/5 1h10m 1.695V ★Stop
#C-Wait 4cyc 1/5 0h01m 1.535V

#D 0.5C 4cyc 1/5 1h01m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 5cyc 1/5 1h02m 1.710V
#C 0.5C 5cyc 1/5 1h03m 1.710V
#C 0.5C 5cyc 1/5 1h04m 1.710V ★Peak #5
#C 0.5C 5cyc 1/5 1h05m 1.709V
#C 0.5C 5cyc 1/5 1h06m 1.707V
#C 0.5C 5cyc 1/5 1h07m 1.706V
#C 0.5C 5cyc 1/5 1h08m 1.704V
#C 0.5C 5cyc 1/5 1h09m 1.701V
#C 0.5C 5cyc 1/5 1h09m 1.700V ★Stop
#C-Wait 5cyc 1/5 0h01m 1.538V

#D 0.5C 5cyc 1/5 1h00m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 6cyc 1/5 0h59m 1.713V
#C 0.5C 6cyc 1/5 1h00m 1.715V
#C 0.5C 6cyc 1/5 1h01m 1.716V ★Peak #6
#C 0.5C 6cyc 1/5 1h02m 1.715V
#C 0.5C 6cyc 1/5 1h03m 1.715V
#C 0.5C 6cyc 1/5 1h04m 1.713V
#C 0.5C 6cyc 1/5 1h05m 1.711V
#C 0.5C 6cyc 1/5 1h06m 1.710V
#C 0.5C 6cyc 1/5 1h07m 1.707V
#C 0.5C 6cyc 1/5 1h07m 1.706V ★Stop
#C-Wait 6cyc 1/5 0h01m 1.540V

#D 0.5C 6cyc 1/5 0h59m 1.000V ←放電時間
~~~~~~~~~~~~~~~~~~~~~
#C 0.5C 7cyc 1/5 0h57m 1.716V
#C 0.5C 7cyc 1/5 0h58m 1.718V
#C 0.5C 7cyc 1/5 0h59m 1.720V
#C 0.5C 7cyc 1/5 1h00m 1.720V ★Peak #7
#C 0.5C 7cyc 1/5 1h01m 1.718V
#C 0.5C 7cyc 1/5 1h02m 1.717V
#C 0.5C 7cyc 1/5 1h03m 1.716V
#C 0.5C 7cyc 1/5 1h04m 1.715V
#C 0.5C 7cyc 1/5 1h05m 1.712V
#C 0.5C 7cyc 1/5 1h05m 1.710V ★Stop
#C-Wait 7cyc 1/5 0h01m 1.541V

~~~~~~~~~~~~~~~~~~~~~

0.5Cでの充電をはじめておよそ1時間してピーク電圧を
検出。
その数分後に10mVのドロップを検知して充電停止。
こんな充電制御が続いています。

2時間12分(132分)のタイマーで止まったのは、この7つ
のサイクルの中ではありませんでした。
   ※200サイクルの試験を経た古い電池だから

充電の次の工程、時間待ち後0.5Cで1.0Vまでの放電。
充電時間と放電時間を抜き出してみると・・・

  充電時間 放電時間 充放電時間比
1cyc 1h49m  1h07m   67/109 = 61.5%
2cyc 1h17m  1h05m   65/77 = 84.4%
3cyc 1h13m  1h03m   63/73 = 86.3%
4cyc 1h10m  1h01m   61/70 = 87.1%
5cyc 1h09m  1h00m   60/69 = 87.0%
6cyc 1h07m  0h59m   59/67 = 88.1%

1サイクル目は0.2Cで1.0Vまでゆっくりと放電したんで、
充電時間が長くなったようです。
その後は84%~88%の時間比で放電が行われています。

そしてチャートで記録している電池側面の温度変化を
見ると温度上昇は5℃ほど。
-ΔV制御無しで132分充電した場合に比べると、
優しい温度上昇になっています。

※以前の実験の温度変化 15℃ほど上昇。
  a1_20200210151501.jpg

もう少し様子を見てから(初めて動かすプログラムなんで)
新品電池をセットして試験をしてみます。


※追記
「-ΔV検出」による充電停止と、「132分充電」で電池の
側面温度がどうなるかしらべてみました。
#1~#7が「-ΔV充電」の時。 (↑の記録を採った時の)
およそ5℃くらいの温度上昇。
@1~@3が「132分充電」。
電圧波形を見ると、-ΔVが現れているのにまだ充電が行わ
れている様子がわかります。
それに連れて温度も上昇。 10℃くらい上がっています。
B32

  ※ナダ電子のプリンターシールドでの記録

「132分充電」後の放電結果を見てみると・・・
   (チャートの@1~@3のところ)
  放電時間  充放電時間比
@1 1h09m   69分/132分 = 52.3%
@2 1h07m   67分/132分 = 50.8%
@3 1h07m   67分/132分 = 50.8%

1.0Vまでの放電時間は-ΔV制御で充電したのと
(↑の#1~#6)ほとんど同じでした。

つまり、-ΔV発生以降の充電エネルギーは無駄に
なっていて、電池を暖めているだけということが
見えてきます。
このあたりもニッ水電池の寿命に関係してくるかと
推測できます。

市販の急速充電器での充電と自作放電器 での充放電繰り返し実験
では見えてこない部分が、今回の「-ΔV検出制御」で見えてきた
ような気がします。


・試運転中の基板
B41


| | コメント (0)

2020年2月21日 (金)

JIS C8708:2019充放電試験回路試運転

JIS C8708:2019充放電試験回路製作中、ざっと
制御ソフトが出来たので試運転中です。

今回はJIS C8708:2019の充電条件へ対応。
「0.5Cで132分 又は -ΔV(5mV~10mV)」
これに対応するため、電池電圧を読むA/D入力に
アンプを入れました。

Arduino(ATmega328P)のA/Dコンバータは10bit。
基準電圧を2.5Vにして0~2.5Vをそのまま入力
すると1ビットが約2.44mV。
5~10mVの-ΔVを拾うとすると、その変化量は
2~4ビットと微少。
もうちょい分解能が欲しいか、ということでこんな
アンプをA/Dの前段に入れました。 (U6 1,2,3のところ)

C1

考え方
・電池の寿命試験で電池電圧を測るのに0V付近は不要。
・0.9Vくらいから2.0Vの範囲を測れたらよいんじゃないか。
・A/Dの分解能を1ビットを1mVくらいにしたい。
・とりあえず、入力範囲を0.85V~2.1Vにして、測定スパン
 を1.25Vに。
・これを2倍に増幅。
・1ビットはきっちり1mVにはならないで約1.22mV。

こんな回路になっています。

また、A/D入力処理も、速度は不要なんで
・1mSごとのA/D変換で、256回の加算平均処理。
 256回分10ビットのA/D値を加算合計して、256回目に
 1/256して平均値を算出。256mSごとにA/Dデータが確定。
・この平均値を16回移動平均。
 リングバッファの古いのを捨てて新しいのを加える。
・さらに、バッファのデータをソートして、上下それぞれ
 4つのデータを捨て(maxに近い4つとminに近い4つ)、
 8つの中央値で平均計算。
・256mSごとにこのスムージング処理したデータが確定。
 16回なんで、A/D入力値が安定するまで約4秒。
・これで、一瞬の短絡や開放はデータに出てこない。

こんな処理で-ΔVを見つけるようにしました。

これでうまく処理できるか、試運転中です。

※OP-AMPについて
今回使ったMCP6072、電源電圧5V以下で使うならおすすめです。
特徴
・安価  100円くらい  ただしDIPは無い
・レール to レール入力/出力
・ゲインバンド幅 1.2MHz
・低オフセット電圧 0.15mV(max) ★
・入力バイアス電流 1pA (CMOSなんで)
・低消費電流 0.11mA
・動作電圧範囲 1.8V~6V

オフセット電圧の小さなCMOSアンプって、なかなか
ないんですよね。
ただ、DIP品が無いので試作にはピッチ変換基板が必須です。


※追記

・1msタイマー処理とその中で起動されるA/D変換。
・A/D割り込みで行う256回のA/D値平均処理。
  ↑
この2つの処理は割り込みで行うので、時間待ちは無し。
勝手にA/D平均データが上がってきます。

この中でスイッチ入力処理とブザー報知処理も実行。
液晶画面表示を行っていると、スイッチのチャタリング除去
処理やブザー報知の処理が遅れることがあるんで、タイマー
割り込みの中で勝手に処理するようにします。

・メインルーチン内でf_adokフラグをチェックして
 行う256msごとのA/D値スムージング処理。
 ソートを行うので、割り込み内からは出してます。

これを示しておきます。 (スペースは全角で)

/********************************/
/*   1ms タイマー割り込み  */
/********************************/
// 関数プロトタイプ宣言 (タイマー割込み内で処理)
void swscan(void);      // SW入力スキャン
void bzzexc(void);      // ブザー報知実行
/***** タイマーデータ  *****/
volatile byte tm_1ms;   // 1msダウンカウントタイマー
volatile byte tm_10ms;   // 10msダウンカウントタイマー
volatile byte f_1sec;   // 1秒経過フラグ
volatile byte f_1min;   // 1分経過フラグ
// 測定用タイマー(カウントアップ)
byte f_tmion;        // 計時指令
              // onで計測タイマーをカウントアップ
volatile word tmi_1min;  // 1分 カウントアップタイマー
              // 24時間で1440分 999分で16時間39分
volatile byte tmi_1sec;  // 1秒 カウントアップタイマー
              // 00~59
volatile byte tmi_10ms;  // 10ms カウントアップタイマー
// tmmreadで読む測定タイマー
word tmm_1min;       // 1分 max5999分=99時間59分
byte tmm_1sec;       // 1秒 0~59
/***** タイマー0コンペアマッチA割込み    *****/
// 割り込みでパルス出力(1kHz周期)
ISR(TIMER0_COMPA_vect)
{
static byte cnt10  = 0;
  PB0_H;           // (!!!) 14pin
  if(tm_1ms)   tm_1ms--;  // 1msダウンカウント
// 10msタイマー
  cnt10++;
  if(cnt10 >= 10){
    cnt10 = 0;
    if(tm_10ms)   tm_10ms--;   // 10mS ダウンカウント
    if(f_tmion){          // 計時する?
     tmi_10ms++;
     if(tmi_10ms >= t_spdup){   // 100カウントで1秒
      tmi_10ms = 0;
      f_1sec = 1;         // 1秒フラグをオン
      tmi_1sec++;         // +1秒
      if(tmi_1sec >= 60){     // 60秒
       tmi_1sec = 0;
       f_1min = 1;        // 1分フラグをオン
       tmi_1min++;        // +1分
       if(tmi_1min >= 6000){   // 6000分越え?
        tmi_1min = 5999;
        tmi_1sec = 59;     // 99時間59分59秒に
       }
      }
     }
    }
  }
// 1ms処理関数
  swscan();      // SW入力スキャン
  bzzexc();      // ブザー報知実行
// 内蔵A/D開始
  ADCSRA |= (1 << ADSC);   // A/D変換開始
  PB0_L;           // (!!!) 14pin
}

/******************************/
/*   内蔵A/D処理    */
/******************************/
/**** A/D データ    *****/
#define ADAVR_ADD   256   // A/D平均回数
volatile word ad_avr;     // A/D変換データ 0~1023
                // 平均処理された結果
volatile ulong ad_add;     // A/D平均処理用加算データ
volatile byte f_adok;     // A/D変換完了フラグ
                // 割り込みでセット
// 平均処理確定でad_avrを転送 電池電圧に変換
byte f_advolt;         // A/Dデータ転送変換完了フラグ
word ad_data;          // 電圧A/D値 (平均値)
word bat_ad;          // スムージング処理結果A/D値 (0~1023)
word bat_volt;         // 電池電圧 bat_adをmV値に変換
word bat_peak;         // 電池電圧ピーク値(mV)
word bat_deltav;        // ΔV値 ピーク-現在値 (mV)
byte f_peakon;         // ピーク検出開始フラグ
/***** A/D割り込み処理  *****/
//  1msタイマー割り込みでA/D変換開始
//  256回で平均値算出
ISR(ADC_vect)
{
word d;
static word cnt = 0;  // A/D変換平均回数
  PB4_H;           // (!!!) 18pin
  d = (word)ADCL;       //
  d |= (ADCH & 0x03) << 8;  // 符号なしで 0~3FF
// 測定値
  ad_add += (ulong)d;     // 平均用に加算
// 平均処理
  cnt++;           // 平均加算回数
  if(cnt >= ADAVR_ADD){    // 256回?
    cnt = 0;
    ad_avr = (word)(ad_add / ADAVR_ADD); // 平均
    ad_add = 0L;      // 次加算データクリア
    f_adok = 1;       // 変換完了
  }
  PB4_L;           // (!!!) 18pin
}
/***********************************/
/* A/Dデータスムージング処理   */
/***********************************/
// 処理バッファ
word ad_smzbff[16];     // A/Dデータ保存バッファ
word ad_sort[16];      // ソート用バッファ
/***** qsort用データ比較    *****/
// wordで比較
int wordcmp( const void *p, const void *q ) {
  if( *(word *)p > *(word *)q )   return 1;
  if( *(word *)p < *(word *)q )   return -1;
  return 0;
}
/***** スムージング処理  *****/
// 16コのA/Dデータを順にサンプル
// ソートして上下4コ(8コ)を捨て、中央の8コを平均
// 256ms X 16 = 4.096秒後に安定
word adsmz(word d)
{
static byte f1 = 0;   // はじめてフラグ
static byte wp;     // 書き込みポインタ
byte i;
long a;
word b;
  if(f1 == 0){        // 初めて
    f1 = 1;
    for(i = 0; i < DIMSIZ(ad_smzbff); i++){
      ad_smzbff[i] = d;    // バッファを初期データで埋める
    }
    wp = 0;           // データ書き込みポインタ
  }
  else{            // 2回目以降
    ad_smzbff[wp] = d;       // A/Dデータをストア
    wp++;              // 書き込みポインタを進める
    if(wp >= DIMSIZ(ad_smzbff))  wp = 0;  // 一周回って先頭に
  }
  memcpy(ad_sort, ad_smzbff, sizeof(ad_smzbff)); // バッファにコピー
  qsort(ad_sort, DIMSIZ(ad_sort), sizeof(word), wordcmp); // qsort実行
// 中央の8つで平均処理 上下4コは捨てる
  a = 0;             // 合計
  for(i = 0; i < 8; i++){     // 中央の8コを加算
    a += ad_sort[4 + i];    // 4コ目から8コを加算
  }
  b = word(a / 8);        // 1/8して平均値
  if((a % 8) >= 4)    b += 1; // 四捨五入
  return b;
}

全体のスケッチはまた今度に。

| | コメント (0)

2020年2月17日 (月)

定電流回路の電流検出抵抗

JIS C8708:2019充放電試験回路製作中、この中の定電流回路
(充電用と放電用の2つ)この電流検出抵抗をあれこれ模索。
今回使ったのは手持ちの関係で5W・0.3Ω。
その発熱が悩みどころ。

セメント抵抗に電力を食わせた時の温度上昇。
こんなグラフが出ていました。

B0
https://www.e-globaledge.com/products/ecomponents/tdo/

温度特性が「±400ppm/℃」。
ということは、10℃変化で0.4%。
触れないくらいの50℃も上がると2%の変動。

電流検出抵抗としてはちょいとなぁ~なんですが、
とりあえず手持ち部品の関係で「5W0.3Ω」というのを
用いました。

今回の回路、最大1.5Aで0.3Ωだと0.7Wほど。
5W定格の14%ほど。
十分に定格内なのですが、↑のグラフを見ると、
上昇温度が30℃ほどと読み取れます。
ということは抵抗値として1%ほどの変動を覚悟しな
くてはとなります。

仕事の装置ではこんな抵抗を使っていました。
   ※手持ちが無いので今回は使えなかった。
B1_20200217091601
「PWR4412」という金属でできたコの字のバー。
これで「±20ppm/℃」。
ただし、ラインナップされている最高の抵抗値が「0.1Ω」。

抵抗値を小さくすると、電力が減って発熱も少なくなりますが、
定電流回路の制御電圧を下げなければなりません。
今は制御電圧0.5Vで1.6A。
電流検出抵抗を0.1Ωにすると、0.16Vで1.6Aという制御に
なり、ちょっと電圧が低いかな~というところです。


※参考
ねがてぃぶろぐ セメント抵抗の温度特性 その1
ねがてぃぶろぐ セメント抵抗の温度特性 その2

※追記
手持ちの「コの字状抵抗」を引っ張り出してきました。
11_20200218090301
30mΩのと50mΩのが各2つ。
調べてると・・・
PWR4412」、廃番になってました!
えらいこっちゃ。
代替品を探さなくてはなりません。

ビシュイでSR3R、SR5Rというのが使えそう。
ちょいと温度係数が悪いか・・・。
SR3R(3Wタイプ)だと0.1Ωがある。
RiedonというメーカーでMSR5というのでも0.1Ωがある。

抵抗値を小さくすると、同じ電流なら電力がダウン。
1.5Aで0.3Ωだと0.675Wだったのが、0.1Ωにすると0.225Wに。
これで、発熱がずいぶんと減ります。
↑にあるグラフのように「負荷率に対する温度上昇曲線」あれば
発生誤差が推定できるんですけれどねぇ。

| | コメント (5)

2020年2月16日 (日)

JIS C8708:2019充放電試験回路製作中

ざっとハードが固まりました。
制御プログラムをごそごそしています。

A2_20200216162501

0.5Cで充放電させる必要があるので、設定できる最大を1.6Aに
しています。

A1_20200216162501
充電側のヒートシンクをもうちょい大きくしたいところ。




| | コメント (0)

2020年2月10日 (月)

ArduinoのanalogWrite 1/255なの?

ミスが広まる 1/1023 vs 1/1024 はA/Dコンバータのスケーリングの話。
Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
はタイマーコンペアレジスタ設定の話。

そして今度は「analogWrite」、ArduinoのPWM出力の話をちょっと。

Arduino-UNOだと3つのタイマーを使った6つのPWM出力が可能です。
仕様では分解能8bit。
  void analogWrite(uint8_t pin, int val)
という関数で使います。
ピン番号で使うタイマーが決まり、例えば5,6ピンならタイマー0が
用いられます。
タイマーのクロックが250kHzで8ビットですので、1.024ms周期で
1ビットが4usの分解能になります。

valの値がPWMのデューティー比で、設定できるのは0~255。
0で出力LOW固定になり、255で出力HIGH固定。
128でデューティー50%の方形波。

analogWriteを単にPWM出力ということではなく「D/Aコンバータ」
として使った時、その出力範囲をどう考えればよいでしょうか?
  (「1/1023 vs 1/1024」に近い話になります)

8ビットの0~255のデータを、可変範囲が0~5VのD/Aコンバータで
出力する時、最大値は「(255×5V)/256」で、フルスケールの5Vより
約20mV低いおよそ4.98Vがmax値となります。

ところがanalogWriteは「0V=LOWに張り付いてパルス無し
5V=HIGHに張り付いてパルス無し」まで出力できます。

本来ならフルスケール「(255×1.024ms)/256」で、
1020usのHIGHパルスに4usのLOWパルスが残るはず。
それがなぜか255でHIGHに張り付いちゃいます。
1/256ではなく1/255で処理されているのじゃないかと考え
られるのです。

analogWriteは「wiring_analog.c」内で処理されていますので、
ソースをちょいと覗いてみると・・・
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void analogWrite(uint8_t pin, int val)
{
  pinMode(pin, OUTPUT);   // 指定ピンを出力に
  if (val == 0){        // ★1
    digitalWrite(pin, LOW);  // 値が0ならLOWに張り付き
  }
  else if (val == 255){     // ★2
    digitalWrite(pin, HIGH); // 値が255ならHIGHに張り付き
  }
  else {      // ArduinoCPUによる区別
    switch(digitalPinToTimer(pin)) {  // その代表で
      case TIMER0A:         // タイマー0
        sbi(TCCR0A, COM0A1);    // OC0Aピン非反転PWM出力モード
        OCR0A = val;  // set pwm duty ★3 値が1~254の時
        break;
       :
    }
  }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
★1と★2で0と255を分離処理して、★3で1~254の時だけ
PWMパルスが出るようにしています。

そこで、タイマー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とは異なるピッチ
なっていることがわかります。

※この部分の注意点
 これ、タイマー0だからなんです。
   OCR0A = val
 とコンペアレジスタに設定値を与えているから。
   OCR0A = val - 1
 にすると、値1で4usのHIGHパルスが得られます。
 そして★2の処理を無くせば255で4usのLOWパルスとなり、
 「(255×1.024ms)/256」、「(255×5V)/256」の値が得られます。

なお、タイマー0が「8bit高速PWMモード」で初期化されている
ことに注意してください。
タイマー1とタイマー2は「8bit位相基準PWMモード」に設定されて
いて、動作が異なるのです。
このあたりの差が、数値に対するPWM波形出力に影響を与えます。

analogWriteにD/A変換機として純粋な精度を求める場合はちょいと
ご注意を。
ハードウェアマニュアルをよく読み、テストプログラムを書いて
動きを確かめてということで。

※追記
デューティー50%の方形波を得ようとして
  analogWrite(6,128);
しても、HIGH=512us、LOW=512usの波形にはなりません。
HIGHが516us、LOWが508usになり、デューティ50.4%くらいの
方形波が出てきます。
きっちり50%にしたいのなら、
  analogWrite(6,127);
です。 (タイマー0で)

※PWMでのD/A変換、こんな回路を使ってます。
B1_20200210161601
Arduinoの5Vは不安定なので、基準電圧ICで4.096Vとか
2.5Vなどを発生。
PWM波をアナログマルチプレクサ(74HC4053など)を使って
GND-VREF電圧を切り替える。
それをLPFで平滑。
OP-AMPでバッファして電圧出力に。
タイマー1を使うと分解能を10bitとか12bitに拡張できる。
analogWriteじゃなく自前のプログラムで制御。

※PWM出力の様子をオシロスコープで観察する
 ためのテストプログラム。 (注:スペースを全角で)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "wiring_private.h"
// I/O MACRO
#define PB0_H  (PORTB |= (1 << PB0))   // (!!!)PB0 H/L D8
#define PB0_L  (PORTB &= ~(1 << PB0))
#define PB4_H  (PORTB |= (1 << PB4))   // (!!!)PB4 H/L D12
#define PB4_L  (PORTB &= ~(1 << PB4))
#define PB5_H  (PORTB |= (1 << PB5))   // (!!!)PB5 H/L D13
#define PB5_L  (PORTB &= ~(1 << PB5))

/***** タイマー0 オーバーフロー回数読み出し *****/
extern volatile unsigned long timer0_overflow_count;
unsigned long readovf0(void)
{
unsigned long t;
  cli();         // 割込禁止
  t = timer0_overflow_count;
  sei();         // 割込許可
  return t;
}
/***** PWM出力    *****/
// val : PWM設定値 0~255
// OC0A,OC1A,OC2Aに出力
void pwm3ch(int val)
{
  analogWrite(6,val);    // タイマー0 OC0A
  analogWrite(9,val);    // タイマー1 OC1A
  analogWrite(11,val);   // タイマー2 OC2A
}
/*****  セットアップ    *****/
//  ATmega328Pのレジスタを直接制御
void setup()
{
  cli();         // 割込禁止
// I/Oイニシャル
  PORTB = 0b00000000;   // data/pull up
  DDRB = 0b00111111;   // port指定
  //     |||||+---- PB0 IO8  out    test pulse
  //     ||||+----- PB1 IO9  out OC1A
  //     |||+------ PB2 IO10  out OC1B
  //     ||+------- PB3 IO11  out OC2A
  //     |+-------- PB4 IO12  out    test pulse
  //     +--------- PB5 IO13  out (LED) test pulse
  PORTD = 0b00000000;   // data/pull up
  DDRD = 0b11111100;   // port指定
  //    |||||||+---- PD0 IO0  RXD
  //    ||||||+----- PD1 IO1  TXD
  //    |||||+------ PD2 IO2  out
  //    ||||+------- PD3 IO3  out OC2B
  //    |||+-------- PD4 IO4  out
  //    ||+--------- PD5 IO5  out OC0B
  //    |+---------- PD6 IO6  out OC0A
  //    +----------- PD7 IO7  out
  sei();         // 割込許可
}
/***** LOOP    *****/
void loop()
{
unsigned long ovf0;
unsigned long ovfck;
byte cnt  = 0;   // オーバーフローカウンタ
byte kubun = 0;   // PWM出力区分
static int val[]={ // PWM設定値
  0, 1, 2, 127, 128, 253, 254, 255, // 8つのPWM値
};                   // 0:LOW~255:HIGH
  ovf0 = readovf0();       // オーバーフロー回数
  while(1){
    ovfck = readovf0();
    if(ovf0 != ovfck){     // 変化あり
      ovf0 = ovfck;      // 1.024msタイマー0オーバーフロー
      PB0_H;         // 1.024msごとにパルス出力
      if(cnt == 0){
        PB4_H;             // 先頭カウントの時
        if(kubun == 0)   PB5_H;   // 区分0の時パルス
        else        PB5_L;
        pwm3ch(val[kubun]);  // 区分によりPWM出力  
      }
      else{
        PB4_L;
      }
      cnt++;
      if(cnt >= 6){      // 6回オーバーフロー
        cnt = 0;
        kubun++;      // PWM区分
        if(kubun > 7)  kubun = 0; // 0~7
      }  
      PB0_L;
    }
  }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

・timer0_overflow_countがタイマー0割り込みでインクリメントされるので、
 これを見て1.024ms経過を検出。
・PB0は1.024msごとにパルス出力。
 PB4はPWM値設定ごとに、PB5は区分一周でパルス出力。
 オシロのトリガー源に。
・タイマー0のPWMはこのパルス周期に同期しているが、
 タイマー1、2のPWMは同期しない。
 タイマー0は1/256処理。タイマー1,2は1/255で処理されている
 ので、周期が異なるから。
・タイマー1,2でデューティー、ジャスト50%を得ようとしても、できない。
  127だと、H:1.016ms + L:10.24msに。
  128だと、H:1.024ms + L:1.016msになってしまう。
   (タイマー0は周期1.024msだけど、タイマー1,2は2.040ms周期)


| | コメント (1)

2020年2月 4日 (火)

Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ

ミスが広まる 1/1023 vs 1/1024は、Arduino-UNO(ATmega328pマイコン)
のA/Dコンバータ・スケーリングのお話しでしたが、今回はタイマーのコンペアマッチ
レジスタのお話しです。

Arduinoのタイマーを直接操作する時(例えばMsTimer2などのタイマー
ライブラリーを使わずにタイマー割り込みを使いたい時など)、CTCモードで
アウトプットコンペアレジスタにタイマーの最大カウント値を設定します。
Arduinoから「タイマー0」を取り上げる(ユーザーが使う) では
「1m秒」割り込みを例にしました。
1msつまり1kHzの割り込み周期を得るには、
 ・CTCモードに。 (コンペアマッチでカウンタゼロクリアー)
 ・16MHzのクロックをプリスケーラーで1/64して250kHzに。
 ・それを1/250して1kHzに。
この1/250するのがアウトプットコンペアレジスタへの設定です。
↑の例では
   OCR0A = 250 - 1;    // 250カウントで1kHzを
の所。
1/250なんで「250 - 1」を設定しています。
これをミスして「-1していない」プログラム例や解説を見かけるのです。

なぜ「n - 1」なのかは、このタイミング図を見れば分かるかと。
A1_20200204115801
TOP値がOCR:コンペアレジスタへの設定値。
そして、CTCモードだとBOTTOM値はゼロになります。
TOP値を検出した次のサイクルがゼロとなるわけです。

例えば「1/4」したい時、TOP値つまりコンペアレジスタの値を
「4」にしてしまうと・・・
0 1 2 3 4  0 1 2 3 4  0・・・」と繰り返しが「5」になってしまいます。
TOP値としてOCRに設定するのは「カウント周期 - 1」でなければいけません。
「-1」しないと、「1/4」が欲しいのに「1/5」になってしまうという
スカタンが発生します。

これに気付かない原因の推測ですが、
・とりあえず、それらしい動きをしてるから。
・16ビットタイマーであるタイマー1に対して、大きな値をセットしてる。
 そのままの値と-1した場合とで、タイマー周期にほとんど違いが出ない
 から気がつかない。
・8ビットタイマーでも、ミスを測定する手段を持ってない、あるいは
  積極的に結果を計ろうとしていない。
   ※周波数カウンタやオシロスコープなどの測定器をつなぎ
    テストパルスを出せばすぐに気がつくはず。
・ハードウェアの非同期カウンタを思い浮かべている。
   ※1/10カウンタなら、7→8→9→[10=0]→1→2→
    と、10検出で直ちにリセットされて0になるので。

こんなところかと。

※見かけたミスの例
ArduinoのTimerを初心者が1からなんとなくわかるためのメモ - Qiita
  16MHzクロックを1/256し、「OCR1A = 62500;」として「1秒」としている。
Arduinoでtimerを使った割込み処理の方法 ? 自作のいろいろ
  「OCR2A=100; //100カウントごと(50μs)毎」と。。。
Timer1 - FreeStyleWiki
  ちょっと違う。
タイマー割り込みを使う - arduino始めました
  惜しいような。。。
Timer0 & 1
  アセンブラでもミス。

多くの正しい解説もありますが、ミスしたのが目立っちゃいます。

そして、もうひとつ気になるのが割り込み処理内で走らせるプログラム。
「呼んですぐに戻ってくる関数」が基本。
そして、それがメインの処理とぶつからないこと。
他の割り込み処理に影響(長い時間待たせる)を与えてもダメ。
割り込みの中で「Serial.println(xxx)」を使ってる例を見かけるのですが、
これはいかがなものかと・・・。


| | コメント (1)

2020年1月28日 (火)

Arduinoから「タイマー0」を取り上げる(ユーザーが使う)

Arduino(UNO)でユーザーが自由に使えるタイマー(ATmega328の)は2つ。
タイマー0をArduinoのシステムが使っているから。
残るタイマー1とタイマー2は自由に使ってokという構成。

で、このタイマー0が何に使われているかというと、この3つ。
 unsigned long millis()・・・経過時間をミリ秒値で得る
 unsigned long micros()・・・経過時間をマイクロ秒値で得る
 void delay(unsigned long ms)・・・ミリ秒値で時間待ち

タイマー0のオーバーフロー割り込みでもって計時しています。
割り込み周期は、16MHzを1/64して256カウントですので、
1.024ms = 976.5625Hz。
  ※なお、「void delayMicroseconds(unsigned int us)」
   は純粋にソフトでの時間待ちですのでこのタイマー0
   には依存しません。

『millis()やmicros()、delay()は使わんぞ』というプログラムなら、
タイマー0もユーザーが使えます。
その検証をちょいと。

・タイマー0のオーバーフロー割り込みをやめる。
   「TIMER0_OVF_vect」の処理ルーチンは残ってしまうけど、
    無視します。

・タイマー0のコンペアマッチA割り込み使って、自由なタイマー
 割り込みを得る。
 例えばジャスト「1ミリ秒=1kHz」と使いやすく。

・そのために、タイマー0のレジスタを直接操作。
・TIMER0_COMPA_vectの処理を書く。

簡単なテストプログラムを紹介しておきます。
  (表示の関係で全角スペースにしてます)

//////////////////////////////////////////////////////////////
// I/O MACRO
#define PB0_H  (PORTB |= (1 << PB0))   // (!!!)PB0 H/L
#define PB0_L  (PORTB &= ~(1 << PB0))
#define PB1_H  (PORTB |= (1 << PB1))   // (!!!)PB1 H/L
#define PB1_L  (PORTB &= ~(1 << PB1))

/***** タイマー0コンペアマッチA割込み    *****/
// 割り込みでパルス出力(1kHz周期)
ISR(TIMER0_COMPA_vect)
{
  PB0_H;             // (!!!)PB0 H
  PB0_L;             // (!!!)PB0 L
}
/*****  セットアップ    *****/
//  ATmega328Pのレジスタを直接制御
void setup()
{
  cli();         // 割込禁止
// I/Oイニシャル
  PORTB = 0b00000000;   // data/pull up
  DDRB = 0b00111111;   // port指定
  //     |||||+---- PB0 IO8  out TEST 割り込みでパルス
  //     ||||+----- PB1 IO9  out TEST メインでパルス
  //     |||+------ PB2 IO10  out
  //     ||+------- PB3 IO11  out
  //     |+-------- PB4 IO12  out
  //     +--------- PB5 IO13  out (LED)
  PORTD = 0b00000000;   // data/pull up
  DDRD = 0b11111100;   // port指定
  //    |||||||+---- PD0 IO0  RXD
  //    ||||||+----- PD1 IO1  TXD
  //    |||||+------ PD2 IO2  out
  //    ||||+------- PD3 IO3  out
  //    |||+-------- PD4 IO4  out
  //    ||+--------- PD5 IO5  out
  //    |+---------- PD6 IO6  out OC0Aトグル出力
  //    +----------- PD7 IO7  out
// タイマー0
  TCCR0B = 0x00;     // タイマー0いったん停止
  TCCR0A = 0b01000010;  // あらためてモード設定
  //     |||| ++--- WGM: CTCモード
  //     ||++------- COM0B
  //     ++--------- COM0A PD6トグル出力
  TCCR0B = 0b00000011;
  //       |+++--- CS:1/64 : 250kHz
  //       +------ WGM02
  OCR0A = 250 - 1;    // 250カウントで1kHzを
  TIMSK0 = 0b00000010;
  //        +----- OCIE0A コンペアマッチA割り込み有効
  //               オーバーフロー割込は無しに
  sei();         // 割込許可
}

/***** LOOP    *****/
// PB1にパルス出力
void loop()
{
  PB1_H;             // (!!!)PB1 H
  PB1_L;             // (!!!)PB1 L
}
//////////////////////////////////////////////////////////////

PB0、PB1そしてPD6をオシロで見たのがこの波形。

Tm01

タイマー割り込み処理を行っている間、メインで出している
PB1のパルスが止まります。
PD6がトグル(ここで割り込み要求が発生している)してPB0が
オン(割り込み内の処理)するまでの間、時間がかかっている
のは割り込みのスタック処理です。
レジスタを待避するなどあれこれ見えない処理が走ります。

Arduinoでの初期設定は「wiring.c」に記されています。
この中で設定されている「TIMER0_OVF_vect」の処理は取り除けません。
しかし、タイマー0オーバーフロー割り込み許可ビット:TOIE0を0に
することでこの処理は走りません。
そして、オーバーフローは無視してコンペアA割り込みを走らせる
のです。

タイマー0のクロック分周設定や割込時間も自由。
   「OCR0A= 25 - 1;」としたら10kHzで割り込み。
   まぁ、1msあたりが使いやすいかと。
計時だけでなく、いろんな処理を入れ込めますので、3つしかない
タイマーが生かせます。

ただ・・・millis()やmicros()、delay()をシステムで(ユーザー
から見えない何かの処理)使っていたら、その処理は止まってしま
います。
シリアルや液晶表示は大丈夫そうでしたが、「ダメ」なのを探し切れて
いません。

※関連
割り込みで処理させるwordデータの扱い
Arduinoのタイマー処理 タイマー0のオーバーフロー割り込みの実行時間
ラジオペンチ Arduinoのmillis()関数が返す値は不連続な場合がある
魔法の言葉「volatile」
AVRマイコンのCコンパイラ 具体的に


※追記
なぜタイマー0を使いたいのか・・・
JIS C8708による充放電サイクル試験回路 これの「2019」対応プログラムを
Arduinoに移植したいから。
現在はアセンブラで書いてます。
「C」に直すならArduinoでと考えました。
現状、
  タイマー0:1mS割り込み
  タイマー1:充放電電流設定用PWM出力
  タイマー2:ブザー報知(4kHz)出力
となっていて、これを踏襲したいからなんです。



| | コメント (0)