平时写上位机,串口、USB或者网口,不论哪种方式都需要下位机的配合。也就是得开发两套程序才能实现完整的功能,无论功能繁简。所以经常是嫌麻烦,连简单的都不想弄。直到xx年xx月xx日,挖到了宝:不用写下位机就能达到同样的效果。但是这种方式也是有局限的,就是需要一个调试器,比如jlink、daplink!!!有时候需要一些简易的小工具,我就直接这样搞了,省事点,反正自己用。
原理
前面有提到如何使用pylink访问SEGGER RTT,就是程序主动的去读写MCU中SEGGER RTT控制块的数据,也就是程序(上位机)主动去操作(读写)RTT的数据(内存)。在ARM中,内存和寄存器是统一编址的,因此也可以直接去读写寄存器,能读写寄存器意味着就能直接控制外设了。而JLinkARM.dll正好提供了读写某个地址的接口,因此pylink配合jlink就可以达到预期的效果。如果使用其他调试器可以尝试使用pyOCD,应该可以达到样的效果,我还没有试过。
读写接口
pylink没有现成封装好的写接口,下面是我封装的。使用的时候添加到pylink\pylink\jlink.py文件的class JLink(object)
类中,或者可以到我的仓库下载。
读接口使用pylink提供的memory_read8
、memory_read16
、memory_read32
即可。
@connection_required
def peripheral_write8(self, addr, value):
"""Writes byte to peripheral register of target system.
Args:
self (JLink): the ``JLink`` instance
addr (int): start address to write to
value (int): the value to write to the register
Returns:
The value written to the register.
Raises:
JLinkException: on write error.
"""
res = self._dll.JLINKARM_WriteU8(addr, value)
if res != 0:
raise errors.JLinkWriteException('Error writing to %d' % addr)
return value
@connection_required
def peripheral_write16(self, addr, value):
"""Writes half-word to peripheral register of target system.
Args:
self (JLink): the ``JLink`` instance
addr (int): start address to write to
value (int): the value to write to the register
Returns:
The value written to the register.
Raises:
JLinkException: on write error.
"""
res = self._dll.JLINKARM_WriteU16(addr, value)
if res != 0:
raise errors.JLinkWriteException('Error writing to %d' % addr)
return value
@connection_required
def peripheral_write32(self, addr, value):
"""Writes word to peripheral register of target system.
Args:
self (JLink): the ``JLink`` instance
addr (int): start address to write to
value (int): the value to write to the register
Returns:
The value written to the register.
Raises:
JLinkException: on write error.
"""
res = self._dll.JLINKARM_WriteU32(addr, value)
if res != 0:
raise errors.JLinkWriteException('Error writing to %d' % addr)
return value
@connection_required
def peripheral_write64(self, addr, value):
"""Writes long word to peripheral register of target system.
Args:
self (JLink): the ``JLink`` instance
addr (int): start address to write to
value (long): the value to write to the register
Returns:
The value written to the register.
Raises:
JLinkException: on write error.
"""
# Default type is uint32_t,so specify the parameter type of the C function, otherwise get "ArgumentError"
self._dll.JLINKARM_WriteU64.argtypes = [ctypes.c_uint32, ctypes.c_uint64]
res = self._dll.JLINKARM_WriteU64(addr, value)
if res != 0:
raise errors.JLinkWriteException('Error writing to %d' % addr)
return value
例子:点灯大法
像USB转串口、转SPI、转IIC,又或者通过ADC实现桌面示波器这种就不多说了,尽情发挥想象。这里简单的演示一下用法:按键(PA0)按下LED(PB10)亮,松开则灭。MCU使用的是NUC472HI8AE。
import pylink
import time
# GPIO相关的寄存器地址,从手册上可以得到
GPIO_BA = 0x40004000
PA_MODE = GPIO_BA + 0x000
PA_PIN = GPIO_BA + 0x010
PB_MODE = GPIO_BA + 0x040
PB_DOUT = GPIO_BA + 0x048
jlink = pylink.JLink()
jlink.open()
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
jlink.connect('NUC472HI8AE')
jlink.reset(halt=False)
# 配置按键(PA0)为双向IO(带内部上拉),这里偷懒,直接将整组PA口都设为双向IO
jlink.peripheral_write32(PA_MODE, 0xFFFFFFFF)
# 配置LED(PB10)为输出
jlink.peripheral_write32(PB_MODE, 0x01 << 20)
while True:
# 读取PA引脚的值
gpio_value = jlink.memory_read32(PA_PIN, 1)
if gpio_value[0] & 0x01:
# 关闭LED
jlink.peripheral_write32(PB_DOUT, 0x01 << 10)
else:
# 点亮LED
jlink.peripheral_write32(PB_DOUT, 0x00 << 10)
# 10ms 检测一次
time.sleep(0.01)
jlink.close()
说明
可以看到,这相当于将原本在mcu上写的程序放到了PC上用python来写。例子中并没有初始化时钟这些操作,是因为使用的NUC472HI8AE上电复位后系统时钟、IO的时钟这些都直接可用,为了简化例子就没有再去配置一遍。如果使用别的MCU,比如STM32L4xx这种,上电复位后IO的时钟默认关闭的,还是得先配置好时钟。
网友评论