MM32-USB

在前面的章节中我们介绍了MM32 USB各种功能类型,也介绍了如何通过串口或者J-Link RTT方式实现shell辅助调试方式,但是其都需要依赖额外的工具,比如串口方式就需要USB转TTL,J-Link RTT需要使用J-Link下载器,所以希望有新的方法实现shell,本次我们介绍USB CDC的方式来实现shell功能。

本次我们采用MM32L373 MiniBoard作为测试开发板,验证USB CDC的方式来实现shell功能。

前面已经为大家讲解了shell的串口方法,其实原理一样,只是用MM32 USB枚举成串口设备替代USB转TTL,直接从USB获取数据到MCU,也不需要额外占用MCU的串口,节省资源和硬件,相关的代码都可以从之前的文章获取,本次只是融合两者,改变实现接口,具体代码参考如下:

对于CDC部分,其函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER                    0

#define USBD_MAX_PACKET0             64

#define USBD_DEVDESC_IDVENDOR      0x2F81 //0x0D28

#define USBD_DEVDESC_IDPRODUCT     0x0001 //0x0204

以上是定义的MM32 MCU CDC设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的CDC设备,如图1所示:

MM32 USB学习笔记——虚拟串口CDC SHELL调试
图1 PC设备管理器列表

对于MM32 MCU的CDC功能来说,

在使用CDC功能之前先调用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)

{     

}

}

对于shell部分其函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct

{

char *command; // shell命令提示符

char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer

unsigned short length; // shell命令长度大小

unsigned short cursor; // shell光标位置偏移

char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量

char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域

unsigned short historyCount; // 历史记录数量

short historyFlag; // 当前记录偏移位置

short historyOffset; // 历史记录偏移大小

SHELL_CommandTypeDef *commandBase; // 命令表基地址

unsigned short commandNumber; // 命令数量

int keyFuncBase; // 按键响应表基地址

unsigned short keyFuncNumber; // 按键响应数量

SHELL_InputMode status; // shell输入状态

unsigned char isActive; //是不是当前激活的shell

shellRead read; // shell读函数接口

shellWrite write; // shell写函数接口

}SHELL_TypeDef;

如上所示,为对象的定义接口,移植的步骤先定义一个shell对象,即:SHELL_TypeDef cdc_shell,然后实例化对象的操作接口,具体说明看注释,对于其中我们需要关注的是shell的读写接口。由于本次我们使用USB CDC接收和发送数据,所以我们只需要在USB CDC的函数中处理接收到的数据即可,我们使用shellHandler(&cdc_shell, EP2RXBuff[i]);来处理数据的交互,具体函数代码参考串口shell代码。

shell的发送接口,只需要把数据拷贝到buffer即可。

shell的读写接口移植到CDC上,代码如下:

void  USBD_CDC_TASK(void)
{
uint8_t i, count;

NotifyOnStatusChange();
if (CDC_UART ->ISR & 0x08)
{
CDC_UART ->GCR &= ~(3 << 3);
CDC_UART ->GCR = 3 << 3;
UART_ClearITPendingBit(CDC_UART, UART_OVER_ERR);
}
// USB -> UART
if (EP2ReceiveFlag == 1)
{
EP2ReceiveFlag = 0;
for (i = 0; i < RxBufLen; i++)
shellHandler(&cdc_shell, EP2RXBuff[i]);
}

// UART -> USB
if (EP2TransferFlag == 1)
{
if (TxBufLen > 0)
{
while (USB->rEP2_CTRL & 0x80);
if (TxBufLen > 64)
{
UART_ReadData(EP2TXBuff, 64);
count = 64;
TxBufLen -= 64;
}
else
{
UART_ReadData(EP2TXBuff, TxBufLen);
count = TxBufLen;
TxBufLen = 0;
}
usb_buf_busy_flag = 1;
for (i = 0; i < count; i++)
{
USB->rEP2_FIFO = *(EP2TXBuff + i);
}
if ((USB ->rEP2_AVIL & 0x3f) == count)
{
USB->rEP2_CTRL = 0x80 | count;
}
else
{
USB->rTOP |= 1 << 3;
USB->rTOP &= ~(1 << 3);
}
USB->rEP2_CTRL = 0x80 | count;
if (0 == TxBufLen)
EP2TransferFlag = 0;
}
}
}

如上,我们就完成通过MM32 MCU的CDC实现shell调试功能,用串口助手打开虚拟串口,用CDC shell测试发送数据,结果如下:

MM32 USB学习笔记——虚拟串口CDC SHELL调试
图2 功能演示

以上就是MM32 MCU USB的CDC shell功能。

本文转自:灵动MM32MCU,转载此文目的在于传递更多信息,版权归原作者所有。

围观 124

前面我们介绍了新出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个不同阶段。

01、枚举

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

02、DFU枚举

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

03、传输

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

04、显示

一旦设备向主机报告重新编程完成,主机箱设备则发送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 "devanlai.github.io/webdfu/dfu-util/?vid="

#define USBD_WEBUSB_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工作状态,网页如下图所示:

MM32 USB 功能学习笔记——WebDFU

通过点击Connect就可以像之前WebUSB设备一样弹出窗口选择我们的USB DFU设备,然后就可以在Vendor ID(hex)窗口看到序列号。可以通过网页上的Detach DFU和Download以及Upload进行DFU升级动作。

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

本文转自:灵动MM32MCU,转载此文目的在于传递更多信息,版权归原作者所有。

围观 60

WebUSB功能

在前面几个章节我们介绍了MM32 MCU的各种常用的USB功能,而随着物联网的发展,有时候希望我们直接可以通过网页访问USB设备,于是出现了一种新的usb使用类型即WebUSB,通过让MCU USB实现WebUSB功能,就可以直接与网页通信,因此我们本节我们讲解MM32 MCU的WebUSB功能。

对于WebUSB来说,其是由Reilly Grant和Ken Rockot开发的,它已经被推入W3C WICG,以求建立一个能够被浏览器制造商引用的平台。WebUSB是一个Javascript API,可以允许网页访问已连接的USB设备,这里的USB设备是指系统和工业的USB设备。通过WebUSB API,我们可以让USB设备,比如键盘、鼠标、3D打印机和硬件驱动连接到物联网,甚至在Web页面上进行定位。这一产品的目的,是为了帮助硬件制造商将他们的USB设备实现跨平台通用(包括Web),此后不需要为特定的平台写本地驱动或者SDK。除了控制硬件,WebUSB也可以通过Web页面安装固件升级或者执行其他重要任务。然而,这个草拟版本的API并不能传输文件。

当然,目前WebUSB现在只是个草拟版本,还没有正式采用W3C标准。其开发工作仍然在进展之中,但是我们现在还是可以在Github上看到完整的WebUSB代码库(https://github.com/wicg/webusb)。

WebUSB原理

当USB设备插入主机时,浏览器会读取设备发送的描述符,然后将其储存在内部USB设备储存器中。此过程由Chrome的浏览器内核Blink处理。日志可以在chrome://device-log(GET参数“refresh = 1”非常有用)中查看。

根据规范要求,设备可以在其二进制对象存储中的平台描述符中明确地声明对WebUSB的支持。

图1 WebUSB功能描述符

浏览器将每个USB设备存储在自己的设备存储器中。WebUSB的可访问性由本机驱动程序支持所决定。在Windows上,我们可以通过浏览器访问由WinUSB驱动程序处理的每个USB设备。其他的诸如大容量存储设备,网络摄像头或HID等就无法通过网络访问了,因为它们具有处理这些设备的专用驱动程序。

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

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

软件资源如下:

对于MM32 MCU的WebUSB,我们可以配置WebUSB的参数来让网页识别设备。

#define USBD_WEBUSB_STRDESC         L"WebUSB: MM32"
//     WebUSB support
#define USBD_WEBUSB_ENABLE          WEBUSB_INTERFACE
#define USBD_WEBUSB_VENDOR_CODE     0x21
#define USBD_WEBUSB_LANDING_URL   "os.mbed.com/webusb/landing-page/?bid="
#define USBD_WEBUSB_ORIGIN_URL      "os.mbed.com/"

参数设置如上可以看到电脑上显示的设备名称WebUSB:MM32,如下:

图2 WebUSB枚举列表

在使用MM32 WebUSB功能之前先调用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)
{      
……
}
}

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

#if (USBD_WEBUSB_ENABLE)
usbd_webusb_if_num = if_num++;   
desc_ptr += webusb_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_webusb_if_num);
#endif
#if (USBD_WEBUSB_ENABLE)
static U16 webusb_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 webusb_desc[] = {
WEBUSB_DESC
};
pD = config_desc;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)
pD = config_desc_hs;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
#if (USBD_WINUSB_ENABLE)
pD = USBD_WinUSBDescriptorSetDescriptor + WINUSB_DESCRIPTOR_SET_HEADER_SIZE;
 ((WINUSB_FUNCTION_SUBSET_HEADER*)pD)->bFirstInterface = if_num;
#else
#error "WEBUSB requires WINUSB!"
#endif
return sizeof(webusb_desc); 
}
#endif

这样我们就完成MM32 MCU的WebUSB功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出WebUSB。在WebUSB枚举成功后,我们需要检查是否真的可以被网页识别。找了一个静态网页,通过该网页检测WebUSB工作状态。

图3 测试网页

要测试设备是否支持,请单击“选择设备”按钮打开权限提示。此提示将列出所有可用的USB设备。通过选择所需的设备并单击“连接”,工具将打开设备,并遍历每个可用的界面,并尝试声明。结果记录在页面底部的表格中。被声明的interfaces列显示可以声明的接口编号,我们点击choose A Device。

图4 WebUSB连接页面

从图上可以看到网页找到了MM32实现的WebUSB设备,以上就是MM32 MCU USB的WebUSB功能。

在下一张章节将会继续带来WebUSB DFU升级教程,后续将对USB的相关功能程序进行单独的封装打包,正式在MM32 MCU的官方发布,方便大家参考使用,敬请期待。

本文转自:灵动MM32MCU

围观 1355

在上一节我们介绍了MM32 MCU的USB模拟U盘功能,通过四个章节把常用的USB设备功能编程一一做了介绍,通常来说,往往希望我们USB设备多个功能可以一起工作,因此我们本节我们讲解USB复合设备。

在USB Specification Revision 2.0定义USB复合装置(Composite Device)为具有多个独立控制接口(Interface)的装置。复合装置是一个具有多个功能和配置多个接口的组合设备,但是其只具有一个唯一的设备(Device)地址,透过相对应的USB设备驱动程式(Device Driver)来与主机通讯。如果复合装置中有单一功能对应多个接口的类别就必须额外使用接口关联描述元(Interface Association Descriptor, IAD),让设备中的不同介面与其相同功能建立关联。

接口(Interface)

复合装置只有一套PID/VID,透过不同的接口定义为不同的类别来实现多个功能。 USB设备必须额外使用接口关联描述元(IAD)来定义多重介面功能的复合装置。复合装置的接口描述元(Interface Descriptor)定义接口的每一个功能,USB主机透过此描述元来得知USB设备可用的功能。

端点(Endpoints)

复合装置的Endpoint分配,除了控制端点用于标准、特定类别和供应商特定的请求,其他Endpoint是给复合装置内的个别功能来使用,使用的传输方式与应用的需求有关。

描述元(Descriptor)

装置描述元(device descriptor)

组态描述元(configuration descriptor)

接口描述元(interface descriptor)

特定的类别描述元(class-specific descriptors)

端点描述元(endpoint descriptor)

字串描述元(String descriptor)

一个USB装置的功能被使用之前,主机会先向USB装置取得组态资料,然后再由USB装置将这些描述元的资料当作组态资料一起传给主机,组态描述元除了本身组态描述元外,另外包含接口描述元、类别描述元和端点描述元,而HID类别则会有特定的类别描述元,此描述元会交代报告(report)描述元和实体(physical)描述元;

本节我们来讲解如何在MM32 MCU实现USB复合设备功能,在前面我们介绍了MM32 实现HID、WINUSB、CDC和MSC功能,MM32系列MCU的USB功能有4个端点,所以我们可以自由组合上述的功能在一起,本节我们实现HID、WINUSB和CDC复合设备。

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

软件资源如下:

对于MM32 MCU的复合设备,我们可以配置一些参数来作为识别。

#define USBD_STRDESC_MAN L"MM32"
#define USBD_STRDESC_PROD L"MM32 Composite" //产品名称
#define USBD_HID_STRDESC L"MM32 HID"
#define USBD_BULK_STRDESC L"MM32 WINUSB"

参数设置如上可以看到电脑上显示的设备名称,如下:


图1 设备显示名称

根据这些参数,通过封装好的函数直接处理符合设备之间的关系,函数如下:

//MM32 USB复合设备类型初始化函数
void usbd_class_init(void)
{   
U8  if_num = 0;
U16 desc_ptr = 0;
desc_ptr += start_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], if_num);    
    
#if (USBD_ADC_ENABLE)
usbd_adc_init();
#endif
#if (USBD_MSC_ENABLE)       
#if !(defined(MSC_BL)) &&  defined(DRAG_N_DROP_SUPPORT)
//change descriptors here
if (config_ram_get_disable_msd() == 1 || flash_algo_valid()==0 ){
usbd_if_num -= USBD_MSC_ENABLE;
USB_CONFIGURATION_DESCRIPTOR * usb_conf_desc = (USB_CONFIGURATION_DESCRIPTOR *)USBD_ConfigDescriptor;
usb_conf_desc->bNumInterfaces = usbd_if_num;
U16 usb_wtotal_len = USBD_WTOTALLENGTH_MAX - (USBD_MSC_DESC_LEN     * USBD_MSC_ENABLE);
usb_conf_desc->wTotalLength = usb_wtotal_len;
USBD_ConfigDescriptor[usb_wtotal_len] = 0;
USBD_HID_DescriptorOffset -= USBD_MSC_ENABLE * USBD_MSC_DESC_LEN;
#if (USBD_HS_ENABLE == 1)
usb_conf_desc = (USB_CONFIGURATION_DESCRIPTOR *)USBD_ConfigDescriptor_HS;
usb_conf_desc->bNumInterfaces = usbd_if_num;
usb_conf_desc->wTotalLength = usb_wtotal_len;
USBD_ConfigDescriptor_HS[usb_wtotal_len] = 0;
#endif         
} else
#endif
{
usbd_msc_if_num = if_num++;
desc_ptr += msc_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_msc_if_num);
usbd_msc_init();
}
#endif //#if (USBD_MSC_ENABLE)  
#if (USBD_CDC_ACM_ENABLE)
usbd_cdc_acm_cif_num = if_num++;
usbd_cdc_acm_dif_num = if_num++;
desc_ptr += acm_cdc_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_cdc_acm_cif_num);
USBD_CDC_ACM_Initialize();
#endif
#if (USBD_HID_ENABLE) 
usbd_hid_if_num = if_num++;
desc_ptr += hid_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_hid_if_num);
usbd_hid_init();
#endif
#if (USBD_WEBUSB_ENABLE)
usbd_webusb_if_num = if_num++;   
desc_ptr += webusb_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_webusb_if_num);
#endif
#if (USBD_BULK_ENABLE)
usbd_bulk_if_num = if_num++;  
desc_ptr += bulk_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_bulk_if_num);
usbd_bulk_init();
#endif
#if (USBD_CLS_ENABLE)
usbd_cls_init();
#endif
}
//初始的描述配置填充
static U16 start_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 start_desc[] = { 
/* Configuration 1 */
USB_CONFIGUARTION_DESC_SIZE,          // bLength 
USB_CONFIGURATION_DESCRIPTOR_TYPE,  // bDescriptorType 
WBVAL(USBD_WTOTALLENGTH_MAX),       // wTotalLength 
USBD_IF_NUM_MAX,                        // bNumInterfaces 
0x01,                                       // bConfigurationValue: 0x01 is used to select this configuration 
0x00,                                       // iConfiguration: no string to describe this configuration 
USBD_CFGDESC_BMATTRIBUTES |          // bmAttributes 
(USBD_POWER << 6),
USBD_CFGDESC_BMAXPOWER   // bMaxPower, device power consumption 
};
pD = config_desc;
memcpy(pD, start_desc, sizeof(start_desc));
#if (USBD_HS_ENABLE == 1)
pD = config_desc_hs;
memcpy(pD, start_desc, sizeof(start_desc));
#endif
return sizeof(start_desc);
}
//HID的描述配置填充
static U16 hid_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 hid_desc[] = {
HID_DESC
#if ((USBD_HID_EP_INTOUT != 0) && (USBD_HID_EP_INTIN != 0))
HID_EP_INOUT
#elif (USBD_HID_EP_INTIN != 0)
HID_EP_IN
#elif (USBD_HID_EP_INTOUT != 0)
HID_EP_OUT
#endif
};
pD = config_desc;
memcpy(pD, hid_desc, sizeof(hid_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1) 
const U8 hid_desc_hs[] = {
HID_DESC
#if ((USBD_HID_EP_INTOUT != 0) && (USBD_HID_EP_INTIN != 0))
HID_EP_INOUT_HS
#elif (USBD_HID_EP_INTIN != 0) //#else
HID_EP_IN_HS
#elif (USBD_HID_EP_INTOUT != 0)
HID_EP_OUT_HS
#endif
};
pD = config_desc_hs;
memcpy(pD, hid_desc_hs, sizeof(hid_desc_hs));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif    
return sizeof(hid_desc);
}
//CDC的描述配置填充
static U16 acm_cdc_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 cdc_desc[] = {
#if (USBD_MULTI_IF)
CDC_ACM_DESC_IAD(0, 2)
#endif
CDC_ACM_DESC_IF0
CDC_ACM_EP_IF0
CDC_ACM_DESC_IF1
CDC_ACM_EP_IF1
};
pD = config_desc;
memcpy(pD, cdc_desc, sizeof(cdc_desc));
#if (USBD_MULTI_IF)
((USB_INTERFACE_ASSOCIATION_DESCRIPTOR *)pD)->bFirstInterface = if_num;
pD += USB_INTERFACE_ASSOC_DESC_SIZE;
#endif     
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
pD += USB_INTERFACE_DESC_SIZE + CDC_HEADER_SIZE + CDC_CALL_MANAGEMENT_SIZE + CDC_ABSTRACT_CONTROL_MANAGEMENT_SIZE;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bMasterInterface = if_num;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bSlaveInterface0 = if_num + 1;pD += CDC_UNION_SIZE + USB_ENDPOINT_DESC_SIZE;
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num + 1;
 
#if (USBD_HS_ENABLE == 1)   
const U8 cdc_desc_hs[] = {
#if (USBD_MULTI_IF)
CDC_ACM_DESC_IAD(0, 2)
#endif
CDC_ACM_DESC_IF0
CDC_ACM_EP_IF0_HS
CDC_ACM_DESC_IF1
CDC_ACM_EP_IF1_HS
};
pD = config_desc_hs;
memcpy(pD, cdc_desc_hs, sizeof(cdc_desc_hs));
#if (USBD_MULTI_IF)
((USB_INTERFACE_ASSOCIATION_DESCRIPTOR *)pD)->bFirstInterface = if_num;
pD += USB_INTERFACE_ASSOC_DESC_SIZE;
#endif   
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
pD += USB_INTERFACE_DESC_SIZE + CDC_HEADER_SIZE + CDC_CALL_MANAGEMENT_SIZE + CDC_ABSTRACT_CONTROL_MANAGEMENT_SIZE;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bMasterInterface = if_num;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bSlaveInterface0 = if_num + 1;
pD += CDC_UNION_SIZE + USB_ENDPOINT_DESC_SIZE;
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num +1 ;
#endif  //(USBD_HS_ENABLE == 1)    
return sizeof(cdc_desc);
}
//MSC的描述配置填充
static U16 msc_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 msc_desc[] = { 
MSC_DESC
MSC_EP
};
pD = config_desc;
memcpy(pD, msc_desc, sizeof(msc_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)    
const U8 msc_desc_hs[] = { 
MSC_DESC
MSC_EP_HS
};
pD = config_desc_hs;
memcpy(pD, msc_desc_hs, sizeof(msc_desc_hs));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
return sizeof(msc_desc);
}
#if (USBD_WEBUSB_ENABLE)
//WEBUSB的描述配置填充
static U16 webusb_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 webusb_desc[] = {
WEBUSB_DESC
};
pD = config_desc;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)
pD = config_desc_hs;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
#if (USBD_WINUSB_ENABLE)
pD = USBD_WinUSBDescriptorSetDescriptor + WINUSB_DESCRIPTOR_SET_HEADER_SIZE;
 ((WINUSB_FUNCTION_SUBSET_HEADER*)pD)->bFirstInterface = if_num;
#else
#error "WEBUSB requires WINUSB!"
#endif
return sizeof(webusb_desc); 
}
#endif
#if (USBD_BULK_ENABLE)
//BUCK USB的描述配置填充
static U16 bulk_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 bulk_desc[] = { 
BULK_DESC
BULK_EP
};
pD = config_desc;
memcpy(pD, bulk_desc, sizeof(bulk_desc));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)  
const U8 bulk_desc_hs[] = { 
BULK_DESC
BULK_EP_HS
};
pD = config_desc_hs;
memcpy(pD, bulk_desc_hs, sizeof(bulk_desc_hs));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
#if (USBD_WINUSB_ENABLE)
pD = USBD_WinUSBDescriptorSetDescriptor + WINUSB_DESCRIPTOR_SET_HEADER_SIZE;
#if (USBD_WEBUSB_ENABLE)
pD += WINUSB_FUNCTION_SUBSET_HEADER_SIZE + WINUSB_FEATURE_COMPATIBLE_ID_SIZE + DEVICE_INTERFACE_GUIDS_FEATURE_LEN;
#endif
 ((WINUSB_FUNCTION_SUBSET_HEADER*)pD)->bFirstInterface = if_num;
#else
#error "BULK interfaces requires WINUSB!"
#endif
return sizeof(bulk_desc);
}
#endif
 

在使用MM32复合设备功能之前先调用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)

{      

……

}

}

然后和之前一样实现HID、WINUSB、CDC的函数接口即可,这样我们就完成MM32 MCU的复合设备功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出复合设备,如下显示:


图2 设备管理器复合设备枚举列表

从图上可以看到MM32实现的复合设备在电脑上同时显示出HID、WINUSB以及CDC。对于MM32的复合设备来说,和单一设备一样简单方便,使用时只需要加入各自功能的代码即可。

来源:灵动MM32MCU

围观 382

在上一节我们介绍了 MM32 MCU 的 HID 功能,对于HID 来说,虽然免驱,但是速度相对慢,有没有更好的选择呢?当然有,那就是WinUSB ,速度更快,对于现在的 PC 来说,基本上都是 Win10 ,WinUSB 在 Win10 免驱,此节我们就介绍如何使用 MM32 MCU的 WinUSB 功能。

WinUSB 设备是一种通用串行总线 (USB) 设备,其固件定义了某些 Microsoft 操作系统 (OS) 特征描述符,这些描述符将兼容 ID 报告为 "WINUSB"。

WinUSB 设备的用途是让 Windows 将 Winusb.sys 作为设备的功能驱动程序载入,而无需自定义 INF 文件。对于 WinUSB 设备,你无须为设备分发 INF 文件,对最终用户而言,这大大简化了驱动程序安装过程。相反,如果你需要提供自定义 INF,则不应将设备定义为 WinUSB 设备和在 INF 中指定设备的硬件 ID。

通常USB自定义设备都需要用户自己开发驱动,为了免去USB驱动开发,减少用户开发时间,微软花了不少心思,在Win8 或更高版本的Windows 系统中,集成了WinUSB 的WCID设备,从而WinUSB 作为微软提供的一个USB设备的通用驱动程序提供给用户,通过使用这个驱动,用户不再需要编写内核层的驱动程序就能够访问到USB设备。WCID是USB驱动一种新的匹配机制,在2012年左右引入的,通常USB设备都是通过VID和PID来进行匹配的,然而使用了WCID之后,USB设备不再通过VID和PID来匹配驱动,而是通过一个叫做兼容ID(Windows Compatible ID)来匹配,这样用户就不用为每一个VID和PID不同的设备编写INF文件了。当然我们要注意到,这里所说的免驱动包含两层含义:一层是用户不需要编写驱动程序,系统自带了驱动程序,只需写一个INF文件,比如USB串口;另一层则是不需要编写INF文件,系统会根据设备类型来安装驱动,这需要操作系统的支持。对于WinUSB设备来说,在Win8之前不用编写驱动程序,但是需要编写INF文件,匹配设备。在Win8之后,如果设备支持WCID,连INF也不用编写。


图1 WinUSB配置

下面将介绍如何在设备中增加对WCID的支持,让在能在Win8之后的系统上实在真正免驱即插即用。

首先我们得要有一个能够使用起来的自定义设备,在这个设备的设备描述符中,USB版本号设置为2.00,在这个设备的基础之上进行如下的修改:

修改一:响应ID为0xEE的字符描述符请求,字符描述的内容为:

{

0x12,                                       /* bLength */

USB_STRING_DESCRIPTOR_TYPE,          /* bDescriptorType */

'M', 0x00,                                    /* wcChar0 */

'S', 0x00,                                    /* wcChar1 */

'F', 0x00,                                    /* wcChar2 */

'T', 0x00,                                    /* wcChar3 */

'1', 0x00,                                    /* wcChar4 */

'0', 0x00,                                    /* wcChar5 */

'0', 0x00,                                    /* wcChar6 */

0x17,                                       /* bVendorCode */

0x00,                                       /* bReserved */

}

修改一:是为了让我们的自定设备被识别为WCID设备。

修改二:响应请求号为0x17并且index为4的厂商自定义请求,返回内容为:

{

0x28, 0x00, 0x00, 0x00,                       /* dwLength */

0x00, 0x01,                                  /* bcdVersion */

0x04, 0x00,                                  /* wIndex */

0x01,                                        /* bCount */

0,0,0,0,0,0,0,                                 /* Reserved */

/* WCID Function  */

0x00,                                        /* bFirstInterfaceNumber */

0x01,                                        /* bReserved */

/* CID */

'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 

/* sub CID */

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

0,0,0,0,0,0,                                  /* Reserved */

}

修改二是为了向Windows系统上报我们USB设备的WCID值,因为我们需要的是WinUSB的驱动程序,所以我们上报的内容信息就是WinUSB驱动的WCID:“WINUSB”。在修改上述内容完成后,将MM32 MCU的USB插入电脑,会发现设备能够自动安装上WinUSB驱动程序,然后显示如下所示:


图2 WinUSB驱动程序

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的WinUSB功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要了解MM32 MCU WinUSB的VID和PID以及如何处理WinUSB的数据接收和发送即可。

对于MM32 MCU的WinUSB功能来说,在使用WinUSB功能之前先调用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)

{      

}

}

然后就是WinUSB数据收发处理函数,USB数据处理函数如下:

static U8 *ptrDataIn;

static U16 DataInReceLen;

static Bulk_queue Bulk_Cmd_queue;

 

static volatile uint8_t  USB_ResponseIdle;

 

void usbd_bulk_init(void)

{

ptrDataIn = USBD_Bulk_BulkOutBuf;

DataInReceLen = 0;

Bulk_queue_init(&Bulk_Cmd_queue);

USB_ResponseIdle = 1;

}

 

/*

 *  USB Device Bulk In Endpoint Event Callback

 *    Parameters:      event: not used (just for compatibility)

 *    Return Value:    None

 */

void USBD_BULK_EP_BULKIN_Event(U32 event)

{

uint8_t * sbuf = 0;

int slen;

if(Bulk_queue_get_send_buf(&Bulk_Cmd_queue, &sbuf, &slen)){

USBD_WriteEP(usbd_bulk_ep_bulkin | 0x80, sbuf, slen);

} else {

USB_ResponseIdle = 1;

}

}

 

/*

 *  USB Device Bulk Out Endpoint Event Callback

 *    Parameters:      event: not used (just for compatibility)

 *    Return Value:    None

*/

void USBD_BULK_EP_BULKOUT_Event(U32 event)

{    

U16 bytes_rece;

uint8_t * rbuf;

 

bytes_rec = USBD_ReadEP(usbd_bulk_ep_bulkout, ptrDataIn, USBD_Bulk_BulkBufSize - DataInReceLen);

ptrDataIn += bytes_rece;

DataInReceLen  += bytes_rece;

if ((DataInReceLen >= USBD_Bulk_BulkBufSize) ||

(bytes_rece <  usbd_bulk_maxpacketsize[USBD_HighSpeed])) {

if(Bulk_queue_execute_buf(&Bulk_Cmd_queue,USBD_Bulk_BulkOutBuf, DataInReceLen, &rbuf)) {

//Trigger the BULKIn for the reply

if (USB_ResponseIdle) {

USBD_BULK_EP_BULKIN_Event(0);

USB_ResponseIdle = 0;

}

}

//revert the input pointers

DataInReceLen = 0;

ptrDataIn = USBD_Bulk_BulkOutBuf;

}

}

 

/*

 *  USB Device Bulk In/Out Endpoint Event Callback

 *    Parameters:      event: USB Device Event

 *                       USBD_EVT_OUT: Output Event

 *                       USBD_EVT_IN:  Input Event

 *    Return Value:    None

*/

void USBD_BULK_EP_BULK_Event(U32 event)

{

if (event & USBD_EVT_OUT) {

USBD_BULK_EP_BULKOUT_Event(0);

}

 

if (event & USBD_EVT_IN) {

USBD_BULK_EP_BULKIN_Event(0);

}

}

 

void Bulk_queue_init(Bulk_queue *queue)

{

queue->recv_idx = 0;

queue->send_idx = 0;

queue->free_count = FREE_COUNT_INIT;

queue->send_count = SEND_COUNT_INIT;

}

BOOL Bulk_queue_get_send_buf(Bulk_queue *queue, uint8_t **buf, int *len)

{

if (queue->send_count)

{

queue->send_count--;

*buf = queue->USB_Request[queue->send_idx];

*len = queue->resp_size[queue->send_idx];

queue->send_idx = (queue->send_idx + 1) % Bulk_PACKET_COUNT;

queue->free_count++;

return (__TRUE);

}

return (__FALSE);

}

 

BOOL Bulk_queue_execute_buf(Bulk_queue *queue, const uint8_t *reqbuf, int len, uint8_t **retbuf)

{

uint32_t rsize;

if (queue->free_count > 0)

{

if (len > Bulk_PACKET_SIZE)

{

len = Bulk_PACKET_SIZE;

}

queue->free_count--;

memcpy(queue->USB_Request[queue->recv_idx], reqbuf, len);

rsize = Bulk_ExecuteCommand(reqbuf, queue->USB_Request[queue->recv_idx]);

queue->resp_size[queue->recv_idx] = rsize & 0xFFFF; //get the response size

*retbuf = queue->USB_Request[queue->recv_idx];

queue->recv_idx = (queue->recv_idx + 1) % Bulk_PACKET_COUNT;

queue->send_count++;

return (__TRUE);

}

return (__FALSE);

}

如上,我们只需要实现修改如下Bulk_ExecuteCommand即可处理我们收到的收据,并且填入我们发送数据出去队列即可发送出去。

为了验证MM32 MCU WinUSB功能,使用如下测试工具测试通信情况。


图3 USB调试工具

在VID栏填入我们MM32 MCU WinUSB的VID号0x2F81,点击Refresh刷新即可识别,点击OK就可以打开WinUSB。在图上可以看到有输入输出端点,我们MM32 MCU WinUSB采用的的端点EP3来收发数据,填入数据收发如下:


图4 USB数据通信

发送端填入01 02 03 04 05 06 07 08 09,点击Send发送,接收端收到01 ff数据,测试结果说明我们的MM32 MCU的WinUSB数据通信正常。

以上就是MM32 MCU USB的WinUSB功能,下一节我们继续介绍MM32 MCU USB的虚拟串口CDC功能。

来源:灵动MM32MCU

围观 903
订阅 RSS - MM32-USB