欲しい! 簡易シリアル通信アプリ
マイコンで装置を製作するとする、例えばXIAO RP2040 マイコンをMIcro pythonで複数のステップモータへの指令パルス発生装置を製作するとする。
シリアル通信コマンドを受けて動作を行うプログラムを作成する時に、コマンドを送信して動作確認を行いたい。または、装置にコマンドを送って動作させたい。
シリアルコマンドを送信するために、その昔は、秀Term、TeraTerm、PuTTYなどを使用していたが、近頃はArduino IDEのシリアル通信アプリが使い良いので、もっぱら使用してる。
Arduino IDE並に軽いTermアプリがないものか、探してみたが、ない。
簡単にシリアル通信コマンドを送信するもうひとつの方法は、PythonでPyserialを使ったプログラムを作って通信を行うやり方だ。これだとマイコン用のpythonエディタThonnyと別にpythonエディタを開いて、両方のプログラムをいじるという煩雑さがある。
そこで、Arduino IDE並に簡単な汎用のTearmアプリをPython、Pyserial、Tkinterを駆使して作ることにした。2023年1月25日から始めて1月27日までに、まあまあのTermアプリに整ったので、御披露します。
簡易シリアル通信アプリ F.Term
まずは、ソースプログラムを掲載する。コメント行を含めて 270行 あまりで出来ました。
#-------------------------------------------------------------------------------
# Name: Term230125
# Purpose: serial comunication
#
# Author: T.F.
#
# Created: 25/01/2023
# Copyright: (c) T.F. 2023
# Licence: <your licence>
#
# Ver1.0 2023/01/25 ScrolledText
# Ver1.1 2023/01/26 add Button,ConboBox
#-------------------------------------------------------------------------------
# How to turn off overwrite mode in text editor
# Windows: [Fn] + [Insert]
#
#==================================================
# import
#==================================================
#import tkinter
from tkinter import *
import tkinter as tk
from tkinter import scrolledtext
from tkinter import messagebox
from tkinter import ttk
import serial
from serial.tools import list_ports
#==================================================
# text_area -> Enter key -> send
#==================================================
def send(event):
global text_area,ser
index = text_area.index(tk.END)
print(f"cursor = {index}",type(index))
#-----------------------------------------
dot = index.find('.')
print(f"dot = {dot}",type(dot))
#-----------------------------------------
ide = int(index[0:dot])
print(f"ide = {ide}",type(ide))
#-----------------------------------------
ids = str(ide-1)+'.0'
ide = str(ide)+'.0'
#-----------------------------------------
# input_text = text_area.get('1.0','2.0') #1行目0番から2行目0番までの文字列
#-----------------------------------------
input_text = text_area.get(ids,ide) #ids行目0番からide行目0番までの文字列
print(f"send_text = {input_text}",type(input_text))
#-----------------------------------------
# serial communication
#-----------------------------------------
if ser.is_open:
## ser.write((input_text + "\n").encode("utf-8")) #改行:'\r'=CR' \n'=LF
ser.write((input_text).encode("utf-8"))
#==================================================
# port open
#==================================================
def open(e,v):
global ser
#print('v=%s' % v.get())
#--------------------------
com = v.get()
## print(com)
#--------------------------
ser.port = com
ser.baudrate = 115200
ser.timeout = 0.1
#--------------------------
try:
ser.open()
print("open serial is success!!")
print(ser)
except:
print("error when opening serial")
#==================================================
# window close by win_X
#==================================================
def delete_window1():
print("ウィンドウのxボタンが押された")
try:
# 終了確認のメッセージ表示
ret = messagebox.askyesno(
title = "終了確認",
message = "プログラムを終了しますか?")
if ret == True:
# 「はい」がクリックされたとき
win.destroy()
except:
print("error")
#==================================================
# window close by Btn2
#==================================================
def delete_window(e):
print("ウィンドウのxボタンが押された")
try:
# 終了確認のメッセージ表示
ret = messagebox.askyesno(
title = "終了確認",
message = "プログラムを終了しますか?")
if ret == True:
# 「はい」がクリックされたとき
win.destroy()
except:
print("error")
#==================================================
#
# ここに定期的に実行する処理を記述する
#==================================================
def loop():
global win, text_area, ser
#----------------------------------------------
# rec -> scrolledtext
#----------------------------------------------
rec=[]
if ser.is_open:
## rec = ser.read_all()
rec = ser.readline() # read to \n
rec = rec.strip().decode('utf-8')
if len(rec) != 0:
text_area.insert(tk.INSERT, rec + '\n')
text_area.see(text_area.index(tk.END))
#----------------------------------------------
win.after(100, loop)
#==================================================
# Tkinter
#==================================================
def Panel():
global ports,win,text_area
#-------------------------------------------------------------
# root base
#-------------------------------------------------------------
win = tk.Tk()
win.title("F_Term")
win.geometry("500x450")
#-------------------------------------------------------------
# frame
#--------------------------------------------------
#frame = tk.Frame(win)
#-----------------------------
#frame.pack()
#-------------------------------------------------------------
# Scrolledtext
#-------------------------------------------------------------
text_area = scrolledtext.ScrolledText(win,width=50,height=20,font=("Helvetica", 12, ""))
text_area.bind('<Return>', send)
#-----------------------------
#-------------------------------------------------------------
# Combobox
#-------------------------------------------------------------
v = StringVar()
cb = ttk.Combobox(win, textvariable=v,values=ports, width=10)
cb.set(ports[0])
## cb.bind('<<ComboboxSelected>>',lambda e: print('v=%s' % v.get()))
#-----------------------------
#-------------------------------------------------------------
# Button
#-------------------------------------------------------------
#button1 = ttk.Button(win, text='Open',command=lambda: print('v=%s' % v.get()))
btn1 = ttk.Button(win, text='Open')
btn2 = ttk.Button(win, text='Close',command=lambda: ser.close())
btn3 = ttk.Button(win, text='終了')
#---------------------------------------------
btn1.bind("<Button-1>", lambda e:open(e,v))
#btn2.bind("<Button-1>", lambda e:delete_window(e))
btn3.bind("<Button-1>", lambda e:delete_window(e))
#-------------------------------------------------------------
# place
#-------------------------------------------------------------
W=90;H=30
#------------------------------------
cb.place( x=20 ,y=10)
btn1.place( x=20+W ,y=10)
btn2.place( x=20+W*2 ,y=10)
btn3.place( x=20+W*4 ,y=10)
text_area.place( x=20 ,y=10+H)
#------------------------------------
# cursol
#------------------------------------
text_area.focus()
e=0
win.protocol("WM_DELETE_WINDOW", delete_window1)
#------------------------------------
# 100ms後にrepeat関数を実行
#------------------------------------
win.after(100, loop)
#------------------------------------
# END (loop start)
#------------------------------------
win.mainloop()
#==================================================
# S1.serch open serial port
#==================================================
##COMs = ['COM%s' % (i + 1) for i in range(256)]
##ports = []
###--------------------------------------------------
##for port in COMs:
## try:
## s = serial.Serial(port)
## s.close()
## ports.append(port)
## except (OSError, serial.SerialException):
## pass
#=======================================================================
# S2.serch open serial port
#=======================================================================
devices = list_ports.comports() # ポートデバイスデータを取得
ports = [info.device for info in devices] # ポート表示形式"COM1"に変換
#=======================================================================
# disp ports
#=======================================================================
if len(ports) == 0:
print("error: device not found")
elif len(ports) == 1:
print(f"only found {ports[0]}")
else:
for i in range(len(devices)):
print(f"input {i}: open {ports[i]}")
#==================================================
# シリアルポート定義
# ser = serial.Serial("COM21", 115200, timeout=0.1)
#==================================================
ser = serial.Serial()
#==================================================
# パネル表示
#==================================================
Panel()
#==================================================
# END (after break loop)
#==================================================
ser.close()
print('program end')
解説
おいおい追記します。