Windows用アプリの作成とパソコンと電子負荷との情報をやり取りする組み込みソフトの作成を行った。
Windows用アプリ: 言語 python CPU:インテル i5-9600KF
マイコンボード用組み込みソフト: 言語 c言語 CPU:ルネサス R8C29 16kB版 (実使用3kB未満)
【 各ソフトウェアの処理内容】
【Windows用アプリ フローチャート】
電圧/電流の測定結果を表示する処理に手間取った。
電圧測定後、一定時間毎にステップ電圧を加算し再度通信と測定をしたら、.placeで表示する。
という処理の繰り返しでは、最終値の表示しかされなかった。(上記表の赤字のループ)
色々、調査/検索した結果、下記2点の対策を行うことで改善できた。(上記表の緑のフロー)
(1).mainloopまで処理を進めないと、Windowsアプリの表示更新は行われない。
(2).mainloopの後に行いたい処理がある場合は、afterメソッドを使用する。
【 組み込みソフト】
R8C29マイコンの各機能(下記)の基本ライブラリは,過去に自作してあったものを流用したので、
メインプログラムの作成のみ行った。
UART(シリアル)/ IICバス / AD変換 / ポート設定 / メインクロック・タイマー
※R8C29の選定理由は、自宅に複数個所有していて、本件の開発環境構築も容易な為。
電源に相当ノイズがのっている(下図)ので、それらの影響を極力抑えるため、
ADポートで電圧を読む際に、下記の処理を行った。
(1) ADポートの値を取り込む。
(2) 500uS待機する。※ノイズが収まっているかもしれないタイミングを狙う。
(3) (1)(2) を50回繰り返す。
(4) 最大値と最小値を破棄する。
(5) 48回の平均値を計算する。
(6) TxDポートから計算結果を出力する。
【ソースリスト】
Windows用pythonソースリスト
- # -*- coding: utf-8 -*-
- ##############################################################################################################################################
- # <目的>
- # 自作の電子負荷装置をパソコンから操作し、自動測定する。
- #
- # <パソコンから操作できる内容>
- # ・指定した電流を本アプリにセットすると電子負荷の制御が開始され、下記3項目の測定と計算を行い、本アプリに結果を表示する。
- # -> 単発測定モード
- # ・目標とする電流(最終値)、ステップ電流値(ステップアップのみ)、ステップ時間を入力すると、その設定に基づき電子負荷装置のパラメータが順次変更され、各ステップ値毎の下記3項目の測定と表示を行う。
- # -> Sweep測定モード
- #
- # -自動測定/計算項目-
- # <1> 電源(入力)電圧 Vpw(V)
- # <2> 負荷装置内の(シャント)抵抗に発生している電圧 Vs(V)
- # <3> 負荷装置内の(シャント)抵抗に流れている電流(シャント抵抗0.5Ωと<2>の結果に基づき計算) Is (A)
- #
- # <電流設定範囲>
- # 0.04A 〜 3A
- # <ステップ電流設定範囲>
- # 0.04A 〜 目標とする電流(最終値)の1/2未満
- # <ステップ時間設定範囲>
- # 0より上、 10秒未満
- #
- # !!!注意事項!!!
- # 電子負荷装置の放熱が貧弱なため、単発/Sweep測定終了後は、速やかに、本アプリの”停止”ボタンを押すこと。 (停止ボタンを押すことで、最低電流値にセットされる)
- #
- # <ハードウェア構成>
- # [PC] -USB - [USB/Serial変換基板] - Serial(TxD/RxD) - [R8C29] - IIC(SCL/SDA) - [電子負荷装置(DigiPOT : MCP4018)]
- # [R8C29]AN10とAN11ポート で 電子負荷装置の電圧2か所を(分圧して)測定。 AD分解能10bit Max 0x03FF (=5V)
- #
- # <シリアル通信内容>
- # PC -> R8C29 : MCP4018 レジスタ設定値 1バイト
- # R8C29 -> PC : 電源電圧AD値 2バイト (?上位1バイト ?下位1バイト)、 シャント電圧AD値 2バイト (?上位1バイト ?下位1バイト) ※?->?->?->?の順で送信される
- #
- # <機能追加予定>
- # データの保存
- # グラフのリアルタイム表示
- ##############################################################################################################################################
- import tkinter
- import serial
- #import datetime # データ保存用
- #import numpy as np # リアルタイムにグラフ表示する機能も追加するとき用
- # グローバル変数の初期値設定
- Stop_mode = 0 #この値は、書き換えない -> 定数 #define Stop_mode 0
- Single_mode = 1 #この値は、書き換えない -> 定数 #define Single_mode 1
- Sweep_mode = 2 #この値は、書き換えない -> 定数 #define Sweep_mode 2
- Sweep_mode_running = 3 #この値は、書き換えない -> 定数 #define Sweep_mode_running 3
- step_time = 0 # 関数"Data_measurement_Sweep()"のみが使用するグローバル変数
- after_ID_Number = None # 関数"Data_measurement_Sweep()"と Stopボタン定義関数 が、使用するグローバル変数
- mode = Stop_mode # グローバル変数
- ##############################################################################################################################################
- # ユーザー入力値のチェック
- def Check_entryCurrent(Current):
- if (Current < 0.04) or (Current > 3): #入力値が、0.04[A]以上で、3[A]以下かチェック
- return False
- Resister_val = 25.6 * Current # -> (128/5) * Current(A) * 1(ohm)
- float_val = Resister_val - int(Resister_val) # 小数点以下の四捨五入の計算
- if float_val<0.5:
- Resister_val = int(Resister_val) # 小数点以下の数値が、0.5未満なら小数点以下を切り捨て(四捨)
- else:
- Resister_val = int(Resister_val)+1 # 小数点以下の数値が、0.5以上なら整数値に1を加算(五入)
- return Resister_val # MCP4018設定値を返す。
- ##############################################################################################################################################
- # シリアル通信処理
- def Serial_com(MCP4018_Resister_Data):
- # シリアルポートの定義とオープン
- ser = serial.Serial(
- port = 'COM4',
- baudrate = 19200, # これより上は、R8C29と通信できない。
- parity = serial.PARITY_NONE,
- bytesize = serial.EIGHTBITS,
- stopbits = serial.STOPBITS_ONE,
- timeout = None,
- xonxoff = 0,
- rtscts = 0,
- )
- #シリアルポートのリセット
- ser.reset_input_buffer() # 受信バッファクリア
- ser.reset_output_buffer() # 送信バッファクリア
- print('測定スタート')
- print('MCP4018_Resister_Data =', MCP4018_Resister_Data) # シリアルデータ読み取りと、結果表示
- ser.write(MCP4018_Resister_Data.to_bytes(1,'big')) # MCP4018へレジスタ値を送信 (10進数データ(Max76=0x4C)をバイト型(1バイト))に変換)
- print("TxD DATA = ", MCP4018_Resister_Data) # debug
- # 電源電圧の読み取り
- ad_serial_2bytes = int.from_bytes(ser.read(), 'big') # 上位1バイト読込
- ad_serial_2bytes_vpw = ad_serial_2bytes * 256 + int.from_bytes(ser.read(), 'big') # 上位1バイト+下位1バイト読込 (Max 5V = 0x03FF(Max))
- # シャント電圧の読み取り
- ad_serial_2bytes = int.from_bytes(ser.read(), 'big') # 上位1バイト読込
- ad_serial_2bytes = ad_serial_2bytes * 256 + int.from_bytes(ser.read(), 'big') # 上位1バイト+下位1バイト読込 (Max 3A * 1ohm = 3V -> 0x0613 (Max))
- ad_serial_2bytes = ad_serial_2bytes * 0.00488 # = ad_serial_2byte * 5 / 1024
- vshunt.set(int(ad_serial_2bytes * 100) / 100) # 1/1000以下を切り捨てた結果をテキストボックスへセット
- print ('Vshunt(AN11) Volt =', ad_serial_2bytes) # debug
- # シャント電流の計算
- isht = int(ad_serial_2bytes * 100) / 100 # 1/1000以下を切り捨てた結果をテキストボックスへセット。 電圧(ad_serial_2bytes) / 抵抗(1ohm)
- ishunt.set(isht)
- print ('Ishunt (Vshunt / 1) A = ', ad_serial_2bytes ) # debug
- # 各電流毎のFET(2SJ334)のVds補正値 ※実測から設定
- Vds = 0
- if isht <= 1: Vds = 0.05
- elif 1 < isht and isht <= 2: Vds = 0.1
- elif 2 < isht and isht <= 3: Vds = 0.153
- # Vds補正後の電源電圧の表示をセット
- ad_serial_2bytes_vpw = 0.01953 * ad_serial_2bytes_vpw + Vds # = ad_serial_2byte * 5 * 4 / 1024 + Vds (電源電圧20V(Max)の場合、読み取り電圧(Max)は1/4の5V)
- vpower.set(int(ad_serial_2bytes_vpw * 100) / 100) # 1/1000以下を切り捨てた結果をテキストボックスへセット
- print ('Vin(AN10) Volt =', ad_serial_2bytes_vpw) # debug
- # 電源電圧/シャント電圧/シャント電流 の表示
- v_power.set(vpower.get()) # 測定値Vpwをテキストボックスへセット
- v_shunt.set(vshunt.get()) # 測定値Vshuntをテキストボックスへセット
- i_shunt.set(ishunt.get()) # 計算結果Ishuntをテキストボックスへセット
- ser.close() # シリアルポートを閉じる。
- print('Data get!! ') # debug
- print('\n') #debug
- return True
- ##############################################################################################################################################
- # 単発測定
- def Data_measurement():
- global mode
- current_data = entry_Current.get() # ユーザー入力値を変数に格納
- dec_data = Check_entryCurrent(current_data) # ユーザー入力値をMCP4018設定値へ変更
- if dec_data == False:
- mode = Stop_mode
- return
- serial_flg = Serial_com(dec_data) # シリアル通信/測定/表示の更新データセット
- while serial_flg == False:{} # シリアル通信/測定/表示の更新データセット が完了するまで待機
- mode = Stop_mode # Stopモードにセット ※注) 電流は最終値のまま流れ続ける。
- ##############################################################################################################################################
- # Sweep測定
- def Data_measurement_Sweep(current_data, step_current, max_count):
- global mode
- global step_time
- global after_ID_Number
- # Sweepモード1回目のみ実行する処理
- if mode == Sweep_mode:
- # Sweepモードの初回だけ、ユーザー入力値(目標電流、ステップ電流、ステップ時間)を取り込み、ローカル変数とグローバル変数に初期値として代入
- current_data = entry_Current.get() # 電流値を取り込んで変数に格納(値のチェック後は、未使用)
- dec_data = Check_entryCurrent(current_data) # ユーザー入力値をMCP4018設定値へ変更
- if dec_data == False:
- mode = Stop_mode
- return
- step_current = entry_Stepcurrent.get() # ユーザーの入力した電流増分を変数に格納
- step_time = int(entry_Steptime.get()*1000) # ステップ時間(S)を(mS)に換算し、整数型へ変換しグローバル変数へ格納
- if step_current == 0 or step_current*2 > current_data: # ステップ電流が範囲内か確認
- mode = Stop_mode
- return
- if step_time <= 0 or step_time > 10000: # ステップ時間が範囲内(0mSより上、 10000mS未満)か確認
- mode = Stop_mode
- return
- max_count = int(current_data/step_current) # 測定を繰り返す回数を計算
- current_data = 0 # 電流初期値をセット。 NG判定値だが、0A状態を指定時間作る為にセットする。
- # Sweepモード2回目以降も実行する処理
- mode = Sweep_mode_running # Sweepモード継続中 にモードを変更
- dec_data = Check_entryCurrent(current_data) # ステップ電流を加算した後の値をMCP4018設定値に変換
- serial_flg = Serial_com(dec_data) # シリアル通信/測定/表示更新データセット
- while serial_flg == False:{} # シリアル通信/測定/表示の更新データセット が完了するまで待機
- current_data += step_current # ステップ電流値を加算
- print('あと何回繰り返す? = ', max_count) #debug
- print('\n') #debug
-
- if max_count == 0:
- # 最大繰り返し回数に到達したら、afterメソッドの宣言を取り消す。
- root_window.after_cancel(after_ID_Number)
- mode = Stop_mode # Stopモードにセット ※注) 電流は最終値のまま流れ続ける。
- else:
- max_count -= 1
- # ステップ時間経過したら本関数を再度実行することを宣言し、root_windowループに処理を返す。windowループに返す事で、テキストラベルの表示が更新される。
- after_ID_Number = root_window.after(step_time, lambda:Data_measurement_Sweep(current_data, step_current, max_count))
- ##############################################################################################################################################
- # Startボタンの定義
- def click_Start_button():
- global mode
- if bln.get() == False and mode == Stop_mode: # SweepチェックがOFFで STOPモードの場合 → 単発測定
- mode = Single_mode
- Data_measurement() # 単発測定処理開始
- elif bln.get() == True and mode == Stop_mode: # SweepチェックがONで STOPモードの場合 → Sweep測定
- mode = Sweep_mode
- Data_measurement_Sweep(0,0,0) # Sweep測定処理開始
- else:{}
- # 何もしない。
- ##############################################################################################################################################
- # Stopボタンの定義
- def click_Stop_button():
- global mode
- if mode == Sweep_mode_running:
- root_window.after_cancel(after_ID_Number) # Sweepモードのafterメソッドの宣言を取り消す。
- mode = Stop_mode # Stopモードにセット
- serial_flg = Serial_com(0) # 最小電流値をセットし、シリアル通信/測定/表示の更新データセット
- while serial_flg == False:{} # シリアル通信/測定/表示の更新データセット が完了するまで待機
- ##############################################################################################################################################
- # Saveボタンの定義
- def click_Save_button():
- global mode
- #########################未作成#########################
- ##############################################################################################################################################
- ############################################################### ここからメイン ####################################################################
- ##############################################################################################################################################
- # 表示ウィンドウの作成
- root_window = tkinter.Tk()
- root_window.title("Electric Load Tester")
- root_window.geometry("300x350")
- #ボタンの作成 3ヶ
- Run_button0=tkinter.Button(root_window, width=5, text="開始", command=lambda:click_Start_button())
- Run_button0.place(x=30, y=10)
- Pause_button0=tkinter.Button(root_window, width=5, text="停止", command=lambda:click_Stop_button())
- Pause_button0.place(x=100, y=10)
- Pause_button0=tkinter.Button(root_window, width=5, text="保存", command=lambda:click_Save_button())
- Pause_button0.place(x=170, y=10)
- #ユーザー入力欄の作成と表示
- bln = tkinter.BooleanVar()
- entry_Current = tkinter.DoubleVar()
- entry_Stepcurrent = tkinter.DoubleVar()
- entry_Steptime = tkinter.DoubleVar()
- label = tkinter.Label(text = "負荷電流 Io A (範囲:0.04〜3.0)")
- label.place(x=10, y=70)
- input_current = tkinter.Entry(root_window, textvariable = entry_Current, width = 5) # ユーザー入力欄作成(入力値はentry_Currentに格納)
- input_current.grid(padx = 110, pady = 70) # ユーザー入力欄表示
- # Sweepモード用チェックボックスの作成と表示
- check_box = tkinter.Checkbutton(root_window, variable=bln, text = "Sweep Mode")
- bln.set(False)
- check_box.place(x=10, y=120)
- # Sweep設定用テキストボックスの作成と表示
- label = tkinter.Label(text = "ステップ電流 A (Min : 0.04 A)")
- label.place(x=10, y=140)
- input_StepCurrent = tkinter.Entry(root_window, textvariable = entry_Stepcurrent, width = 5) # ステップ電流ユーザー入力欄作成(入力値はentry_Stepcurrentに格納)
- input_StepCurrent.place(x = 110, y=140) # ユーザー入力欄表示
- label = tkinter.Label(text = "ステップ時間 秒 (Max:10 秒)")
- label.place(x=10, y=170)
- input_StepTime = tkinter.Entry(root_window, textvariable = entry_Steptime, width = 5) # ステップ時間ユーザー入力欄作成(入力値はentry_Steptimeに格納)
- input_StepTime.place(x = 110, y=170) # ユーザー入力欄表示
- # 入力欄の初期入力値を消去
- input_current.delete(0, tkinter.END)
- input_StepCurrent.delete(0, tkinter.END)
- input_StepTime.delete(0, tkinter.END)
- # 測定値表示用テキストボックスの作成と表示
- v_power = tkinter.DoubleVar()
- v_shunt = tkinter.DoubleVar()
- i_shunt = tkinter.DoubleVar()
- vpower = tkinter.DoubleVar()
- vshunt = tkinter.DoubleVar()
- ishunt = tkinter.DoubleVar()
- label = tkinter.Label(text = "入力電圧 Vpw V")
- label.place(x=10, y=230)
- text_Vpower = tkinter.Entry(root_window, textvariable=v_power, width=10) # 測定値Vpw表示欄作成
- text_Vpower.place(x = 110, y=230) # 測定値Vpw表示
- label = tkinter.Label(text = "シャント電圧 Vs V")
- label.place(x=10, y=260)
- text_Vshunt = tkinter.Entry(root_window, textvariable=v_shunt, width=10) # 測定値Vs表示欄作成
- text_Vshunt.place(x = 110, y=260) # 測定値Vs表示
- label = tkinter.Label(text = "シャント電流 Is A")
- label.place(x=10, y=290)
- text_Ishunt = tkinter.Entry(root_window, textvariable=i_shunt, width=10) # 測定値Is表示欄作成
- text_Ishunt.place(x = 110, y=290) # 測定値Is表示
- label = tkinter.Label(text = "Is = Vs / 1Ω")
- label.place(x=110, y=311)
- # MCP4018に対する初期設定
- serial_flg = Serial_com(0) # 最小電流値をセットし、シリアル通信/測定/表示の更新データセット
- while serial_flg == False:{} # シリアル通信/測定/表示の更新データセット が完了するまで待機
- print('MCP4018 is Ready!!')
- print('\n') #debug
- # 繰り返し実行
- root_window.mainloop() # ここで、表示が更新される。
R8C29用メインプログラム
- /////////////////////////////////////////////////////////////////////////////////////////////////////////
- // File名 :UART_Monitor
- // CPU型名 :R8C29
- // 作成日 :2021/02/24
- // 処理内容 :
- // PCからのUART信号をRxD0で受信し、そのデータをIICバスでMCP4018へ送信する。
- // AN10とAN11ポートで電圧を読み取り(それぞれ2バイトデータ)、シリアルでPCへ出力する。
- //
- // AD変換50回(500uS待機含む)にかかる時間は25mS 実測約26mS
- // = ( 33 / (20MHz / 4) + 500uS )* 50 (33 = サンプルホールド有り、10bitモード時)
-
- // 使用するPort :
- // p1_0-p1_3 :AD (AN8-AN11)
- // p4_2 :VREF (AD用) -> 5Vラインに接続すること。
- // p1_4,p1_5 :UART0
- // p3_4,p3_5 :IIC
- ////////////////////////////////////////////////////////////////////////////////////////////////////////
- #include "sfr_r829.h"
- #include <Main_clock_init.h>
- #include <wait_x.h>
- #include <uart0_init.c>
- #include <resi_init.h> // レジスタ初期化ファイル
- #include <iic.h> // IICバス初期設定ファイル
- #include <AD_init.h> // ADレジスタ初期設定ファイル
- // 関数定義
- void main(void);
- unsigned int ad_read(unsigned char ad_port);
- void uart_trans(unsigned int ad_dat);
- void debug_port(void);
- // マクロ定義
- #define MCP4018 0x2f // MCP4018 device address
- #define RESET 0xff // MCP4018 RESET command
- #define bps 19200 // 転送レートを19200bpsに設定 ※これより上は、PCと通信できない。
- #define AN10 0b00000110
- #define AN11 0b00000111
- unsigned char resister_data;
- unsigned int ad_an10, ad_an11;
- unsigned char check_dat; // debug
- //////////////////////////////////////////////////////
- // メイン
- //////////////////////////////////////////////////////
- void main(void)
- {
- Main_clock_init(); // メインクロック初期化
- function_init(); // レジスタ初期化
- iic_init(); // IICバス 初期設定
- uart0_set(bps); // UART0初期設定
- AD_init(); // AD初期設定 (10bitモード、単発、サンプルホールド)
- debug_port(); // デバッグポート設定
- // IICバスの通信速度を250kHzにセット
- iccr1 &= 0b11110000; // 上位4ビットをマスク
- iccr1 |= 0b00001001; // 下位4ビットにf1/80をセット(= 20MHz/80 = 250kHz)
- iic_write(MCP4018, 0, RESET); // MCP4018 ソフトウェアリセット
- iic_write(MCP4018, 0, 0); // シャント電流を最小値にセットする。
-
- while(1){
- u0c1 |= 0b00000100; // UARTO 受信許可 from PC (b2 = 1)
- while(ri_u0c1 == 0); // 1Byte受信完了まで待機
-
- // STOP_BITまで受信したら、u0rbレジスタに受信データを転送し、ri_u0c1フラグが1(データ有り)へ変化する。
- // 受信バッファレジスタは 16bit単位で読み出す事 ※但し本Sourceでは上位8bitは未使用
- resister_data = (unsigned char)u0rb; // 受信バッファレジスタの下位8bitを保存
- u0c1 &= 0b11111011; // UARTO 受信禁止 from PC (b2 = 0)
- iic_write(MCP4018, 0, resister_data); // MCP4018へレジスタ値を出力
- iic_read(MCP4018, 0, 1); // デバッグ用 MCP4018からレジスタ値を読込
- check_dat = iic_rcv_buff[0]; // デバッグ用 MCP4018から読み込んだレジスタ値が一致しているか値を確認
- // ADポートの電圧を測定 (AD変換)し、UART送信する。
- ad_an10 = ad_read(AN10); // AN10ポートの電圧をAD変換
- ad_an11 = ad_read(AN11); // AN11ポートの電圧をAD変換
- uart_trans(ad_an10); // AN10ポートのAD変換値(2バイト)をUART送信する。
- uart_trans(ad_an11); // AN11ポートのAD変換値(2バイト)をUART送信する。
- }
- }
- //////////////////////////////////////////////////////
- // ADポートの電圧を測定する。
- //////////////////////////////////////////////////////
- unsigned int ad_read(unsigned char ad_port)
- {
- unsigned char i, count;
- unsigned int ad_val, ad_sum, ad_max, ad_min;
- count = 50;
- ad_sum = 0;
- // ad_portで指定されたポートの電圧を測定 (AD変換)
- adcon0 &= 0b11111000;
- adcon0 |= ad_port; // AN1xポートを選択
- p1 |=0b10000000; // P1_7 Hi
- for(i = 0; i<=count; i++){
- if (i == 0){
- ad_max = 0;
- ad_min = 0;
- }
- adcon0 |= 0b01000000; // AD変換開始 (b6 = 1)
- while ( adst == 1 ); // AD変換完了(adst=0)まで繰り返し
- ad_val = ad; // AD値取り込み
- adcon0 &= 0b10111111; // AD変換停止 (b6 = 0)
-
- if( ad_val>ad_max )
- ad_max = ad_val; // Max値の取り込み
- if( ad_val<ad_min )
- ad_min = ad_val; // Min値の取り込み
- ad_sum += ad_val;
- wait_x(5); // 500uS待機
- }
- p1 &= 0b01111111; // P1_7 Low AD変換50回(500uS待機含む)にかかる時間は25.3mS 実測約26mS
- ad_val = (ad_sum-(ad_max)-(ad_min)) / (count -2); // count回合計値-max値-min値の平均 / count回-2回(Max値の回とMin値の回 の2回)
- return ad_val;
- }
- //////////////////////////////////////////////////////
- // AD変換値(2バイト)をUART送信する。
- //////////////////////////////////////////////////////
- void uart_trans(unsigned int ad_dat)
- {
- // 測定値(AD値2バイト)をUART送信
- u0c1 |= 0b00000001; // UART送信許可 (b0 =1)
- u0tb = ad_dat >> 8; // 上位1バイトをUART送信
- while(ti_u0c1 == 0); // 送信完了まで待機
- u0tb = ad_dat & 0x00ff; // 下位1バイトをUART送信
- while(ti_u0c1 == 0); // 送信完了まで待機
- u0c1 &= 0b11111110; // UART送信禁止 (b0 =0)
- }
- //////////////////////////////////////////////////////
- // デバッグ用 (処理時間測定用にP1_7を使用する。)
- //////////////////////////////////////////////////////
- void debug_port(void)
- {
- pd1 |= 0b10000000; //P1_7 を出力設定
- p1 &= 0b01111111; //P1_7 をLow出力設定
- }
コメント