python で LED点灯制御

Hardware & Software
Pocket

【概要】
パソコンから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も全て異なるデータ型になる。

 

  1. # -*- coding: utf-8 -*-
  2. import binascii
  3. import serial
  4. import time
  5. import tkinter
  6. from tkinter import *
  7. from tkinter import ttk
  8. # 表示ウィンドウの作成
  9. root_window = tkinter.Tk()
  10. root_window.title('Scale')
  11. root_window.geometry("300x350")
  12. root_window.columnconfigure(0, weight=1)
  13. root_window.rowconfigure(0, weight=1)
  14. # Frame
  15. frame = ttk.Frame(root_window, padding=10,relief="ridge")
  16. frame.grid(sticky=(N, W, S, E))
  17. frame.columnconfigure(0, weight=1)
  18. frame.rowconfigure(0, weight=1)
  19. frame.propagate(False)
  20. # スライダー値を整数に変換して、シリアルポートへ出力する関数
  21. def Serial_com():
  22.   sleep_val = 0.005                # UART受信待機時間 5mS
  23.   check_code = 0x00              # チェックコード初期値セット
  24.   retry_count = 0                # リトライ回数クリア
  25.   val2 = val.get()                  # スライダー値を加工したい場合、val2を使用する。
  26.   val3 = IntVar()                  # val2(整数)変換値 格納用
  27.   val4 = StringVar()              # 16進文字列 格納用
  28.   val5 = StringVar()              # 16進文字列 格納用
  29.   val6 = StringVar()              # 受信結果 格納用
  30.   data_upper = int(val2 / 256)                        # val2の上位1バイト取り出し
  31.   data_lower = val2 - data_upper * 256                  # val2の下位1バイト取り出し
  32.   culc_data = ((data_upper+data_lower) & 0xff) ^ data_lower    # 上位1バイトと下位1バイトのサム値の下位1バイト と val2の下位1バイト のXOR計算
  33.   print(hex(culc_data))            # debug
  34.   serial_data_upper = data_upper.to_bytes(1,'big')          # 上位1バイトの整数を(バイナリ)文字列に変換。
  35.   serial_data_lower = data_lower.to_bytes(1,'big')          # 下位1バイトの整数を(バイナリ)文字列に変換。
  36.   serial_culc_data = culc_data.to_bytes(1,'big')            # チェックデータ(整数)を(バイナリ)文字列に変換。
  37.   # シリアルポートの定義とオープン
  38.   ser = serial.Serial(    
  39.        port = 'COM4',
  40.          baudrate = 19200,          # これより上は、R8C29と通信できない。
  41.            parity = serial.PARITY_NONE,
  42.              bytesize = serial.EIGHTBITS,
  43.               stopbits = serial.STOPBITS_ONE,
  44.            timeout = None,
  45.              xonxoff = 0,
  46.               rtscts = 0,
  47.   )
  48.   while True:              # スライダー値を繰り返して送信
  49.     retry_count+=1        # リトライ回数インクリメント
  50.     #シリアルポートのリセット 
  51.     ser.reset_input_buffer()      # 受信バッファクリア
  52.     ser.reset_output_buffer()    # 送信バッファクリア
  53.     # UART送信    上位1バイト + 下位1バイト + チェックデータ1バイト
  54.     ser.write(serial_data_upper)  # 上位1バイトをUART送信
  55.     time.sleep(sleep_val)        # 一定時間(受信側デバイスが1バイト受信するまで)待機
  56.     ser.write(serial_data_lower)  # 下位1バイトをUART送信
  57.     time.sleep(sleep_val)        # 一定時間(受信側デバイスが1バイト受信するまで)待機
  58.     ser.write(serial_culc_data)    # チェックデータをUART送信
  59.     time.sleep(sleep_val+0.03)    # 一定時間(受信側デバイスが1バイト受信するまで)30mS長めに待機(待機時間35mS)
  60.     while ser.in_waiting == 0:    # シリアル受信するまで待機
  61.       if retry_count > 10:      # 受信待ちの繰り返しが10回を超えた場合、受信待機を終了する。
  62.         break              # 受信待機を抜ける。
  63.       retry_count+=1        # リトライ回数 インクリメント
  64.     if retry_count <= 10:        # リトライ回数が10回以内なら、正常コードの受信として受信バッファからデータ転送する。
  65.       check_code = int.from_bytes(ser.read(1), byteorder='big')    # 受信した1バイトをbytes型からint型へ変換
  66.     if check_code == 0xa5:      # OKコードを受信ならメインループへ戻る。
  67.         val6.set('Data OK')
  68.         fg_color='blue'
  69.         break              # メインループへ戻る
  70.     if (retry_count > 10) & (check_code != 0xa5):    # 10回以内のリトライでOKコードが受信できない場合は、メインループへ戻る。
  71.       val6.set('Data Error')
  72.       fg_color='red'
  73.       break                # メインループへ戻る
  74.     print(' Retry count = ', retry_count)          # debug
  75.   ser.close()                # シリアルポートを閉じる。
  76.   # テキストボックスの生成 4ヶ
  77.   val3.set(int(str(val2)))                    # 整数に変換。
  78.   label = tkinter.Label(text = "Original_data (Dec) ")
  79.   label.grid(row=2, column=0, padx=10, sticky=(W))
  80.   text_Val = tkinter.Entry(root_window, textvariable=val3, width=10)  # テキストボックスにスライダー値をセット
  81.   text_Val.configure(font=("Arial", 14))
  82.   text_Val.grid(row=2, column=10, padx=10, sticky=(W))
  83.   val4.set(binascii.b2a_hex(serial_data_upper))      # (バイナリ)文字列を16進文字列に変換。
  84.   label = tkinter.Label(text = "Serial_data_upper (Hex)")
  85.   label.grid(row=4, column=0, padx=10, sticky=(W))
  86.   text_Val = tkinter.Entry(root_window, textvariable=val4, width=10)  # テキストボックスにUART送信値をセット
  87.   text_Val.configure(font=("Arial", 14))
  88.   text_Val.grid(row=4, column=10, padx=10, sticky=(W))
  89.   val5.set(binascii.b2a_hex(serial_data_lower))      # (バイナリ)文字列を16進文字列に変換。
  90.   label = tkinter.Label(text = "Serial_data_lower (Hex)")
  91.   label.grid(row=6, column=0, padx=10, sticky=(W))
  92.   text_Val = tkinter.Entry(root_window, textvariable=val5, width=10)  # テキストボックスにUART送信値をセット
  93.   text_Val.configure(font=("Arial", 14))
  94.   text_Val.grid(row=6, column=10, padx=10, sticky=(W))
  95.   label = tkinter.Label(text = "UART Data check ")
  96.   label.grid(row=8, column=0, padx=10, sticky=(W))
  97.   text_Val = tkinter.Entry(root_window, textvariable=val6, fg=fg_color, width=10)  # テキストボックスにUARTデータが正常か異常かセット
  98.   text_Val.configure(font=("Arial", 14))
  99.   text_Val.grid(row=8, column=10, padx=10, sticky=(W))
  100. # 初期値をセット
  101. val = IntVar()
  102. val.set(0)
  103. Serial_com()
  104. # スライダー(スケール)の作成
  105. #sc = ttk.Scale(      # (1)  (2)とスライダーのデザインが違う
  106. sc = tkinter.Scale(    # (2)  (1)とスライダーのデザインが違う
  107.     frame,
  108.     variable=val,
  109.     orient=VERTICAL,  #HORIZONTAL,
  110.     length=300,
  111.     from_=65535,
  112.     to=0,
  113.     command=lambda s:Serial_com())
  114. sc.grid(row=0, column=0, sticky=(N, E, S, W))
  115. # リトライ用ボタン
  116. button1 = ttk.Button(    #(1)  (2)とボタンと形状が違う 幅広
  117. #button1 = tkinter.Button(  #(2)  (1)とボタンと形状が違う 幅狭
  118.     root_window,
  119.     text='Retry',
  120.     command=lambda: Serial_com())
  121. button1.grid(row=10, column=0, padx=5, sticky=(W))
  122. root_window.mainloop()
Hardware & Softwarepython
スポンサーリンク
シェアする
MTNブログ

コメント