基于STM32F103自制CMSIS-DAP下载器
关注+星标公众号,不错过精彩内容
编排 | strongerHuang
微信公众号 | strongerHuang
市面上针对Cortex-M处理器的下载器,有很多是基于CMSIS-DAP演变而来,比如:e-Link、GD-Link等。
之前给大家分享过自制ST-Link的教程,今天继续为大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下载器。
1
关于CMSIS-DAP
CMSIS-DAP是支持访问 CoreSight 调试访问端口(DAP)的固件规范和实现,以及各种Cortex处理器提供CoreSight调试和跟踪。
地址:
https://arm-software.github.io/CMSIS_5/DAP/html/index.html
CMSIS-DAP固件作为源代码提供,并且可以完全配置为新的调试单元。
这里相关的更多内容,可以参看我之前分享过的一篇文章:Cortex-M软件接口标准CMSIS那些重要内容。
2
CMSIS-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)
3.源码描述
从文件目录可以看出,官方源码提供了一些模板和例子。
目前只提供了LPC处理器的例子,如果你有这个处理器对应的板卡,可以直接使用该源码做一个下载调试器。(下面就针对于LPC这个例子进行“改装”)
3
配置
利用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来调试我的代码。现在我在使用新做出来的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。第一个关键位置如下:
#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.
\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.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
我移除了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
。
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
结构体中,我新增了一个成员:
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_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 */
}
6
编写将所有源代码关联起来的桥梁
为了移除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("");}
}
至此,相关源码就介绍完毕,源码文件:
http://wiki.geniekits.com/downloads
参考来源:
http://wiki.geniekits.com/doku.php?id=usb_express:cmsis-dap
https://blog.csdn.net/qq_21506881/article/details/102633184
免责声明:本文部分素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除
------------ END ------------
推荐阅读:
精选汇总 | 专栏 | 目录 | 搜索
C语言结构体描述BMP的文件格式
C语言printf()函数具体解释和安全隐患
关注微信公众号『嵌入式专栏』,底部菜单查看更多内容,回复“加群”按规则加入技术交流群。
点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。
基于STM32F103自制CMSIS-DAP下载器相关推荐
- 逐飞K66核心板+逐飞DAP下载器调试方式及接线(SWD)
以逐飞K66例程的LED Blink为例,环境是IAR.接线方式为SWD. 和逐飞DAP教程一样,看到侧边窗口,在工程名上右键,选择Options,在左侧菜单选中Debugger,右边Driver选择 ...
- 关于自制CMSIS_DAP离线下载器下载算法的代码说明:“0xE00ABE00, 0x062D780D, 0x24084068, 0xD3000040, 0x1E644058, 0x1C49D1FA“
关于自制CMSIS_DAP离线下载器下载算法的代码说明:"0xE00ABE00, 0x062D780D, 0x24084068, 0xD3000040, 0x1E644058, 0x1C49 ...
- 正点原子 高速DAP 下载器解除芯片读保护
使用正点原子高速DAP解除芯片读保护 用阿波罗F429开发板下载程序提示下面这个错误: 发现下载器是可以正常识别芯片的. 排查后发现很大可能是芯片被读保护了. 下面使用高速DAP下载器来解除这个芯片读 ...
- 正点原子 DAP下载器简单使用
使用DAP下载器给开发板下载程序 文章目录 使用DAP下载器给开发板下载程序 前言 0.接线方法(支持模拟串口) 1.开发板接线 2.核心板接线 1.驱动安装 2.配置下载器 3.虚拟串口功能 前言 ...
- 基于iOS 10封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
原文 资源来自:http://www.cocoachina.com/ios/20170316/18901.html 概要 在决定自己封装一个下载器前,我本以为没有那么复杂,可在实际开发过程中困难重重, ...
- 自制python图片下载器
这个代码的原理是用百度作为访问点,脚本根据使用者所输入的关键字去搜索图片,并且下载到使用者所创建的文件夹中. 下面为代码实例: # encoding = 'UTF-8' __author__ = '_ ...
- 基于C语言实现http下载器
C语言实现http的下载器. 例:做OTA升级功能时,我们能直接拿到的往往只是升级包的链接,需要我们自己去下载,这时候就需要用到http下载器. 这里分享一个: 功能 1.支持chunked方式传输的 ...
- 基于python3 的百度图片下载器
自己写了玩的一个小脚本,百度图片下载 import re import os import requests import hashlibdef dowmloadPic(html, keyword): ...
- 联盛德毕业设计--01 JLink 下载器 无法下载程序问题 解决方法--DAP
准备用 W601 做一个毕业设计, 现在开始学习 W601. (解决方法就是换一个DAP下载器,以下纯属扯淡) 学习 W601 第一步应该就是搭建一个开发平台,我这里使用的开发板是正点原子的 W6 ...
- 制作新版STC单片机WiFi下载器
简 介: 基于WiFi的STC单片机下载器可以方便对STC的8A,8G,8H,15系列的单片机完成程序下载,方便了程序的开发与调试.特别适应于需要强磁隔离.运动平台的单片机开发,做到程序的快速更新与测 ...
最新文章
- 一张图看懂新一代人工智能知识体系
- 秒杀系统流量削峰,这事应该怎么做?
- 洛谷 【P1252】马拉松接力赛
- Android 使用ViewPager实现导航页面
- 02_MySQL约束课堂笔记
- [转] 在 Mac OS X 下编译 Objective-C 运行时
- 首批骁龙875旗舰!小米11屏幕依然是挖孔
- 十分钟python入门基础
- Spring Boot: 加密应用配置文件敏感信息
- 数据结构----二路归并排序
- chrome浏览pdf 字体变了
- 服务器硬盘常用的阵列方式有几种,三种常见磁盘阵列设置
- VCC、 VDD、VSS、VEE 电压符号的解释
- 2021查询高考成绩提前批分数线,快看看!2021高考部分省市“预测分数线”出炉!两点需要注意...
- 一分钟详解三维重建学习路线
- SpringBoot+Vue实现前后端分离OA办公管理系统
- matlab 双目 景深,双照相机景深分析的思路
- 梆梆安全:移动安全新“梆”主 专一加固移动应用
- mLife | DANMEL:面向细菌耐药移动元件分析的手工精细注释参考数据库
- 金蝶K3案例教程物料需求计划后台配置
热门文章
- idea学生授权申请
- Egret + TiledMap 快速上手
- matlab spearman秩相关系数,matlab统计分析与应用_基于copula函数的蒙特卡洛模拟计算var matlab-其它文档类资源...
- docker客户端和服务端
- linux添加网络节点,Pi网络节点配置教程(windows10专业版)
- 荒野行动 android 鼠标,荒野行动键盘映射模拟器
- st7735屏幕移植-高清图片显示
- matlab实验教程,matlab实验教程
- 13个免费的开源GIS软件
- ies文件 vray_光域网ies文件免费