市面上針對Cortex-M處理器的下載器,有很多是基于CMSIS-DAP演變而來,比如:e-Link、GD-Link等。
之前給大家分享過自制ST-Link的教程,今天繼續為大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下載器。
關于CMSIS-DAP
CMSIS-DAP是支持訪問
CMSIS-DAP固件作為源代碼提供,并且可以完全配置為新的調試單元。
這里相關的更多內容,可以參看我之前分享過的一篇文章:Cortex-M軟件接口標準CMSIS那些重要內容。
CMSIS-DAP固件
CMSIS-DAP固件Arm以源碼形式提供,不存在版權問題(因為針對Arm Cortex處理器,他們還希望更多人使用)。
1.固件版本
目前有兩個版本:
版本1配置使用USB HID作為與主機PC的接口。
版本2配置使用WinUSB作為與主機PC的接口,并提供高速SWO跟蹤流。
2.源碼位置
目前源碼提供在Keil MDK V5版本,安裝好Keil MDK,你在安裝目錄下就能找到源碼。
C:Keil_v5ARMPacksARMCMSIS5.7.0CMSISDAPFirmware
(目前MDK V5.33,CMSIS版本為5.7.0)
3.源碼描述
從文件目錄可以看出,官方源碼提供了一些模板和例子。
目前只提供了LPC處理器的例子,如果你有這個處理器對應的板卡,可以直接使用該源碼做一個下載調試器。(下面就針對于LPC這個例子進行“改裝”)
配置
利用STM32CubeMX圖形化配置工具,幫助用戶選擇單片機引腳的功能,并自動生成外設初始化代碼。配置了USB、SPI1和USART1,并選擇了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置為JTAG調試需要的引腳。GPIOC13用于驅動單片機上的LED燈。
ST公司也開發了他們自己的JTAG調試器——STLink。當然它并不是必要的,你也可以使用J-Link或者其他種類的調試器。STLink的驅動和程序可以在ST官網上下載。在網站里還有一個基于Eclipse開發環境開發的IDE,STM32CubeMX也被包含其中。我選擇的IDE是基于CodeBlocks的Embitz,IDE中的arm_none_eabi_gcc版本是5.4.1。在我完成我的CMSIS-DAP之前,我必須使用STLink來調試我的代碼?,F在我在使用新做出來的CMSIS-DAP結合OpenOCD進行日常的開發。
從CMSIS-DAP的源碼開始
源碼可以在官網下載
也可以直接在Keil MDK安裝目錄下獲取:
C:Keil_v5ARMPacksARMCMSIS5.7.0CMSISDAP
將從示例V1的頭文件 DAP_config.h 開始分析。
選擇LPC-Link-II V1作為我的參考是因為它是通過USB HID實現的(V2是通過WinUSB實現)。我分析的第一個文件是DAP_Config.h。第一個關鍵位置如下:
#ifdef _RTE_
#include "RTE_Components.h"
#include CMSIS_device_header
#else
#include "device.h"
#endif
不用RTE的相關文件,創建我自己的device.h。 我將參數
CPU_CLOCK
重定義為72000000
(72MHz)。根據文件里的注釋,參數DAP_PACKET_SIZE
必須重新定義為64U
。我把SWO_UART
改為0,這讓我的工作輕松不少。參數TIMESTAMP_CLOCK
也要重定義為72000000
。LPC-Link-II使用Cortex-M3 的 DWT模塊實現時間戳(TIMESTAMP),這也是為什么我想在CubeMX中嘗試配置STM32F103的DWT。最后我自己寫了一小段代碼來實現這個功能(在device.c
中):CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; /** * On Cortex-M7 core there is a LAR register in DWT domain. * Any time we need to setup DWT registers, we MUST write * 0xC5ACCE55 into LAR first. LAR means Lock Access Register. */ DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; 我定義的引腳和NXP LPCxx的完全不同。我為STM32F103重寫了所有的引腳的操作代碼。在
DAP_Config.h
這個文件中還有一些奇怪的地方,比如:// SWCLK/TCK I/O pin ------------------------------------- /** SWCLK/TCK I/O pin: Get Input. eturn Current status of the SWCLK/TCK DAP hardware I/O pin. */ __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN (void) { return ((LPC_GPIO_PORT->PIN[PIN_SWCLK_TCK_PORT]>> PIN_SWCLK_TCK_BIT) & 1U); }
我不明白為什么DAP的代碼里需要讀取
SWCLK/TCK
引腳的電平,這個引腳明明是被配置為推挽輸出來產生時鐘信號輸送給JTAG從機的。從上面列出來的代碼可以看出,它是希望返回當前引腳的電平值,我的實現方式稍微有點不同:__STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN (void) { return (uint32_t)(JTAG_TCK_GPIO_Port->ODR & JTAG_TCK_Pin ? 1:0); }
我返回的是當前引腳的輸出值。我不確定這是否正確。在整個代碼中,這句話只被
DAP.c
中的一個叫DAP_SWJ_Pins
的函數調用了兩次。我猜測DAP_SWJ_Pins
這個函數是用來測試IO口是否工作正常的。
另一個奇怪的地方是PIN_nRESET_OUT
:/** nRESET I/O pin: Set Output. param bit target device hardware reset pin status: - 0: issue a device hardware reset. - 1: release device hardware reset. */ __STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) { if (bit) { LPC_GPIO_PORT->DIR[PIN_nRESET_PORT] &= ~(1U <<PIN_nRESET_BIT); LPC_GPIO_PORT->CLR[PIN_nRESET_OE_PORT] = (1U <<PIN_nRESET_OE_BIT); } else { LPC_GPIO_PORT->SET[PIN_nRESET_OE_PORT] = (1U <<PIN_nRESET_OE_BIT); LPC_GPIO_PORT->DIR[PIN_nRESET_PORT] |= (1U <<PIN_nRESET_BIT); } } 為猜測
release
的意思可能是將nRESET
引腳重新配置為一個失能的引腳。我的代碼如下:__STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if ((bit & 1U) == 1) { JTAG_nRESET_GPIO_Port->BSRR = JTAG_nRESET_Pin; GPIO_InitStruct.Pin = JTAG_nRESET_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct); } else { JTAG_nRESET_GPIO_Port->BRR = JTAG_nRESET_Pin; GPIO_InitStruct.Pin = JTAG_nRESET_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct); } }
給你一個特殊的提示:
- 你可能在一些地方看見我寫了
if(...)
之類的代碼,像下面這樣:
if ((bit & 1U) == 1) { ... } else { ... }
我使用參數bit
的最低位是因為有時候這個bit
既不是0也不是1,它可能是2或者其他奇怪的值,所以不要把你的代碼寫成這樣:
if (bit) { ... } else { ... }
這會出問題的!
- osObjects.h
#ifndef __osObjects_h__ #define __osObjects_h__ #include "cmsis_os2.h" #ifdef osObjectsExternal extern osThreadId_t DAP_ThreadId; #else extern osThreadId_t DAP_ThreadId; osThreadId_t DAP_ThreadId; #endif extern void DAP_Thread (void *argument); extern void app_main (void *argument); #endif /* __osObjects_h__ */ 這是一個很簡單的頭文件。但它引用了另一個叫做
cmsis_os2.h
的頭文件,這是CMSIS庫的一部分,但我沒有沒有從CMSIS庫中復制到我的工程中,因為并不是其中所有的內容我都需要。我選擇寫一個“假”的cmsis_os2.h
頭文件而不是直接使用原有的頭文件。這里還有另一個叫做DAP.h
的頭文件,它屬于DAP的核心模塊,在這里面引用了cmsis_compiler.h
文件,這也是CMSIS庫的一部分。毫無疑問,我也寫了一個“假”的cmsis_compiler.h
。分析到現在,我需要創建三個頭文件(device.h
&cmsis_os2.h
&cmsis_compiler.h
)來實現我的DAP工程。
接下來我會對main.c
和USBD_User_HID_0.c
做一些一些簡單的介紹。- main.c
rl_usb.h
文件。于是我還需要一個頭文件來定義一些關于USB通信的函數和參數。這里有一些來自CMSIS RTOS庫的函數,其中最重要的一個是osThreadNew
,在我的工程中我把它實現如下:osThreadId_t osThreadNew(void (*func)(void *), void * n, void * ctx) { (void)n; (*func)(ctx); return 0; } 我直接“跳轉”到本需要被創建的線程函數中,這就意味著
main.c
中的osKernelGetState
&osKernelStart
&osDelay
三個函數永遠不會被執行。下一個重要的函數是USBD_Configured
,我將在使用STM32CubeMX生成初始化代碼那一節解釋這個函數。- USBD_User_HID_0.c
RTEUSBUSBD_Config_HID_0.h
并在我自己的rl_usb.h
中重新定義了USBD_HID0_OUT_REPORT_MAX_SZ
&USBD_HID0_IN_REPORT_MAX_SZ
兩個參數。
USB HID通信的核心是由兩個接口中斷函數管理的兩個循環隊列:int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf) { (void)rid; switch (rtype) { case HID_REPORT_INPUT: ... break; } return (0); }
bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len) { (void)req; (void)rid; switch (rtype) { case HID_REPORT_OUTPUT: ... break; } return true; } 當上位機向DAP發送
OUTPUT REPORT
報文后,DAP會調用USBD_HID0_SetReport
函數,該參數的輸入形參rtype
必須為HID_REPORT_OUTPUT
。當DAP成功向上位機發送INPUT REPORT
報文時,函數USB_HID0_GetReport
被調用,該函數的輸入形參rtype
必須為HID_REPORT_INPUT
,并且形參req
必須為USBD_HID_REQ_EP_INT
。這意味著我們所有的報文必須通過64B數據包大小的USB中斷端點傳輸。
線程DAP_Thread
只是一個命令判斷選擇器。在這個函數中有一個很重要的語句:USBD_HID_GetReportTrigger(0U, 0U, USB_Response[n], DAP_PACKET_SIZE); 我們必須實現一個叫做
USBD_HID_GetReportTrigger
的函數來想上位機發送INPUT REPORT
。
使用STM32CubeMX生成初始化代碼
在我的單片機上有一個8MHz的晶振,所以我選擇HSE為時鐘信號源。PLLMul
配置為x9
,得到72MHz的PLLCLK
,提供給CPU和AHB/APB2總線,提供給APB1總線的PCLK1
配置為36MHz。USB預分頻配置為1.5分頻,得到48MHz的USB時鐘。SPI1配置為Full-Duplex Master
,舍去NSS
信號,USART1配置為Asynchronous
。USB設備進一步配置為Custom HID Class
,USBD_CUSTOMHID_OUTREPORT_BUF_SIZE
設置為64 Bytes。
注意: 我沒有修改設備的VID和PID。但我猜測有些上位機軟件會檢測這兩個ID如果你發現你的軟件不能識別我這個CMSIS-DAP,或許你需要恰當的VID和PID。可以試試示例代碼中的VID/PID,它在一個叫做
USBD_Config_0.c
的文件中,我的工程中沒有這個文件。有STM32CubeMX生成的代碼需要一些修改。在
usbd_customhid.h
中,CUSTOM_HID_EPIN_SIZE
和CUSTOM_HID_EPOUT_SIZE
必須設置為0x40U
。我把CUSTOM_HID_FS_BINTERVAL
改為0x01
來嘗試提升HID的通信速度。在
_USBD_CUSTOM_HID_Itf
結構體中,我新增了一個成員:typedef struct _USBD_CUSTOM_HID_Itf { uint8_t *pReport; int8_t (* Init)(void); int8_t (* DeInit)(void); int8_t (* OutEvent)(uint8_t event_idx, uint8_t state); /* I add an extra interface func below. Zach Lee */ int8_t (* InEvent)(uint8_t event_idx, uint8_t state); } USBD_CUSTOM_HID_ItfTypeDef; 當
INPUT REPORT
報文成功發給上位機時,InEvent
函數應當被調用,所以usbd_customhid.c
中的USBD_CUSTOM_HID_DataIn
函數需要修改如下:static uint8_t USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) { /* Ensure that the FIFO is empty before a new transfer, this condition could be caused by a new transfer before the end of the previous transfer */ USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData; hhid->state = CUSTOM_HID_IDLE; /* I add a new interface func in the structure USBD_CUSTOM_HID_ItfTypeDef. Zach Lee */ ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->InEvent(hhid->Report_buf[0], hhid->Report_buf[1]); return USBD_OK; } 設備描述符
CUSTOM_HID_ReportDesc_FS
被定義在usbd_suctom_hid_if.c
中,我定義了一個簡單的描述符:/** Usb HID report descriptor. */ __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END = { /* USER CODE BEGIN 0 */ /* A minimal Report Desc with INPUT/OUTPUT/FEATURE report. Zach Lee */ 0x06,0x00,0xFF, /* Usage Page (vendor defined) ($FF00) global */ 0x09,0x01, /* Usage (vendor defined) ($01) local */ 0xA1,0x01, /* Collection (Application) */ 0x15,0x00, /* LOGICAL_MINIMUM (0) */ 0x25,0xFF, /* LOGICAL_MAXIMUM (255) */ 0x75,0x08, /* REPORT_SIZE (8bit) */ // Input Report 0x95,64, /* Report Length (64 REPORT_SIZE) */ 0x09,0x01, /* USAGE (Vendor Usage 1) */ 0x81,0x02, /* Input(data,var,absolute) */ // Output Report 0x95,64, /* Report Length (64 REPORT_SIZE) */ 0x09,0x01, /* USAGE (Vendor Usage 1) */ 0x91,0x02, /* Output(data,var,absolute) */ // Feature Report 0x95,64, /* Report Length (64 REPORT_SIZE) */ 0x09,0x01, /* USAGE (Vendor Usage 1) */ 0xB1,0x02, /* Feature(data,var,absolute) */ /* USER CODE END 0 */ 0xC0 /* END_COLLECTION */ }; 可能
Feature Report
在CMSIS-DAP中不是必要的,就留著它吧。
我在這個C文件中還實現了一個新的接口函數CUSTOM_HID_InEvent_FS
:static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state); /* An extra interface func. */ USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS = { CUSTOM_HID_ReportDesc_FS, CUSTOM_HID_Init_FS, CUSTOM_HID_DeInit_FS, CUSTOM_HID_OutEvent_FS, /* I add an extra interface func below. Zach Lee */ CUSTOM_HID_InEvent_FS };
extern void USBD_OutEvent(void); /* Implemented in file "device.h" */ static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state) { /* USER CODE BEGIN 6 */ USBD_OutEvent(); /* OUTPUT REPORT was received. Zach Lee */ return (USBD_OK); /* USER CODE END 6 */ } extern void USBD_InEvent(void); /* Implemented in file "device.h" */ static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state) { /* USER CODE BEGIN extra */ USBD_InEvent(); /* INPUT REPORT has been sent. Zach Lee */ return (USBD_OK); /* USER CODE END extra */ }
CUSTOM_HID_Init_FS
和CUSTOM_HID_DeInit_FS
兩個函數被實現為使能/失能DAP功能:extern void USBD_HID0_Initialize (void); static int8_t CUSTOM_HID_Init_FS(void) { /* USER CODE BEGIN 4 */ USBD_HID0_Initialize(); /* Initialize USB communication of DAP. Zach Lee */ return (USBD_OK); /* USER CODE END 4 */ } extern void USBD_HID0_Uninitialize (void); static int8_t CUSTOM_HID_DeInit_FS(void) { /* USER CODE BEGIN 5 */ USBD_HID0_Uninitialize(); /* Uninitialize. Zach Lee */ return (USBD_OK); /* USER CODE END 5 */ }
編寫將所有源代碼關聯起來的橋梁
為了移除CMSIS RTOS,我寫了一些函數來模擬RTOS:
/** * Replace CMSIS RTOS api */ static volatile int osFlags; /* Use "volatile" to prevent GCC optimizing the code. */ void osKernelInitialize(void) { osFlags = 0; return; } int osThreadFlagsWait(int mask, int b, int c) { (void)b; (void)c; int ret; while((osFlags&mask) == 0) { ; } ret = osFlags; osFlags &= ~mask; return ret; } void osThreadFlagsSet(int tid, int f) { (void)tid; osFlags |= f; return; } 函數
USBD_Configured
和USBD_HID_GetReportTrigger
實現如下:intUSBD_Configured(int n){ (void)n; return(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED ?1:0);}
void USBD_HID_GetReportTrigger(int a, int b, void * report, int len) { (void)a; (void)b; if (USBD_OK != USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len)) { ; } return; } 函數
USBD_CUSTOM_HID_SendReport
是由STM32CubeMX生成的,它被定義在usbd_customhid.c
中,我自己的事件句柄如下:bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len); void USBD_OutEvent(void) { USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData; USBD_HID0_SetReport(HID_REPORT_OUTPUT, 0, 0, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE); } int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf); void USBD_InEvent(void) { int32_t len; USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData; if ((len=USBD_HID0_GetReport(HID_REPORT_INPUT, USBD_HID_REQ_EP_INT, 0, hhid->Report_buf)) > 0) { USBD_HID_GetReportTrigger(0, 0, hhid->Report_buf, len); } }
注意: 在DAP.h
中,這里有個名為PIN_DELAY_SLOW
的函數,它原本的實現是這樣的:
__STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) { uint32_t count; count = delay; while (--count); }
這里的空循環while (–count);
會被GCC優化。我在StackOverflow中找到了一個好點子,它能正常工作但不是太合適,你有更好的方法嗎?
__STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) { uint32_t count; count = delay; while (--count) { /** * Empty loop will be totally omitted by GCC. * Search "How to prevent GCC from optimizing out a busy wait loop?" @ StackOverflow. * This solution isn't portable. Zach Lee */ __ASM(""); } }
至此,相關源碼就介紹完畢。
-
處理器
+關注
關注
68文章
19159瀏覽量
229113 -
STM32
+關注
關注
2266文章
10871瀏覽量
354787 -
下載器
+關注
關注
1文章
62瀏覽量
12802 -
Cortex-M
+關注
關注
2文章
227瀏覽量
29726
原文標題:基于STM32自制CMSIS-DAP下載器
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論