(一) pyocd
(1) 什麼是pyocd
pyocd 是 arm 開發的一個 python 包(python package),該軟體包可以使用多種USB偵錯程式對 arm cortex-M 微控制器進行除錯、程式設計(燒錄程式)。該軟體包還是跨平臺的,支援Linux、Mac、Windows。
也就是說,可以通過 pyocd 使用一些偵錯程式來除錯、擦除、燒錄基於 arm cortex-M(M0/M3/M4/M7/M23/M33) 核心的微控制器。目前支援 Daplink、ST-Link、jlink。
目前從 pyocd github 上看到的,pyocd 內部(預設)支援70多個常用的微控制器,但是通過使用 CMSIS-Packs 幾乎支援所有基於 comtex-m 核心的微控制器。
這裡不關注 pyocd 除錯功能,只關注把 pyocd 用作上位機來操作 MCU 的功能,可以通過兩種方式使用 pyocd:
- pyocd 提供了命令列工具,使用這工具可以用來除錯、燒錄、擦除 MCU
- pyocd 提供了python API,可是使用這些API來實現控制 MCU(讀取MCU暫存器、暫停、執行、復位MCU等)
(2) 安裝
pyocd 支援的系統: windows、linux、Mac
由於 pyocd 是基於 python,所以需要先安裝 python,pyocd 支援的 python 版本為:Python 3.6.0 or later.
安裝方法有:
- 下載 pyocd 的原始碼安裝
從 GitHub 獲取原始碼:
git clone https://github.com/mbedmicro/pyOCD.git
進入pyOCD目錄,使用如下命令安裝:
python setup.py install
- 通過pip安裝
pip install -U pyocd
python3 -mpip install --pre -U git+https://github.com/pyocd/pyOCD.git@develop
參考:https://pypi.org/project/pyocd/
(3) Libusb
daplink V1 使用的是 HID 協議,只要安裝pyocd是可以用的,如果使用 daplink V2、ST-Link、j-link 的話,需要安裝 libusb,pyocd 裡面的文件給出了安裝方法。
我的做法是把 libusb.dll 複製到 python 安裝目錄:
(二)如何使用pyocd命令列工具
首先開啟命令列,windows 中可以是命令提示符或者 powershell,如下:
如果安裝了 git,也可以使用 git bash,如下:
pyocd 的使用方法是在命令列中輸入:
pyocd + 子命令 + 引數
在命令列中輸入 pyocd 或者 pyocd -h 回車,如下圖,就會輸出 pyocd 的使用方法及 pyocd 的子命令:
根據上面說明,看下我安裝的版本:
從上圖中可以看到 0.30.2 版本有 9 個子命令,但其中 commander 跟 cmd、gdbserver 跟 gdb 是相同功能的。所以,總的來說有 9 個功能不同的子命令。分別是:
- commander跟cmd: 可互動的終端,
- erase: 擦除命令
- flash:燒錄命令
- reset:復位裝置
- gdbserver跟gdb:用於gdb除錯的
- json:以json格式輸出資訊
- list:可以列出dap-link、目標IC、特定板子的資訊
- pack:用於管理CMSIS-Pack
- server
如果想知道上述命令的用法可以使用 pyocd + 上述中的一個命令 + -h/--help,如下:
這些命令各有什麼用呢?什麼情況下要用什麼命令?這些命令怎麼配合使用?
比如,使用 j-link 給 MCU 下載韌體,一般會使用上位機 j-flash 通過 jlink 下載,首先 上位機 j-flash 能找到 j-link,然後需要選擇所下載的 MCU,然後選擇所要下載的韌體,這 3 步都設定好後,就可以下載程式了,
根據這些,依次介紹 pyocd 的命令:
- list or json:檢視能夠使用的偵錯程式、支援的 MCU
- pack:管理支援的 MCU
- flash & erase : 對 MCU 進行程式設計、擦除
(1)pyocd list 命令
從 pyocd 幫助資訊來看:
list List information about probes, targets, or boards.
可以知道 list 命令使用來檢視 偵錯程式、目標晶片、板子的資訊,
- pyocd list / pyocd list -p
列出連線到電腦上的 dap link 或者 ST link,當沒接如任何pyocd所支援的裝置時,如下:
接入了一個 daplink 跟 jlink 後如下:
(1.1) pyocd list -t/--target
列出所支援的IC,可以是內建的,也可以是通過安裝pack獲得支援的,
上面圖片展示了列出了 pyocd 內建的一部分 MCU。
我的電腦中已近安裝了 GD32F350 的pack,上圖中,GD32F350 系列後面就顯示了 pack
(1.2) pyocd list -b/--board
列出所支援的板子,如下:
(2)pyocd json 命令
json Output information as JSON.
以 json 格式輸出資訊,輸出什麼資訊呢? 看下 json 命令幫助:
從上圖可以看到 pyocd json 一共可以輸出4中資訊:probes、targets、boards、features。分別看下這4中會輸出什麼。
(2.1)pyocd list -p
當電腦沒有接入任何 pyocd 支援的裝置時:
可以看到輸出了所安裝的 pyocd 版本資訊,接了一塊ST的開發板後,如下:
可以看到,輸出的除了 pyocd 版本資訊外,還有所接板子的資訊:板子上偵錯程式的ID、板的名字等。
再試一下,接一塊沒有連線目標 MCU 的 Daplink,輸出的資訊如下:
(2.2) pyocd json -t
從幫助資訊來看,是輸出所有已知的 target,先試下pyocd json -t
命令,如下:
輸出一大堆資料,除了有pyocd 版本資訊外,就是一些MCU的資訊,有MCU的廠商、型號,還有該資訊是內建的還是來自pack。
試下能不能輸出指定 MCU 的資訊:
從上述結果來看,好像是不行。
pyocd list 跟 pyocd json 功能應該是差不多的,都是輸出一些資訊,如接入了電腦的偵錯程式資訊,系統所支援的微控制器等,只不過輸出方式不同,pyocd list 是直接輸出相關資訊,pyocd json 是以 json 格式輸出相關資訊。
(3)pyocd pack 命令
pyocd 通過兩種方法支援 MCU,一個是內建的(builtin),這個數量有限,還有一個是通過 pack 來支援,需要用pyocd 操作什麼 MCU,安裝對應的 pack , 類似 keil5 ,新安裝的 Keil5 不支援任何微控制器,如果要使用新安裝的keil 支援某型別號微控制器,需要安裝對應的軟體包。
pyocd 提供了一個內建的子命令來對pack進行管理,安裝、查詢、刪除等,來看下 pyocd pack 命令的幫助資訊:
送上圖來看,有5個功能選項:
- -c:清楚儲存在電腦上的pack資訊
- -u:更新pack索引
- -s:顯示已安裝的pack
- -f:查詢某個IC對應的pack
- -i:安裝指定的pack
pyocd 使用的 pack 有個預設的存放路勁,我電腦上為:
C:\Users\Administrator\AppData\Local\cmsis-pack-manager\cmsis-pack-manager
pyocd pack 命令就是對該目錄的檔案進行管理。
(3.1)pyocd pack -U
使用方法為:
pyocd pack -u
或者
pyocd pack --update
第一次使用該命令的時候,會在pyocd存放pack檔案的預設路勁下下載不同廠商不同系列 MCU 的 pack 的描述檔案(pdsc檔案)和 index.json、aliases.json 檔案。執行過一次之後,會從網路上更新相應的檔案。下圖是一個執行pyocd pack -u的結果,有出現錯誤。
下圖是執行了pyocd pack -u 後,pack 所在資料夾多了很多檔案:
(3.2)pyocd pack -f
使用方法為:
pyocd pack -f partnumber
該命令會顯示出對應型號 MCU 的 pack 的資訊,如下圖,顯示了 stm32f042 所對應 pack 的資訊
(3.3)pyocd pack -i
使用方法為:
pyocd pack -i partnumber
該命令安裝指定型號MCU的pack,如下圖:
不過,由於網路的問題,一般很難下載完成,跟keil下載pack一樣,非常慢,經常下載失敗,可以手動下載所需的pack,然後放到對應目錄。
(3.4)pyocd pack -s
使用方法為:
pyocd pack -s
列出所安裝的pack,如果還未安裝任何pack,該命令執行結果為:
安裝有pack後,執行結果為:
(4)pyocd flash 命令
測試硬體是 Daplink+STM32F042F4 最小系統,Daplink 我自己用 STM32F042F4做的,如下:
STM32F042F42 的測試程式是用 Cube MX 建立工程,由於板子上沒有其他外設,但是 Daplink 有 USB 轉 TTL,實現了個 STM32F042 Uart 輸出的測試程式,主要程式如下:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("STM32F042:%d\r\n",i);
i++;
HAL_Delay(1000);
}
/* USER CODE END 3 */
正常跑起來是這樣:
第一次測試,首先執行 pyocd list
檢視是否成功識別到裝置,確認能夠找到 daplink 後。
pyocd flash -h
輸出的幫助資訊看不出來 pyocd flash 應該怎麼用:
非常多選項,這裡只關注燒錄的功能,通過查閱 pyocd 的文件,瞭解到可以使用如下命令來給MCU燒錄程式:
pyocd flash -t mcu_partnumber firmware
我的 MCU 是 stm32f042f4,測試韌體是 pyocd_test.hex
,嘗試使用命令 :
pyocd flash --t stm32f042f4 ./pyocd_test.hex
來燒錄程式,結果如下:
結果顯示不支援 stm32f042f4,使用 list 檢視下目前環境下支不支援 042:
下載 stm32f042 的 pack:
可以找到 STM32F042 了,重新試下燒錄:
從這結果看是燒錄成功了,看了下板子,也成功跑起來了:
(5)pyocd erase 命令
pyocd 幫助資訊中對該指令的描述是:
erase Erase entire device flash or specified sectors.
也就是說可以使用該指令對 裝置 進行全擦或者指定扇區,pyocd earse幫助資訊如下:
我嘗試使用瞭如下命令:
(5.1) 整片擦除:-c/--chip
看了下板子已經沒有輸出了,
(5.2) 擦除扇區:-s/--sector
該命令需要加一個扇區地址,如果沒加的話,會不成功,如下是沒加地址的:
對於目標晶片,如果新增的地址不對,也會操作不成功:
新增正確的地址結果如下:
(5)使用 pyocd 操作 STM32F051
試下安裝 STM32051 的 pack,看下能不能通過安裝 STM32f051 的 pack 來使 pyocd 支援 STM32f051,首先檢視已安裝的 pack,查詢結果如下,
PS D:\project\USB\Daplink\my_dap\doc> pyocd pack -s
Vendor Pack Version
---------------------------
從上述結果來看,尚未安裝任何 pack,然後嘗試下載 stm32f051 pack:
PS D:\project\USB\Daplink\my_dap\doc> pyocd pack -i STM32F051C8Tx
Downloading packs (press Control-C to cancel):
Keil::STM32F0xx_DFP::2.0.0
等了很久,執行完上述命令後,嘗試燒錄,結果如下:
PS D:\project> pyocd flash -t stm32f051r8 .\stm32051_test.hex
0001009:CRITICAL:__main__:Failed to open CMSIS-Pack 'C:\Users\dell\AppData\Local\cmsis-pack-manager\cmsis-pack-manager\Keil\STM32F0xx_DFP\2.0.0.pack': File is not a zip file
Traceback (most recent call last):
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\target\pack\cmsis_pack.py", line 88, in __init__
self._pack_file = zipfile.ZipFile(file_or_path, 'r')
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\zipfile.py", line 1200, in __init__
self._RealGetContents()
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\zipfile.py", line 1267, in _RealGetContents
raise BadZipFile("File is not a zip file")
zipfile.BadZipFile: File is not a zip file
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\__main__.py", line 343, in run
self._COMMANDS[self._args.cmd](self)
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\__main__.py", line 470, in do_flash
options=convert_session_options(self._args.options))
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\core\helpers.py", line 241, in session_with_chosen_probe
return Session(probe, auto_open=auto_open, options=options, **kwargs)
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\core\session.py", line 175, in __init__
or Board(self, self.options.get('target_override'))
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\board\board.py", line 48, in __init__
pack_target.ManagedPacks.populate_target(target)
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\target\pack\pack_target.py", line 84, in populate_target
targets = ManagedPacks.get_installed_targets()
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\target\pack\pack_target.py", line 71, in get_installed_targets
pack = CmsisPack(pack_path)
File "C:\Users\dell\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyocd-0.23.1.dev12-py3.7.egg\pyocd\target\pack\cmsis_pack.py", line 91, in __init__
file_or_path, err)), err)
File "<string>", line 3, in raise_from
pyocd.target.pack.cmsis_pack.MalformedCmsisPackError: Failed to open CMSIS-Pack 'C:\Users\dell\AppData\Local\cmsis-pack-manager\cmsis-pack-manager\Keil\STM32F0xx_DFP\2.0.0.pack': File is not a zip file
還是不能成功下載,從上述資訊來看應該是解析stm32f0的pack失敗,到目錄C:\Users\dell\AppData\Local\cmsis-pack-manager\cmsis-pack-manager\Keil\STM32F0xx_DFP\
看了下,有檔案2.0.0.pack,20多兆。
手動從https://www.keil.com/dd2/Pack/#/eula-container
下載了Keil.STM32F0xx_DFP.2.0.0.pack
,有60多兆,對比下,問題是使用pyocd pack -i安裝的stm32f051的pack有問題。把自己下載的Keil.STM32F0xx_DFP.2.0.0.pack
重新命名為2.0.0.pack
,放到目錄C:\Users\dell\AppData\Local\cmsis-pack-manager\cmsis-pack-manager\Keil\STM32F0xx_DFP\
,然後重新燒錄:
PS D:\project\USB\Daplink\my_dap\doc> pyocd flash -t stm32f051r8 .\stm32051_test.hex
[====================] 100%
0001733:INFO:loader:Erased 3072 bytes (3 sectors), programmed 3072 bytes (3 pages), skipped 0 bytes (0 pages) at 7.12 kB/s
板子執行起來了,下載成功。
還做了實驗,還是使用STM32F051板子,跑一個很簡單的程式,控制一個LED閃爍,試了下指定晶片跟不指定晶片有什麼區別,效果怎樣,如下:
從上面結果來看,如果沒指定晶片,雖然說從結果來看,是執行了擦除操作,可是板子還在執行,LED還在閃爍,指定了晶片後,執行完任務後,LED不再閃爍了。
(三)使用 pyocd python api
這裡嘗試下使用pyocd python api來操作微控制器。
環境是:
-
PC: windows10
-
python
-
硬體
偵錯程式:一塊跑Daplink V1的STM32F042最小系統
目標板:一個STM32F042最小系統
如下:
首先匯入庫:
from pyocd.probe.aggregator import DebugProbeAggregator
from pyocd.board.board import Board
from pyocd.core.helpers import ConnectHelper
from pyocd.core.target import Target
import logging
from pyocd.core.memory_map import MemoryType
from pyocd.core.coresight_target import CoreSightTarget
from pyocd.coresight.cortex_m import CortexM
按照 pyocd 的文件,首先是建立一個 Session
,如下:
session = ConnectHelper.session_with_chosen_probe(None, None)
session.open()
然後可以獲取到當前 session 中的 board,如下:
board = session.board
print("Board MSG:")
print("Board's name:%s" % board.name)
print("Board's description:%s" % board.description)
print("Board's target_type:%s" % board.target_type)
print("Board's unique_id:%s" % board.unique_id)
print("Board's test_binary:%s" % board.test_binary)
print("Unique ID: %s" % board.unique_id)
上述程式執行結果如下:
從上述資訊來看,是可以成功是別到我的偵錯程式,但是沒有正確顯示目標晶片,除了我要輸出的資訊外,還有一段內容,說的是當前目標類晶片型別型是 cortex_m,選這型別可以進行除錯但不能燒錄,可以通過 --target 或者 target_override 選項來指定目標晶片。
修改程式碼如下,通過 target_override 指定了我的目標晶片:
session = ConnectHelper.session_with_chosen_probe(target_override="stm32f042f4")
session.open()
board = session.board
print("Board MSG:")
print("Board's name:%s" % board.name)
print("Board's description:%s" % board.description)
print("Board's target_type:%s" % board.target_type)
print("Board's unique_id:%s" % board.unique_id)
print("Board's test_binary:%s" % board.test_binary)
print("Unique ID: %s" % board.unique_id)
執行結果為:
這回顯示了我的目標晶片。
成功了獲取到了偵錯程式的資訊,現在來試下獲取當前環境下目標晶片的資訊,程式碼如下:
target = board.target
print("Part number:%s" % target.part_number)
memory_map = target.get_memory_map()
ram_region = memory_map.get_default_region_of_type(MemoryType.RAM)
rom_region = memory_map.get_boot_memory()
print("menory map:")
print(memory_map)
print("ram_region:")
print(ram_region)
print("rom_region:")
print(rom_region)
執行結果為:
從上述結果來看,是可以成功獲取到目標晶片的 part number、Flash 跟 RAM 資訊。
通過檢視 pyocd 的原始碼,target 例項有個 irq_table 屬性,看下這個屬性會輸出什麼資訊:
print("Irq:")
print(target.irq_table)
輸出如下:
試下讀取微控制器一些暫存器:
print("pc reg: 0x%X" % target.read_core_register('pc'))
print("CPUID:0x%x" % target.read32(CortexM.CPUID))
print("device id:0x%x" % target.read32(0x40015800))
print("flash size:%x KB" % target.read32(0x1FFFF7CC))
執行結果如下:
-
第一個讀取 PC 暫存器的值
這個讀出來的值究竟對不對,沒去檢視,不過從這個值的大小來看(在0x8000000到8004000之前),應該是對的。
-
第二個,讀取CPUID的值
RM0091 Reference manual (stm32f0x2 參考手冊) 中關於CPU ID 部分如下:
stm32f042是arm M0 ,根據上述內容,該晶片的CPU ID為:
ARM V0 ARMv6-M M0 Revision
0x41 0 c c20 0
也就是0x140cc200,跟讀取到的結果對的上,
-
第三個讀取裝置ID
RM0091 Reference manual (stm32f0x2 參考手冊) 中也有 裝置 ID 的內容,如下:
從文件來看,讀取出來的值也是對的,
- 第四個,讀取Flash大小
目標 IC 是 STM32F042F4,Flash 實際大小是 16K,讀出來的也是 16K
完整程式碼為:
from pyocd.probe.aggregator import DebugProbeAggregator
from pyocd.board.board import Board
from pyocd.core.helpers import ConnectHelper
from pyocd.core.target import Target
import logging
from pyocd.core.memory_map import MemoryType
from pyocd.core.coresight_target import CoreSightTarget
from pyocd.coresight.cortex_m import CortexM
session = ConnectHelper.session_with_chosen_probe(target_override="stm32f042f4")
session.open()
board = session.board
print("Board MSG:")
print("Board's name:%s" % board.name)
print("Board's description:%s" % board.description)
print("Board's target_type:%s" % board.target_type)
print("Board's unique_id:%s" % board.unique_id)
print("Board's test_binary:%s" % board.test_binary)
print("Unique ID: %s" % board.unique_id)
target = board.target
print("Part number:%s" % target.part_number)
memory_map = target.get_memory_map()
ram_region = memory_map.get_default_region_of_type(MemoryType.RAM)
rom_region = memory_map.get_boot_memory()
print("menory map:")
print(memory_map)
print("ram_region:")
print(ram_region)
print("rom_region:")
print(rom_region)
print("Irq:")
print(target.irq_table)
print("pc reg: 0x%X" % target.read_core_register('pc'))
print("CPUID:0x%x" % target.read32(CortexM.CPUID))
print("device id:0x%x" % target.read32(0x40015800))
print("flash size:%d KB" % (target.read32(0x1FFFF7CC) & 0x0000ffff))
對於這段程式碼,我試著在建立 session 的時候,把指定目標晶片的型號跟實際使用的晶片型號設定成不一樣,
第一次看到跑的結果的時候,有點懵逼,不過想了下,合理。
參考:
api_examples:pyOCD\docs\api_examples.md,即pyocd原始碼目錄下docs/api_examples.md
architecture:pyOCD\pyOCD-0.25.0\docs\architecture.md
(四)可以用 pyocd 用來做什麼
還是隻關注 pyocd 燒錄的功能,在這個點上面,可以怎麼使用 pyocd 呢?
對於 pyocd 命令,當然是直接用來燒錄了,pyocd 是跨平臺的,不管使用的是 ST-Link、J-link、Daplink,由於一般情況下使用的都是固定的幾種 MCU,把 pyocd 命令 做成指令碼檔案,如 windows 的 bat、Linux Shell 指令碼,很方面使用。
對於使用 pyocd python api,可以使用 python 做個燒錄的上位機,對於使用 ST-Link 燒錄 ST MCU、J-link 有個j-flash 來說好像是多次一舉,不過對於一些使用場景,如對於不是做微控制器開發的,如硬體工程師、做上位機開發的純軟體工程師、或者其他不懂這方面的人來說,使用 j-flash 設定選項比較多可能一時用不起來,如果使用 pyocd python api 做個上位機一鍵燒錄,就可以解決這問題。
還有對於使用 daplink 的人來說,把 daplink 用於除錯是非常方便的,不用擔心什麼時候被識別為盜版的導致用不了而耽誤工作,由於 daplink 沒有對應的上位機,要使用 daplink 燒錄的話就不方便了,pyocd python api 做個上位機就可以解決這問題,如下是我用 pyocd + PyQT 做的一個上位機給一個軟體工程師用,只能燒錄 STM32F103C8,直接把軟體給過去就可以用起來,不用教這軟體的使用方法,這還可以做的跟簡單,一鍵燒錄。
還有一些場景,如,如果機器給到了客戶,要更新韌體的話,直接把韌體給客戶,怕客戶拿到了韌體,另外找個便宜點的供應商做個硬體,不再跟你買了,豈不虧大了,如果自己做個燒錄上位機,對給過去的韌體做些加密什麼的,客戶就無法用其他燒錄軟體燒錄這韌體了,還可以加個數量限制,就不怕客戶直接用這個來生成燒錄了,或者直接把韌體放到一個伺服器裡,只給個燒錄上位機就可以燒錄,原理上這也是可以實現的。