你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32F103自制CMSIS-DAP下载器

[复制链接]
STMCU-管管 发布时间:2020-12-2 12:35
基于STM32F103自制CMSIS-DAP下载器


市面上针对Cortex-M处理器的下载器,有很多是基于CMSIS-DAP演变而来,比如:e-Link、GD-Link等。


之前给大家分享过自制ST-Link的教程,今天继续为大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下载器。




1关于CMSIS-DAP
CMSIS-DAP是支持访问 CoreSight 调试访问端口(DAP)的固件规范和实现,以及各种Cortex处理器提供CoreSight调试和跟踪。
11.png
地址:
https://arm-software.github.io/CMSIS_5/DAP/html/index.html

CMSIS-DAP固件作为源代码提供,并且可以完全配置为新的调试单元。
12.png
这里相关的更多内容,可以参看我之前分享过的一篇文章:Cortex-M软件接口标准CMSIS那些重要内容。

2CMSIS-DAP固件
CMSIS-DAP固件Arm以源码形式提供,不存在版权问题(因为针对Arm Cortex处理器,他们还希望更多人使用)。




1.固件版本
目前有两个版本:
版本1配置使用USB HID作为与主机PC的接口。
版本2配置使用WinUSB作为与主机PC的接口,并提供高速SWO跟踪流。


2.源码位置
目前源码提供在Keil MDK V5版本,安装好Keil MDK,你在安装目录下就能找到源码。


C:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP\Firmware
(目前MDK V5.33,CMSIS版本为5.7.0)
13.png
3.源码描述


从文件目录可以看出,官方源码提供了一些模板和例子。

目前只提供了LPC处理器的例子,如果你有这个处理器对应的板卡,可以直接使用该源码做一个下载调试器。(下面就针对于LPC这个例子进行“改装”)

3配置
利用STM32CubeMX图形化配置工具,帮助用户选择单片机引脚的功能,并自动生成外设初始化代码。配置了USB、SPI1和USART1,并选择了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置为JTAG调试需要的引脚。GPIOC13用于驱动单片机上的LED灯。
14.png
ST公司也开发了他们自己的JTAG调试器——STLink。当然它并不是必要的,你也可以使用J-Link或者其他种类的调试器。STLink的驱动和程序可以在ST官网上下载。在网站里还有一个基于Eclipse开发环境开发的IDE,STM32CubeMX也被包含其中。我选择的IDE是基于CodeBlocks的Embitz,IDE中的arm_none_eabi_gcc版本是5.4.1。在我完成我的CMSIS-DAP之前,我必须使用STLink来调试我的代码。现在我在使用新做出来的CMSIS-DAP结合OpenOCD进行日常的开发。

4从CMSIS-DAP的源码开始
源码可以在官网下载:


https://github.com/ARM-software/CMSIS_5

也可以直接在 Keil MDK 安装目录下获取:


C:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP

将从示例V1的头文件 DAP_config.h 开始分析。

选择LPC-Link-II V1作为我的参考是因为它是通过USB HID实现的(V2是通过WinUSB实现)。我分析的第一个文件是DAP_Config.h。第一个关键位置如下:

  1. #ifdef _RTE_
  2. #include "RTE_Components.h"
  3. #include CMSIS_device_header
  4. #else
  5. #include "device.h"  
  6. #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中):
  1. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  2. /**
  3.   * On Cortex-M7 core there is a LAR register in DWT domain.
  4.   * Any time we need to setup DWT registers, we MUST write
  5.   * 0xC5ACCE55 into LAR first. LAR means Lock Access Register.
  6.   */
  7. DWT->CYCCNT = 0;
  8. DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
复制代码
我定义的引脚和NXP LPCxx的完全不同。我为STM32F103重写了所有的引脚的操作代码。在DAP_Config.h这个文件中还有一些奇怪的地方,比如:
  1. // SWCLK/TCK I/O pin -------------------------------------

  2. /** SWCLK/TCK I/O pin: Get Input.
  3. \return Current status of the SWCLK/TCK DAP hardware I/O pin.
  4. */
  5. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {
  6.   return ((LPC_GPIO_PORT->PIN[PIN_SWCLK_TCK_PORT]>> PIN_SWCLK_TCK_BIT) & 1U);
  7. }
复制代码
我不明白为什么DAP的代码里需要读取SWCLK/TCK引脚的电平,这个引脚明明是被配置为推挽输出来产生时钟信号输送给JTAG从机的。从上面列出来的代码可以看出,它是希望返回当前引脚的电平值,我的实现方式稍微有点不同:
  1. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {
  2.   return (uint32_t)(JTAG_TCK_GPIO_Port->ODR & JTAG_TCK_Pin ? 1:0);
  3. }
复制代码
我返回的是当前引脚的输出值。我不确定这是否正确。在整个代码中,这句话只被DAP.c中的一个叫DAP_SWJ_Pins的函数调用了两次。我猜测DAP_SWJ_Pins这个函数是用来测试IO口是否工作正常的。


另一个奇怪的地方是PIN_nRESET_OUT:
  1. /** nRESET I/O pin: Set Output.
  2. \param bit target device hardware reset pin status:
  3.            - 0: issue a device hardware reset.
  4.            - 1: release device hardware reset.
  5. */
  6. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {
  7.   if (bit) {
  8.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    &= ~(1U <<PIN_nRESET_BIT);
  9.     LPC_GPIO_PORT->CLR[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);
  10.   } else {
  11.     LPC_GPIO_PORT->SET[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);
  12.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    |=  (1U <<PIN_nRESET_BIT);
  13.   }
  14. }
复制代码
为猜测release的意思可能是将nRESET引脚重新配置为一个失能的引脚。我的代码如下:
  1. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {
  2.   GPIO_InitTypeDef GPIO_InitStruct = {0};

  3.   if ((bit & 1U) == 1) {
  4.     JTAG_nRESET_GPIO_Port->BSRR = JTAG_nRESET_Pin;

  5.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
  6.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  7.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  8.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  9.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
  10.   } else {
  11.     JTAG_nRESET_GPIO_Port->BRR = JTAG_nRESET_Pin;

  12.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
  13.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  14.     GPIO_InitStruct.Pull = GPIO_PULLUP;
  15.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  16.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
  17.   }
  18. }
复制代码
给你一个特殊的提示:


你可能在一些地方看见我写了if(...)之类的代码,像下面这样:
  1.   if ((bit & 1U) == 1) {
  2.   ...
  3.   } else {
  4.   ...
  5.   }
复制代码
我使用参数bit的最低位是因为有时候这个bit既不是0也不是1,它可能是2或者其他奇怪的值,所以不要把你的代码写成这样:
  1.   if (bit) {
  2.   ...
  3.   } else {
  4.   ...
  5.   }
复制代码

这会出问题的!



osObjects.h
  1. #ifndef __osObjects_h__
  2. #define __osObjects_h__

  3. #include "cmsis_os2.h"

  4. #ifdef osObjectsExternal
  5. extern osThreadId_t DAP_ThreadId;
  6. #else
  7. extern osThreadId_t DAP_ThreadId;
  8.        osThreadId_t DAP_ThreadId;
  9. #endif

  10. extern void DAP_Thread (void *argument);

  11. extern void app_main (void *argument);

  12. #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,在我的工程中我把它实现如下:
  1. osThreadId_t osThreadNew(void (*func)(void *), void * n, void * ctx)
  2. {
  3.   (void)n;

  4.   (*func)(ctx);
  5.   return 0;
  6. }
复制代码
我直接“跳转”到本需要被创建的线程函数中,这就意味着main.c中的osKernelGetState&osKernelStart&osDelay三个函数永远不会被执行。下一个重要的函数是USBD_Configured,我将在使用STM32CubeMX生成初始化代码那一节解释这个函数。


USBD_User_HID_0.c


我移除了RTE\USB\USBD_Config_HID_0.h并在我自己的rl_usb.h中重新定义了USBD_HID0_OUT_REPORT_MAX_SZ & USBD_HID0_IN_REPORT_MAX_SZ两个参数。


USB HID通信的核心是由两个接口中断函数管理的两个循环队列:
  1. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf) {
  2.   (void)rid;

  3.   switch (rtype) {
  4.     case HID_REPORT_INPUT:
  5.     ...
  6.       break;
  7.   }
  8.   return (0);
  9. }
复制代码
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len) {
  2.   (void)req;
  3.   (void)rid;

  4.   switch (rtype) {
  5.     case HID_REPORT_OUTPUT:
  6.     ...
  7.       break;
  8.   }
  9.   return true;
  10. }
复制代码
当上位机向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只是一个命令判断选择器。在这个函数中有一个很重要的语句:
  1. USBD_HID_GetReportTrigger(0U, 0U, USB_Response[n], DAP_PACKET_SIZE);
复制代码
我们必须实现一个叫做USBD_HID_GetReportTrigger的函数来想上位机发送INPUT REPORT。






5使用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结构体中,我新增了一个成员:
  1. typedef struct _USBD_CUSTOM_HID_Itf
  2. {
  3.   uint8_t                  *pReport;
  4.   int8_t (* Init)(void);
  5.   int8_t (* DeInit)(void);
  6.   int8_t (* OutEvent)(uint8_t event_idx, uint8_t state);
  7.   /* I add an extra interface func below. Zach Lee */
  8.   int8_t (* InEvent)(uint8_t event_idx, uint8_t state);
  9. } USBD_CUSTOM_HID_ItfTypeDef;
复制代码
当INPUT REPORT报文成功发给上位机时,InEvent函数应当被调用,所以usbd_customhid.c中的USBD_CUSTOM_HID_DataIn函数需要修改如下:
  1. static uint8_t  USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev,
  2.                                        uint8_t epnum)
  3. {
  4.   /* Ensure that the FIFO is empty before a new transfer, this condition could
  5.   be caused by  a new transfer before the end of the previous transfer */
  6.   USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
  7.   hhid->state = CUSTOM_HID_IDLE;

  8.   /* I add a new interface func in the structure USBD_CUSTOM_HID_ItfTypeDef. Zach Lee */
  9.   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->InEvent(hhid->Report_buf[0],
  10.                                                            hhid->Report_buf[1]);

  11.   return USBD_OK;
  12. }
复制代码
设备描述符CUSTOM_HID_ReportDesc_FS被定义在usbd_suctom_hid_if.c中,我定义了一个简单的描述符:
  1. /** Usb HID report descriptor. */
  2. __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
  3. {
  4.   /* USER CODE BEGIN 0 */ /* A minimal Report Desc with INPUT/OUTPUT/FEATURE report. Zach Lee */
  5.   0x06,0x00,0xFF,         /*  Usage Page (vendor defined) ($FF00) global */
  6.   0x09,0x01,              /*  Usage (vendor defined) ($01) local */
  7.   0xA1,0x01,              /*  Collection (Application) */
  8.   0x15,0x00,              /*   LOGICAL_MINIMUM (0) */
  9.   0x25,0xFF,              /*   LOGICAL_MAXIMUM (255) */
  10.   0x75,0x08,              /*   REPORT_SIZE (8bit) */

  11.   // Input Report
  12.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */
  13.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */
  14.   0x81,0x02,              /*   Input(data,var,absolute) */

  15.   // Output Report
  16.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */
  17.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */
  18.   0x91,0x02,              /*   Output(data,var,absolute) */

  19.   // Feature Report
  20.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */
  21.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */
  22.   0xB1,0x02,              /*   Feature(data,var,absolute) */
  23.   /* USER CODE END 0 */
  24.   0xC0                    /*  END_COLLECTION                     */
  25. };
复制代码
可能Feature Report在CMSIS-DAP中不是必要的,就留着它吧。


我在这个C文件中还实现了一个新的接口函数CUSTOM_HID_InEvent_FS:
  1. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state);  /* An extra interface func. */

  2. USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =
  3. {
  4.   CUSTOM_HID_ReportDesc_FS,
  5.   CUSTOM_HID_Init_FS,
  6.   CUSTOM_HID_DeInit_FS,
  7.   CUSTOM_HID_OutEvent_FS,
  8.   /* I add an extra interface func below. Zach Lee */
  9.   CUSTOM_HID_InEvent_FS
  10. };
复制代码
  1. extern void USBD_OutEvent(void); /* Implemented in file "device.h" */

  2. static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
  3. {
  4.   /* USER CODE BEGIN 6 */
  5.   USBD_OutEvent();        /* OUTPUT REPORT was received. Zach Lee */
  6.   return (USBD_OK);
  7.   /* USER CODE END 6 */
  8. }

  9. extern void USBD_InEvent(void); /* Implemented in file "device.h" */

  10. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state)
  11. {
  12.   /* USER CODE BEGIN extra */
  13.   USBD_InEvent();       /* INPUT REPORT has been sent. Zach Lee */
  14.   return (USBD_OK);
  15.   /* USER CODE END extra */
  16. }
复制代码
CUSTOM_HID_Init_FS和CUSTOM_HID_DeInit_FS两个函数被实现为使能/失能DAP功能:
  1. extern void USBD_HID0_Initialize (void);

  2. static int8_t CUSTOM_HID_Init_FS(void)
  3. {
  4.   /* USER CODE BEGIN 4 */
  5.   USBD_HID0_Initialize(); /* Initialize USB communication of DAP. Zach Lee */
  6.   return (USBD_OK);
  7.   /* USER CODE END 4 */
  8. }

  9. extern void USBD_HID0_Uninitialize (void);

  10. static int8_t CUSTOM_HID_DeInit_FS(void)
  11. {
  12.   /* USER CODE BEGIN 5 */
  13.   USBD_HID0_Uninitialize(); /* Uninitialize. Zach Lee */
  14.   return (USBD_OK);
  15.   /* USER CODE END 5 */
  16. }
复制代码
编写将所有源代码关联起来的桥梁
为了移除CMSIS RTOS,我写了一些函数来模拟RTOS:
  1. /**
  2.   * Replace CMSIS RTOS api
  3.   */
  4. static volatile int osFlags;  /* Use "volatile" to prevent GCC optimizing the code. */

  5. void osKernelInitialize(void)
  6. {
  7.   osFlags = 0;
  8.   return;
  9. }
  10. int osThreadFlagsWait(int mask, int b, int c)
  11. {
  12.   (void)b;
  13.   (void)c;

  14.   int ret;

  15.   while((osFlags&mask) == 0)
  16.   {
  17.     ;
  18.   }
  19.   ret = osFlags; osFlags &= ~mask;
  20.   return ret;
  21. }
  22. void osThreadFlagsSet(int tid, int f)
  23. {
  24.   (void)tid;

  25.   osFlags |= f;
  26.   return;
  27. }
复制代码
函数USBD_Configured和USBD_HID_GetReportTrigger实现如下:
  1. intUSBD_Configured(int n){
  2.   (void)n;

  3.   return(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED ?1:0);}
复制代码
  1. void USBD_HID_GetReportTrigger(int a, int b, void * report, int len)
  2. {
  3.   (void)a;
  4.   (void)b;

  5.   if (USBD_OK != USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len))
  6.   {
  7.     ;
  8.   }
  9.   return;
  10. }
复制代码
函数USBD_CUSTOM_HID_SendReport是由STM32CubeMX生成的,它被定义在usbd_customhid.c中,我自己的事件句柄如下:
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len);

  2. void USBD_OutEvent(void)
  3. {
  4.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;
  5.   USBD_HID0_SetReport(HID_REPORT_OUTPUT, 0, 0, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
  6. }

  7. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf);

  8. void USBD_InEvent(void)
  9. {
  10.   int32_t len;

  11.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;
  12.   if ((len=USBD_HID0_GetReport(HID_REPORT_INPUT, USBD_HID_REQ_EP_INT, 0, hhid->Report_buf)) > 0)
  13.   {
  14.     USBD_HID_GetReportTrigger(0, 0, hhid->Report_buf, len);
  15.   }
  16. }
复制代码
注意:
在DAP.h中,这里有个名为PIN_DELAY_SLOW的函数,它原本的实现是这样的:
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {
  2.   uint32_t count;

  3.   count = delay;
  4.   while (--count);
  5. }
复制代码
这里的空循环while (–count);会被GCC优化。我在StackOverflow中找到了一个好点子,它能正常工作但不是太合适,你有更好的方法吗?
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {
  2.   uint32_t count;

  3.   count = delay;
  4.   while (--count) {
  5.     /**
  6.       * Empty loop will be totally omitted by GCC.
  7.       * Search "How to prevent GCC from optimizing out a busy wait loop?" @ StackOverflow.
  8.       * This solution isn't portable. Zach Lee
  9.       */
  10.     __ASM("");
  11.   }
  12. }
复制代码
至此,相关源码就介绍完毕,源码文件:


http://wiki.geniekits.com/downloads









收藏 评论2 发布时间:2020-12-2 12:35

举报

2个回答
radio2radio 回答时间:2020-12-2 13:17:40
最后的PIN_DELAY_SLOW的问题,请参考我的方案:
https://www.stmcu.org.cn/module/forum/thread-616081-1-1.html

评分

参与人数 1ST金币 +20 收起 理由
role_2099 + 20 赞一个!

查看全部评分

radio2radio 回答时间:2020-12-2 20:25:00
谢网友role_2099。

顺便提一下,从实用的角度来说,我的代码有以下优点:
1. 硬件便宜,几乎人人都有bluepill小板。 最实用的是用STLINKv2手指来改,稳定,方便。
2. 软件有VCP(CDC)功能,有软启动功能。送SWO功能,虽然用的人不多。
3. 速度已经调到最高了。如要进一步提高,不换F103的情况下,就得超频。还有一种方法就是,用SPI驱动USB通信。
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版