本帖最后由 飞鸿踏雪 于 2014-10-16 13:05 编辑



前言

USB的用途就不多说了,下面的内容主要就是讲解如何利用ST提供的USB驱动库和libusb上位机驱动库实现一个USB数据传输功能,为了降低开发难度,我们仅仅讲解Bulk传输模式,当然这也是用得比较多的传输模式。



开发流程

1,完成STM32单片机端的USB程序;

2,利用linusb自带的inf-wizard工具生成USB驱动;

3,基于libusb编写USB通信程序;

4,测试PC和单片机的数据通信;



STM32程序编写

1,完成描述符的修改,修改后的描述符如下(在usb_desc.c文件中)

设备描述符:

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
const uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =
{
    0x12,                       /*bLength */
    USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/
    0x00,                       /*bcdUSB */
    0x02,
    0x00,                       /*bDeviceClass*/
    0x00,                       /*bDeviceSubClass*/
    0x00,                       /*bDeviceProtocol*/
    0x40,                       /*bMaxPacketSize40*/
    LOBYTE(USBD_VID),           /*idVendor*/
    HIBYTE(USBD_VID),           /*idVendor*/
    LOBYTE(USBD_PID),           /*idVendor*/
    HIBYTE(USBD_PID),           /*idVendor*/
    0x00,                       /*bcdDevice rel. 2.00*/
    0x02,
    1,                          /*Index of string descriptor describing manufacturer */
    2,                          /*Index of string descriptor describing product*/
    3,                          /*Index of string descriptor describing the device serial number */
    0x01                        /*bNumConfigurations*/
}; /* CustomHID_DeviceDescriptor */





配置描述符:

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
    0x09, /* bLength: Configuation Descriptor size */
    USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
    CUSTOMHID_SIZ_CONFIG_DESC,
    /* wTotalLength: Bytes returned */
    0x00,
    0x01,         /* bNumInterfaces: 1 interface */
    0x01,         /* bConfigurationValue: Configuration value */
    0x00,         /* iConfiguration: Index of string descriptor describing
                                 the configuration*/
    0xE0,         /* bmAttributes: Bus powered */
                  /*Bus powered: 7th bit, Self Powered: 6th bit, Remote wakeup: 5th bit, reserved: 4..0 bits */
    0xFA,         /* MaxPower 500 mA: this current is used for detecting Vbus */
    /************** Descriptor of Custom HID interface ****************/
    /* 09 */
    0x09,         /* bLength: Interface Descriptor size */
    USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
    0x00,         /* bInterfaceNumber: Number of Interface */
    0x00,         /* bAlternateSetting: Alternate setting */
    0x04,         /* bNumEndpoints */
    0xDC,         /* bInterfaceClass: Class code = 0DCH */
    0xA0,         /* bInterfaceSubClass : Subclass code = 0A0H */
    0xB0,         /* nInterfaceProtocol : Protocol code = 0B0H */
    0,            /* iInterface: Index of string descriptor */
    /******************** endpoint descriptor ********************/
    /* 18 */
    0x07,         /* endpoint descriptor length = 07H */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
    0x81,         /* endpoint 1 IN */
    0x02,                                        /* bulk transfer = 02H */
    0x40,0x00,    /* endpoint max packet size = 0040H */
    0x00,         /* the value is invalid when bulk transfer */
    0x07,         /* endpoint descriptor length = 07H */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
    0x01,         /* endpoint 1 OUT */
    0x02,                                        /* bulk transfer = 02H */
    0x40,0x00,    /* endpoint max packet size = 0040H */
    0x00,         /* the value is invalid when bulk transfer */
                 
    0x07,         /* endpoint descriptor length = 07H */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
    0x82,         /* endpoint 2 IN */
    0x02,                                        /* bulk transfer = 02H */
    0x40,0x00,    /* endpoint max packet size = 0040H */
    0x00,         /* the value is invalid when bulk transfer */
                 
    0x07,         /* endpoint descriptor length = 07H */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
    0x02,         /* endpoint 2 OUT */
    0x02,                                        /* bulk transfer = 02H */
    0x40,0x00,    /* endpoint max packet size = 0040H */
    0x00,         /* the value is invalid when bulk transfer */
}; /* CustomHID_ConfigDescriptor */



配置描述符就包含了端点描述符,我们用了4个端点,两个BULK-OUT端点,两个BULK-IN端点。



其他的描述符:

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* USB String Descriptors (optional) */
const uint8_t CustomHID_StringLangID[CUSTOMHID_SIZ_STRING_LANGID] =
{
    CUSTOMHID_SIZ_STRING_LANGID,
    USB_STRING_DESCRIPTOR_TYPE,
    0x09,
    0x04
}; /* LangID = 0x0409: U.S. English */
const uint8_t CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR] =
{
    CUSTOMHID_SIZ_STRING_VENDOR, /* Size of Vendor string */
    USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType*/
    // Manufacturer: "STMicroelectronics"
    'M', 0, 'y', 0, 'U', 0,'S', 0,'B', 0, '_', 0, 'H', 0,'I',0,'D',0
};
const uint8_t CustomHID_StringProduct[CUSTOMHID_SIZ_STRING_PRODUCT] =
{
    CUSTOMHID_SIZ_STRING_PRODUCT,          /* bLength */
    USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
    'B', 0, 'y', 0, ' ', 0, 'e', 0, 'm', 0, 'b', 0,'e',0,'d',0,'-',0,'n',0,'e',0,'t',0
};
uint8_t CustomHID_StringSerial[CUSTOMHID_SIZ_STRING_SERIAL] =
{
    CUSTOMHID_SIZ_STRING_SERIAL,           /* bLength */
    USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
    'x', 0, 'x', 0, 'x', 0,'x', 0,'x', 0, 'x', 0, 'x', 0
};





2,根据端点缓冲区大小配置端点缓冲区地址,配置信息如下(在usb_conf.h文件中):

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/* buffer table base address */
#define BTABLE_ADDRESS      (0x00)
/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x18)
#define ENDP0_TXADDR        (0x58)
/* EP1  */
/* tx buffer base address */
//地址为32位对其,位4的倍数,不能超过 bMaxPacketSize
//EP1
#define ENDP1_RXADDR        (0x98)
#define ENDP1_TXADDR        (0x98+64)
EP2
#define ENDP2_RXADDR        (0xA0+64+64)
#define ENDP2_TXADDR        (0xA0+64+64+64)





3,初始化每个端点(在usb_prop.c文件中的CustomHID_Reset函数中)

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Initialize Endpoint 0 */
SetEPType(ENDP0, EP_CONTROL);
SetEPTxStatus(ENDP0, EP_TX_STALL);
SetEPRxAddr(ENDP0, ENDP0_RXADDR);
SetEPTxAddr(ENDP0, ENDP0_TXADDR);
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
SetEPRxValid(ENDP0);
/* Initialize Endpoint 1 */
       SetEPType(ENDP1, EP_BULK);
       SetEPRxAddr(ENDP1, ENDP1_RXADDR);
       SetEPTxAddr(ENDP1, ENDP1_TXADDR);
       SetEPRxCount(ENDP1, EP_SIZE);
       SetEPRxStatus(ENDP1, EP_RX_VALID);
 SetEPTxStatus(ENDP1, EP_TX_NAK);
/* Initialize Endpoint 2 */
       SetEPType(ENDP2, EP_BULK);
       SetEPRxAddr(ENDP2, ENDP2_RXADDR);
       SetEPTxAddr(ENDP2, ENDP2_TXADDR);
       SetEPRxCount(ENDP2, EP_SIZE);
       SetEPRxStatus(ENDP2, EP_RX_VALID);
       SetEPTxStatus(ENDP2, EP_TX_NAK);





4,实现端点的回调函数(需要在usb_conf.h中注释掉对应的回调函数宏定义)

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*******************************************************************************
* Function Name  : EP1_OUT_Callback.
* Description    : EP1 OUT Callback Routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP1_OUT_Callback(void)
{
        EP1_ReceivedCount = GetEPRxCount(ENDP1);
        PMAToUserBufferCopy(USB_Receive_Buffer, ENDP1_RXADDR, EP1_ReceivedCount);
        SetEPRxStatus(ENDP1, EP_RX_VALID);
}
/*******************************************************************************
* Function Name  : EP2_OUT_Callback.
* Description    : EP2 OUT Callback Routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP2_OUT_Callback(void)
{
        EP2_ReceivedCount = GetEPRxCount(ENDP2);
        PMAToUserBufferCopy(USB_Receive_Buffer, ENDP2_RXADDR, EP2_ReceivedCount);
        SetEPRxStatus(ENDP2, EP_RX_VALID);
}





5,完成主函数的测试程序

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int main(void)
{
        uint8_t data[256];
        uint32_t i=0;
        Set_System();//系统时钟初始化
        USART_Configuration();//串口1初始化
        printf("\x0c\0");printf("\x0c\0");//超级终端清屏
        printf("\033[1;40;32m");//设置超级终端背景为黑色,字符为绿色
        printf("\r\n*******************************************************************************");
        printf("\r\n************************ Copyright 2009-2012, EmbedNet ************************");
        printf("\r\n*************************** [url=http://www.embed-net.com]http://www.embed-net.com[/url] **************************");
        printf("\r\n***************************** All Rights Reserved *****************************");
        printf("\r\n*******************************************************************************");
        printf("\r\n");
        USB_Interrupts_Config();
        Set_USBClock();
        USB_Init();
        while(1)
        {
                if(EP1_ReceivedCount > 0){
                        USB_GetData(ENDP1,data,EP1_ReceivedCount);
                        USB_SendData(ENDP1,data,EP1_ReceivedCount);
                        printf("usb EP1 get data %d byte data\n\r",EP1_ReceivedCount);
                        for(i=0;i<EP1_ReceivedCount;i++){
                                printf("0x%02X ",data[i]);
                        }
                        printf("\n\r");
                        EP1_ReceivedCount=0;
                }
                if(EP2_ReceivedCount > 0){
                        USB_GetData(ENDP2,data,EP2_ReceivedCount);
                        USB_SendData(ENDP2,data,EP2_ReceivedCount);
                        printf("usb EP2 get data %d byte data\n\r",EP2_ReceivedCount);
                        for(i=0;i<EP2_ReceivedCount;i++){
                                printf("0x%02X ",data[i]);
                        }
                        printf("\n\r");
                        EP2_ReceivedCount=0;       
                }
        }
}





到此,STM32的程序基本上编写完成,然后编译下载程序,如果一切顺利,系统会检测到一个新的设备并试图加载对应的驱动,由于我们还没做驱动程序,所以肯定会加载驱动失败,如下图所示:

 



驱动程序生成

下面我们就利用libusb自带的inf-wizard工具生成USB驱动程序,该工具可以到本文章的附件下载,其具体过程如下:

 



运行该程序,出现下图对话框,点击“Next”;

 



出现下图对话框后选择我们需要生成驱动程序的设备;

 



这里可以写该Device Name,我们保持默认值,其他的都不需要修改;

 



点击Next后出现下图对话框,我们选择一个目录保存这个inf文件;

 



保存后的文件

 



若要立即安装驱动,可以点击下面对话框的红色框按钮;

 



Win7下可能会出现如下对话框,点击始终安装;

 



到此,USB驱动程序自动生成完毕,若安装了驱动,则在设备管理器里面会看到如下信息

 



基于libusb的上位机驱动程序编写

首先建立一个驱动程序工程,然后将libusb的库(附件有下载)添加到工程里面,编写以下几个函数

设备扫描函数,该函数用来找到插入电脑上的USB设备

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
  * @brief  扫描设备连接数
  * @param  NeedInit 是否需要初始化,第一次调用该函数需要初始化
  * @retval 识别到的指定设备个数
  */
int __stdcall USBScanDev(int NeedInit)
{
        if(NeedInit){
                usb_init(); /* initialize the library */
                usb_find_busses(); /* find all busses */
                usb_find_devices(); /* find all connected devices */
        }
        return scan_dev(pBoard);
}





打开设备

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
  * @brief  打开指定的USB设备
  * @param  devNum        需要打开的设备号
  * @retval 打开状态
  */
int __stdcall USBOpenDev(int DevIndex)
{
        pBoardHandle[DevIndex] = open_dev(DevIndex,pBoard);
        if(pBoardHandle[DevIndex]==NULL){
                return SEVERITY_ERROR;
        }else{
                return SEVERITY_SUCCESS;
        }
}





关闭设备

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
/**
  * @brief  关闭指定的USB设备
  * @param  devNum        需要关闭的设备号
  * @retval 打开状态
  */
int __stdcall USBCloseDev(int DevIndex)
{
        return close_dev(DevIndex,pBoardHandle);
}





BULK端点写数据

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
  * @brief  USB Bulk端点写数据
  * @param  nBoardID 设备号
  * @param  pipenum 端点号
  * @param  sendbuffer 发送数据缓冲区
  * @param  len 发送数据字节数
  * @param  waittime 超时时间
  * @retval 成功发送的数据字节数
  */
int __stdcall USBBulkWriteData(unsigned int nBoardID,int pipenum,char *sendbuffer,int len,int waittime)
{
        int ret=0;
        if(pBoardHandle[nBoardID] == NULL){
                return SEVERITY_ERROR;
        }
#ifdef TEST_SET_CONFIGURATION
    if (usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)
    {
        usb_close(pBoardHandle[nBoardID]);
        return SEVERITY_ERROR;
    }
#endif
#ifdef TEST_CLAIM_INTERFACE
    if (usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)
    {
        usb_close(pBoardHandle[nBoardID]);
        return SEVERITY_ERROR;
    }
#endif
#if TEST_ASYNC
    // Running an async write test
    ret = transfer_bulk_async(dev, pipenum, sendbuffer, len, waittime);
#else
        ret = usb_bulk_write(pBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);
        /*if((len%64) == 0){
                usb_bulk_write(pBoardHandle[nBoardID], pipenum, sendbuffer, 0, waittime);
        }*/
#endif
#ifdef TEST_CLAIM_INTERFACE
    usb_release_interface(pBoardHandle[nBoardID], 0);
#endif
    return ret;
}





BULK端点读数据

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
  * @brief  USB Bulk读数据
  * @param  nBoardID 设备号
  * @param  pipenum 端点号
  * @param  readbuffer 读取数据缓冲区
  * @param  len 读取数据字节数
  * @param  waittime 超时时间
  * @retval 读到的数据字节数
  */
int __stdcall USBBulkReadData(unsigned int nBoardID,int pipenum,char *readbuffer,int len,int waittime)
{
        int ret=0;
        if(pBoardHandle[nBoardID] == NULL){
                return SEVERITY_ERROR;
        }
#ifdef TEST_SET_CONFIGURATION
    if (usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)
    {
        usb_close(pBoardHandle[nBoardID]);
        return SEVERITY_ERROR;
    }
#endif
#ifdef TEST_CLAIM_INTERFACE
    if (usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)
    {
        usb_close(pBoardHandle[nBoardID]);
        return SEVERITY_ERROR;
    }
#endif
#if TEST_ASYNC
    // Running an async read test
    ret = transfer_bulk_async(pGinkgoBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);
#else
        ret = usb_bulk_read(pBoardHandle[nBoardID], pipenum, readbuffer, len, waittime);
#endif
#ifdef TEST_CLAIM_INTERFACE
    usb_release_interface(pBoardHandle[nBoardID], 0);
#endif
    return ret;
}





到此,PC端的驱动程序编写基本完成,下面就是驱动程序的测试,我们可以把之前这个程序生成为一个dll文件,然后单独建立一个测试工程来测试这个dll文件中的函数,测试程序如下:

[C] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// USB_DriverTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#define        EP1_OUT_SIZE        64
#define        EP1_IN_SIZE        64
int _tmain(int argc, _TCHAR* argv[])
{
        int DevNum;
        int ret;
        char WriteTestData[256]={1,2,3,4,5,6,7,8,9};
        char ReadTestData[256]={0};
        for(int i=0;i<256;i++){
                WriteTestData[i] = i;
        }
        //扫描设备连接数,需要初始化
        DevNum = USBScanDev(1);
        printf("设备连接数为:%d\n",DevNum);
        //打开设备0
        ret = USBOpenDev(0);
        if(ret == SEVERITY_ERROR){
                printf("打开设备失败!\n");
                return SEVERITY_ERROR;
        }else{
                printf("打开设备成功!\n");
        }
        //端点1写数据
        ret = USBBulkWriteData(0,EP1_OUT,WriteTestData,EP1_OUT_SIZE,500);
        if(ret != EP1_OUT_SIZE){
                printf("端点1写数据失败!%d\n",ret);
                return SEVERITY_ERROR;
        }else{
                printf("端点1写数据成功!\n");
        }
        //端点1读数据
        ret = USBBulkReadData(0,EP1_IN,ReadTestData,EP1_IN_SIZE,500);
        if(ret != EP1_IN_SIZE){
                printf("端点1读数据失败!%d\n",ret);
                return SEVERITY_ERROR;
        }else{
                printf("端点1读数据成功!\n");
                for(int i=0;i<EP1_IN_SIZE;i++){
                        printf("%02X ",ReadTestData[i]);
                        if(((i+1)%16)==0){
                                printf("\n");
                        }
                }
                printf("\n");
        }
        Sleep(100);
        //端点2写数据
        ret = USBBulkWriteData(0,EP2_OUT,WriteTestData+64,64,500);
        if(ret != 64){
                printf("端点2写数据失败!%d\n",ret);
                return SEVERITY_ERROR;
        }else{
                printf("端点2写数据成功!\n");
        }
        //端点2读数据
        ret = USBBulkReadData(0,EP2_IN,ReadTestData,64,500);
        if(ret != 64){
                printf("端点2读数据失败!%d\n",ret);
                return SEVERITY_ERROR;
        }else{
                printf("端点2读数据成功!\n");
                for(int i=0;i<64;i++){
                        printf("%02X ",ReadTestData[i]);
                        if(((i+1)%16)==0){
                                printf("\n");
                        }
                }
                printf("\n");
        }
        getchar();
        return 0;
}





到此,整个开发流程基本完成,下面是本套程序的测试图片



串口打印输出

 



PC端测试程序输出

 



Bus Hound抓取到的USB数据

 



程序源码下载

libusb驱动生成工具下载:  inf_tool.rar (778.26 KB, 下载次数: 592)

STM32程序源码下载:  USB_DriverSTM32F103.rar (677.81 KB, 下载次数: 611)

PC端USB驱动下载:  USB Driver.rar (266.56 KB, 下载次数: 456)

PC端USB驱动程序源码下载:  USB_DriverBulk.rar (20.61 KB, 下载次数: 336)

PC端USB驱动测试程序源码下载:  USB_DriverTest.rar (12.34 KB, 下载次数: 352)

libusb驱动包下载:  libusb-win32-bin-1.2.6.0.rar (821.57 KB, 下载次数: 529)

本主题由 admin 于 2014-10-26 23:01 加入精华

STM32自定义USB设备开发详细流程讲解及全套资料源码下载(基于libusb)相关推荐

  1. Linux/Windows配置stm32免费开发环境详细流程

    系统:linux mint 18.3 xfce,windows10  stm32开发板:正点原子mini板(stm32f103rc)  烧写器:stlink v2  如果是JLINK的可以参考这篇  ...

  2. 那些年跟领导聊过的数据归档【DB篇】:从梳理到落地-DB单表千万级归档详细流程讲解

    文章目录 知人论世 执笔蓝图 V1 - 浅尝辄止 V2 - 初窥门镜 V3 - 木已成舟 躬行方案 安内 攘外 卓有成效 沉淀之石 道阻且长 知人论世 无论何种需求的出现都是因为某种迫切解决的问题契机 ...

  3. DHCP分配IP地址详细流程讲解(附图,建议PC观看)

    目录 一.DHCP分配IP地址流程: ​第一步:DHCP Client请求IP--DHCP Client以广播的方式发出DHCP Discover报文 第二步:server响应--DHCP Serve ...

  4. java fangfa_daicanfangfa java中的方法 刚入门的分不清带参方法的作用和用处 这个可以详细的讲解如何使用带参方法 - 下载 - 搜珍网...

    第14章 带参数的方法/01 教学演示示例/示例1:带一个参数的方法/StudentsBiz.java 第14章 带参数的方法/01 教学演示示例/示例1:带一个参数的方法/TestAdd.java ...

  5. cesium 之自定义气泡窗口 infoWindow 后续优化篇(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 该 ...

  6. Express使用nodemailer完成邮箱验证功能详细流程(含封装,可作自定义模块)

    Express使用nodemailer完成邮箱验证功能详细流程(含封装,可作自定义模块) 记录大创项目中,在express中使用第三方模块nodemailer完成邮箱验证功能,含客户端请求验证邮件和服 ...

  7. 基于stm32的自定义HID设备开发与上位机通讯实现

    现在主流的安卓手机数据连接线,Mini-usb.Micro-usb,Type-c,产品追随主流,非联网设备,摒弃ST-LINK.JLINK,直接用usb数据传输升级.主要实现与HID设备的通信即人机交 ...

  8. STM32自定义键盘(二)STM32单片机的USB接口-HID键盘

    STM32自定义键盘(二)STM32单片机的USB接口-HID键盘 HID描述符 生成HID键盘工程模板 修改HID报告描述符 键值数据发送 USB HID 键盘键值表 HID描述符 请参考这位博主的 ...

  9. Android自定义View使用详细分析与绘制流程全解

    目录 目录.png 1. 自定义View基础 1.1 分类 自定义View的实现方式有以下几种 类型 定义 自定义组合控件 多个控件组合成为一个新的控件,方便多处复用 继承系统View控件 继承自Te ...

最新文章

  1. matlab解决多旅游商问题,多旅行商问题的matlab程序
  2. windows下编译jsoncpp 1.y.z
  3. 【研发管理】聊一聊DevOps
  4. rocketmq 几种队列_这篇进阶必看的RocketMQ,答应我看完好吗?
  5. stm32电机控制定时器1_STM32通过PWM控制电机速度
  6. 单元和集成测试的代码覆盖率
  7. 【操作系统】连续内存分配策略
  8. 向jre中添加安全证书
  9. it试用评估_it试用期员工自我评价
  10. django:自动生成接口文档
  11. 第五课 大数据技术之Fink1.13的实战学习-状态编程和容错机制
  12. 线性代数之矩阵逆的求法
  13. Sql语句查询今天、昨天、本月等日期数据
  14. linux命令手册安卓版,linux手册app-linux手册 安卓版v3.0.0-PC6安卓网
  15. 实体机跑gtest单体测试,Linux平台代码覆盖率测试
  16. 【嵌入式】——串口实验——实现芯片串口收发数据,按键中断串口发送数据,串口接收数据中断来控制LED亮/灭
  17. Spring Data Redis学海拾贝
  18. 过时的Macbook回收是最佳的选择
  19. 洛谷 T284709 怨念(resent)
  20. 行式数据库评测:Oracle 11g R2企业版

热门文章

  1. sql怎么撤回update_如何写好5000行的SQL代码
  2. velocity模板大小写转换
  3. 使用Dl4j训练的一个手写数字识别软件
  4. 电脑中的快捷键(常用)
  5. CSS:“ ”这个符号在css中一般用 arial字体
  6. OSAL系统框架专题
  7. 外盘期货分仓软件(如智星系统,信管家)等功能
  8. bzoj1375 双调路径
  9. Spark Submit任务提交流程
  10. 弱加密算法有哪几种_不安全的加密算法有哪几种