微少電流計の自作1|INA260+RL78|マイコン入門

当サイトにはアフィリエイト広告が表示されます。

興味があるところを読む

全体の流れ

微少電流の変化を目で見る必要があるんで、微少電流計と言うか微少電流の変化を表示するものを作ってみた。

こちらを参考にさせて頂いて、CPUをPICからG10(R5F10Y47)に変更し、表示装置をLCDからPC(パソコン)に変更した。
つまりADC(MCP3901)の生値をSPIで取得してUARTでPCに送信し、生値の処理や表示は全部PC側で行う・・・G10はSPI-UARTゲートウエイみたいなもん。

電源は、ADCが5V、CPUもG10は5Vで使えるので、電源は5V一本でOK。

CPUのメインクロックは内蔵OSCを20MHzで使用。
UARTは2,000,000=2Mbps・・・こんな高速度でPCと通信できるのか分からなかったけど、やってみたら普通に通信できた。
SPIはポートをソフトでパタパタして実現した。速度は、6byte取得に56uS=9.3uS/byte → 107,143byte/秒 → 856Kbps。
200μS毎のインターバル割込みでサンプリングしてUARTで送信(5Ksps)。

スポンサーリンク

SPI

このADCは24bitでSPIインターフェースだ。
CH0とCH1の2CH有り、各CHが24bitなので合計で48bit=6byteがSPIで出力される。
↓LAP-Cで見るとこんな感じ。

読み出すときは_CSをLOWに固定したままで、_DR=0を確認しながら単純に連続で6バイト読み込めばOK。なのでADC初期化終了時に_CS=0にしてしまえば、後は_CSを動かす必要は無い=6byte毎に_CSを上下させる必要は無い(元記事では6byte毎に_CSを動かしてるけど、そうしなくても取得できます)。

PCとの通信にはUARTが必要なのだが、G10はSPIかUARTのどっちかしか使えないんで、SPIはポートを自分でパタパタすることにした。

G13だとSPIとUART両方を同時に使えるんで、G13で動作チェックして、大体OKになってからG10に移植した(G13はちょい大きいし勿体ないんで)。

SPI操作はアセンブラでやることにしました(先にCで書いておいて、動作確認後にアセンブラで書き直した)。
コードはこれで、アセンブラで書き直すとCの約二倍の速度になった。

// Cでの連続6バイト入力(検証済み)
uint8_t* SPIRcv6(uint8_t rd[])
{
int i, lp;
uint8_t BitPos, data;
for (lp=0; lp<6; ++lp) {
data = 0;
BitPos =0x80; // ビット位置リセット
for(i=0; i<8; i++){ // 8ビット繰り返し
SCK = 0;
SCK = 0;
SCK = 1;
if (SDI) data |= BitPos;// ビット入力
BitPos = BitPos >> 1; // ビットシフト
}
rd[lp] = data;
}
SCK = 1;
return(rd); // 受信データ*を戻す
}

=====================================================================
; 自分用のアセンブラ関数用ファイル 2018/8/9
;====================================================================
; PUBLIC宣言はここ(TEXT内で宣言すると引数のアドレスが変になる)
.PUBLIC _asmSPIRcv6

; TEXT
.SECTION .text,TEXT
;———————————————————————
; void _asmSPIRcv6(uint8_t* data)
; AX=data pointer
; 6byte取得に56uS=9.3uS/byte → 107,143byte/秒
;———————————————————————
_asmSPIRcv6:
push hl ; HLは要保存
movw hl,ax ; 引数はAXで届くんで HL ← data
; for(X=0; X&lt;6; ++X)
clrb x ; X は6byteカウント用
.lp0:
clrb b ; B は8bitカウント用
clrb c ; C はMISOを1byteに纏めたもの(それを[HL]に入れる)
mov d,#0x80 ; D は bit7-6-5…0 とシフトしてる
.lp1:
; 立ち上がりエッジをCLKに出力
mov a,p0
and a,#0xf7 ; CLK=0 P0.3=0
mov p0,a
or a,#0x8 ; CLK=1 P0.3=1
mov p0,a
; MISO(P0.4)を読む
mov a,p0 ; A=MISO
and a,#0x10 ; check MISO bit
bz $.lp2 ; if 0 skip
; MISO=HならCにDのbitをセット
mov a,c
or a,d ; D goes to 0x80-0x40-0x20…0x0
mov c,a ; set to C
.lp2:
mov a,d ; shr D
shr a,1
mov d,a
; chek 8 bits loop
inc b ; ++B
mov a,b
cmp a,#8
bnz $.lp1
; set 8 bits to data[]
mov a,c
mov [hl],a
incw hl
; chk 6 loop count
inc x ; ++X
mov a,x
cmp a,#6
bnz $.lp0
; end
pop hl
ret

RL78のアセンブラ

RL78アセンブラ言語仕様のリンク
RL78ユーザーズ・マニュアル(S1コアの命令セットはP32~)

RL78のアセンブラを良く知らないんで適当に書いてるけど、今のところは動いてる。
Cからの引数の受け渡しや戻り値などの決まりは、Cで書いてコンパイル・オプションでアセンブラソースを出してみたり、逆アセンブラでステップ実行すれば直ぐに分かります。

このRL78アセンブラ、雰囲気的にはZ80なんだけど、妙に書きにくい部分が多々ありで、μVisionのように自由自在ではない。
特にインライン・アセンブラがメチャクチャ不便で、殆どインラインでは書けないんで、別ファイルにしてASMソースとしてゼロから書いてる。つまりCで書いた関数内の一部だけを高速化するとかが出来ない・・・多分。

その他「20bitのCPUである」ってことがアセンブラレベルでは非情に使い難い。
20bitだから昔の8086みたいなセグメント・レジスタやら64k範囲内アクセス用のnearやらfarやらミラー領域やら、手間のかかることが大量に出てくる。んま、Cで書いてる際には気にならないから良いか。

それにしても「#0x80」って何?なんなのこの書き方は?「$.lp0」だって凄い変態だよね。
なんで「mov d,80h」や「JPNZ LP0」じゃダメなの?・・・んま文句を垂れたらキリが無いんで(^^;

取り敢えずレジスタだけを使って書いたけど、変数を使おうとすると更に変態になる必要があるんでパス。

CS+内でデバッグ時、ポート値はSFRウインドウに、レジスタ値はCPUレジスタ・ウインドウに表示される。

スポンサーリンク

PC側のアプリ

PC側はVisual Studio 2019でC#で作った。
単純にUARTから6byte受信して、どちらかのCHの3byteを補正してチャートに書いてるだけ。
通信のチェックとか何もしてない。落としたりズレたりすればチャートを見てれば分かるので、そう言うときは再起動して誤魔化す。

左:CH0の入力をショートしても±5μA程度のノイズが出る。右:大きめの電流を流せば目立たないけどね。

アプリはG10側に「サンプリングを開始せよ」を送信し、G10は200μS毎に6byteをPCに送信する。
アプリ側では受け取った6byteの半分(前半がCH0で後半がCH1)の3byeをInt32に変換してからADCのオフセットを加え、更にdoubleに変換してからてチャートに表示する。

オフセット量はADC入力をショート(電流ゼロ状態だから本当なら3byteのゼロが来るはず)したときの値から読み出した。
また1bitの大きさ(重み)は、適当な抵抗に決まった電圧をかけ、本来流れる電流を計算で出しておき、ADCから来た値と比べて出した。
もっとまともな計算方法があるはずだけど、ざっと合ってるから良しって事で。

<抵抗に3.0Vを通電してCH0を実測した>
ショート時、ADCから 740C0 が来た。740C0・・・これをオフセットとして使う
116.6Kの抵抗に3Vをかければ26uA=260uV(CH0のシャント抵抗は10Ω)流れるはず。この時ADCからは 87FC0 が来た。
87FC0 – 740C0 = 13F00 (81664D) で 、1uA辺りの係数は 26uA ÷ 81664 ≒ 0.00032・・・これを1bitの値とする。

for (int i = 0; i < rcvbytes; i += 6) {
int idx = (ch == 0) ? 0 : 3; // CH0は受信データの[0]から、CH1は[3]から
Int32 temp = (rxbuf[i + idx] << 16) + (rxbuf[i + idx + 1] << 8) + rxbuf[i + idx + 2];
double ad = 0;
if (ch == 0) {
temp = (temp – 0x740C0); // CH0はPLUS側に多めに出るのでマイナスする
if (temp < 0) temp = 0;
ad = (double)temp * ((double)(26.0 / 81664.0)); // CH0 0.00032
} else {
temp = (temp + 0x4300) & 0xffffff; // CH1はMINUS側に少な目に出るのでプラスする
if (temp > 0x7fffff) temp = 0;
ad = (double)temp * ((double)(30.430 / 1197446.0)); // CH1 0.000025
}
// トリガチェック
if (trgpos == 0 && checkBox_Trig.Checked == true) {
double trigval = double.Parse(textBox_Trig.Text);
if (ad >= trigval) trgpos = dcnt;
}
// チャート用に保存
df[dcnt++] = ad;
total += ad;
// for debug
_totalH += temp;
++_lpH;
}

反省

24bitのADCを使っても素人工作なのでノイズ成分が多い。

ってことなら16bitのADCで構わないわけで、それならここのINA226のシャント抵抗を取り替えて使う方が良さそう。
余計な工作も工夫もいらないし簡単だよね。
写真で見ると6432(2512inch)な薄膜チップ抵抗な感じなんで、これの1Ωと取り替えれば行けそうな予感だけど、サイズの合うのが見付からないかも。

ミスミの検索結果 (温度係数が±15ppm/℃で2500円だ!)。ちなみに巻き線抵抗で構わなければ1500円・・・やっぱ高いわ。

スポンサーリンク

INA226PRCで試してみた

2019/12/7 INA226マニュアル Configレジスタ設定など 簡単な計算式
購入時点では25mΩ1W(MAX 3.2A)の薄膜シャント抵抗(誤差1%)が付いてるんだけど、これを外して手元にあった秋月の1Ω3W(誤差1%)を無理矢理くっつけて実験。本当はこの2500円のが良いんだろうけど高くて買えませんわ。チップ抵抗の型番とサイズ表

INA226の動作電圧範囲は0~36V、シャント抵抗は1Ω、8000~0x7FFFだから1bit辺り2.5μAかな?だとすれば電流の計測レンジはMAX±81.917mAなはず。
菊水の電圧発生器で10Vを出し、手元にあった誤差5%の抵抗を繋いで実測した。「」は計算値。

<10Vでシャント抵抗1Ω>
1M:27~30μA(0.03mA) ← 異常 「10μA」
470K:40~42μA(0.04mA) ← 異常「21.2μA」
100K:117~120μA(0.12mA) ← 怪しい「100μA」
47K:227~230μA(0.23mA)
20K:520~522μA(0.52mA)
10K:1,025~1,027μA(1mA)
2K:5,060~5,062μA(5mA)
1K:10,125~10,127μA(10mA)
470:21,515~21,517μA(21mA)
240:42,925~42,927μA(43mA)
ショートすると81,917でOverFlow
抵抗値が大きいと誤差が大きく出るようなので、電圧を1Vにして計測してみた。

<1Vでシャント抵抗1Ω>
100K:10μA
47K:22.5μA
良いみたい。1bitの誤差は仕方ないんで結構正確。

ファームは元々INA260用に作ってあった「LPC1114+mbed」のを改造した。

INA260とINA226はソケットで取り替え可能にし、電源投入時にConfigRegを読み出して、0x6127ならINA260、0x4127ならINA226と判定してる。

I2Cで読み出してUARTでバラバラ連続送信してTeraTermで表示。

シャント抵抗10Ωで実験
手元にあった秋月の10Ω3W(誤差1%)をくっつけて実験した。
良い感じで10倍の精度で電流を測れた。
MAXは±8.1mAになるけど、0.25μA/bitで出てる感じで、これで行きますわ。

この後でこのINA226を使いLPC1114で第二弾を製作しました。

スポンサーリンク
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


興味があるところを読む