【概要】
パソコンからLEDの明るさを調整できるプログラムをpythonで作ってみた。
python(のtkinter)に用意されているscaleを使用して、調整できるようにした。
【プログラム 動作概要】
<1> パソコンでLEDの明るさを 0%~100% まで調整する。
<2> 明るさ調整は、マイコンでダイナミック点灯制御しているLEDのデューティ比を
以下の範囲で変化させることで行う。
・パソコン: 0~65535
・LED(タイマーレジスタ調整幅): 0~0xFFFF (=65535(Dec))
【プログラム概要】 ソースリストを最下段に添付
パソコン側アプリ ソースリスト概要
Line 3-8 必要ライブラリの読み込み
Line 10-15 メインウィンドウ(root_window)の作成
Line 18-22 スケールの外枠の作成
Line 24-120 Serial_com() 関数定義
(1) シリアルポート定義とオープン
(2) スケール値の読み込みと加工
(3) シリアル通信処理 3バイト送信+1バイト受信
(4) 受信データの判定
(5) シリアルポートのクローズ
(6) 通信データと通信結果の表示
→ 正常受信なら本関数処理を終了
(7) 受信値が期待値と異なる場合、(2)~(4)を10回繰り返す。
(8) 10回のリトライで期待値が得られない場合、本関数処理を終了
Line 122-125 初期値設定
Line 127-137 スケールの作成 と Serial_com()関数の呼び出し
Line 139-145 リトライボタンの作成 と Serial_com()関数の呼び出し
Line 147 メインウインドウの表示更新
【tkinter.Scale と ttk.Scale との違い】
仕上がりが下図の様に異なっており、個人的には"tkinter"が好みなので、
こちらを使用した。同様にButtonも、tkinterとttkで異なるデザインになる。
左記ソースで設定した内容は、
(1) スケールを配置する枠を設定
(2) スケールのタブ位置を示す数値を入れる変数名を指定
※ 今回はその変数名をvalと設定。
※ タブをマウスで移動させる度に、タブ位置
(数値)が"±1"刻みにvalにセットされる。
(3) スケールは、縦移動させるタイプ(VERTICAL)に設定。
※ 横スライドならHORIZONTAL
(4) 縦の長さを300に設定
(5) タブ位置の最大値を65535に設定
※ PWMの最大設定値が、0xFFFF なので、
その10進数値を設定。
※ from_のアンダーバーの付け忘れに注意。
(6) タブ位置の最小値を0に設定
※ PWMの最小設定値が、0 なので、その数値を設定
(7) タブを移動させた時に実行したい関数を設定
※ s:Serial~の 最初の"s"は、関数Serial_com()の
先頭文字とした。 これは任意に設定できる。
今回のプログラム作成と評価で、以下の内容を理解した。
【scale値の使用方法】
今回のプログラムでは、タブ移動で得られる数値(val値)を 関数(Serial_com)内で加工してUART出力する。
valの値を読みたい/加工したい場合は、get()で読み出して別の変数(val2)に代入し、
その値(val2)を使用する。
例 val2 = val → ×
val2 = val.get() → 〇
上記でval2に取り出した後、最大で65535(0xFFFF)となるデータを、上位1バイトと下位1バイトに分割し、
1バイトずつUART送信する。
【2バイトデータのUART送信】
<ソース抜粋> 例) 0x1234 -> 0x12 (上位1バイト) と 0x34(下位1バイト) に分割して送信
data_upper = int(val2 / 256) # 上位1バイト取り出し
data_lower = val2 - data_upper * 256 # 下位1バイト取り出し
マイコン側のc言語の場合、上記データをマイコンの送信レジスタにセットするだけで良いが、
pythonの場合、上記データ(数値)を以下のようにbytes型に変換しないとUART送信できない。
<ソース抜粋>
serial_data_upper = data_upper.to_bytes(1,'big')
# 上位1バイトの整数を(バイナリ)文字列に変換。
serial_data_lower = data_lower.to_bytes(1,'big')
# 下位1バイトの整数を(バイナリ)文字列に変換。
【相手側のUART受信待ち】
パソコンからUARTデータを1バイト送信する毎に、(相手の受信完了)待ち時間を設けた。
この待ち時間が無いと、パソコンから次のデータ送信が速すぎて?、マイコン側の取りこぼしが多くなった。
-> 結果として送ったデータが反映されない。
-> ビット単位でデータ化けが起きるのではなく、バイト単位でズレて受信してしまう。
<ソース抜粋>
sleep_val = 0.005 # UART受信待機時間 5mS
time.sleep(sleep_val) # 一定時間(受信側デバイスが1バイト受信するまで)待機
合計値とXOR値をマイコン側が計算するタイミングでは、待ち時間を更に長くした。
<ソース抜粋>
time.sleep(sleep_val+0.03)
# 一定時間(受信側デバイスが1バイト受信するまで)30mS長めに待機(待機時間35mS)
パソコンと組み込みマイコンとのUART通信は、データの送受が失敗しやすいように思える。
パソコン側で、5ms、35mSの待機時間を設けているが、マイコン側にmS単位を要する処理はない。
パソコン側で他のアプリを立ち上げていると、データ通信に失敗することが増える。
【UART受信データの比較方法】
pythonでUART受信してコードチェックする場合、以下の処理でコード比較が行える。
check_code = int.from_bytes(ser.read(1), byteorder='big')
if check_code == 0xa5:
受信データが0xa5であった場合、デバッグするためにprint文で、check_code の値を
表示(print(check_code))すると 165 と表示される。(0xa5の10進数値)
そこで、10進数値(165)を16進数値(0xa5)に変換しようとして
check_code = hex(int.from_bytes(ser.read(1), byteorder='big'))
としてから、if文でコード比較すると、期待値(0xa5)と一致しない。
上記の場合は、hex()変換せずに、直接比較する。 (165 = 0xa5)
pythonの場合、hex()で変換した結果は数値ではなくなる。
pythonは、intもhexもbytesも全て異なるデータ型になる。
- # -*- coding: utf-8 -*-
- import binascii
- import serial
- import time
- import tkinter
- from tkinter import *
- from tkinter import ttk
- # 表示ウィンドウの作成
- root_window = tkinter.Tk()
- root_window.title('Scale')
- root_window.geometry("300x350")
- root_window.columnconfigure(0, weight=1)
- root_window.rowconfigure(0, weight=1)
- # Frame
- frame = ttk.Frame(root_window, padding=10,relief="ridge")
- frame.grid(sticky=(N, W, S, E))
- frame.columnconfigure(0, weight=1)
- frame.rowconfigure(0, weight=1)
- frame.propagate(False)
- # スライダー値を整数に変換して、シリアルポートへ出力する関数
- def Serial_com():
- sleep_val = 0.005 # UART受信待機時間 5mS
- check_code = 0x00 # チェックコード初期値セット
- retry_count = 0 # リトライ回数クリア
- val2 = val.get() # スライダー値を加工したい場合、val2を使用する。
- val3 = IntVar() # val2(整数)変換値 格納用
- val4 = StringVar() # 16進文字列 格納用
- val5 = StringVar() # 16進文字列 格納用
- val6 = StringVar() # 受信結果 格納用
- data_upper = int(val2 / 256) # val2の上位1バイト取り出し
- data_lower = val2 - data_upper * 256 # val2の下位1バイト取り出し
- culc_data = ((data_upper+data_lower) & 0xff) ^ data_lower # 上位1バイトと下位1バイトのサム値の下位1バイト と val2の下位1バイト のXOR計算
- print(hex(culc_data)) # debug
- serial_data_upper = data_upper.to_bytes(1,'big') # 上位1バイトの整数を(バイナリ)文字列に変換。
- serial_data_lower = data_lower.to_bytes(1,'big') # 下位1バイトの整数を(バイナリ)文字列に変換。
- serial_culc_data = culc_data.to_bytes(1,'big') # チェックデータ(整数)を(バイナリ)文字列に変換。
- # シリアルポートの定義とオープン
- 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,
- )
- while True: # スライダー値を繰り返して送信
- retry_count+=1 # リトライ回数インクリメント
- #シリアルポートのリセット
- ser.reset_input_buffer() # 受信バッファクリア
- ser.reset_output_buffer() # 送信バッファクリア
- # UART送信 上位1バイト + 下位1バイト + チェックデータ1バイト
- ser.write(serial_data_upper) # 上位1バイトをUART送信
- time.sleep(sleep_val) # 一定時間(受信側デバイスが1バイト受信するまで)待機
- ser.write(serial_data_lower) # 下位1バイトをUART送信
- time.sleep(sleep_val) # 一定時間(受信側デバイスが1バイト受信するまで)待機
- ser.write(serial_culc_data) # チェックデータをUART送信
- time.sleep(sleep_val+0.03) # 一定時間(受信側デバイスが1バイト受信するまで)30mS長めに待機(待機時間35mS)
- while ser.in_waiting == 0: # シリアル受信するまで待機
- if retry_count > 10: # 受信待ちの繰り返しが10回を超えた場合、受信待機を終了する。
- break # 受信待機を抜ける。
- retry_count+=1 # リトライ回数 インクリメント
- if retry_count <= 10: # リトライ回数が10回以内なら、正常コードの受信として受信バッファからデータ転送する。
- check_code = int.from_bytes(ser.read(1), byteorder='big') # 受信した1バイトをbytes型からint型へ変換
- if check_code == 0xa5: # OKコードを受信ならメインループへ戻る。
- val6.set('Data OK')
- fg_color='blue'
- break # メインループへ戻る
- if (retry_count > 10) & (check_code != 0xa5): # 10回以内のリトライでOKコードが受信できない場合は、メインループへ戻る。
- val6.set('Data Error')
- fg_color='red'
- break # メインループへ戻る
- print(' Retry count = ', retry_count) # debug
- ser.close() # シリアルポートを閉じる。
- # テキストボックスの生成 4ヶ
- val3.set(int(str(val2))) # 整数に変換。
- label = tkinter.Label(text = "Original_data (Dec) ")
- label.grid(row=2, column=0, padx=10, sticky=(W))
- text_Val = tkinter.Entry(root_window, textvariable=val3, width=10) # テキストボックスにスライダー値をセット
- text_Val.configure(font=("Arial", 14))
- text_Val.grid(row=2, column=10, padx=10, sticky=(W))
- val4.set(binascii.b2a_hex(serial_data_upper)) # (バイナリ)文字列を16進文字列に変換。
- label = tkinter.Label(text = "Serial_data_upper (Hex)")
- label.grid(row=4, column=0, padx=10, sticky=(W))
- text_Val = tkinter.Entry(root_window, textvariable=val4, width=10) # テキストボックスにUART送信値をセット
- text_Val.configure(font=("Arial", 14))
- text_Val.grid(row=4, column=10, padx=10, sticky=(W))
- val5.set(binascii.b2a_hex(serial_data_lower)) # (バイナリ)文字列を16進文字列に変換。
- label = tkinter.Label(text = "Serial_data_lower (Hex)")
- label.grid(row=6, column=0, padx=10, sticky=(W))
- text_Val = tkinter.Entry(root_window, textvariable=val5, width=10) # テキストボックスにUART送信値をセット
- text_Val.configure(font=("Arial", 14))
- text_Val.grid(row=6, column=10, padx=10, sticky=(W))
- label = tkinter.Label(text = "UART Data check ")
- label.grid(row=8, column=0, padx=10, sticky=(W))
- text_Val = tkinter.Entry(root_window, textvariable=val6, fg=fg_color, width=10) # テキストボックスにUARTデータが正常か異常かセット
- text_Val.configure(font=("Arial", 14))
- text_Val.grid(row=8, column=10, padx=10, sticky=(W))
- # 初期値をセット
- val = IntVar()
- val.set(0)
- Serial_com()
- # スライダー(スケール)の作成
- #sc = ttk.Scale( # (1) (2)とスライダーのデザインが違う
- sc = tkinter.Scale( # (2) (1)とスライダーのデザインが違う
- frame,
- variable=val,
- orient=VERTICAL, #HORIZONTAL,
- length=300,
- from_=65535,
- to=0,
- command=lambda s:Serial_com())
- sc.grid(row=0, column=0, sticky=(N, E, S, W))
- # リトライ用ボタン
- button1 = ttk.Button( #(1) (2)とボタンと形状が違う 幅広
- #button1 = tkinter.Button( #(2) (1)とボタンと形状が違う 幅狭
- root_window,
- text='Retry',
- command=lambda: Serial_com())
- button1.grid(row=10, column=0, padx=5, sticky=(W))
- root_window.mainloop()
コメント