Nuoeriris · 2020年06月22日

MM32 USB 功能学习笔记——WebDFU

面我们介绍了新出USB设备类型WebUSB,其中使用MM32 MCU实现WebUSB功能。既然可以通过网页与USB设备通信,那是否可以做别的功能,比如USB-DFU,当然是可以的,我们通过网页进行DFU功能,即WebDFU功能。我们本节将讲解如何在MM32 MCU现实WebDFU功能。

DFU是使用USB作为微控制器和编程工具之间的通信信道,通常是PC。在DFU类规格书说明中指出所有的DFU命令、状态和数据交换都需要通过端点0进行。命令集和基本协议都定义好的,但是上层协议(数据格式,错误信息等)是客户相关的。也就是说DFU类并没有定义数据传输格式(s19,16进制,纯2进制等等)

由于一个设备同时进行DFU操作和正常运行功能活动是不现实的,因此在DFU操作期间必须停止正常运行活动,这就意味着设备必须改变运行模式——也就是说我们在进行固件更新时比如打印机不再是打印机了,它是一个flash存储器编程器。但是支持DFU的设备不能自主改变模式,这需要接受外部(人或者主机操作系统)的干预。

对于DFU功能,其完成实现固件升级可以分为4个不同阶段。

1.枚举:

设备把自身的一些特性告知主机,嵌入在设备正常运行描述符中的一个DFU类接口描述符和相关的函数符能够完成这个目的,并且能够为通过控制管道的类专用的请求提供目标。

2.DFU枚举:

主机和设备同意开始固件升级,主机向设备发出USB复位,设备发出第二个描述符集合,并且为传输阶段做准备,这会是相应设备的运行时驱动无效,并使得DFU驱动不受其他目标为该设备通信妨碍,重编程设备的固件。

3.传输:

主机将固件映像传输给设备,功能描述符中的参数用于确保非易失性存储器编程的块大小和时序的正确性。状态请求用于保持主机和设备之间的同步。

4.显示:

一旦设备向主机报告重新编程完成,主机箱设备则发送usb复位,设备重枚举并执行升级后的固件。为了保证只有DFU驱动加载,有必要的在枚举DFU描述符集合改变id-product字段。

本节我们来讲解如何在MM32 MCU实现WebDFU设备功能,对于MM32 MCU来说,实现WebDFU只需要在之前程序基础上修改添加部分代码即可,按照开源的WebDFU协议加入功能。

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的WebDFU设备功能,我们重新封装好全部代码,用户不需要自己配置那些麻烦的描述符等参数,只需要知道用之前的单一设备函数即可。

软件资源如下:

对于MM32 MCU的WebDFU,我们可以配置WebDFU的参数。

#define USBD_DFU_DNLOAD_ENABLE      1
#define USBD_DFU_UPLOAD_ENABLE      0
#define USBD_DFU_STRDESC              L"USB_DFU"
#define USBD_DFU_XFERBUF_SIZE        1024
#define USBD_WEBUSB_VENDOR_CODE   0x21
#define USBD_WEBUSB_BASE_LANDING_URL
"
CONCAT_MACRO_TO_STRING(USBD_WEBUSB_BASE_LANDING_URL,
USBD_DEVDESC_IDVENDOR)
#define USBD_WEBUSB_ORIGIN_URL      "devanlai.github.io/"
#define USBD_WEBUSB_IF_NUM          USBD_DFU_IF_NUM

参数设置如上。当进行DFU升级时候,可以看到电脑上显示的设备名称为USB\_DFU,就是配置的USBD\_DFU\_STRDESC参数。

在使用MM32 WebDFU功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)
{
// USB Device Initialization and connect
usbd_init();
usbd_connect(__TRUE);
while (!usbd_configured())     // Wait for USB Device to configure
{
}
while (1)
{
……
}
}

然后依然和之前一样只是在WebUSB基础上修改添加WebDFU相关参数函数接口即可,代码如下:

//DFU初始化
void usbd_dfu_init(void)
{
DFU_Reset();
current_write_addr = 0;
}
//USB DFU开始升级
BOOL USBD_DFU_StartUpgrade(void) {
error_t err = flash_manager_init(target_device);
current_write_addr = target_device.flash_start;
switch (err) {
case ERROR_SUCCESS:
initialized = true;
break;
case ERROR_RESET:
case ERROR_ALGO_DL:
case ERROR_ALGO_DATA_SEQ:
case ERROR_INIT:
case ERROR_SECURITY_BITS:
case ERROR_UNLOCK:
DFU_SetStatus(DFU_STATUS_ERR_PROG);
break;
case ERROR_ERASE_SECTOR:
case ERROR_ERASE_ALL:
DFU_SetStatus(DFU_STATUS_ERR_ERASE);
break;
case ERROR_WRITE:
DFU_SetStatus(DFU_STATUS_ERR_WRITE);
break;
case ERROR_FAILURE:
case ERROR_INTERNAL:
default:
DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);
break;
}

return (err == ERROR_SUCCESS) ? (__TRUE) : (__FALSE);
}
//复位目标
static bool reset_target(bool error_condition) {
current_write_addr = 0;
if (initialized) {
error_t err = flash_manager_uninit();
switch (err) {
case ERROR_SUCCESS:
if (config_get_auto_rst()) {
// Target is reset and run by the uninit
} else if (!error_condition) {
// Reset and run the target at the end of a successful upgrade
target_set_state(RESET_RUN);
}
break;
case ERROR_RESET:
case ERROR_ALGO_DL:
case ERROR_ALGO_DATA_SEQ:
case ERROR_INIT:
case ERROR_SECURITY_BITS:
case ERROR_UNLOCK:
DFU_SetStatus(DFU_STATUS_ERR_PROG);
break;
case ERROR_ERASE_SECTOR:
case ERROR_ERASE_ALL:
DFU_SetStatus(DFU_STATUS_ERR_ERASE);
break;
case ERROR_WRITE:
DFU_SetStatus(DFU_STATUS_ERR_WRITE);
break;
case ERROR_FAILURE:
case ERROR_INTERNAL:
default:
DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);
break;
}
initialized = false;
return (err == ERROR_SUCCESS);
}

return true;
}
//USB DFU结束升级
BOOL USBD_DFU_FinishUpgrade(void) {
return reset_target(false) ? (__TRUE) : (__FALSE);
}
//USB DFU写数据
BOOL USBD_DFU_WriteBlock(const U8 *buffer, U16 blockSize) {
error_t err = flash_manager_data(current_write_addr, (U8*)buffer, blockSize);
switch (err) {
case ERROR_SUCCESS:
current_write_addr += blockSize;
break;
case ERROR_RESET:
case ERROR_ALGO_DL:
case ERROR_ALGO_DATA_SEQ:
case ERROR_INIT:
case ERROR_SECURITY_BITS:
case ERROR_UNLOCK:
DFU_SetStatus(DFU_STATUS_ERR_PROG);
break;
case ERROR_ERASE_SECTOR:
case ERROR_ERASE_ALL:
DFU_SetStatus(DFU_STATUS_ERR_ERASE);
break;
case ERROR_WRITE:
DFU_SetStatus(DFU_STATUS_ERR_WRITE);
break;
case ERROR_FAILURE:
case ERROR_INTERNAL:
default:
DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);
break;
}

return (err == ERROR_SUCCESS);
}

这样我们就完成MM32 MCU的WebDFU功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出USB DFU。在USB DFU枚举成功后,我们需要检查是否真的可以被WebDFU网页识别。打开https://devanlai.github.io/webdfu/dfu-util/通过该网页检测WebDFU工作状态,网页如下图所示:
插图1.png
通过点击Connect就可以像之前WebUSB设备一样弹出窗口选择我们的USB DFU设备,然后就可以在Vendor ID(hex)窗口看到序列号。可以通过网页上的Detach DFU和Download以及Upload进行DFU升级动作。

以上就是MM32 MCU USB的WebDFU功能,具体的WebDFU协议过程想详细了解看dapboot,大家可以自由发挥修改底层和上层的代码实现自己的WebDFU网页端和设备端。

推荐阅读
关注数
6108
内容数
272
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息