返回列表 发新帖我要提问本帖赏金: 60.00元(功能说明)

[APM32F4] 【技术分享】还可以这样玩?APM32F411与pyocd的火花

[复制链接]
1776|2
 楼主| kai迪皮 发表于 2023-11-21 10:49 | 显示全部楼层 |阅读模式
#申请原创# @21小跑堂

1 背景

前段时间学习了一下如何使用pyocd配合APM32F411VCTINY板在命令行下给它进行各种骚操作,在使用一段时间后就想着:pyocd是基于python的,那是不是也可以使用python脚本+pyocd使用起来呢?

完成我们的一些重复的操作的自动化(因为我比较懒),嘿嘿。想到就去做。

2 pyocd 的python api

之前有介绍pyocd的时候发现遗漏了pyocd的api没有看,它还给了利用python+pyocd的一些例子(https://pyocd.io/docs/api_examples.html)。

比如下载bin文件的例子。
image-20231120172131918.png

本文档就对近段时间我学习到的pyocd+python,基于APM32F411TINY板的一些收获。由于我也是初学python,里面的一些不科学的操作,也请大家指出斧正。此致感谢!

2.1 连接

首先是连接的API:
  1. session_with_chosen_probe()
这个api主要是控制我们选择哪个link去连接目标芯片,可以使用link的UID去指定,比如说我这里的link UID是:00350054500000144e5448590258(注:可以在CMD命令行用:pyocd list命令查看)。

image-20231120173004395.png

那我这里设置指定使用我的 Geehy CMSIS-DAP WinUSB的设置就是:
  1. ConnectHelper.session_with_chosen_probe(unique_id='00350054500000144e5448590258')
2.2 程序控制

让程序停下
  1. target.halt()
让程序继续运行
  1. target.resume()
2.3 数据读取

数据的读取指令可以使用:
  1. target.read32(address)
这个可以读取我们MCU的flash、ram、外设寄存器等内容。

我们也可以使用指令:
  1. target.read_core_register("pc")
读取我们程序的运行到的地方。

2.4 数据写入

数据的写入,我们可以使用:
  1. target.write32(address,data)
这个可以对我们MCU的fram、外设寄存器等可以直接写入内容的地址进行操作。

Q:为什么不能直接对Flash进行直接写入?

A:因为flash的写入其实是flash控制器(解锁、控制、状态等寄存器),去进行的。我们通过swd的指令只能通过操作flash的控制器,从而才能对Flash进行写入。

3 程序设计

我这里设计了两个程序,对学习到的知识进行验证。

3.1 读取PE5/6的状态

这个程序我设想的是,我的APM32F411VCTINY板已经下载了一个LED闪烁的程序,我要知道LED当前的一个状态。这个其实可以类比于一个黑盒子(芯片端),我们在不开盒子的情况下去获取我们想知道的寄存器信息。

程序的基本设计流程:

1. 连接APM32F411VC,
2. 读取GPIOE的ODATA寄存器,用于判断PE5/PE6的高低电平。
3. 输出寄存器内容,PE5/PE6的状态,以及相应的PC的内容。

程序如下:
  1. import time
  2. from pyocd.core.helpers import ConnectHelper

  3. # Replace the following string with your target device serial number
  4. TARGET_DEVICE_SERIAL_NUMBER = '00350054500000144e5448590258'

  5. # APM32F411 GPIOE base address and ODATA offset
  6. GPIOE_BASE = 0x40021000
  7. GPIOE_ODATA_OFFSET = 0x14

  8. # Connect to the target device with the specified serial number
  9. with ConnectHelper.session_with_chosen_probe(unique_id=TARGET_DEVICE_SERIAL_NUMBER) as session:
  10.     # Get the target object
  11.     board = session.board
  12.     target = board.target
  13.     # ensure the target device in the running state
  14.     target.resume()

  15.     # Compute the address of the ODATA register
  16.     gpioe_odata_address = GPIOE_BASE + GPIOE_ODATA_OFFSET

  17.     # Monitor PE5 and PE6 pin status
  18.     # Monitor 10 times
  19.     for i in range(10):  
  20.         target.halt()
  21.         odata = target.read32(gpioe_odata_address)
  22.         pc = target.read_core_register("pc")
  23.         target.resume()
  24.         pe5 = (odata >> 5) & 0x1
  25.         pe6 = (odata >> 6) & 0x1

  26.         # Print the contents of the odata read
  27.         print("odata: %s " % bin(odata))
  28.         print(f'PE5: {"High" if pe5 else "Low"}, PE6: {"High" if pe6 else "Low"}')
  29.         # Read some registers.
  30.         print("pc: 0x%X" % pc)
  31.         print("")

  32.         # Wait 0.5 seconds
  33.         time.sleep(0.5)  

程序运行(vscode)起来得到的结果如下:

image-20231120183043708.png

发现可以读取回来PE5/PE6的状态,且可以明确知道此时PC的内容。

3.2 解除/上锁APM32F411的读保护

由于我们的程序烧录进APM32F411后一般会对它进行读保护的操作,从而使得我们的程序不会被“有心人”读取**。

通过查阅APM32F411的手册,我们知道对其进行上读保护的操作的流程有:

1. 解锁选项字节编程区域;
2. 对读保护进行操作;
3. 重载选项字节。(PS:重载选项字节会引起复位,此时我们需要重新连接SWD,才能重新读取内容)

下面就根据这个流程对python脚本进行设计。
  1. import time
  2. from pyocd.core.helpers import ConnectHelper

  3. # Replace the following string with your target device serial number
  4. TARGET_DEVICE_SERIAL_NUMBER = '00350054500000144e5448590258'

  5. # APM32F411 Option Bytes related register addresses and key values
  6. FMC_OPTKEY = 0x40023C08
  7. FMC_OPTCTRL = 0x40023C14
  8. OPTCTRL_BYTE1_ADDRESS = FMC_OPTCTRL + 1  # Points to the second byte of OPTCTRL
  9. FMC_OPT_KEY1 = 0x08192A3B
  10. FMC_OPT_KEY2 = 0x4C5D6E7F
  11. OB_RDP_LEVEL_1 = 0x55  # Level 1 read protection
  12. OB_RDP_LEVEL_0 = 0xAA  # No read protection

  13. # Connect to the target MCU
  14. with ConnectHelper.session_with_chosen_probe(unique_id=TARGET_DEVICE_SERIAL_NUMBER) as session:
  15.     target = session.board.target

  16.     # Read the current value of OPTCTRL
  17.     optctrl_value = target.read32(FMC_OPTCTRL)
  18.     rdp_level = target.read8(OPTCTRL_BYTE1_ADDRESS)  # Read the second byte directly

  19.     if rdp_level != OB_RDP_LEVEL_0:
  20.         print("Target MCU is already read protected")
  21.     else:
  22.         print("Target MCU is not read protected, proceeding with read protect operation...")

  23.         # Unlock Option Bytes programming
  24.         if optctrl_value & (1 << 0):  # Check the OPTLOCK bit
  25.             target.write32(FMC_OPTKEY, FMC_OPT_KEY1)
  26.             target.write32(FMC_OPTKEY, FMC_OPT_KEY2)

  27.         optctrl_value = target.read32(FMC_OPTCTRL)
  28.         print("optctrl_value: 0x%X" % optctrl_value)

  29.         # Set the read protection level (only modify the second byte)
  30.         target.write8(OPTCTRL_BYTE1_ADDRESS, OB_RDP_LEVEL_1)

  31.         # Start the Option Bytes programming
  32.         optctrl_value = target.read32(FMC_OPTCTRL)
  33.         optctrl_value |= (1 << 1)  # Set the OPTSTRT bit
  34.         print("optctrl_value: 0x%X" % optctrl_value)
  35.         target.write32(FMC_OPTCTRL, optctrl_value)

  36.         # Wait for programming to complete and trigger a reset
  37.         while True:
  38.             optctrl_value = target.read32(FMC_OPTCTRL)
  39.             if not (optctrl_value & (1 << 1)):  # Wait for the OPTSTRT bit to be cleared
  40.                 break

  41.         # Perform a hardware reset of the target MCU
  42.         session.probe.reset()

  43.         # Wait 0.5 seconds
  44.         time.sleep(0.5)  

  45. # Re-establish the connection after the reset
  46. with ConnectHelper.session_with_chosen_probe(unique_id=TARGET_DEVICE_SERIAL_NUMBER) as new_session:
  47.     new_target = new_session.board.target

  48.     # Verify if read protection has been successfully set
  49.     new_optctrl_value = new_target.read32(FMC_OPTCTRL)
  50.     new_rdp_level = (new_optctrl_value >> 8) & 0xFF
  51.     if new_rdp_level == OB_RDP_LEVEL_1:
  52.         print("Read protect operation successful")
  53.     else:
  54.         print("Read protect operation failed")

脚本运行结果如下:

image-20231121103643026.png

当然我也给大家准备了解锁的操作的脚本,脚本运行结果如下:

image-20231121103734116.png

4 总结

个人对python一知半解,本文仅对一些简单使用进行总结,纯属当抛砖引玉,以启迪大家更高深的**。

这里是代码: APM32F411_python_pyocd.zip (15.99 KB, 下载次数: 8)

打赏榜单

21小跑堂 打赏了 60.00 元 2023-11-21
理由:恭喜通过原创审核!期待您更多的原创作品~

评论

@21小跑堂 :感谢支持  发表于 2023-11-21 15:59
探索新的知识,利用python+pyocd,使用Python脚本进行APM32F411的简单操作,以点窥面,已经开始期待大佬的后续开发了。(蓝V用户,打赏已提升)  发表于 2023-11-21 15:16
您需要登录后才可以回帖 登录 | 注册

本版积分规则

43

主题

292

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部