従来に公開していたSHマイコンのフラッシュライターでは、Windows7以降の環境やUbuntu18以降の環境では動作しないケースがありました。
このような環境でも動作するように新規にPython3でGUI版フラッシュライターを開発しました。

対象となるマイコンは、SH3/SH4/SH4A、SH7084、SH7216、SH7124となっており、Ubuntu21とWIndows10で動作確認をしました。

シリアルポートは自動検出しますので、複数ある場合はプルダウンリストで選択します。

loadボタンで書き込むファイルを選択し、writeボタンでマイコンボードのフラッシュに書き込みます。
このときにマイコンボードのターゲットは自動検出します。

起動は

$ python3 flush.py -g

で起動します。

また、Pythonのツールにより、このスクリプトは単一の実行ファイルに変換できますので、簡単に再配布可能といなっています。

import serial
from serial.tools import list_ports
import sys
from sys import exit
import os
from os.path import expanduser
import struct
import glob
import tkinter as tk
from tkinter import ttk
import tkinter.filedialog

BIT_RATE_SET_MAX = 256
BIT_SET_RES = 0xaa
BIT_RATE_SET_MAX_INTER = 30
BIT_SET_RES_INTER = 0xe6
BIT_SET_RES_NG = 0xff
BIT_SET_CODE = 0x00
BIT_SET_CHECK = 0x55
BIT_SET_FIN = 0x00
QUERY_DEV_CMD = 0x20
QUERY_DEV_RES = 0x30
QUERY_DEV_SIZE = 4
SELECT_DEV_CMD = 0x10
SELECT_DEV_RES = 0x06
SELECT_DEV_ERROR_RES = 0x90
SELECT_DEV_ERROR_SUM = 0x11
SELECT_DEV_ERROR_CODE = 0x21
QUERY_CLOCK_CMD = 0x21
QUERY_CLOCK_RES = 0x31
SELECT_CLOCK_CMD = 0x11
SELECT_CLOCK_RES = 0x06
SELECT_CLOCK_ERROR_RES = 0x91
SELECT_CLOCK_ERROR_SUM = 0x11
SELECT_CLOCK_ERROR_MODE = 0x22
SELECT_CLOCK_SIZE = 1
SELECT_CLOCK_MODE0 = 0
SELECT_CLOCK_MODE1 = 1
QUERY_SCALE_CMD = 0x22
QUERY_SCALE_RES = 0x32
QUERY_FREQ_CMD = 0x23
QUERY_FREQ_RES = 0x33
QUERY_PAGE_SIZE_CMD = 0x27
QUERY_PAGE_SIZE_RES = 0x37
SET_RATE_CMD = 0x3f
SET_RATE_RES = 0x06
SET_RATE_ERROR_RES = 0xbf
SET_RATE_ERROR_SUM = 0x11
SET_RATE_ERROR_RATE = 0x24
SET_RATE_ERROR_NEW_FREQ = 0x25
SET_RATE_ERROR_PLL = 0x26
SET_RATE_ERROR_FREQ = 0x27
SET_RATE_CHECK_CMD = 0x06
SET_RATE_SIZE = 7
QUERY_AREA_CMD = 0x25
QUERY_AREA_RES = 0x35
SWITCH_TO_ERASE_CMD = 0x40
SELECT_WRITE_FORMAT = 0x43
QUERY_ERROR_RES	= 0x80
NON_ERROR_RES = 0x06
WRITE_CMD = 0x50
WRITE_ERROR_ADDRESS = 0x2a
WRITE_ERROR_WRITE = 0x53

def err_message(msg, gui):
	if gui:
		status.set(msg)
	else:
		print(msg)
		exit()

def err_message_f(msg, f, gui):
	f.close()
	if gui:
		status.set(msg)
	else:
		print(msg)
		exit()

def out_message(msg, gui):
	if gui:
		status.set(msg)
		label2.update()
	else:
		print(msg, flush=True)

def out_label(msg, cpu, gui):
	if gui:
		cpuname.set(cpu)
		label3.update()
	else:
		print(msg, cpu, flush=True)

def flush_write(port, filename, baud, osc, gui, pb):
	if not os.path.isfile(filename):
		err_message('File not found.\n', gui)
		return
	try:
		ser = serial.Serial(port, baud, timeout=0.05)
	except:
		err_message('Serial port not found\n', gui)
		return
	for i in range(10):
		for j in range(BIT_RATE_SET_MAX_INTER):
			ser.write(b'\x00')
		c = ser.read()
		if c != b'':
			break
	if c != b'\x00':
		ser.close()
		ser = serial.Serial(port, 115200, timeout=0.2)
		for i in range(8):
			ser.write(b'\x5a')
			c = ser.read()
			if c != b'':
				break
		if i == 7:
			err_message('Serial port not found', gui)
			return
		for i in range(255):
			c1 = struct.pack('B', i)
			if c1 != B'E':
				ser.write(c1)
				c2 = ser.read()
				if c1 != c2:
					err_message('Serial port not found', gui)
					return
		cpu = 'SH3/4/4A'
		flushsize = 0x80000
		pagesize = 256
		ser.timeout = 5
		ser.write(b'E')
		c = ser.read()
		out_label('CPU is', cpu, gui)
		out_message('Flush erase', gui)
		while c != b'S':
			c = ser.read()
			if c == b'.':
				if gui == False:
					print('.', end='', flush=True)
#				else:
#					pb.update()
			if c != b'S' and c != b'.':
				err_message('Flush earse fail', gui)
				return
		if gui == False:
			print('.', flush=True)
	else:
		ser.write(struct.pack('B', BIT_SET_CHECK))
		while True:
			stat = int.from_bytes(ser.read(),'big')
			if stat == BIT_SET_RES_NG:
				err_message('Bit set check error', gui)
				return
			if stat == BIT_SET_RES_INTER:
				break
		ser.write(struct.pack('B', QUERY_DEV_CMD))
		stat = int.from_bytes(ser.read(),'big')
		sum1 = stat
		if stat != QUERY_DEV_RES:
			err_message('Query device error', gui)
			return
		size = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + size
		devnum = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + devnum
		status = bytearray(size - 1)
		for i in range(size - 1):
			status[i] = int.from_bytes(ser.read(),'big')
			sum1 = sum1 + status[i]
		sum1 = sum1 + int.from_bytes(ser.read(),'big')
		sum1 = sum1 % 0x100
		if sum1 != 0:
			err_message('Query device error', gui)
			return
		devcode = bytearray(QUERY_DEV_SIZE)
		for i in range(QUERY_DEV_SIZE):
			devcode[i] = status[i + 1]
		length = status[0] - QUERY_DEV_SIZE;
		devname = bytearray(length)
		for i in range(length):
			devname[i] = status[i + QUERY_DEV_SIZE + 1]
		cpu = devname.decode()
		if cpu == 'R5F7084':
			if osc == 0:
				osc = 1000
		elif cpu == 'R5F7216':
			if osc == 0:
				osc = 1200
		elif cpu == 'R5F7124':
			if osc == 0:
				osc = 1250
		else:
			err_message('This is a unknown cpu', gui)
			return
		out_label('CPU is', cpu, gui)
		c = SELECT_DEV_CMD
		sum1 = c
		ser.write(struct.pack('B', c))
		c = QUERY_DEV_SIZE
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		for i in range(QUERY_DEV_SIZE):
			c = devcode[i]
			ser.write(struct.pack('B', c))
			sum1 = sum1 + c
		sum1 = sum1 % 0x100
		sum1 = (0x100 - sum1) % 0x100
		ser.write(struct.pack('B', sum1))
		c = int.from_bytes(ser.read(),'big')
		if c != SELECT_DEV_RES:
			err_message('Select device error', gui)
			return
		ser.write(struct.pack('B', QUERY_CLOCK_CMD))
		c = int.from_bytes(ser.read(),'big')
		sum1 = c;
		if c != QUERY_CLOCK_RES:
			err_message('Query clock error', gui)
			return
		size = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + size
		status = bytearray(size)
		for i in range(size):
			status[i] = int.from_bytes(ser.read(),'big')
			sum1 = sum1 + status[i];
		sum1 = sum1 + int.from_bytes(ser.read(),'big')
		sum1 = sum1 % 0x100
		if sum1 != 0:
			err_message('Query clock error', gui)
			return
		c = SELECT_CLOCK_CMD
		sum1 = c;
		ser.write(struct.pack('B', c))
		c = SELECT_CLOCK_SIZE
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		if cpu == 'R5F7216':
			c = SELECT_CLOCK_MODE1
		else:
			c = SELECT_CLOCK_MODE0
		sum1 = sum1 + c;
		ser.write(struct.pack('B', c))
		sum1 = sum1 % 0x100
		sum1 = (0x100 - sum1) % 0x100
		ser.write(struct.pack('B', sum1))
		c = int.from_bytes(ser.read(),'big')
		if c != SELECT_CLOCK_RES:
			err_message('Select clock error', gui)
			return
		gain = 6
		speed = baud * gain
		bitrate = speed // 100
		c = SET_RATE_CMD
		sum1 = c
		ser.write(struct.pack('B', c))
		c = SET_RATE_SIZE
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		c = bitrate // 0x100
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		c = bitrate % 0x100
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		c = osc // 0x100
		sum1 = sum1 + c
		ser.write(struct.pack('B', c))
		c = osc % 0x100
		sum1 = sum1 + c;
		ser.write(struct.pack('B', c))
		pll = bytearray(3)
		if cpu == 'R5F7084':
			pll[0] = 2
			pll[1] = 4
			pll[2] = 4
		elif cpu == 'R5F7216':
			pll[0] = 2
			pll[1] = 8
			pll[2] = 4
		elif cpu == 'R5F7124':
			pll[0] = 2
			pll[1] = 4
			pll[2] = 2
		for i in range(3):
			c = pll[i]
			sum1 = sum1 + c
			ser.write(struct.pack('B', c))
		sum1 = sum1 % 0x100
		sum1 = (0x100 - sum1) % 0x100
		ser.write(struct.pack('B', sum1))
		c = int.from_bytes(ser.read(),'big')
		if c != SET_RATE_RES:
			c = int.from_bytes(ser.read(),'big')
			if c == SET_RATE_ERROR_SUM:
				err_message('Set rate checksum error', gui)
			elif c == SET_RATE_ERROR_RATE:
				err_message('Set rate speed error', gui)
			elif c == SET_RATE_ERROR_NEW_FREQ:
				err_message('Set rate new freq error', gui)
			elif c == SET_RATE_ERROR_PLL:
				err_message('Set rate pll error', gui)
			elif c == SET_RATE_ERROR_FREQ:
				err_message('Set rate new freq error', gui)
			else:
				err_message('Set rate unknown error', gui)
			return
		ser.close()
		ser = serial.Serial(port, speed, timeout=0.05)
		ser.write(struct.pack('B', SET_RATE_CHECK_CMD))
		c = int.from_bytes(ser.read(),'big')
		if c != NON_ERROR_RES:
			err_message('Set rate clock error', gui)
			return
		ser.write(struct.pack('B', QUERY_AREA_CMD))
		c = int.from_bytes(ser.read(),'big')
		sum1 = c;
		if c != QUERY_AREA_RES:
			err_message('Query area error', gui)
			return
		size = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + size
		num = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + num
		saddr = list(range(num))
		taddr = list(range(num))
		for i in range(num):
			saddr[i] = 0
			for j in range(4):
				c = int.from_bytes(ser.read(),'big')
				sum1 = sum1 + c
				saddr[i] = saddr[i] * 0x100
				saddr[i] = saddr[i] + c
			taddr[i] = 0
			for j in range(4):
				c = int.from_bytes(ser.read(),'big')
				sum1 = sum1 + c;
				taddr[i] = taddr[i] * 0x100
				taddr[i] = taddr[i] + c
		sum1 = sum1 % 0x100
		sum1 = (0x100 - sum1) % 0x100
		c = int.from_bytes(ser.read(),'big')
		if sum1 != c:
			err_message('Query area error', gui)
			return
		flushsize = taddr[0] + 1;
		ser.write(struct.pack('B', QUERY_PAGE_SIZE_CMD))
		c = int.from_bytes(ser.read(),'big')
		sum1 = c
		if c != QUERY_PAGE_SIZE_RES:
			err_message('Query page size error', gui)
			return
		size = int.from_bytes(ser.read(),'big')
		sum1 = sum1 + size;
		pagesize = 0;
		for i in range(size):
			c = int.from_bytes(ser.read(),'big')
			sum1 = sum1 + c;
			pagesize = pagesize * 0x100;
			pagesize = pagesize + c;
		sum1 = sum1 % 0x100
		sum1 = (0x100 - sum1) % 0x100
		c = int.from_bytes(ser.read(),'big')
		if sum1 != c:
			err_message('Query page size error', gui)
			return
		out_message('Flush erase', gui)
		ser.timeout = 10
		ser.write(struct.pack('B', SWITCH_TO_ERASE_CMD))
		c = int.from_bytes(ser.read(),'big')
		if c != NON_ERROR_RES:
			err_message('Switch to erase error', gui)
			return
		ser.timeout = 0.05
		ser.write(struct.pack('B', SELECT_WRITE_FORMAT))
		c = int.from_bytes(ser.read(),'big')
		if c != NON_ERROR_RES:
			err_message('Select write format error', gui)
			return
	out_message('Flush write ready', gui)
	buff = bytearray(flushsize)
	for i in range(flushsize):
		buff[i] = 255
	verify = bytearray(pagesize)
	nopgm = bytearray(pagesize)
	for i in range(pagesize):
		nopgm[i] = 255
	with open(filename) as f:
		while True:
			s_line = f.readline()
			if not s_line:
				break
			s_line = s_line.replace('\n','')
			if s_line[0] != 'S':
				err_message_f('File format error', f, gui)
				return
			linesize = int(s_line[2:4],16)
			length2 = linesize * 2 + 4
			length1 = len(s_line)
			if length1 != length2:
				err_message_f('File format error', f, gui)
				return
			addrsize = (int(s_line[1:2],16) + 1) * 2
			if addrsize >= 4 and addrsize <= 8:
				address = int(s_line[4:addrsize + 4], 16)
				datasize = linesize - (addrsize // 2) - 1
				data = s_line[addrsize + 4:addrsize + 4 + datasize * 2]
				if (address + datasize) > flushsize:
					err_message_f('Flush address is overflow', f, gui)
					return
				for i in range(datasize):
					buff[address + i] = int(data[i * 2:i * 2 + 2], 16)
	if gui == True:
		pagecount = 0
		for address in range(0, flushsize, pagesize):
			for i in range(pagesize):
				if buff[address + i] != 255:
					pagecount = pagecount + 1
					break
		pagemax = pagecount // 10 + 1
		pb.config(maximum = pagemax)
		pagecount = 0
		pb.config(variable = pagecount)
		pb.start()
	out_message('Flush writing', gui)
	f.close()
	count = 0;
	for address in range(0, flushsize, pagesize):
		for i in range(pagesize):
			if buff[address + i] != 255:
				break
		if i < (pagesize - 1):
			if cpu == 'SH3/4/4A':
				ser.writetimeout = 0.24
				c2 = struct.pack('B',(address // 0x10000) % 0x100)
				ser.write(c2)
				c1 = struct.pack('B',(address // 0x100) % 0x100)
				ser.write(c1)
				ser.write(buff[address:address+pagesize])
				verify = ser.read(pagesize)
				c = ser.read()
				if c != b'S':
					err_message(c, 'Flush write error', gui)
					return
				if buff[address:address+pagesize] != verify:
					err_message(address, 'Verify error', gui)
					return
			else:
				c = WRITE_CMD;
				ser.write(struct.pack('B', c))
				sum1 = c
				ser.write(b'\x00')
				c = (address // 0x10000) % 0x100
				ser.write(struct.pack('B', c))
				sum1 = sum1 + c;
				c = (address // 0x100) % 0x100
				ser.write(struct.pack('B', c))
				sum1 = sum1 + c;
				c = address % 0x100
				ser.write(struct.pack('B', c))
				sum1 = sum1 + c;
				for j in range(pagesize):
					c = buff[address + j]
					ser.write(struct.pack('B', c))
					sum1 = sum1 + c;
				sum1 = sum1 % 0x100
				sum1 = (0x100 - sum1) % 0x100
				ser.write(struct.pack('B', sum1))
				c = int.from_bytes(ser.read(),'big')
				if c != NON_ERROR_RES:
					c = int.from_bytes(ser.read(),'big')
					err_message('Flush write error', gui)
					return
			count = count + 1
			if (count % 10) == 0:
				if gui == False:
					print('.', end='', flush=True)
				else:
					pb.update()
					pagecount = pagecount + 1
			if (count % 800) == 0:
				if gui == False:
					print('\n', end='', flush=True)	
	
	ser.close()
	out_message('\nFlush write complete', gui)

def select_quit(event):
	exit()

def select_file():
	fTyp = [("motororaS", "*.mot")]
	home = expanduser("~")
	file_name = tk.filedialog.askopenfilename(filetypes=fTyp, initialdir=home)
	if len(file_name) > 0:
		fname.set(file_name)

def select_flush():
	flush.config(state = 'disable') 
	load.config(state = 'disable')
	quit.config(state = 'disable')
	if fname.get() == '':
		select_file()
	pb = ttk.Progressbar(root, maximum=10, value=0, length=100, mode='determinate')
	pb.pack(in_=canvas2, expand = True, fill = tk.X)
	osc = 0
	baud = 19200
	flush_write(combobox.get(), fname.get(), baud, osc, True, pb)
	pb.stop()
	flush.config(state = 'normal') 
	load.config(state = 'normal')
	quit.config(state = 'normal')
	pb.destroy()
	canvas2.config(height = 0)

def click_file(event):
	root.after(1, select_file)

def click_flush(event):
	root.after(1, select_flush)

argc = len(sys.argv)
if argc == 2 and sys.argv[1] == '-g':
	ports = list_ports.comports()
	devices = [info.device for info in ports]
	if len(devices) == 0:
		exit()
	devices.reverse()
	
	root = tk.Tk()
	root.title("Flush writer")
	root.geometry("400x100")
	
	canvas1 = tk.Canvas(root)
	canvas1.pack(expand = True, fill = tk.X, side='top')

	fname = tk.StringVar()
	fname.set('')
	label1 = tk.Label(root, textvariable=fname)
	label1.pack(fill = tk.X, side='top')
		
	style = ttk.Style()
	style.configure("office.TCombobox", padding=2)
	
	v = tk.StringVar()
	
	combobox = ttk.Combobox(root, textvariable=v, values=devices, width=12, style="office.TCombobox")
	combobox.pack(in_=canvas1, side='left')
	combobox.set(devices[0])
	
	cpuname = tk.StringVar()
	cpuname.set('')
	label3 = tk.Label(root, textvariable=cpuname)
	label3.pack(in_=canvas1, expand = True, fill = tk.X, side='left')
	
	quit = tk.Button(root, text='quit')
	quit.bind('<ButtonPress>', select_quit)
	quit.pack(in_=canvas1, side='right')
	
	flush = tk.Button(root, text='write')
	flush.bind('<ButtonPress>', click_flush)
	flush.pack(in_=canvas1, side='right')
	
	load = tk.Button(root, text='load')
	load.bind('<ButtonPress>', click_file)
	load.pack(in_=canvas1, side='right')
	
	canvas2 = tk.Canvas(root, height = 0)
	canvas2.pack(expand = True, fill = tk.X)
	
	status = tk.StringVar()
	status.set('')
	label2 = tk.Label(root, textvariable=status)
	label2.pack(fill = tk.X, side='bottom')
	
	root.mainloop()

else:
	
	if argc >= 3:
		if sys.argv[1] != '-p':
			argc = 1
	if argc < 4:
		err_message('Usage: flush -p port file', False)
	filename = sys.argv[argc - 1]
	port = sys.argv[2]
	osc = 0
	baud = 19200
	flush_write(port, filename, baud, osc, False, 0)