- def update_device_list():
- probes = ConnectHelper.get_all_connected_probes()
- device_list = [probe.unique_id for probe in probes]
- device_cb['values'] = device_list
- if device_list:
- device_cb.current(0)
- else:
- device_cb.set('')
这样我们就可以`update_device_list()`来更新获取我们的设备列表。
3.2 读取数据的保存
由于我设计想着能够切换显示格式:可以选择字节、半字或字的显示方式。不能每次切换显示方式就读一次APM32F411的内存,这样会造成不必要的消耗。
我这里直接设计一个全局变量,用来保存读取到的数据,即相当于一个缓存,切换显示格式我们就直接对缓存里面的数据重新排列就好。
- # 全局缓存区,保存读取到的内容
- memory_cache = {
- 'data': None,
- 'address': 0x08000000,
- 'size': 0x1000,
- }
当然我这里也一并保存了需要读取的起始地址和读取的数据长度。
3.3 进度的显示
读大块的内容时可能需要花费一些时间,这个我想着用一个小地方显示读取的进度以及另存为bin文件的结果显示。这里就涉及到了一个线程的操作。
1. UI界面做一个主线程
2. 读取数据的操作做一个线程,并且读取的过程中计算百分比,然后把百分比推送至UI界面进行显示。
为什么要这样操作?回到刚刚说的,读取大块内容花费时间较长,若读取数据和UI在一个线程,那就会造成读取的时候UI有点“卡卡”。
读取的线程操作如下:
- def read_memory_thread():
- try:
- # 更新标签显示读取正在进行(在主线程中执行)
- output_label.config(text="Read 0%")
- root.update_idletasks() # 强制更新UI
-
- selected_probe = device_cb.get()
- address = int(address_entry.get() or "08000000", 16)
- size = int(size_entry.get() or "1000", 16)
- memory_cache['address'] = address
- memory_cache['size'] = size
- # 按块读取内存并更新百分比
- block_size = size // 20 # 计算每5%需要读取的大小
- if block_size == 0:
- block_size = size
- memory_cache['data'] = []
- with ConnectHelper.session_with_chosen_probe(unique_id=selected_probe) as session:
- target = session.board.target
- for i in range(0, size, block_size):
- end_address = i + block_size
- if end_address > size:
- end_address = size
- memory_cache['data'] += target.read_memory_block8(address + i, end_address - i)
- # 计算并更新百分比
- percent_complete = (i + block_size) * 100 // size
- if percent_complete > 100:
- percent_complete = 100
- root.after(0, lambda p=percent_complete: output_label.config(text=f"Read {p}%"))
- root.update_idletasks() # 更新UI以显示百分比
-
- # 在主线程中更新UI显示读取成功
- root.after(0, lambda: output_label.config(text="Read successfully"))
- root.after(0, update_memory_display)
- except ValueError as e:
- root.after(0, lambda: output_label.config(text="Error: Please enter valid hexadecimal address and size."))
- except Exception as e:
- root.after(0, lambda: output_label.config(text=f"Error: {str(e)}"))
- def read_memory():
- # 创建并启动后台线程进行内存读取
- threading.Thread(target=read_memory_thread, daemon=True).start()
3.4 保存到文件
这一步就比较简单,只是简单的文件的操作,需要注意的是,我们读取到的数据其实是16进制的,保存成bin文件需要是2进制的(需要用`bytes`转换一下)。
- def save_memory_to_file():
- if memory_cache['data'] is None:
- output_label.config(text="Error: No data to save. Please read memory first.")
- return
- file_path = filedialog.asksaveasfilename(
- defaultextension=".bin",
- filetypes=[("Binary files", "*.bin"), ("All files", "*.*")],
- title="Save memory as binary file"
- )
- if not file_path:
- # User cancelled the file dialog
- return
- with open(file_path, 'wb') as file:
- file.write(bytes(memory_cache['data']))
- output_label.config(text=f"Memory saved to {file_path} successfully")
文件的打开与保存就需要进行一些判断了:保存前判断一下有没有数据,用户是否取消保存等。
3.5 界面的设计
界面的设计主要是利用Tkinter控件,我们要清楚需要的组件:
1. 设备选择——下拉框;
2. 地址的设置、读取长度的设置——文本框;
3. 读取、另存为、显示切换——按钮;
4. 显示数据——视图控件;
5. 输出信息/提示内容等——标签;
然后考虑位置及大小等信息。
代码参考如下:
1. update_memory_display函数更新内存树视图控件,将读取的数据以十六进制和ASCII格式展示。
2. change_display_format函数允许用户改变内存数据的显示格式(字节、半字、字)。
3. GUI部分设置了窗口、框架、输入框、下拉列表、按钮和标签等控件。
- def update_memory_display():
- memory_tree.delete(*memory_tree.get_children())
- display_format = display_format_var.get()
- if not memory_cache['data']:
- return
- read_data = memory_cache['data']
- bytes_per_row = 0x10
- num_cols = bytes_per_row >> {'byte': 0, 'halfword': 1, 'word': 2}[display_format]
- row_format = {'byte': '{:02X}', 'halfword': '{:04X}', 'word': '{:08X}'}[display_format]
- for i in range(0, len(read_data), bytes_per_row):
- row_data = read_data[i:i + bytes_per_row]
- ascii_representation = ''.join(chr(b) if 0x20 <= b <= 0x7E else '.' for b in row_data)
- if display_format == 'halfword':
- row_data = [int.from_bytes(row_data[j:j + 2], byteorder='little') for j in range(0, len(row_data), 2)]
- elif display_format == 'word':
- row_data = [int.from_bytes(row_data[j:j + 4], byteorder='little') for j in range(0, len(row_data), 4)]
- addr = f"{memory_cache['address'] + i:08X}"
- hex_data = ' '.join(row_format.format(byte).rjust(8 if display_format == 'word' else 5) for byte in row_data[:num_cols])
- memory_tree.insert('', 'end', text=addr, values=[hex_data, ascii_representation])
- def change_display_format(new_format):
- display_format_var.set(new_format)
- update_memory_display()
- # Set up the GUI
- root = tk.Tk()
- root.title("Memory Reader for APM32F411VC TinyBoard")
- root.rowconfigure(1, weight=1)
- root.columnconfigure(0, weight=1)
- frame = ttk.Frame(root, padding="10")
- frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
- frame.columnconfigure(1, weight=1)
- # Device selection combobox
- device_label = ttk.Label(frame, text="DAPlink Device:")
- device_label.grid(row=0, column=0, sticky=tk.W)
- device_cb = ttk.Combobox(frame, width=10, postcommand=update_device_list)
- device_cb.grid(row=0, column=1, sticky=(tk.W, tk.E))
- # 启动便获取一次支持的设备列表,
- update_device_list()
- # Address input entry with default placeholder text
- address_label = ttk.Label(frame, text="Address (hex):")
- address_label.grid(row=1, column=0, sticky=tk.W)
- address_entry = ttk.Entry(frame, width=10)
- address_entry.insert(0, "08000000")
- # address_entry.grid(row=1, column=1, sticky=(tk.W, tk.E))
- address_entry.grid(row=1, column=1, sticky=(tk.W, tk.E))
- # Size input entry with default placeholder text
- size_label = ttk.Label(frame, text="Size (hex):")
- size_label.grid(row=1, column=2, sticky=tk.W)
- size_entry = ttk.Entry(frame, width=10)
- size_entry.insert(0, "1000")
- size_entry.grid(row=1, column=3, sticky=(tk.W, tk.E))
- # Read memory button
- read_button = ttk.Button(frame, text="Read Memory", command=read_memory)
- read_button.grid(row=1, column=4, columnspan=2)
- # Output label for messages
- frame.rowconfigure(2, minsize=10)
- frame.rowconfigure(4, minsize=10)
- output_label = ttk.Label(frame, text="",anchor="e")
- output_label.grid(row=3, column=0,sticky="we", columnspan=10)
- # Save memory to file button
- save_button = ttk.Button(frame, text="Save as", command=save_memory_to_file)
- save_button.grid(row=1, column=7, columnspan=2, sticky=(tk.E, tk.W))
- # Display format buttons
- display_format_var = tk.StringVar(value='byte')
- formats_frame = ttk.Frame(frame)
- formats_frame.grid(row=5, column=0 , columnspan=20,sticky="e")
- byte_btn = ttk.Button(formats_frame, text="Byte View", command=lambda: change_display_format('byte'))
- byte_btn.pack(side=tk.LEFT, padx=5)
- halfword_btn = ttk.Button(formats_frame, text="Halfword View", command=lambda: change_display_format('halfword'))
- halfword_btn.pack(side=tk.LEFT, padx=5)
- word_btn = ttk.Button(formats_frame, text="Word View", command=lambda: change_display_format('word'))
- word_btn.pack(side=tk.LEFT, padx=5)
- # Treeview widget for memory display with an extra ASCII column
- memory_tree = ttk.Treeview(root, columns=('data', 'ascii'), show='tree headings')
- memory_tree.heading('data', text='Data (Hexadecimal)')
- memory_tree.heading('ascii', text='ASCII')
- memory_tree.column('data', width=300, stretch=True)
- memory_tree.column('ascii', width=150, stretch=True)
- memory_tree.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
- # Make sure the data is displayed in a monospaced font
- style = ttk.Style()
- style.configure('Treeview', font=('Courier', 10))
- # Scrollbar for the Treeview widget
- scrollbar = ttk.Scrollbar(root, orient='vertical', command=memory_tree.yview)
- scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S))
- memory_tree.configure(yscrollcommand=scrollbar.set)
4 运行效果
完成以上代码后,我们点击运行。
读取并保存:
读取GPIOE的寄存器:
5 总结
这个程序也是心血来潮,一边学习一边完成的,里面少考虑了很多异常情况或者需求:
1. 支持的芯片读取区域的校验,超越区域需要保持;
2. 保存成hex、s19等文件;
3. 支持芯片选型。
代码在这里
APM32F411_python_pyocd_read_Memory.zip
(2.85 KB, 下载次数: 11)
,大家也可以试试看,欢迎大家在评论区一起讨论~。