ページ

2022/08/29

ラズパイとラズピコで分電盤センサを作成⑥

ADCの精度の実験は一区切りつけ、分電盤センサの次の要素に目を向けてみる。

変流器、カレントトランス、CTと呼ばれるセンサだ。分電盤を流れる電流はそのままでは扱えない大きな電流なので、小さくする役割がある。小さくしたところで抵抗に通せば電圧に代わり、電圧をADCで計測するという流れ。

調べたところ、お手軽なCTは変流比2000:1。
Ali Expressで手に入れた、YHDC社製SCT-013シリーズ。CTはたくさん使うし、その割に単価が高め(電子工作の部品の中では)のため、安い方がよいということでこれにした。
SCT-013

この変流比で60Aを測る場合、2.495Vのシャントレギュレータ出力をVrefに入れたMCP3208で最大値60Aまで測れるようにするには、83.17Ωの抵抗が必要となる。

SCT-013は、ご丁寧に?ケーブルの先がステレオミニプラグになっている。ということは、それを受けるジャックが必要だ。秋月電子にステレオミニジャックのDIP化キットが売っていたので購入。

DIP化キットの基板の穴すべてにピンをはんだ付けしたら、ブレッドボード上では干渉して逆に使いづらくなってしまった。。

さて、このCTをお試ししてみるために、2芯の電源延長コードをカッターで二つに割いて、1芯ごとに分割した。割くときに失敗して金属の芯線が見えてしまった。ここには100Vがかかるので、要注意。絶縁テープでグルグル巻きにした。メガネコードのような切りやすいタイプにすればよかった。。

さて、この特製コードにマルチテスタのクランプを挟んだり、購入したCTをかませたりして、いろいろ測定してみる。

ちなみにCTは2種類購入している。YHDC SCT013-100とSCT013-005だ。恐らく、抵抗がビルトインされているバージョンとそうでないバージョンと思われる。


-100は抵抗の種類を変え、両端の交流電圧を測定。
-005は抵抗を入れず、ステレオミニプラグの交流電圧を測定。

結果は以下。

●HIOKIの結果が交流0.28Aのとき

HIOKI-005-100
AC0.28A0.06V0.268V@抵抗なし
AC0.28A0.051V@0.33kΩ
AC0.28A0.122V@1kΩ
AC0.28A0.184V@2kΩ
AC0.28A0.214V@3kΩ
AC0.28A0.234V@4.55kΩ
AC0.28A0.251V@9.57kΩ
AC6.04A1.20V3.84V@1.749kΩ
AC6.04A5.08V@3.21kΩ
AC6.04A5.53V@4.55kΩ
AC6.04A5.93V@9.57kΩ

抵抗を増やすほど電圧があがる。。。想定外。これはもしや、自分の学生時代意味不明だった電流源というやつか・・?

(追記)CTは電流源として振舞うようだ。そして、オープンにすると高電圧がかかって危ないので、オープンにしてはいけないらしい。逆に変圧器は電圧源として振舞い、ショートにしてはいけないらしい。同じようなものに見えるのに・・よくわからん

まあ、基礎データは取れた。ぱっと見で-005の方が抵抗が内蔵されており、初心者向けのように見える。が、抵抗で自在に調整できる-100の方がスタンダードなのだろう。

(追記)やはり-100は電流源扱いのようだ。

交流電流
22Ω(18.5Ω)0.044V6.03A
100Ω0.182V0.301V
150Ω(145.8Ω)0.261V0.432V
270Ω(261.7Ω)0.464V0.770V
CTが流れた電流を1/2000するとして、V=RIに当てはめるとよく数値があう。
ということは、-100を使った場合、
  • 2.495VのTL431をVrefとして使用
  • ブレーカーの20Aまで検知→CTには0.01A流れる
  • 249.5Ωが正解
ということになる。また、有効数字を増やしたければ、抵抗は1個ずつテスタで測った方がよさそうだ。

-005の方は結果をいじるには分圧が必要だが、そうすると抵抗の実装面積が増えるので、低電流が期待されるところに使おう。

ラズパイとラズピコで分電盤センサを作成⑤

 別のサイトでこんな記事を見つけた。

ラズパイでアナログ電圧を扱う (7) MCP3208のプログラム③

Raspberry Pi 4にてMCP3208を使用して測距センサーの値を得る

アナロググラウンドとデジタルグラウンドをショートさせる策は既にやっているので、Vddの入力側に0.1μFのコンデンサを入れるのと、ADCの疑似差動入力を試してみる。

ふむふむ。

さらに、測定対象を単3乾電池にしてやってみた。これは化学反応による電圧なので、ノイズがほぼないらしい。



疑似差動のほうが精度が悪いように見えるが、これは集計上の誤差と思われる。(0点を出すときにINT関数で桁落ちさせているため)
+2とかの差では、わずかに疑似差動がまさっている。しかし、シングルエンドも十分使える性能だな。シングルエンド入力は、ADCのピンが倍使えるので、メリットは十分にあるな。

同じく単3乾電池使用で、Vrefとしてシャントレギュレータではなくラズパイから出力される3.3V電源を使ってみた場合がこれ。


ここから学んだのは次の点。
  • ラズパイから出力される3.3V電圧は結構変動する ロングテール。Vrefとして使わないほうが良い。
  • 乾電池の電圧安定性は高い。但し、長期間の電圧低下・内部抵抗変化はどうなるか分からないので要注意。
  • ADCの疑似差動入力が効果を出すようなコモンモードノイズは、今回組んだ回路ではもともと少ない(今回1分程度の計測ではあるが)
ということなので、シングルエンド入力を使うことに決めた。

それにしてもこの一連の実験はなかなか勉強になった。分電盤アプリとは関係ないが今後も実験してみたい。他にも気になることもある。
  • MCP3208のDoutから出ているデジタル出力電圧は実際何Vなのか?
  • Doutを何Vまで下げると、ラズパイSPI入力はデジタル信号を読めなくなるのか?

ラズパイとラズピコで分電盤センサを作成④

ADCの測定精度を上げる。

まずはシャントレギュレータTL431とパスコンを使ってみる。定番らしい。
MCP3208のデータシートにも書いてある。

ラズパイでアナログ電圧を扱う (3) 使用するパーツ

現場で役立つ パスコンの容量値選定方法

パスコンの容量選びは難しそうだな。

面倒な計算や理解をするほど意識は高くないので、パスコンは0.1μF~10μFを試してみる。

→どうも、シャントレギュレータにパスコンはつけないほうが良いようだ。付けてもよいが、きちんと容量計算をしないと、発振?してしまうらしい。上のパスコンも3端子レギュレータに対して計算をしている。

シャントレギュレータの大元の抵抗rはどうするかだが、いろいろ調べてみて下記の計算か。

電圧リファレンスを使用した 設計のヒントとコツ

MCP3208のVrefに流れる電流は、データシート上2行書いてあってどちらを採用してよいかわからなかったが、Iref_min=0.001uA, Iref_max=150uAとした。Vddは4.5~5.5Vの範囲とした。グラウンド側に流れる電流はどうしたらよいか書いていなかった(読み取れなかった)ので、1mAとした。すると、Rは1.74kΩ~3kΩとなる。
→別のTL431データシートをみると、カソード電流1~100mAとあった。この範囲に入るように外部抵抗を調整する。

もともとブレッドボードに刺してあった抵抗4.7kΩでやってみた。それっぽく動いている。まずはこれで統計を取ってみよう。負荷に変動がなければ抵抗の範囲に収まっていなくてもいいのかもしれない。


シャントレギュレータ効果は表れたようだ。明らかに分散が減った。相変わらず最大値や最小値は結構幅があって、これの発生理由がよくわからない。本当にこのくらい電圧が変動しているのだろうか。シャントレギュレータにパスコンは要注意であるが、入り口と出口側にコンデンサをつけてみるか。

これが結果。1秒間隔で10分プロットしたので、サンプル数は少ないが、0.1μFはあまりよくないように見える。そして、パスコンを入れても入れなくても結果が変わらないようにも見える。
次は、抵抗4.7kΩを、当初予定の抵抗に変えて実験してみる。

外部抵抗2kΩでも問題なさそう。このとき外部抵抗に1.2mA流れてた。概ね設計内か。ちなみにデジタルマルチメータでははかれず、アナログ電流計を使用。
外部抵抗を0.968kΩにすると2.5mA。こちらの方が安パイだな。









ラズパイとラズピコで分電盤センサを作成③

 分電盤センサ作りに何が必要なのか、まだ全体像が見えていないない中、思いついたところから実験してみる。ADコンバータで計測する場合の測定値のばらつきについて実験。

ラズパイの3.3V出力をADコンバータ(ADC)MCP3208で測定した。MCP3208のVrefにはラズパイの5V出力を入力した。


 グラフ横軸は測定結果の平均値を0として、そこからの差分を示している。MCP3208の分解能は12bitなので4096段階の回答が返ってくる中で、平均値に対して-20から+64までぶれる結果となった。その差84というのは二進数にすると1010110、つまり7ビット分である。これがよくある測定結果なのか、何か問題があるのか分からないが、12ビットADCコンバータが実質5ビットの分解能だとすると少し寂しい。ADCの精度を高めるため、ネットで調べた対策を打ってみることとする。(ちなみに-12から+12で全データの95%をこえている。これだと5ビット落ち。)

 上記の結果は2万サンプル以上とったうち、冒頭の5300サンプル程度の結果である。なぜそうしたかというと、当初、全ての測定結果をプロットしたところ、平均値のトレンドが時間変化しているように見られたからだ。なぜ時間変化したのか?ラズパイ内のDDコンバータに温湿度依存特性がある(長く実行して温度が上がり、特性が変わった)のかもしれないし、その前段、家からの供給電源に変動があるのかもしれないし、誤差要因の積み重ねはやればやるほど難しい。

 とりあえず、今回は明らかに怪しい電子回路内のノイズ等に起因する誤差を減らす工夫をしてみることにする。これにより、この後の分電盤センサでも実測値とのかけ離れを減らすことが期待できるはずである。
 仮に、ADC7ビット精度で電流センサを作ったとすると、最大60A・127段階として、1段階で0.47Aとなる。もう少し細かく把握したいところ。実際は最大60Aとはしないだろうけども。



2022/05/08

ラズパイとラズピコで分電盤センサを作成②(SW最終形)

続いて、SWの最終形はこんなかんじ。

CT2DB.py(ラズパイ側常時起動プログラム)

import time
import datetime
import MySQLdb
from datetime import datetime as dt

import smtplib
import ssl
from email import encoders
from email import message
from email.mime import multipart
from email.mime import text

import serial

#ttyACM0はタイミングによって変わる可能性あり
ser = serial.Serial('/dev/ttyACM0', 115200)

filename = "ログファイル"

#メールを大量に送信しないようにするためのフラグ
sendmail = 0

from subprocess import getoutput

def _update():
    # ADCの電圧を取得する(USBより)
    str_serial = ""
    str_serial = ser.readline()
    while ser.in_waiting:
        str_serial = str_serial + ser.readline()

    #ラズピコからのテキストデータを整形する
    str_serial = str(str_serial).replace('b', '').replace('\\r\\n', '').replace('\'', '')
    nowdatastr = str(str_serial)
    str_list = str(str_serial).split(',')

    #データべースへの書き込み
    connector = MySQLdb.connect(host="localhost", db="log_current", user="root", passwd="パスワード", charset="utf8")
    cursor = connector.cursor()
    sql = u"insert into current values(now(), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (str_list[0] , str_list[1] , str_list[2] , str_list[3] , str_list[4] , str_list[5] , str_list[6] , str_list[7] , str_list[8]>    #print(sql)
    try:
        cursor.execute(sql)
    except MySQLdb.Error as e:
        print(e)
        nowtime = dt.now()
        nowtimestr = nowtime.strftime('%Y/%m/%d %H:%M:%S')
        fileobj = open(file, 'a', encoding='UTF-8')
        fileobj.write(nowtimestr)
        fileobj.write(e)
        fileobj.close()
    connector.commit()
    cursor.close()

    #画面表示用の整形 0.4A以下の場合はノイズと区別がつかないので( )をつける
    for i in range(10):
        if float(str_list[i]) < 0.4:
            str_list[i] = "(" + str_list[i][:5] + "A)"
        else:
            str_list[i] = str_list[i][:5] + "A"
    str_list[16] = str_list[16][:4]

    #標準出力へ表示
    nowtime = dt.now()
    nowtimestr = nowtime.strftime('%Y/%m/%d %H:%M:%S')
    print(nowtimestr);
    print("CH0:" + str_list[0])
    print("CH1:" + str_list[1])
    print("CH2:" + str_list[2])
    print("Ch3:" + str_list[3])
    print("CH4:" + str_list[4])
    print("CH5:" + str_list[5])
    print("CH6:" + str_list[6])
    print("CH7:" + str_list[7])
    print("CH8:" + str_list[8])
    print("CH9:" + str_list[9])
    print("気温" + str_list[16] + "℃")
    print(" ")

    #ログファイルに現在時刻を書き込む プロセスの稼働監視用
    try:
        file = open(filename, 'w')
        lasttime = file.write(str(time.time()))
    except Exception as e:
        print(e)
    finally:
        file.close()

    #気温センサが70度以上を示した場合、メール送信
    if float(str_list[16]) > 70.0:
        sendAlertMail(1, str_list[16], '')

#メール送信用メソッド
def sendAlertMail(reason, temperature, errorbody):
    global sendmail

    #メール送信は条件を満たしたとき1回のみ行う
    if sendmail == 0:
        smtp_server_host = '送信メールサーバ'
        smtp_server_port = 送信メールサーバのポート番号

        account = 'アカウント名'
        password = 'パスワード'
        from_email = 'メールアドレス'

	msg = multipart.MIMEMultipart()
        msg["Subject"] = "ラズピコ:通報メール"
        msg["To"] = "メールアドレス"
        msg["From"] = "メールアドレス"
        if reason == 0:
            msgstr = "ラズピコが停止しました" + str(temperature) + "℃ " + errorbody
        elif reason == 1:
            msgstr = "高温です " + str(temperature) + "℃ " + errorbody
        elif reason == 999:
            msgstr = "正常です " + str(temperature) + "℃ " + errorbody
        else:
            msgstr = "不明な事象が起こっています " + str(temperature) + "℃ " + errorbody
        msg.attach(text.MIMEText(msgstr, 'plain', 'utf-8'))

        server = smtplib.SMTP(smtp_server_host, smtp_server_port)
        server.ehlo()
        server.starttls()
        server.ehlo()
        server.login(account, password)
        server.send_message(msg)
        server.close()
        sendmail = 1
        print("!!Send Mail!!")

def main():

    while True:
        _update()

if __name__ == '__main__':
    main()

CT_mesurement.py(ラズピコ内のプログラム)

※一部HTML表示が崩れるため、全角文字を使っている部分あり

import machine
import utime

from machine import Pin, SPI

#動作している間はオンボードLEDを点滅させる
led_onboard = machine.Pin(25, machine.Pin.OUT)

#ラズピコ内蔵の温度センサを利用
sensor_temp = machine.ADC(4)
conversation_factor = 3.3 / (65535)

#CSピンを使うことで、1つのSPIバスで2つのADCを制御
cs1 = machine.Pin(5, machine.Pin.OUT)
cs2 = machine.Pin(14, machine.Pin.OUT)
cs1.value(1)
cs2.value(1)

#SPIの初期化
spi = SPI(0, baudrate=300000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=Pin(2), mosi=Pin(3), miso=Pin(4))

ite1 = 0
ite2 = 0

Vref = 2.447 #TL431の基準電圧(テスタ測定値)
Resistance = [42.2, 41.8, 50.5, 41.9, 41.9, 42.1, 42.0, 41.9, 50.8, 43.2, 43.8, 50.2, 50, 50, 50, 50] #各chの抵抗値(テスタ測定値)
sampling_count = 10000 #RMSをとるサンプリング数
moving_avg = 3 #移動平均区間
CT_ratio = [1866.662, 1792.035, 2238.42, 1870.499, 1865.004, 1849.13, 1781.08, 1849.199, 2231.249, 1842.651, 2000, 2000, 2000, 2000, 2000, 2000] #chごとの計測結果の補正
bias_voltage_bit10 = [445.5, 445.3, 445.8, 445.5, 445.7, 445.0, 445.1, 444.9, 445.5, 446.9, 445.9, 446.0, 0, 0, 0, 0] #各chのバイアス電圧の初期値
prev_bias_voltage_bit10 = []

CHNs = [0, 1, 2, 3, 4, 5, 6, 7]

#10bit ADC MCP3008の場合
read_buf = bytearray(3)
write_buf_mcp3008 = []
for i in CHNs:
    write_buf_mcp3008.append(bytearray([0b00000001,
    	(0b00001000+CHNs[i]) << 4,
        0b00000000]))

for i in range(16):
    prev_bias_voltage_bit10.append([])
    for j in range(moving_avg):
        prev_bias_voltage_bit10[i].append(445)

adc_rawdata_sum = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
avg_voltage = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
avg_current = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

bit10_sum = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

while True: 
    for i in CHNs:
        #ch0~7に対する処理
        cs1.value(0)
        spi.write_readinto(write_buf_mcp3008[i], read_buf)
        cs1.value(1)
        bit10_0 = ((read_buf[1] アンド 0b00000011) << 8) + read_buf[2]
        adc_rawdata_sum[i] += ((((bit10_0 - bias_voltage_bit10[i])/1023)*Vref)**2) #ADCが読み取った値を瞬時電圧に変換し、2乗したものを加算
        bit10_sum[i] += bit10_0
        
        #ch8~15に対する処理
        cs2.value(0)
        spi.write_readinto(write_buf_mcp3008[i], read_buf)
        cs2.value(1)
        bit10_0 = ((read_buf[1] アンド 0b00000011) << 8) + read_buf[2]
        adc_rawdata_sum[i+8] += ((((bit10_0 - bias_voltage_bit10[i+8])/1023)*Vref)**2)
        bit10_sum[i+8] += bit10_0        
    
    ite1 = ite1 + 1
    
    if ite1 > sampling_count - 1:
        for i in CHNs:
            #ch0~7に対する処理
            avg_voltage[i] = (adc_rawdata_sum[i] / sampling_count) ** 0.5 #瞬時電圧の2乗の積算値から平均電圧を計算し、さらに平方根を計算(RMS)
            avg_current[i] = (avg_voltage[i] / Resistance[i]) * CT_ratio[i] #電圧RMSから電流RMSを計算
            adc_rawdata_sum[i] = 0
            #バイアス電圧の移動平均処理
            for j in range(1,moving_avg):
                prev_bias_voltage_bit10[i][j-1] = prev_bias_voltage_bit10[i][j]
            prev_bias_voltage_bit10[i][moving_avg-1] = bit10_sum[i] / sampling_count
            bias_voltage_bit10[i] = sum(prev_bias_voltage_bit10[i]) / len(prev_bias_voltage_bit10[i])
            bit10_sum[i] = 0
            
            #ch8~15に対する処理
            avg_voltage[i+8] = (adc_rawdata_sum[i+8] / sampling_count) ** 0.5
            avg_current[i+8] = (avg_voltage[i+8] / Resistance[i+8]) * CT_ratio[i+8]
            adc_rawdata_sum[i+8] = 0
            for j in range(1,moving_avg):
                prev_bias_voltage_bit10[i+8][j-1] = prev_bias_voltage_bit10[i+8][j]
            prev_bias_voltage_bit10[i+8][moving_avg-1] = bit10_sum[i+8] / sampling_count
            bias_voltage_bit10[i+8] = sum(prev_bias_voltage_bit10[i+8]) / len(prev_bias_voltage_bit10[i+8])
            bit10_sum[i+8] = 0

        print(avg_current) #ラズパイへ各chの電流値をシリアル送信
        ite2 = ite2 + 1
        ite1 = 0
        led_onboard.toggle()
        
        reading_temp = sensor_temp.read_u16() * conversation_factor #周囲温度を計算
        temperature = 27 - (reading_temp - 0.706) / 0.001721

spi.deinit()

2022/04/22

ラズパイとラズピコで分電盤センサを作成①(HW最終形)

ラズパイを使って家の電気使用量を調べたいーーー

ラズパイを使って何をするかを考えていた時に、割と初期からその想いはあったが、アイデアを実行に移すのは結構大変だった。半年ほどかけて勉強し、分電盤センサを作成したので、記録として残しておく。まずは出来上がった最終形を載せる。

やりたいこと:
家の分電盤に電流センサを仕込み、どの家電がいつどのくらい電気を使っているかを分かるようにする。主幹ブレーカーではなく、枝のブレーカー単位で測れるようにする。
→節電や、将来の電気プラン変更、太陽光発電の導入(?)時に参考情報として使いたい。

感想:
ブレッドボードでLチカ、とは違い、実際に使えるものを作ろうとするとこんなに苦労するのか、と思った。前半の勉強・実験パートもまあまあ大変だったが、後半の詳細設計・実装になると、モノ作りの経験がゼロのため、発想が出てこない。ネット上には要素実験、PoCをして終了、みたいな記事ばかりで、「で、実際作るときどうするの?」みたいな答えは少なかった。数少ないネット上の作例などを参考にしながらひたすら思考実験。部品を買うときも、実装するときも、失敗=即追加出費になるのも怖かった。こればかりは、経験を積まないとダメなんだろうなあ。

できあがった全体概要図はこのようなかんじ。


回路図はこう。

完成写真ものせる。
・電流センサ

・電流電圧変換基板

・ラズピコ基板

・分電盤施工後(汚い・・・)

使用したハードウェア
  • Raspberry Pi 3A+ ×1
  • Raspberry Pi Pico ×1
  • MCP3008 ×2
  • KCT-6 ×10
  • TL431シャントレギュレータ ×1
  • カーボン抵抗 多数
  • 積層セラミックコンデンサ 10μF 25V ×10
  • C型ユニバーサル基板 ×2
  • C型ユニバーサル基板用アクリル板 ×4
  • 樹脂ナット、スペーサ
  • 対熱ビニル電線 AWG22
  • UEW線
  • XHコネクタセット ×12
  • L型ピンソケット ×28
  • コネクタ付きケーブル
  • USBケーブル
  • アダプタ
ラズパイは除くとしても、トータル5000円以上かかっている。勉強とか実験で無駄な部品も買ったので、実際は10000円以上・・まあ、趣味と実益を兼ねていてかなり勉強にもなったので、安い方だろう。

ソフトウェア周りは次回。




2022/03/13

スクロールができなくなったトラックボールの分解(エレコム M-HT1URBK)

 エレコムのトラックボール M-HT1URBK を愛用していたが、ある日突然スクロールホイールが効かなくなってしまった。普段はホイールを回すたびにクリック感があるのだが、それもなくなり、完全に自由に回るようになってしまった。ドライバやユーティリティを更新してみたが、なおらないので、マウスを分解してみることにした。

【トラックボール分解】ELECOM HUGEの動画がとても参考になった。

このトラックボールはトルクスねじで止められている製品もあるようだが、自分のものは運よくプラスねじだった。

スクロールホイールまわりのみ分解を進める。



ホイール周りにゴミ等はみあたらない。


ここまできて原因判明。
ホイールの軸が折れている。。
軸が折れているので、ロータリーエンコーダに回転が伝わらないのが原因。
ロータリーエンコーダに六角レンチを入れて回してみると、ちゃんとクリック感があった。

折れた軸の部分を接着剤でつけようとしたのだが、軸が細すぎるのか素材と接着剤の相性が悪いのか、つけられなかった。スクロール以外は問題なかったので、しばらくつかってみたが、スクロールがないというのはとても不便だった。残念ながらこのトラックボールは処分行き。。

それにしても、スクロールホイールの軸はとても細い印象。こんなんでよくすぐ折れないな~。このホイールは2,3年はもった。

ラズパイとラズピコで分電盤センサを作成(14)

回路の実装図作成に取り掛かろうと思ったが、ここで一つ寄り道。 ADCへの入力前に交流電圧に対して直流バイアスをかける手段として、オペアンプを使う方法が出てきた。 アナデジ太郎の回路設計 というか、バイアスをかけたい場合、この方法の方がメジャー・・? と思ったのだが、オペアンプは以...