当前文档版本为 v0.9.0,您可以访问当前页面的 开发中 版本以获取最近可能的更新。

USB DFU 固件升级(鼠标方案)

设备描述

多模鼠标方案自定义了一个 USB DFU 固件升级流程,当使用此功能时,鼠标被初始化为多接口设备。

USB描述符

  • interface0作为鼠标通信接口,ep1 in作为鼠标上报通道

  • interface2作为dfu通信接口,ep3 in作为通信上报通道,ep3 out作为通信输出通道

    • ep2最大支持64B, 需要预留首位report id 0x0a作为扩展,避免与键盘ep冲突

    • ep3最大支持64B,需要预留首位report id 0x0a作为扩展,避免与键盘ep冲突

  • 识别USB设备为双接口设备后,上位机与Mouse下位机通过ep2,ep3进行协议升级

设备描述符

const uint8_t DeviceDscDat[] = {
	/* Device descriptor */
	0x12,   	// bLength
	0x01,   	// bDescriptorType
	0x00,		// bcdUSB L			- USB version(2.0), LSB
	0x02, 		// bcdUSB H			- USB version(2.0), MSB
	0x00,   	// bDeviceClass
	0x00,   	// bDeviceSubclass
	0x00,   	// bDeviceProtocol
	0x08,   	// bMaxPacketSize0
	0x6d, 0x04,     //idVendor;
	0x77, 0xc0,     //idProduct;
	0x00,   	// bcdDeviceL		- device code
	0x01,   	//
	0x22,   	// iManufacturer	- string index
	0x10,   	// iProduct			- string index
	0x00,   	// SerialNumber		- string index
	0x01    	// bNumConfigs
};

控制描述符

控制描述符只有一个,内容包含了双接口描述符,每个接口描述符子集具有hid描述符,每个hid描述符指定了端点,通过不同的hid report描述符进行对应描述

const uint8_t ConfigDscDat[] = {
	/* ============== CONFIGURATION 1 =========== */
	/* Configuration 1 descriptor */
	0x09,		// CbLength
	0x02,   	// CbDescriptorType
	0x42,   	// CwTotalLength 2 EP + Control 14+27=41=32+9=0x29
	0x00,
	0x02,   	// CbNumInterfaces
	0x01,   	// CbConfigurationValue
	0x00,   	// CiConfiguration
	0xA0,		// CbmAttributes Bus powered
	0xF8,   	// CMaxPower: 100mA

	/* Mouse Interface Descriptor Requirement */
	0x09,		// bLength
	0x04,		// bDescriptorType
	0x00,		// bInterfaceNumber
	0x00,		// bAlternateSetting
	0x01,		// bNumEndpoints
	0x03,		// bInterfaceClass: HID code
	0x01,		// bInterfaceSubclass
	0x02,		// 0: None, 1: keyboad, 2: mouse
	0x00,		// iInterface





	/* HID Descriptor */
	0x09,		// bLength
	0x21,		// bDescriptor type - HID Descriptor
	0x11,		// bcdHIDL - HID version = 0x100
	0x01,		// bcdHIDH
	0x00,		// bCountryCode
	0x01,		// bNumDescriptors
	0x22,		// bDescriptorType
	M_MAXREPS,
	0x00,		// wItemLengthH

	/* Endpoint 1 descriptor */
	0x07,   	// bLength
	0x05,   	// bDescriptorType		Endpoint
	0x81,   	// bEndpointAddress		EP 02 - IN
	0x03,   	// bmAttributes			INT
	0x40,//0x07,		// wMaxPacketSize		0x0040 = 64 bytes
	0x00,		//
	0x01,   	// bInterval			1 ms





	/* HID Interface Descriptor Requirement */
	0x09,		// bLength
	0x04,		// bDescriptorType
	0x01,		// bInterfaceNumber
	0x00,		// bAlternateSetting
	0x02,		// bNumEndpoints
	0x03,		// bInterfaceClass: HID code
	0x00,		// bInterfaceSubclass
	0x00,		// 0: None, 1: keyboad, 2: mouse
	0x00,		// iInterface

	/* HID Descriptor */
	0x09,		// bLength
	0x21,		// bDescriptor type - HID Descriptor
	0x00,		// bcdHIDL - HID version = 0x100
	0x01,		// bcdHIDH
	0x00,		// bCountryCode
	0x01,		// bNumDescriptors
	0x22,		// bDescriptorType
	DFU_SIZE,
	0x00,		// wItemLengthH

	/* Endpoint 2 in descriptor */
	0x07,   	// bLength
	0x05,   	// bDescriptorType		Endpoint
	0x82,   	// bEndpointAddress		EP 02 - IN
	0x03,   	// bmAttributes			INT
	0x40,		// wMaxPacketSize		0x0040 = 64 bytes
	0x00,		//
	0x01,   	// bInterval			1 ms

	/* Endpoint 3 out descriptor */
	0x07,   	// bLength
	0x05,   	// bDescriptorType		Endpoint
	0x03,   	// bEndpointAddress		EP 03 - OUT
	0x03,   	// bmAttributes			INT
	0x40,		// wMaxPacketSize		0x0040 = 64 bytes
	0x00,		//
	0x01		// bInterval			1 ms
};

报告描述符

hid鼠标报告描述符

#define M_MAXREPS        				52
uint8_t report[M_MAXREPS]={
	0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
	0x09, 0x02, /* Usage (Mouse) */
	0xA1, 0x01, /* Collection (Application) */
	// 0x85, 0x01, /* report id(1) */
	0x09, 0x01, /*   Usage (Pointer) */
	0xA1, 0x00, /*   Collection (Physical) */
	0x05, 0x09, /*     Usage Page (Button) */
	0x19, 0x01, /*     Usage Minimum (0x01) */
	0x29, 0x03, /*     Usage Maximum (0x03) */
	0x15, 0x00, /*     Logical Minimum (0) */
	0x25, 0x01, /*     Logical Maximum (1) */
	0x95, 0x03, /*     Report Count (3) */
	0x75, 0x01, /*     Report Size (1) */
	0x81, 0x02, /*     Input (Data,Var,Abs,No Wrap,Linear,...) */
	0x95, 0x01, /*     Report Count (1) */
	0x75, 0x05, /*     Report Size (5) */
	0x81, 0x03, /*     Input (Const,Var,Abs,No Wrap,Linear,...) */
	0x05, 0x01, /*     Usage Page (Generic Desktop Ctrls) */
	0x09, 0x30, /*     Usage (X) */
	0x09, 0x31, /*     Usage (Y) */
	0x09, 0x38, /*     Usage (Wheel) */
	0x15, 0x81, /*     Logical Minimum (129) */
	0x25, 0x7F, /*     Logical Maximum (127) */
	0x75, 0x08, /*     Report Size (8) */
	0x95, 0x03, /*     Report Count (3) */
	0x81, 0x06, /*     Input (Data,Var,Rel,No Wrap,Linear,...) */
	0xC0,       /*   End Collection */
	0xC0,       /* End Collection */
};

hid dfu报告描述符

#define DFU_SIZE						35

uint8_t dfu_report[DFU_SIZE]={

		0x05, 0x8C,				//0x01,//0x8C,
		0x09, 0x06,
		0xa1, 0x01, 			// app clooection c0 end
		0x85, 0x0a,				// report ID (0x0a)  reserver for keyboard implement
		0x09, 0x06,
		0x15, 0x00,
		0x26, 0x00,
	    0xff, 0x75,
		0x08, 0x95,
		0x40, 0x91,
		0x82, 0x09,
		0x06, 0x15,
		0x00, 0x26,
	    0x00, 0xff,
		0x75, 0x08,
		0x95, 0X40,
		0x81, 0x82,
		0xc0,

};

参考截图

设备描述

device_des

接口0 鼠标功能

interface0_mouse

接口1 DFU功能

interface1_dfu

模拟输入输出数据

ana_inout

通信协议

连接

连接命令,主机ep3发送,确认设备已连接

report id(1B)

cmd(1B)

0x0a

0x55

连接成功,从机ep2上报

report id(1B)

cmd(1B)

0x0a

0x55

USB DFU Control

DFU Control包括升级流程中,控制流程的实现,但不包括数据传输的cmd

DFU Control cmd最高4bit位为0x0

Get Version

查询当前运行固件信息,主机ep3发送

report id(1B)

dfu control cmd(1B)

version area(1B)

0x0a

0x00

0x00 controller
0x01 app

上报当前运行固件信息,从机ep2上报 len 7

report id(1B)

dfu control cmd(1B)

version area(1B)

version Major(1B)

version Minor(1B)

version Revision(2B)

version Build Num(4B)

0x0a

0x00

0x00 controller 0x01 app

Check Version

发送确认待升级固件信息,从机ep3发送

report id(1B)

dfu control cmd(1B)

start addr(4B)

version Major(1B)

version Minor(1B)

version Revision(2B)

version Build Num(4B)

image size(4B)

crc(4B)

0x0a

0x01

固件img解析出的下载地址

image size hex

上位机运算整个image的crc32信息

从机收取待升级固件信息,检查版本兼容性(待升级固件是否可以在程序中升级),检查固件大小,存储crc信息

确认后通过ep2上报确认信息 len 4

report id(1B)

dfu control cmd(1B)

status(1B)

version area(1B)

0x0a

0x01

0x00: success else: fail reason

0x00 controller 0x01 app

PS: image 头部信息存储格式如下,对应bin文件首地址开始ih_img_size ih_ver

VERSION_PATCH 对应 iv_revision低位

VERSION_APP 对应 iv_build_num 低位

image size(4B) 对应 固件bin整体大小

struct image_version {
    uint8_t iv_major;
    uint8_t iv_minor;
    uint16_t iv_revision;
    uint32_t iv_build_num;
};

/** Image header.  All fields are in little endian byte order. */
struct image_header {
    uint32_t ih_magic;
    uint32_t ih_load_addr;
    uint16_t ih_hdr_size;           /* Size of image header (bytes). */
    uint16_t ih_protect_tlv_size;   /* Size of protected TLV area (bytes). */
    uint32_t ih_img_size;           /* Does not include header. */
    uint32_t ih_flags;              /* IMAGE_F_[...]. */
    struct image_version ih_ver;
    uint32_t _pad1;
};

DFU Start

Check Version成功后,主机ep3发送DFU开始命令

report id(1B)

dfu control cmd(1B)

0x0a

0x02

从机收到 DFU start后,首先ep2回复进行消息收到确认

report id(1B)

dfu control cmd(1B)

0x0a

0x02

从机收到 DFU start后,开始擦除指定区域和大小的flash,擦除成功后,停止ep1的通信端点上报,ep2上报回复信息

fail reason 包含擦除失败,固件未确认等(可以扩展) len 3

report id(1B)

dfu control cmd(1B)

status(1B)

0x0a

0x02

0x00: success else: fail reason

DFU Finish

发送完所有数据后,主机ep3发送传输完成命令

report id(1B)

dfu control cmd(1B)

0x0a

0x03

从机收到完成命令后,首先进行消息收到确认

report id(1B)

dfu control cmd(1B)

0x0a

0x03

之后对收到数据进行crc校验,与Check Version获取到的CRC信息进行核对,通过ep2进行上报 len 7

report id(1B)

dfu control cmd(1B)

status(1B)

crc check value(4B)

0x0a

0x03

0x00: success else: fail reason

若主机收到失败消息,可以选择重新升级(重新点击开始升级)或者发送DFU END退出升级

DFU End

升级成功结束后,主机ep3发送结束命令,指定是否重启进行固件搬运升级,升级失败时reboot flag置为0x01

report id(1B)

dfu control cmd(1B)

reboot flag(1B)

0x0a

0x04

0x00: reboot 0x01: not reboot

从机收到升级结束命令,开启鼠标正常ep1上报功能,若不需要重启,通过ep2上报回复消息 len 2

report id(1B)

dfu control cmd(1B)

0x0a

0x04

USB DFU Transfer

DFU Transfer为数据传输的cmd

DFU Transfer cmd最高4bit位为0x1

DFU Transfer预定义一条消息,主机通过ep3连续传输待升级固件

report id(1B)

dfu transfer cmd(1B)

data(62B)

0x0a

0x10

升级数据

*从机收到传输消息后,暂定不回复,如果需要回复可以回复相同数据进行确认(参考客户方案)

report id(1B)

dfu control cmd(1B)

data(62B)

0x0a

0x10

回复收到的数据

实际参考panlink升级,指定从机flash write单元256B,当收到的数据超过flash write单元后,ep2上报一条写成功消息通知主机继续发送

report id(1B)

dfu control cmd(1B)

block num(2B)

0x0a

0x10

数据长度/256
ex:首包block num0x0000,在收到5包后进行上报

扩展 EP1 操作消息

ep1 test cmd最高4bit位为0x2

需要手动关闭开启ep1上报功能时(暂时不确认升级过程中是否需要关闭ep1),主机通过ep3发送控制消息

report id(1B)

ep1 control cmd(1B)

ep1 status

0x0a

0x20

0x00: off 0x01: on

从机收到消息成功控制后,通过ep2回复确认消息

report id(1B)

ep1 control cmd(1B)

ep1 status

0x0a

0x20

0x00: off 0x01: on

扩展 EMI 命令

emi命令为rf test时的cmd

DFU Transfer cmd最高4bit位为0x3

例如

report id(1B)

dfu transfer cmd(1B)

data(xB)

0x0a

0x30(蓝牙测试模式)

蓝牙测试命令

report id(1B)

dfu transfer cmd(1B)

data(xB)

0x0a

0x31(2.4g测试模式)

2.4g测试命令(见如下命令格式)

2.4G 测试命令

1、test start

cmd

data

status

0x1a

0x01(test start)

0x01(send);0x00(recv)

2、tx carrier cmd

cmd

freq

tx power

status

0x1b

2byte(2402~2480)LSB

1byte(-45~7)

0x01(send);0x00(recv)

3、tx cmd

cmd

freq max

freq min

tx power

phy

0x1c

2byte(2402~2480)LSB

2byte(2402~2480)LSB

1byte(-45~7)

1byte(1M/2M)

hop time

tx count

status

1byte(0~10s)跳频间隔

2byte(0:一直发)LSB

0x01(send);0x00(recv)

4、rx cmd

cmd

freq

phy

status

0x1d

2byte(2402~2480)LSB

1byte(1M/2M)

0x01(send);0x00(recv)

5、test end

cmd

data

status

0x1a

0x00(test end)

0x01(send)

cmd

data

status

count

0x1a

0x00(test end)

0x00(recv)

2byte(rx模式才有该字段)LSB

获取mac地址命令

GET OWN ADDR

report id(1B)

dfu transfer cmd(1B)

0x0a

0x35

REPLY OWN ADDR

report id(1B)

dfu transfer cmd(1B)

own mac(6B)

0x0a

0x35

本设备的mac地址

GET PAIR ADDR

report id(1B)

dfu transfer cmd(1B)

0x0a

0x36

REPLY PAIR ADDR

report id(1B)

dfu transfer cmd(1B)

pair mac(6B)

0x0a

0x36

配对设备的mac地址

emi确认版本命令

EMI CHECK VERSION

report id(1B)

dfu transfer cmd(1B)

version Major(1B)

version Minor(1B)

version Revision(2B)

version Build Num(4B)

0x0a

0x37

EMI CHECK VERSION REPLY

report id(1B)

dfu transfer cmd(1B)

status(1B)

0x0a

0x37

0x00: success 0x01: fail

扩展其他命令

用户需要通过Endpoint3实现类似pid,vid更新的操作时,可以参考以上DFU/EMI的消息模式,自定义消息进行通信

report id保持0x0a

第一字节确保不和其他消息冲突即可 消息格式如

USB SET VID

report id(1B)

usb set param(1B)

set vid(1B)

status(2B)

0x0a

0x50

0x00

0x12,0x34

USB SET PID

report id(1B)

usb set param(1B)

set vid(1B)

status(2B)

0x0a

0x50

0x01

0x12,0x34

#define USB_VENDOR_CONNECT                                      0x55
#define USB_VENDOR_DFU_GET_VERSION                              0x00
#define USB_VENDOR_DFU_CHECK_VERSION                            0x01
#define USB_VENDOR_DFU_START                                    0x02
#define USB_VENDOR_DFU_FINISH                                   0x03
#define USB_VENDOR_DFU_END                                      0x04
#define USB_VENDOR_DFU_RESET                                    0x05
#define USB_VENDOR_DFU_TRANSFER                                 0x10
#define USB_VENDOR_DFU_EP1_CONTROL                              0x20
#define USB_VENDOR_BLE_TEST                                     0x30
#define USB_VENDOR_PRF_TEST                                     0x31
#define USB_GET_OWN_ADDR                                        0x35
#define USB_GET_PAIR_ADDR                                       0x36
#define USB_EMI_CHECK_VERSION                                   0x37
#define USB_VENTDOR_TEST1                                       0x40

#define USB_VENTDOR_SET_PARAM                                   0x50
#define USB_VENTDOR_SET_VID                                     0x00
#define USB_VENTDOR_SET_PID                                     0x01

参考添加逻辑如下

switch (usb_data[1]) {
	case USB_VENTDOR_SET_PARAM:
		switch (usb_data[2]) {
			case USB_VENTDOR_SET_VID:
			// set_vid();
			memcpy(usb_reply_data, usb_data, 2);
			usb_vendor_ep3_in(2, usb_reply_data);//usb ep3 上报2字节数据
			break;
		}
		break;
	}

USB通过中断,ep3端点进入数据解析

if (IntrOut & 0x8) {
	usb_vendor_ep3_out();
}

在处理完成后通过以下接口进行64字节数据ep3上报

static void usb_vendor_ep3_in(uint8_t len, uint8_t *data)
{
	uint8_t send_data[64];

	memset(send_data, 0xff, sizeof(send_data));
	memcpy(send_data, data, len);
	WRITE_REG(USB->INDEX, 3);
	USB_Write((uint32_t)3, 64, (void *)send_data);
	WRITE_REG(USB->CSR0_INCSR1, M_INCSR_IPR);
}

补充:VID PID生效逻辑

vid,pid单次设置通过参考以下代码进行存储,复位后生效

uint8_t usb_vid[2] = { 0x6e, 0x04 };
uint8_t usb_pid[2] = { 0x78, 0xc0 };

settings_save_one("usb/vid", &usb_vid, 2);
settings_save_one("usb/pid", &usb_pid, 2);

每次上电后,读取是否存储了vid、pid,若没有存储采用默认值,若存储过采用存储值

static void usb_settings_load(void)
{
	uint8_t usb_pid[2];
	uint8_t usb_vid[2];

	if (load_immediate_value("usb/vid", &usb_vid, 2) != -ENOENT) {
		/* have saved usb_vid */
		printk("usb vid %02x %02x!!!\n", usb_vid[0], usb_vid[1]);
		update_usb_vid(usb_vid);
	} else { /* first reset 1000 */
		printk("usb vid not exist no update\n");
	}

	if (load_immediate_value("usb/pid", &usb_pid, 2) != -ENOENT) {
		/* have saved usb_pid */
		printk("usb pid %02x %02x!!!\n", usb_pid[0], usb_pid[1]);
		update_usb_pid(usb_pid);
	} else { /* first reset 1000 */
		printk("usb pid not exist no update\n");
	}
}