寄存器查看器
一、需求和效果展示
嵌入式应用开发经常要读写寄存器,比如把GPIO寄存器的某一位置1,然后去看寄存器的值,又比如说看到一个寄存器的值是0xBC023A00,要查看这个值哪些置0,哪些置1.
所以就开发了这款寄存器查看器,它能直观的展示寄存器的每一位的置位效果,还能看到十六进制,十进制,八进制,二进制的值。直接看效果:
二、项目代码
使用python的tkinter包开发,代码只有一个文件。
registerViewer.py
import os
import subprocess
import sys
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
# --------------------全局变量-----------------------#
DEBUG = False # 调试,如果为True,打印log
g_bitButtons = []
g_winWidth = 550
g_winHeight = 382
dict_entry = {} # 存储单行文本框
#---------------------------------------------------#
# --------------------函数定义-----------------------#
def show_tps(text):
""" 如果软件出现问题, 给出提示 """
messagebox.showinfo("提示", text)
def debug_log(message):
""" debug开关,开启就有log打印,否则不打印 """
if DEBUG:
print(f"[DEBUG]{message}")
def cal_center_position(tk_wind, width, height):
""" 计算窗口居中位置, 程序展示在窗口中间 """
screen_width = tk_wind.winfo_screenwidth()
screen_height = tk_wind.winfo_screenheight()
pos_x = (screen_width - width) // 2
pos_y = (screen_height - height) // 2
return pos_x,pos_y
def get_resource_path(relative_path):
""" 获取打包后的资源路径 """
if getattr(sys, 'frozen', False): # 是否已被 PyInstaller 打包
base_path = sys._MEIPASS # PyInstaller 的临时目录
else:
base_path = os.path.abspath(".") # 开发模式下使用当前目录
return os.path.join(base_path, relative_path)
def show_ascii():
""" 展示ascii码表 """
path = get_resource_path("./image/AscII.jpg")
try:
subprocess.run(["start", path], shell=True) # 直接打开图片
except FileNotFoundError:
show_tps("AscII码表被损坏或丢失!!!") # 如果图片不存在,产生提示
debug_log(f"AscI码表被损坏或丢失!!!path=[{path}]")
def set_form_icon(tk_wind):
""" 设置icon """
path = get_resource_path("./image/register.png")
try:
icon = Image.open(path)
icon = ImageTk.PhotoImage(icon)
tk_wind.wm_iconphoto(True, icon)
except FileNotFoundError:
show_tps("icon被损坏或丢失")
debug_log(f"icon被损坏或丢失!!!path=[{path}]")
def on_resize(event):
""" 当窗口大小改变时,获取宽度和高度 """
global g_winWidth,g_winHeight
width = event.width
height = event.height
if (g_winWidth != width) or (g_winHeight != height):
g_winWidth = width
g_winHeight = height
debug_log(f"窗口的新宽度: {width}, 新高度: {height}")
def tk_window_init():
""" 初始化主窗口 """
# 创建主窗口
tk_window = tk.Tk()
tk_window.title("寄存器工具")
pos_x, pos_y = cal_center_position(tk_window, g_winWidth, g_winHeight)
tk_window.geometry(f"{g_winWidth}x{g_winHeight}+{pos_x}+{pos_y}") # 设置窗口大小
# 更换图标
set_form_icon(tk_window)
# 绑定计算窗口宽度和高度的函数
tk_window.bind("<Configure>", on_resize)
# 设置窗口宽度和高度不可变
tk_window.resizable(False, False)
return tk_window
def register_init(tk_wind):
""" 初始化寄存器组件 """
global g_bitButtons
frame_register = tk.Frame(tk_wind)
frame_register.pack(anchor="w", pady=(20,1))
# 创建多个按钮
widget_idx = 0 # 控件所在位置
pad_x = 5 # 左右边距
pad_y = 2 # 上下边距
widget_line = 0 # 显示在哪一行
for i in range(31, -1, -1):
if i == 15:
widget_line += 2 # 第15个按钮的时候换行
widget_idx = 0 # 从第0列重新开始显示
if (i + 1) % 4 == 0:
# 设置一个空label,插入到按钮当中
label_nop1 = tk.Label(frame_register, text="")
label_nop2 = tk.Label(frame_register, text="")
debug_log(f"btn get i={i}")
label_nop2.grid(row=widget_line, column=widget_idx, padx=pad_x, pady=pad_y)
label_nop1.grid(row=widget_line + 1, column=widget_idx, padx=pad_x, pady=pad_y)
widget_idx += 1
# 标签文本
label_bit = tk.Label(frame_register, text=f"{i}")
label_bit.grid(row=widget_line, column=widget_idx, padx=pad_x, pady=pad_y)
# 寄存器按钮
btn_bit = tk.Button(frame_register, text="0")
btn_bit.bind("<Button-1>", btn_bit_click)
btn_bit.grid(row=widget_line + 1, column=widget_idx, padx=pad_x, pady=pad_y)
# 寄存器按钮对象加入到全局列表中存储
g_bitButtons.append(btn_bit)
widget_idx += 1
# 存储按钮的列表进行反向
g_bitButtons.reverse()
def btn_bit_click(event):
""" 多个按钮的点击事件 """
global g_bitButtons
btn_bit = event.widget
cur_reg32 = 0 # 每次触发之前,需要赋值为0 再计算当前寄存器的值
# 位翻转
if btn_bit['text'] == '0':
btn_bit['text'] = '1' # 按钮文本时0,翻转变为1
else:
btn_bit['text'] = '0' # 按钮文本时1,翻转变为0
# 获取所有寄存器按钮的文本
for i in range(31, -1, -1):
cur_reg32 += int(g_bitButtons[i]["text"]) * 2**i
# 修改寄存器, 同步所有单行文本框
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, bin(cur_reg32))
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, hex(cur_reg32))
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, oct(cur_reg32))
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, cur_reg32)
debug_log(f"当前寄存器的值:{cur_reg32}")
def sync_entry(event):
""" 修改一个单行文本框,同步其他文本框和寄存器 """
global dict_entry, g_bitButtons
binary_type = 16 # 要转换的进制类型,默认是10
is_validNum = True # 判断数值是否有效,发生异常即为False
widget = event.widget # 获取触发事件的组件
text = widget.get().strip()
if widget == dict_entry["hex_entry"]:
try:
binary_type = 16 # 要转换的进制类型
# Hex -> Bin
hex_toBin = bin(int(text, binary_type))
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, hex_toBin)
# Hex -> Oct
hex_toOct = oct(int(text, binary_type))
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, hex_toOct)
# Hex -> Dec
hex_toDec = int(text, binary_type)
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, hex_toDec)
except ValueError:
is_validNum = False
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, "Invalid Val")
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, "Invalid Val")
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, "Invalid Val")
elif widget == dict_entry["bin_entry"]:
try:
binary_type = 2 # 要转换的进制类型
# Bin -> Hex
bin_toHex = hex(int(text, binary_type))
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, bin_toHex)
# Bin -> Oct
bin_toOct = oct(int(text, binary_type))
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, bin_toOct)
# Bin -> Dec
bin_toDec = int(text, binary_type)
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, bin_toDec)
except ValueError:
is_validNum = False
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, "Invalid Val")
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, "Invalid Val")
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, "Invalid Val")
elif widget == dict_entry["oct_entry"]:
try:
binary_type = 8 # 要转换的进制类型
# Oct -> Hex
oct_toHex = hex(int(text, binary_type))
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, oct_toHex)
# Oct -> Bin
oct_toBin = bin(int(text, binary_type))
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, oct_toBin)
# Oct -> Dec
oct_toDec = int(text, binary_type)
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, oct_toDec)
except ValueError:
is_validNum = False
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, "Invalid Val")
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, "Invalid Val")
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, "Invalid Val")
else:
try:
binary_type = 10 # 要转换的进制类型
# Dec -> Hex
dec_toHex = hex(int(text, binary_type))
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, dec_toHex)
# Dec -> Bin
dec_toBin = bin(int(text, binary_type))
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, dec_toBin)
# Dec -> Oct
dec_toOct = oct(int(text, binary_type))
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, dec_toOct)
except ValueError:
is_validNum = False
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, "Invalid Val")
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, "Invalid Val")
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, "Invalid Val")
if is_validNum:
# 任何单行文本框的值都将被转换为32位二进制字符串
binary_str = format(int(text, binary_type), "032b")
debug_log(f"binary_str:{binary_str}")
reversed_bin_str = binary_str[::-1]
# 设置寄存器的值 0000 0000 0000 0000 0000 0000 0000 0101
for i in range(31, -1, -1):
# 展示32位寄存器
g_bitButtons[i]["text"] = reversed_bin_str[i]
def reg_text_init(tk_wind):
""" 寄存器文本框 """
global dict_entry # 所有单行文本框组件都将被添加到这个全局字典中存储
curr_row = 4
# 添加frame
frame_regText = tk.Frame(tk_wind)
frame_regText.pack(anchor="w")
# 添加组件-1
label_hexEn = tk.Label(frame_regText, text="十六进制:")
entry_hexadecimal = tk.Entry(frame_regText, width=35)
dict_entry["hex_entry"] = entry_hexadecimal
entry_hexadecimal.bind("<KeyRelease>", sync_entry)
label_hexEn.grid(row=curr_row, column=0, padx=(15,1), pady=5)
entry_hexadecimal.grid(row=curr_row, column=1, padx=5, pady=5)
curr_row += 1
# 添加组件-2
label_decEn = tk.Label(frame_regText, text="十进制:")
entry_decimal = tk.Entry(frame_regText, width=35)
dict_entry["dec_entry"] = entry_decimal
entry_decimal.bind("<KeyRelease>", sync_entry)
label_decEn.grid(row=curr_row, column=0, padx=(15, 1), pady=5)
entry_decimal.grid(row=curr_row, column=1, padx=5, pady=5)
curr_row += 1
# 添加组件-3
label_decEn = tk.Label(frame_regText, text="八进制:")
entry_octal = tk.Entry(frame_regText, width=35)
dict_entry["oct_entry"] = entry_octal
entry_octal.bind("<KeyRelease>", sync_entry)
label_decEn.grid(row=curr_row, column=0, padx=(15, 1), pady=5)
entry_octal.grid(row=curr_row, column=1, padx=5, pady=5)
curr_row += 1
# 添加组件-4
label_decEn = tk.Label(frame_regText, text="二进制:")
entry_binary = tk.Entry(frame_regText, width=35)
dict_entry["bin_entry"] = entry_binary
entry_binary.bind("<KeyRelease>", sync_entry)
label_decEn.grid(row=curr_row, column=0, padx=(15, 1), pady=5)
entry_binary.grid(row=curr_row, column=1, padx=5, pady=5)
def clear_reg_entry():
""" 清空寄存器和所有单行文本框的值 """
global g_bitButtons, dict_entry
for i in range(31, -1, -1):
g_bitButtons[i]["text"] = '0'
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["dec_entry"].delete(0, tk.END)
def extend_component(tk_window):
""""扩展功能"""
frame_bottom = tk.Frame(tk_window)
frame_bottom.pack(anchor="w")
# 创建展示Ascii码的按钮
btn_ascii = tk.Button(frame_bottom, text="AscII码", command=lambda: show_ascii())
btn_ascii.grid(row=10, column=5, padx=(15, 1), pady=5)
#---------------------------------------------------#
# ---------------------main-------------------------#
if __name__ == '__main__':
# 主窗口初始化
root = tk_window_init()
# -------设置第一个frame
register_init(root)
# -------设置第二个frame
reg_text_init(root)
# 清空寄存器和单行文本框
clear_reg_entry()
# -------设置第三个frame
extend_component(root)
# 运行 Tkinter 事件循环
root.mainloop()
#---------------------------------------------------#
三、项目知识点解析
1.tinker布局
tinker布局有pack()和grid()方法,pack是垂直布局,grid是网格布局,我这里设置的是一个主窗口,里面有三个子框架frame,3个frame是pack布局,frame里面的控件(按钮,标签,单行文本框)是grid布局
def reg_text_init(tk_wind):
""" 寄存器文本框 """
...
# 添加frame
frame_regText = tk.Frame(tk_wind)
frame_regText.pack(anchor="w") # pack 布局
# 添加组件-1
label_hexEn = tk.Label(frame_regText, text="十六进制:")
entry_hexadecimal = tk.Entry(frame_regText, width=35)
dict_entry["hex_entry"] = entry_hexadecimal
entry_hexadecimal.bind("<KeyRelease>", sync_entry)
# 这里的组件grid布局
label_hexEn.grid(row=curr_row, column=0, padx=(15,1), pady=5)
entry_hexadecimal.grid(row=curr_row, column=1, padx=5, pady=5)
# 主窗口初始化
root = tk_window_init()
# -------设置第一个frame
register_init(root)
# -------设置第二个frame
reg_text_init(root)
2.如何将窗口显示在桌面的中间位置?
设置程序窗口大小以后,获取当前桌面窗口的大小,screen_width和screen_height,然后除以2,就在中间位置,tk_window.geometry设置居中的窗口位置。
def cal_center_position(tk_wind, width, height):
""" 计算窗口居中位置, 程序展示在窗口中间 """
screen_width = tk_wind.winfo_screenwidth()
screen_height = tk_wind.winfo_screenheight()
pos_x = (screen_width - width) // 2
pos_y = (screen_height - height) // 2
return pos_x,pos_y
pos_x, pos_y = cal_center_position(tk_window, g_winWidth, g_winHeight)
tk_window.geometry(f"{g_winWidth}x{g_winHeight}+{pos_x}+{pos_y}") # 设置窗口大小
3.寄存器显示界面
3.1 按钮组件排布
循环排布32个按钮,每4组控件中插入一个空标签,占位,达到看起来更宽的效果,第16个按钮进行换行
btn_bit.grid(row=widget_line, ) # row控制换行
3.2 多个按钮绑定一个事件函数
- g_bitButtons全局列表存储31个按钮对象
- 绑定Button-1,表示左键按下,Button-2应该是右键按下,全部绑定到一个事件函数,类似于QT的信号与槽函数机制
- 事件函数检测到当前按钮产生左键按下,位翻转 0->1, 1->0
- 计算当前31个按钮的的值,示例:0b1011 = (1x2^3) + (0x2 ^ 2) + (1x2 ^ 1) + (1x2 ^ 0)
# 寄存器按钮
btn_bit = tk.Button(frame_register, text="0")
btn_bit.bind("<Button-1>", btn_bit_click)
# 存储按钮的列表进行反向
g_bitButtons.reverse()
def btn_bit_click(event):
""" 多个按钮的点击事件 """
global g_bitButtons
btn_bit = event.widget
cur_reg32 = 0 # 每次触发之前,需要赋值为0 再计算当前寄存器的值
# 位翻转
if btn_bit['text'] == '0':
btn_bit['text'] = '1' # 按钮文本时0,翻转变为1
else:
btn_bit['text'] = '0' # 按钮文本时1,翻转变为0
# 获取所有寄存器按钮的文本
for i in range(31, -1, -1):
cur_reg32 += int(g_bitButtons[i]["text"]) * 2**i
...
4.十六进制和十进制同步显示
-
dict_entry全局字典存储每个单行文本框对象
# entry_hexadecimal为创建的单行文本框,dict_entry["hex_entry"]存储它,调用时就这样调用 entry_hexadecimal = tk.Entry(frame_regText, width=35) dict_entry["hex_entry"] = entry_hexadecimal
-
绑定事件KeyRelease,当光标在文本框内时触发
-
widget = event.widget 获取触发事件的组件
def sync_entry(event): ... widget = event.widget # 获取触发事件的组件 text = widget.get().strip() if widget == dict_entry["hex_entry"]: try: binary_type = 16 # 要转换的进制类型 # Hex -> Bin hex_toBin = bin(int(text, binary_type)) dict_entry["bin_entry"].delete(0, tk.END) dict_entry["bin_entry"].insert(0, hex_toBin) # Hex -> Oct hex_toOct = oct(int(text, binary_type)) dict_entry["oct_entry"].delete(0, tk.END) dict_entry["oct_entry"].insert(0, hex_toOct) # Hex -> Dec hex_toDec = int(text, binary_type) dict_entry["dec_entry"].delete(0, tk.END) dict_entry["dec_entry"].insert(0, hex_toDec) except ValueError: is_validNum = False dict_entry["bin_entry"].delete(0, tk.END) dict_entry["bin_entry"].insert(0, "Invalid Val") dict_entry["oct_entry"].delete(0, tk.END) dict_entry["oct_entry"].insert(0, "Invalid Val") dict_entry["dec_entry"].delete(0, tk.END) dict_entry["dec_entry"].insert(0, "Invalid Val") elif widget == dict_entry["bin_entry"]: try: binary_type = 2 # 要转换的进制类型 # Bin -> Hex bin_toHex = hex(int(text, binary_type)) dict_entry["hex_entry"].delete(0, tk.END) dict_entry["hex_entry"].insert(0, bin_toHex) # Bin -> Oct bin_toOct = oct(int(text, binary_type)) dict_entry["oct_entry"].delete(0, tk.END) dict_entry["oct_entry"].insert(0, bin_toOct) # Bin -> Dec bin_toDec = int(text, binary_type) dict_entry["dec_entry"].delete(0, tk.END) dict_entry["dec_entry"].insert(0, bin_toDec) ....
5.寄存器和文本框相互同步
# 修改寄存器, 同步所有单行文本框
dict_entry["bin_entry"].delete(0, tk.END)
dict_entry["bin_entry"].insert(0, bin(cur_reg32))
dict_entry["hex_entry"].delete(0, tk.END)
dict_entry["hex_entry"].insert(0, hex(cur_reg32))
dict_entry["oct_entry"].delete(0, tk.END)
dict_entry["oct_entry"].insert(0, oct(cur_reg32))
dict_entry["dec_entry"].delete(0, tk.END)
dict_entry["dec_entry"].insert(0, cur_reg32)
debug_log(f"当前寄存器的值:{cur_reg32}")
if is_validNum:# 判断数值是否有效,发生异常即为False
# 任何单行文本框的值都将被转换为32位二进制字符串
binary_str = format(int(text, binary_type), "032b")
debug_log(f"binary_str:{binary_str}")
reversed_bin_str = binary_str[::-1]
# 设置寄存器的值 0000 0000 0000 0000 0000 0000 0000 0101
for i in range(31, -1, -1):
# 展示32位寄存器
g_bitButtons[i]["text"] = reversed_bin_str[i]
6.展示图片以及路径设置
- 软件是需要使用pyinstaller打包的,需要考虑图片路径问题,开发模式下,是在当前工程下创建的image文件夹,如果已被 PyInstaller 打包,则是在PyInstaller 的临时目录。
- subprocess.run 直接打开图片
def get_resource_path(relative_path):
""" 获取打包后的资源路径 """
if getattr(sys, 'frozen', False): # 是否已被 PyInstaller 打包
base_path = sys._MEIPASS # PyInstaller 的临时目录
else:
base_path = os.path.abspath(".") # 开发模式下使用当前目录
return os.path.join(base_path, relative_path)
def show_ascii():
""" 展示ascii码表 """
path = get_resource_path("./image/AscII.jpg")
try:
subprocess.run(["start", path], shell=True) # 直接打开图片
except FileNotFoundError:
show_tps("AscII码表被损坏或丢失!!!") # 如果图片不存在,产生提示
debug_log(f"AscI码表被损坏或丢失!!!path=[{path}]")
7.pyinstaller打包
–add-data 添加图片,直接编译到程序里面,–name 设置软件名称 。
REM --add-data "image_path;image" 添加图片,直接编译到程序里面
pyinstaller --onefile --windowed --add-data "E:/pythonWorkspace/image/AscII.jpg;image" --add-data "E:/pythonWorkspace/image/register.png;image" --name registerViewer registerViewer.py
registerViewer.py打包文件名称,这里最好是创建虚拟环境进行打包,下面是pycharm里面如何创建虚拟环境示例: