最近在研究无线下载器,就用ARM开源的CMSIS-DAP协议,结果自闭了四五天,终于在一篇非常棒的文章里找到了方法,因为写的太好了,所以这里我直接把原文翻译成中文,英文原文见:usb_express:cmsis-dap [Geniekits]

在STM32F103C8上实现CMSIS-DAP

  • 本工程的相关代码已经上传至Github,你也可以从我的下载页面获取到源文件。

如果你想在主流的MCU上进行嵌入式开发,一般来说你需要一个JTAG来连接单片机和电脑。对于ARM的MCU来说,J-Link是一个非常有名的设备,它可以提供JTAG协议将单片机个电脑连接起来。CMSIS-DAP是一个由ARM公司发布的一款类似的设备。DAP和J-Link最主要的区别在于DAP使开源的,而J-Link是一个付费产品。另外,CMSIS-DAP是基于USB HID的,所以你不需要额外安装一个固件驱动就能使用。

CMSIS-DAP 2.0.0的源码在CMSIS V5的GitHub仓库里。在源码里还有两个基于LPC-Link-II的示例。这两个示例使用的是Keil MDK的RTE组件和USB库。在源码中还有一个叫做USB_CM3.lib的库文件。正因为这些,官方提供的这个示例很不方便(即非ARM的平台就没法用)。作为一个开源的狂热粉丝,我决定以完全自由的方式实现CMSIS-DAP。

STM32F103是一款非常受欢迎,非常便宜的MCU,它有着ARM Cotex-M3的内核,由STMicroelectronics设计。正巧我手里有一些叫做Bluepill的最小系统板,它上面搭载了一片小巧的STM32F103C8T6芯片,并且上面也没有多余的无用的外设组件。这块开发板的唯一不足是连接在USB D+引脚的上拉电阻是10K,但按照标准它应该是1.5K,不过它的USB依然能够正常工作,没有出什么问题。

给STM32创建一个完整的嵌入式开发环境是非常简单的。在ST的官网上,我们可以下载一些免费软件,其中最重要的是STM32CubeMX。这是一个图形化配置工具,帮助用户选择单片机引脚的功能,并自动生成外设初始化代码。我配置了USB、SPI1和USART1,并选择了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置为JTAG调试需要的引脚。GPIOC13用于驱动单片机上的LED灯。但是我不知道怎么在STM32CubeMX中配置CoreDebug和DWT,它能在软件里面配置吗?

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进行日常的开发。

从CMSIS-DAP的源码开始

CMSIS V4 和 V5 中都有CMSIS-DAP的源码,我从Github上下载了CMSIS_5。CMSIS-DAP的核心文件有五个,但示例其实比核心代码更重要。我将从示例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"                             // Debug Unit Cortex-M Processor Header File
#endif

我想抛弃RTE的相关文件,所以_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.
\return 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;
#endifextern 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.cUSBD_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

我移除了RTE\USB\USBD_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 ClassUSBD_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_SIZECUSTOM_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 couldbe 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 Report0x95,64,                /*   Report Length (64 REPORT_SIZE) */0x09,0x01,              /*   USAGE (Vendor Usage 1) */0x81,0x02,              /*   Input(data,var,absolute) */// Output Report0x95,64,                /*   Report Length (64 REPORT_SIZE) */0x09,0x01,              /*   USAGE (Vendor Usage 1) */0x91,0x02,              /*   Output(data,var,absolute) */// Feature Report0x95,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_FSCUSTOM_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_ConfiguredUSBD_HID_GetReportTrigger实现如下:

int USBD_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("");}
}

如何使用这个CMSIS-DAP

硬件连接非常简单。我使用一块STM32F103C8T6作为CMSIS-DAP,另一块相同的开发板作为被调试的目标板,下图是两块开发板之间的连线,只需要四根线。

一些有用的链接

  • Another implementation of CMSIS-DAP with SWO support

结语

翻译完了,接下来的是我自己的一些话。

真的非常感谢这位大大,过去的四五天我看了很多份代码,基本都是基于X893大神的代码修改的,但毕竟X893的代码历史太悠久了,已经是七年前写的代码了,使用的又是MDK Keil的RL-USB库,核心代码居然是在一个库文件中,根本看不到内核,导致我一直不知道usbd_hid_get_reportusbd_hid_set_reportusbd_hid_get_report_trigger这三个函数应该怎么实现,是在什么情况下被调用的,功能具体是啥。只能说能照搬着用,但是一旦换成其他系列的芯片就不好办了。

后来我看到了这篇文章:高速USB 2.0的CMSIS-DAP调试器:CMSIS-DAP正确打开方式(3月18日更新速度和稳定性),跟着走了一遍,但不知道为什么下载进去后,USB插电脑上一点反应都没有,电脑既没有识别这个HID设备也没有提示请求描述符失败,至今还是感觉很迷。

还有一些代码把X893代码里的库文件替换成了可以打开的C文件和H文件,比如xjtuecho/CMSIS-DAP,但是还是看不懂,最后的最后,在今天,我遇见了这篇好文章,跟着它的步骤走,只用了两个小时不到,就自己从零实现了一个CMSIS-DAP,当时别提多激动了。

不过就像这篇文章最开始说的那样,我的最终目标是实现无线的CMSIS-DAP,也就是想正点原子的无线调参盒那种,正点原子的无线调试器要卖两百多,而我自己做的话成本可以压到三四十块,用起来很随心所欲的那种。

关于无线的CMSIS-DAP,我找到了三个开源的代码:

  • K-O-Carnivist/CMSIS-DAP-Wireless
  • wuxx/nanoDAP-wireless
  • SibiryakovVictor/STM32F10X-CMSIS-DAP-Debugger-through-UART-or-Bluetooth-HC-05

我也想尝试着实现一个,但目前还卡在一个问题中。等我彻底实现了无线CMSIS-DAP再把相关的东西写出来。

自制CMSIS-DAP下载器相关推荐

  1. 逐飞K66核心板+逐飞DAP下载器调试方式及接线(SWD)

    以逐飞K66例程的LED Blink为例,环境是IAR.接线方式为SWD. 和逐飞DAP教程一样,看到侧边窗口,在工程名上右键,选择Options,在左侧菜单选中Debugger,右边Driver选择 ...

  2. 关于自制CMSIS_DAP离线下载器下载算法的代码说明:“0xE00ABE00, 0x062D780D, 0x24084068, 0xD3000040, 0x1E644058, 0x1C49D1FA“

    关于自制CMSIS_DAP离线下载器下载算法的代码说明:"0xE00ABE00, 0x062D780D, 0x24084068, 0xD3000040, 0x1E644058, 0x1C49 ...

  3. 正点原子 高速DAP 下载器解除芯片读保护

    使用正点原子高速DAP解除芯片读保护 用阿波罗F429开发板下载程序提示下面这个错误: 发现下载器是可以正常识别芯片的. 排查后发现很大可能是芯片被读保护了. 下面使用高速DAP下载器来解除这个芯片读 ...

  4. 正点原子 DAP下载器简单使用

    使用DAP下载器给开发板下载程序 文章目录 使用DAP下载器给开发板下载程序 前言 0.接线方法(支持模拟串口) 1.开发板接线 2.核心板接线 1.驱动安装 2.配置下载器 3.虚拟串口功能 前言 ...

  5. 自制python图片下载器

    这个代码的原理是用百度作为访问点,脚本根据使用者所输入的关键字去搜索图片,并且下载到使用者所创建的文件夹中. 下面为代码实例: # encoding = 'UTF-8' __author__ = '_ ...

  6. 联盛德毕业设计--01 JLink 下载器 无法下载程序问题 解决方法--DAP

    准备用 W601 做一个毕业设计, 现在开始学习 W601.  (解决方法就是换一个DAP下载器,以下纯属扯淡) 学习 W601 第一步应该就是搭建一个开发平台,我这里使用的开发板是正点原子的  W6 ...

  7. [开源]CMSIS-DAP高速下载器

    CMSIS-DAP高速下载器 Winusb版 源码 OLED界面 实现 速度测试 固件下载 F4全速版 F7高速版 源码 高速USB2.0 https://gitee.com/H0x9DEFA478/ ...

  8. 转:自制CMSIS-DAP下载器

    看到一篇好文章,怕丢了,所以转发过来,如有侵权联系本人删除. 最近在研究无线下载器,就用ARM开源的CMSIS-DAP协议,结果自闭了四五天,终于在一篇非常棒的文章里找到了方法,因为写的太好了,所以这 ...

  9. 教你自制ST-LinkV2下载器

    原由 前几天遇到ST-link下载问题,我自制的STM32F107板子发现非得接上NRST引脚才能正常下载,这就很郁闷了.但是手头上的Mini版本的又没留RST引脚出来,于是干脆决心自己自制一个ST- ...

  10. 自制板载ST-LINKV2-1下载器

    自制板载ST-LINKV2-1下载器 自制板载ST-LINKV2-1下载器,占用板卡空间小且成本较低,实际使用起来极其方便,每次程序下载调试时再也不用找杜邦线对着引脚连线了,闭着眼连接一根USB-A ...

最新文章

  1. rpm包管理功能全解
  2. 【arduino】DIY音乐播放器,arduino音箱播放wav音乐
  3. hudson部署过程
  4. Jenkins中安装Role-based Authorization Strategy插件来实现用户角色权限管理
  5. when is OData model initialized - finally found done by Framework
  6. 云服务器主机内网 ip 和外网 ip 的区别
  7. dos虚拟机如何全屏显示_实用工具 | 虚拟机软件VirtualBox详细使用介绍
  8. 发掘 CPU 与超级工厂的共性,程序员的心思你别猜
  9. Mysql学习总结(38)——21条MySql性能优化经验
  10. java中是值传递引用传递_Java是按值传递而不是按引用传递
  11. Go实战--golang中defer的使用
  12. 一文看懂PPPoE协议——PPPoE协议简介
  13. BZOJ1812: [Ioi2005]riv(树形dp)
  14. PDF加密以及去除密码小妙招
  15. 软件开发模型有哪些?
  16. javascript gba游戏模拟器
  17. linux常用命令小结
  18. 寒霜朋克计算机丢失,寒霜朋克无法启动运行解决方法 寒霜朋克不能运行怎么办?...
  19. shader篇-纹理-遮罩纹理
  20. 网盘背后的秘密(稍作修改)

热门文章

  1. 科普:淘宝网的反爬虫变迁史
  2. Ubuntu系统安装Ghostscript
  3. matlab三轴定位程序,三边测量定位MATLAB源码
  4. matlab图片测量尺寸_基于视觉的零件尺寸测量方法
  5. 在走迷宫任务中实现强化学习(持续更新中)——第二课:移动体的路径规划(小川雄太郎《边做边学深度强化学习》项目复刻)
  6. 信息化管理系统在企业的应用
  7. SAP中与物料BOM有关的表关联
  8. 淘宝按图搜索商品(拍立淘)、图片上传API接口、图片识别商品接口img2text标题栏、链接及图片相关参数字段API数据获取调用示例
  9. 朗文3000词汇表带音标_SBS朗文国际英语教程第二册单词表(带音标)
  10. mcafee杀死oracle,如何从卸载McAfee卸载工具