寄存器查看器

寄存器查看器

一、需求和效果展示

嵌入式应用开发经常要读写寄存器,比如把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里面如何创建虚拟环境示例:

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值