只谈思路,莫谈效益。
单片机开发中,会经常遇到用SPI或UART驱动某MCU的场景,通常会购买对应的通信工具,虽工具大多开源,但终究显些繁琐,并且功能比较单一,可操作性不强。因此用较廉价的STM32F103系列实现诸多日常开发需要的功能。最终实现SPI、UART、IIC、自定义通信协议、PWM输出、逻辑分析仪(因该系列性能较低,故采样率不高)。
STM32F103(下文称Tools)系列具备USB虚拟串口功能,故PC与Tools以USB通信。PC端可视化工具功能并不复杂,故用Python+Tkinter实现。
此篇中,仅实现SPI相关部分,下图为SPI部分功能截图。源码链接放在文末,文中仅对关键部分详细说明。
# app.py
"""
PC向Tools发送数据时,需要区分编码,即Hex或Utf8编码,其他编码亦可
同样,Tools也需要设定好编码格式,精确点是指用Keil编辑Tools代码时,Keil需要设定的编码,只有设定好正确的编码格式,编译时字符串才会以正确的编码被编译。
"""
def func_com_write(self, content: str | list, send_type: int = 0):
if (not self.seri) or (not self.seri.is_open):
self.log_info("Com is not connected")
return
try:
# Tools以接收到 \r\n(0x0D, 0x0A) 作为一次传输的结束
# utf8
if send_type == ToolsFlag.SPI_TYPE_UTF8:
content += "\r\n"
self.seri.write(content.encode("utf-8"))
pass
# hex
elif send_type == ToolsFlag.SPI_TYPE_HEX:
content.extend([0x0D, 0x0A])
self.seri.write(content)
except serial.serialutil.SerialTimeoutException:
self.btn_open_com.config(text="Open")
self.log_info("Com is not connected")
self.func_com_disconnect()
except serial.serialutil.SerialException:
self.btn_open_com.config(text="Open")
self.log_info("Com connect failed")
self.func_com_disconnect()
return
# 接受数据时以bytes流接受,之后以何种编码显示再单独处理。
def func_com_read(self, recv_type: int = 0):
if (not self.seri) or (not self.seri.is_open):
self.log_info("Com is not connected")
return bytearray()
com_print = bytearray()
try:
while True:
t = self.seri.read(1)
if t:
com_print += t
else:
break
except serial.serialutil.SerialTimeoutException:
self.btn_open_com.config(text="Open")
self.log_info("Com is not connected")
self.func_com_disconnect()
except serial.serialutil.SerialException:
self.btn_open_com.config(text="Open")
self.log_info("Com connect failed")
self.func_com_disconnect()
return com_print
# 此函数会从Tools通过SPI接受若干字节数据,在此处理SPI的编码转换
def func_btn_spi_recv(self, btn: Button, ipt: Entry, idx: int = 0, type_: int = 0):
if not self.seri or not self.seri.is_open:
size = ipt.get().strip().split(" ")[0]
if re.search(r"[^0-9]", size) or len(size) == 0:
self.log_info("Wrong input")
return
size = int(size)
cntt_ls = [0x00, 0x00, (ToolsEvent.SPI_CMD_RECVDATA >> 8) & 0xFF, ToolsEvent.SPI_CMD_RECVDATA & 0xFF, size]
self.func_spi_send(cntt_ls, 1)
res = self.func_spi_recv(size)
if (self.spi_recv_type.get() == "utf8"):
# utf8 兼容 ASCII编码,但ASCII编码采用有符号形式,即仅有0x00~0x7F为有效字符,所以为防止数据显示丢失,故解析异常时,提示以hex显示
try:
self.log_info(res.decode("utf-8").replace('\x00', ''))
except:
self.log_info("Contains invalid characters, please use hex mode")
elif self.spi_recv_type.get() == "hex":
# res[:10] 为Tools的发送头,Received: 共计10个字节
self.log_info(res[:10].decode("utf-8") + "".join("0x{:0>2X} ".format(he) for he in res[10:]))
pass