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,
};
通信协议¶
连接¶
连接命令,主机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 |
上报当前运行固件信息,从机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 |
扩展 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 |
扩展其他命令¶
用户需要通过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");
}
}