当前位置: 首页 > news >正文

USB通讯学习

理论学习


本章将由浅入深介绍USB原理,逐步解释以下问题:
    第一节:USB从接入到使用,讲述USB设备接入主机后经历了哪些过程;
    第二节:USB通信过程,解释USB设备和主机之间如何通信;
    第三节:从机的属性,也称从机的描述符集合,介绍如何区分不同类型的USB设备;
    第四节:枚举的详细过程,概括主机认识USB设备的具体过程;
另外,下文将用主机/从机统一描述USB主机和USB设备:
    主机:USB主机(Win/Android/Mac等)
    从机:USB设备(鼠标/键盘/U盘等)

USB从接入到使用

三张图+3句话

USB主从拓扑图

四大传输类型对比表(控制/批量/中断/等时)

差分线DP/DM硬件简图

  • 所有通讯都是 Host 先发令牌,Device 才能回包。

  • 端点(Endpoint)是“设备内部的小邮箱”,方向+编号唯一。

  • 管道(Pipe)是 Host 到端点的“逻辑高速公路”。

USB插拔检测

          主机发现从机接入后,开始识别从机,成功识别后就可以使用从机的功能了。其中,发现从机接入/拔出的过程称为USB拔插,识别从机的过程称为枚举。

USB拔插:主机发现从机的接入/拔出

【摘要】主机通过检测USB D+/D-的电平变化感知从机接入/拔出。
主机端采用15K电阻将D+,D-进行下拉,从机端使用1.5K将D+,D-进行上拉。如果主机端检测到D+/D-的电压变为3V,这样通过检测电平的变化来感知主机端是否接入USB

USB枚举:主机认识从机的方式

【摘要】主机通过获取设备的描述符集合来识别USB设备,这个过程称为“枚举”。

 USB设备(从机)的类型非常多,常见的有鼠标、键盘、游戏手柄等USB HID(Human Interface Device)设备,串口调试的CDC(Communication Device Class)设备,User自定义传输内容的WINUSB设备等。
    那么对于新接入的从机,主机如何区分它属于哪种类型呢?
    当然是让从机“介绍”自己。但主机是很忙的(软件、其他从机、其他接口的设备等),不会时时刻刻等着新从机的加入。从机不合时宜地发消息,只会对主机造成困扰。因此,从机就像门口排队的面试者一样,手里拿着自己的“简历”,等待着主机问询递交。
    从机的“简历”,称为描述符集合(Descriptor Collection)。它包含从机的名字、籍贯、性别等最基本的信息(设备描述符)、从事的职业(配置描述符)、掌握的技能(接口描述符、端点描述符)和补充信息(字符串描述符、其他特殊描述符)。他们都必须遵循相应的格式,以便主机可以快速了解从机的所有信息。只要从机正确地遵循主机的流程(枚举),按固定格式提供主机索要的信息,就可以通过“面试”,成为主机的USB部门的一员

每个USB设备都必须有描述符集合来详细介绍自己的所有功能和用途。USB连接后,主机通过访问描述符集合来识别从机并配置从机(枚举过程),就可以根据从机提供的信息使用从机的功能。

USB使用:主机使用从机的功能

【摘要】从机以等待主机轮询的方式发数据,以中断的方式收数据,从而实现相应的功能。

枚举成功后,从机开始履行自己的职责。
    前文提到,从机不能擅自介绍自己,因为主机是很忙的。同样,在主机认识并接受从机后,从机依然不能擅自报告自己的行为、状态等信息。那么从机和主机要如何通信呢?
    主机会定期到USB部门来视察工作,依次询问USB部门的所有成员是否需要汇报工作。当然,有些从机希望主机询问自己的频率高一些,就必须在“简历”中附上声明:请每隔XXX的时间问一次我的情况。对于没有附上声明的从机,主机会以自己的设定定期询问,或者由应用软件“催促”主机询问(自定义的USB设备)。
    以鼠标为例,它一般会声明:请每隔10毫秒来问一下我的情况。主机会尽量遵守这个声明,及时询问鼠标。在某一次询问中,鼠标报告自己:我刚刚移动了10个像素点。那么主机就会让屏幕上的光标移动。

        因此,从机准备好发送的数据后必须进入等待(一般不会等太久),直到主机轮询到此功能时,才开始发送。假设从机可以任意触发数据的发送过程,且主机连接多个从机,那么当多个从机同时发送数据到主机的USB总线上时就会引发冲突。
    反之,当主机需要发送数据时,从机必须尽快接收,所以从机一般会用中断处理主机发送数据的请求。这是因为主机需要轮询很多从机,每次轮询都有固定的时间,超时后就通信失败了。

【Q】从机发送/接收数据,主机发送/接收数据是否容易概念混淆?
【A】 是的。因此USB的数据传输过程描述以主机端为主。“从机–>主机”(Device-to-host) 方向的数据传输称为输入(Data In),“从机<–主机”(Host-to-device) 方向的数据传输为输出(Data Out)。
【Q】主机轮询到从机的输入功能时,没有数据要发送怎么办?
【A】 当然是PASS,从机直接回复NAK(即没有数据)或STALL(设备挂起)。

USB通讯过程

主机如何访问指定USB设备?

【摘要】主机为所有从机分配唯一的设备地址,通过该地址来访问从机。
    以PC为例,一般PC的USB设备可能包括鼠标、键盘、HUB扩展坞、蓝牙/WiFi适配器等。那么假如PC想访问鼠标设备时,该如何实现呢?

答案是设备地址。主机给所有已连接的从机分配设备地址,并确保不会重复。对刚接入还没来得及分配地址的从机,主机使用默认地址<Addr0>与之通信。交换少量的信息后,主机分配新地址,然后双方用新地址(Addr1~AddrN)通信。

【Q】枚举成功后,从机再次拔插还可以用之前分配的地址通信吗?
【A】 主机会重新分配设备地址,但可能分配的碰巧就是之前的地址。
【Q】主机为分配地址前,如何与从机通信?
【A】 USB规定,对于刚接入的从机,主机用默认地址(Addr0)通信。

主机是如何访问指定USB设备的指定功能的

【摘要】主机通过<设备地址(Address),设备端点(Endpoint)>访问指定从机的指定接口(功能)。

假设设备A是USB复合设备,同时支持鼠标、键盘、CDC功能,那么主机给设备A分配设备地址后,如何访问从机A的其中一个功能(比如键盘功能)?且当这个键盘功能同时支持发送和接收数据时,如何避免收发冲突呢?

答案是用端点(Endpoint,EP)加以区分。主机通过设备地址找到从机后,再通过端点访问从机的指定功能的指定用途。端点具有唯一性,它们和从机的功能及用途一一对应,按照端点的属性构建专用的端点通道(Pipe)来通信。另外,端点还标识了特定用途的数据传输方向。因此,对于USB复合设备A,通过端点号可区分键盘功能的发送或接收

【Q】有多少个功能/用途就分配多少个设备地址不就可以了吗?
【A】 如果这么做,当主机接入多个USB设备,而每个USB设备又支持多种功能、每个功能又包含多个用途时,主机需要分配的地址数量非常之多,且每次拔插设备需要多次分配地址,最终通信效率变低了。
【Q】主机未识别从机的功能之前用什么端点通信?
【A】 与默认地址0一样,从机也会有默认端点0(Default Endpoint, EP0)。准确来讲,对初次接入的从机,双方通过<Addr0,EP0>进行通信。

主机,从机如何读/写数据

【摘要】主机用默认端点0(EP0)创建通道枚举从机,根据描述符集中的其他端点创建对应通道访问其他功能。

首先,从机必须支持默认端点EP0。对刚接入的从机,主机使用<Addr0, EP0>访问从机,创建EP0的端点通道,开始枚举并分配地址,然后使用<new Addr, EP0>重新枚举。枚举成功后,主机根据从机提供的信息创建相应的资源和通道,访问从机的功能。
    当然,从机的功能多种多样,可能要持续传输大量数据,也可能要求实时性高,或是偶尔传输数据等。那么访问的需求不一样,主机怎么区分呢?
    当然是给端点加上属性(Attribute)。在端点描述符中声明属性,可以告诉主机构建什么样的数据通道,以何种方式读/写数据。

一次完整的通信过程

【摘要】一次完整的通信分为三个过程:请求过程(令牌包)、数据过程(数据包)和状态过程(握手包),没有数据要传输时,跳过数据过程。

 通信过程包含以下三种情况:

主机发送令牌包(Token)开始请求过程,如果请求中声明有数据要传输则有数据过程,最后由数据接收方(有数据过程)或从机(无数据过程)发起状态过程,结束本次通信。
    与USB全速设备通信时,主机将每秒等分为1000个帧(Frame)。主机在每帧开始时,向所有从机广播一个帧起始令牌包(Start Of Frame,SOF包)。它的作用有两个:一是通知所有从机,主机的USB总线正常工作;二是从机以此同步主机的时序。
    与USB高速设备通信时,主机将帧进一步等分为8个微帧(Microframe),每个微帧占125μ \muμs。在同一帧内,8个微帧的帧号都等于当前SOF包的帧号。

设备属性

【摘要】描述符集描述了从机的所有功能细节,它包含唯一的设备描述符,至少一个配置描述符和接口描述符,每个接口描述符至少包含一个端点描述符,此外还有其他可选的特殊描述符进行补充。

        描述符集主要包含设备描述符(Device Descriptor)、配置描述符(Configuration Descriptor)、接口描述符(Interface Descriptor)、端点描述符(Endpoint Descriptor)、字符串描述符(String Descriptor)及其他描述符。

描述符集的层次结构

    一个USB设备有且仅有一个设备描述符;
    一个设备描述符指向一个(或多个)配置描述符;
    一个配置描述符指向一个(或多个)接口描述符;
    一个接口描述符指向一个(或多个)端点描述符,还可能带有接口补充描述符;

    上述描述符如果带有字符串索引号(String Index),主机会根据索引号向从机请求对应的字符串描述符,进一步提供可供用户阅读的信息。
    对于一些接口(HID/CDC等),配置集合就包含一种接口补充描述符——特殊类描述符。不同的接口补充描述符作用不同,结构也可能不一样。如HID描述符会声明报告描述符的存在,由报告描述符进一步补充接口信息。如果补充描述符中又声明了其他描述符,主机会按接口索引号单独向从机请求其他描述符。
    需要注意的是,同一时间从机只能有一个生效的配置集合,生效的配置通过主机选择(Set Configuration)来指定,因为配置集合“复用”了从机的硬件资源。
    上述描述符中,除其他特殊描述符外主机能够单独获取的只有设备描述符、字符串和配置描述符,因为这些描述符是全局有效的。但接口描述符、端点描述符和特殊类描述符是某个配置集合内(局部)生效的,需要补充配置描述符一起发送。事实上,枚举过程中主机会一次性获取整个配置集合。

原文连接:USB原理:从零基础入门到放弃-CSDN博客

 STM32f103

        STM32F103 的 MCU 自带 USB 从控制器,符合 USB 规范的通信连接;PC 主机和微控制器

之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被 USB 外设直接访

问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,

每个端点最大可使用 512 字节缓冲区(专用的 512 字节,和 CAN 共用),最多可用于 16 个单

向或 8 个双向端点。USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的检测,数据发送

/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC 的生成和校验。

         

        每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节

数。当 USB 模块识别出一个有效的功能/端点的令牌分组时,(如果需要传输数据并且端点已配

置)随之发生相关的数据传输。USB 模块通过一个内部的 16 位寄存器实现端口与专用缓冲区的

数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手

分组。在数据传输结束时,USB 模块将触发与端点相关的中断,通过读状态寄存器和/或者利用

不同的中断来处理。

        USB 的中断映射单元:将可能产生中断的 USB 事件映射到三个不同的 NVIC 请求线上:

        1、USB 低优先级中断(通道 20):可由所有 USB 事件触发(正确传输,USB 复位等)。固件

在处理中断前应当首先确定中断源。

        2、USB 高优先级中断(通道 19):仅能由同步和双缓冲批量传输的正确传输事件触发,目

的是保证最大的传输速率。

        3、USB 唤醒中断(通道 42):由 USB 挂起模式的唤醒事件触发。

沁恒USB教程记录

ch32

CDC类

设备描述符

设备描述符一共18个字节

标注:第四字节为02h通讯类CDC类设备

配置描述符

接口描述符 

通讯类接口

数据类接口

 

特定类描述符

端点描述符

请求类

GetLineCoding:获取当前串口数据格式

SetLineCoding:设置当前串口数据格式

0-3字节表示数据波特率

第4字节表示停止位

第5字节表示奇偶校验

第6字节表示数据位(5,6,7,8 or 16)


 

CH32V20/30/32XUSB实战

USBD(USB全速设备控制器)

USBD 模块是基于USB2.0全速设备技术规范,设计的USB全速、低速协议通讯控制器。内置硬件 自动处理物理信号的反向不归零(NRZI)编码/解码、位填充。控制可驱动出 USB 总线多种状态、协 议包收发,并提供自动应答进行流控保证应用程序处理时间等功能。

特性

 符合USB2.0全速设备技术规范

 支持USB全速12Mbps、低速1.5Mbps模式  支持配置16个传输通道

 支持端点地址范围0-15

 支持控制、中断、批量、同步传输

 支持批量/同步端点的双缓冲机制

 USB挂起、唤醒、恢复操作

 硬件自动进行数据PID翻转、传输流控

 帧锁定时钟脉冲生成

注:USBD和CAN控制器在设计中共享了一个专用的512字节SRAM区域用于数据的发送和接收,因此 同时使用USBD和CAN功能时,需要合理分配此共享区域,防止出现数据冲突。

功能描述

功能介绍

模块中包含一块共享的512字节专用SRAM区域作为 USB 收发数据缓冲区,由配置的端点数目和每个端点最大数据包长度决定实际使用范围。最多可用于 16 个单向或8个双向端点。

功能配置

模块初始化

        首先USB收发器相关的模拟部分需要标准的48MHz时钟作为基准时钟,此时钟来源于HB总线。需要先通过配置时钟管理逻辑的相应控制位(RCC_CFGR0寄存器)保证当前USB时钟是48MHz, 再使能USB接口时钟,使程序可以访问USBD模块的寄存器。

其次,在模块强制复位时(USBD_CNTR寄存器上的FRES位默认为1),应用程序应该初始化所需 要的寄存器和分组缓冲区描述表。包括:分组缓冲区描述表地址寄存器(USBD_BTABLE)、端点配置寄 存器(USBD_EPRx)和分组缓冲区描述表寄存器。配置USBD_DADDR寄存器ADD[6:0]域为0(USB协议 默认地址),置位EF位使能端点传输功能。

最后,启用内部1.5K上拉电阻和设置速度模式(EXTEN_CTR寄存器),然后,清除USBD_CNTR寄 存器上的FRES位,撤销USBD模块强制复位状态来使能USBD模块,清除USBD_ISTR寄存器的各种状 态标志,以便在使能其他任何单元的操作之前清除未处理的假中断标志。开启USBD_CNTR寄存器中需 要的中断控制位。

USB复位

USB 复位包括:USBD模块强制复位和USB总线复位(协议复位)。两者皆会产生USBD_ISTR寄存 器的RST 标志。发生USB 复位时,所有端点的通信都被禁止(USBD模块不会响应任何包传输)。在 USB 复位后,USBD模块被使能,同时USB端点也需要被使能以便可以响应USB主机(USBD_DADDR寄存 器的EF 位为1)。在USB 设备的枚举阶段,主机将分配给设备一个唯一的地址,这个地址必须写入 USBD_DADDR 寄存器的 ADD[6:0]位中。 注:RST标志来源USBD模块强制复位控制位(FRES)的状态和USB总线复位信号起始。

4)端点配置及缓冲区描述表

CH32X035USB 编程实战

前言:我是小白,在以后使用usb的过程中我会不断地去更新补充这篇博客,分享如何将usb设置为cdc与上位机进行通讯

代码框架

一共有三个文件

ch32x035_usbfs_device.c ------------------------USB驱动相关

ch32x035_usbfs_device.h

ch32x035_desc.c---------------------------USB描述符相关

ch32x035_desc.h

app_usb.c --------------------用户app,usb数据发送函数,初始化函数

app_usb.h

讲的方式会根据代码段分别来讲解,代码是干什么用的,配置好之后如何进行调试的。

USB中断

最重要的就是USB中断函数的过程

第一步:

USB初始化完成后进入的第一个中断就是总线复位中断,清除USB的配置信息以及状态

第二步

主机向设备发送SETUP包,触发USBFS_UIS_TOKEN_SETUP中断

首先设备端保存主机发过来SETUP中的数据(信息)

根据令牌包中的USBFS_SetupReqType看是标准请求还是非标准

非标准请求

标准请求
走else分支

if((USBFS_SetupReqType & USB_REQ_TYP_MASK) != USB_REQ_TYP_STANDARD) { //是否为标准请求}else{非标准请求}

再根据请求进行分支,看主机请求什么

USB_GET_DESCRIPTOR   主机请求设备描述符
USB_SET_ADDRESS          主机为设备分配唯一地址
USB_GET_CONFIGURATION     主机获取当前设备激活的配置值,验证配置是否设置成功
USB_SET_CONFIGURATION      请求用于主机告诉设备使用哪个配置。当主机完成了设备描述符、配置描述符等的获取,并为设备分配了地址之后,就会发送这个请求来激活设备的一个配置。
USB_CLEAR_FEATURE请求用于清除设备、接口或端点的一些特定功能。它有两个主要用途:

1、设备特性清除

if((USBFS_SetupReqType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE) {if((uint8_t)(USBFS_SetupReqValue & 0xFF) == USB_REQ_FEAT_REMOTE_WAKEUP) {USBFS_DevSleepStatus &= ~0x01;}
}

当接收者是设备且特性是远程唤醒时,清除设备的睡眠状态标志。这意味着设备不再准备进入睡眠状态。

2、端点特性清除
 

else if((USBFS_SetupReqType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) {if((uint8_t)(USBFS_SetupReqValue & 0xFF) == USB_REQ_FEAT_ENDP_HALT) {// 根据端点索引处理不同端点switch((uint8_t)(USBFS_SetupReqIndex & 0xFF)) {// 端点1 INcase (DEF_UEP_IN | DEF_UEP1):USBFSD->UEP1_CTRL_H = USBFS_UEP_T_RES_NAK;break;// 端点2 OUTcase (DEF_UEP_OUT | DEF_UEP2):USBFSD->UEP2_CTRL_H = USBFS_UEP_R_RES_ACK;break;// 端点3 INcase (DEF_UEP_IN | DEF_UEP3):USBFSD->UEP3_CTRL_H = USBFS_UEP_T_RES_NAK;break;}}
}

当接收者是端点且特性是端点暂停时,将指定端点从暂停状态恢复到正常状态:

  • 对于IN端点,设置为NAK状态(表示设备暂时无法发送数据)
  • 对于OUT端点,设置为ACK状态(表示设备可以接收数据)

USB_GET_INTERFACE    是USB标准请求之一,请求代码值为0x0A。它用于获取指定接口的当前备用设置。返回0表明当前设备接口没有实现备用设置功能,只有一个默认设置  

USB_SET_INTERFACE请求允许主机在设备已经配置后更改特定接口的备用设置。这使得设备可以在运行时切换接口的不同配置,例如:

如果不进行动作,意味着当前设备不支持备用设置功能

  • 如果设备支持备用设置,USB_SET_INTERFACE请求的典型实现可能包括:

  • 检查参数有效性

    • 验证指定的接口是否存在
    • 验证备用设置是否有效
  • 重新配置端点

    • 根据新的备用设置重新配置相关端点
    • 可能需要启用或禁用某些端点
  • 更新设备状态

    • 记录当前激活的备用设置
    • 执行与新设置相关的初始化操作

USB_GET_STATUS    请求允许主机查询设备、接口或端点的当前状态。这是USB标准请求中的第一个请求,用于获取各种对象的状态信息。                 

switch(USBFS_SetupReqCode) {/* get device/configuration/string/report/... descriptors */case USB_GET_DESCRIPTOR://当主机请求设备描述符时break;//第四步主机为设备分配唯一地址/* Set usb address */case USB_SET_ADDRESS:USBFS_DevAddr = (uint8_t)(USBFS_SetupReqValue & 0xFF);// 实际地址设置在IN阶段完成break;/* Get usb configuration now set */case USB_GET_CONFIGURATION:USBFS_EP0_4Buf[0] = USBFS_DevConfig;if(USBFS_SetupReqLen > 1) {USBFS_SetupReqLen = 1;}break;/* Set usb configuration to use */case USB_SET_CONFIGURATION:break;/* Clear or disable one usb feature */case USB_CLEAR_FEATURE:break;/* set or enable one usb feature */case USB_SET_FEATURE:break;/* This request allows the host to select another setting for the specified interface  */case USB_GET_INTERFACE:USBFS_EP0_4Buf[0] = 0x00;if(USBFS_SetupReqLen > 1) {USBFS_SetupReqLen = 1;}break;case USB_SET_INTERFACE:break;/* host get status of specified device/interface/end-points */case USB_GET_STATUS:break;default:errflag = 0xFF;break;
}
第三步
case USBFS_UIS_TOKEN_OUT:// 数据OUT阶段(主机发送数据给设备)
switch(intst & (USBFS_UIS_TOKEN_MASK | USBFS_UIS_ENDP_MASK)) {/* end-point 0 data out interrupt */case USBFS_UIS_TOKEN_OUT | DEF_UEP0:len = USBFSD->RX_LEN;break;/* end-point 2 data out interrupt */case USBFS_UIS_TOKEN_OUT | DEF_UEP2:default:break;
}
break;

USBFS_UIS_TOKEN_OUT 数据输出阶段,数据输入输出只针对主机来讲。该中断是从机来接收数据。

USBFS_UIS_TOKEN_OUT | DEF_UEP0:主机向端点0发送配置信息给设备,根据SETUP阶段得到SETUP包信息存储在USBFS_SetupReqCode中

进入这个分支先    USBFS_UIS_TOG_OK是USB传输中的同步校验标志位,用于检查数据包的同步状态是否正确。

在USB通信中,每个数据包都有一个数据切换位(Data Toggle),用于确保数据包的顺序正确:

  • 主机和设备各自维护一个切换位(TOGGLE bit)
  • 每次成功传输后,切换位会翻转(0变1,1变0)
  • 接收方通过比较发送方的切换位和自己维护的切换位来验证数据包顺序

然后判断是标准请求还是非标准请求(类请求或厂商请求)

在USB协议中,根据bRequestType字段的位定义,USB请求可以分为以下几类:
#define USB_REQ_TYP_MASK 0x60

#define USB_REQ_TYP_STANDARD 0x00

#define USB_REQ_TYP_CLASS 0x20

#define USB_REQ_TYP_VENDOR 0x40

  • 标准请求(USB_REQ_TYP_STANDARD): 适用于所有USB设备的基本请求,如USB_GET_DESCRIPTOR、USB_SET_ADDRESS等
  • 类请求(USB_REQ_TYP_CLASS): 特定USB设备类的请求,如CDC类的CDC_SET_LINE_CODING
  • 厂商请求(USB_REQ_TYP_VENDOR): 特定厂商自定义的请求

之后就是判断USB的设备类型,对设备进行配置

if(USBFS_SetupReqCode == CDC_SET_LINE_CODING) {// 处理CDC设置线路编码请求 设置波特率,停止位等配置/* Save relevant parameters such as serial port baud rate *//* The downlinked data is processed in the endpoint 0 OUT packet, the 7 bytes of the downlink are, in order4 bytes: baud rate value: lowest baud rate byte, next lowest baud rate byte, next highest baud rate byte, highest baud rate byte.1 byte: number of stop bits (0: 1 stop bit; 1: 1.5 stop bit; 2: 2 stop bits).1 byte: number of parity bits (0: None; 1: Odd; 2: Even; 3: Mark; 4: Space).1 byte: number of data bits (5,6,7,8,16); */usbd_ctl.Com_Cfg[ 0 ] = USBFS_EP0_4Buf[ 0 ];usbd_ctl.Com_Cfg[ 1 ] = USBFS_EP0_4Buf[ 1 ];usbd_ctl.Com_Cfg[ 2 ] = USBFS_EP0_4Buf[ 2 ];usbd_ctl.Com_Cfg[ 3 ] = USBFS_EP0_4Buf[ 3 ];usbd_ctl.Com_Cfg[ 4 ] = USBFS_EP0_4Buf[ 4 ];usbd_ctl.Com_Cfg[ 5 ] = USBFS_EP0_4Buf[ 5 ];usbd_ctl.Com_Cfg[ 6 ] = USBFS_EP0_4Buf[ 6 ];usbd_ctl.Com_Cfg[ 7 ] = 30;USBFSD->UEP2_DMA = (uint32_t)(uint8_t *)&usbd_Rx_Buf[0];USBFSD->UEP2_CTRL_H &= ~USBFS_UEP_R_RES_MASK;USBFSD->UEP2_CTRL_H |= USBFS_UEP_R_RES_ACK;
}

端点2数据接收

case USBFS_UIS_TOKEN_OUT | DEF_UEP2:

                    case USBFS_UIS_TOKEN_OUT | DEF_UEP2:USBFSD->UEP2_CTRL_H ^= USBFS_UEP_R_TOG;tempRxDataLen = USBFSD->RX_LEN;usbd_ctl.Rx_DataLen += tempRxDataLen;// 情况1:接收到的数据长度小于端点最大包大小if(tempRxDataLen <= DEF_USBD_ENDP2_SIZE) {//一次接受的数据量小于端点最大包大小USBFSD->UEP2_DMA = (uint32_t)(uint8_t *)usbd_Rx_Buf;printf("data len < 64\r\n");// msg_item.msg_id = USB_ID;// msg_item.msg_arg = usbd_ctl.Rx_DataLen - 2;// printf("%s",usbd_Rx_Buf);// printf("usb rx usbd_Rx_Buf:%2x\r\n",usbd_Rx_Buf[0]);USB_RX_LEN = tempRxDataLen;usbd_ctl.Rx_PackLen = 0;usbd_ctl.Rx_DataLen = 0;usbd_ctl.Rx_Flag = 0;// msg_box_post_fifo(&msg_item);}// 情况2:接收到的数据长度等于端点最大包大小else{printf("data len > 64\r\n");if(0 == usbd_ctl.Rx_Flag) {// if(0x03 == usbd_Rx_Buf[2]) { //spi 读//     usbd_ctl.Rx_PackLen = usbd_Rx_Buf[4] + 14;// }else if(0x02 == usbd_Rx_Buf[2]) { //spi 写//     usbd_ctl.Rx_PackLen = ((((uint16_t)usbd_Rx_Buf[9]) << 8) | usbd_Rx_Buf[10]) + 14;// }usbd_ctl.Rx_Flag = 1;}if(usbd_ctl.Rx_DataLen >= usbd_ctl.Rx_PackLen) {// msg_item.msg_id = USB_ID;// msg_item.msg_arg = usbd_ctl.Rx_DataLen - 2;usbd_ctl.Rx_PackLen = 0;usbd_ctl.Rx_DataLen = 0;usbd_ctl.Rx_Flag = 0;// msg_box_post_fifo(&msg_item);}USBFSD->UEP2_DMA = (uint32_t)(uint8_t *)&usbd_Rx_Buf[usbd_ctl.Rx_DataLen];}break;

由于端点缓冲区只有64字节,因此一次最大接受量为64字节,多余的数据会被阉割掉,如果想要发送多数据,可以采用数据包形式,第一报数据指定包长度,包数量等信息,之后接收数据根据包长度和包数量将接收到的数据存储到自己的buffer中。

第四步

数据输入阶段,设备将描述符发送到主机。

case USBFS_UIS_TOKEN_IN:// 数据IN阶段(设备发送数据给主机)switch(intst & (USBFS_UIS_TOKEN_MASK | USBFS_UIS_ENDP_MASK)) {/* end-point 0 data in interrupt *///在IN阶段处理地址设置case USBFS_UIS_TOKEN_IN | DEF_UEP0:if(USBFS_SetupReqLen == 0) {//传输完成,准备接收状态确认USBFSD->UEP0_CTRL_H = USBFS_UEP_R_TOG | USBFS_UEP_R_RES_ACK;}if((USBFS_SetupReqType & USB_REQ_TYP_MASK) != USB_REQ_TYP_STANDARD) {/* Non-standard request endpoint 0 Data upload */}else{// 继续发送数据/* Standard request endpoint 0 Data upload */switch(USBFS_SetupReqCode) {case USB_GET_DESCRIPTOR://获取描述符len = USBFS_SetupReqLen >= DEF_USBD_UEP0_SIZE ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;memcpy(USBFS_EP0_4Buf, pUSBFS_Descr, len);USBFS_SetupReqLen -= len;pUSBFS_Descr += len;USBFSD->UEP0_TX_LEN = len;USBFSD->UEP0_CTRL_H ^= USBFS_UEP_T_TOG;break;case USB_SET_ADDRESS://设置地址USBFSD->DEV_ADDR = (USBFSD->DEV_ADDR & USBFS_UDA_GP_BIT) | USBFS_DevAddr;break;default:break;}}break;/* end-point 1 data in interrupt */case (USBFS_UIS_TOKEN_IN | DEF_UEP1):
//                        USBFSD->UEP1_CTRL_H ^= USBFS_UEP_T_TOG;
//                        USBFSD->UEP1_CTRL_H = (USBFSD->UEP1_CTRL_H & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_NAK;
//                        USBFS_Endp_Busy[DEF_UEP1] = 0;break;/* end-point 3 data in interrupt */case (USBFS_UIS_TOKEN_IN | DEF_UEP3):USBFSD->UEP3_CTRL_H ^= USBFS_UEP_T_TOG;USBFSD->UEP3_CTRL_H = (USBFSD->UEP3_CTRL_H & ~USBFS_UEP_T_RES_MASK) | \USBFS_UEP_T_RES_NAK;USBFS_Endp_Busy[DEF_UEP3] = 0;usbd_ctl.USB_Up_IngFlag = 0x00;if(usbd_ctl.Tx_Flag == 1) {usbd_ctl.Tx_Flag = 0;usbd_ctl.Tx_DealPtr = 0;}break;default :break;}break;

USB描述符

/* Device Descriptor 设备描述符*/
const uint8_t MyDevDescr[] = {0x12,       // bLength0x01,       // bDescriptorType (Device)0x10, 0x01, // bcdUSB 1.100x02,       // bDeviceClass0x00,       // bDeviceSubClass0x00,       // bDeviceProtocolDEF_USBD_UEP0_SIZE,   // bMaxPacketSize0 64(uint8_t)DEF_USB_VID, (uint8_t)(DEF_USB_VID >> 8),  // idVendor  0x1A86(uint8_t)DEF_USB_PID, (uint8_t)(DEF_USB_PID >> 8),  // idProduct 0xFE0CDEF_IC_PRG_VER, 0x00, // bcdDevice 0.010x01,       // iManufacturer (String Index)0x02,       // iProduct (String Index)0x00,       // iSerialNumber (String Index)0x01,       // bNumConfigurations 1
};/* Configuration Descriptor 配置描述符*/
const uint8_t MyCfgDescr[] = {/* Configure descriptor */0x09, 0x02, 0x43, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32,/* Interface 0 (CDC) descriptor */0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01,  0x00,/* Functional Descriptors */0x05, 0x24, 0x00, 0x10, 0x01,/* Length/management descriptor (data class interface 1) */0x05, 0x24, 0x01, 0x00, 0x01,0x04, 0x24, 0x02, 0x02,0x05, 0x24, 0x06, 0x00, 0x01,/* Interrupt upload endpoint descriptor */0x07, 0x05, 0x81, 0x03, (uint8_t)DEF_USBD_ENDP1_SIZE, (uint8_t)( DEF_USBD_ENDP1_SIZE >> 8 ), 0x01,/* Interface 1 (data interface) descriptor */0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00,/* Endpoint descriptor */0x07, 0x05, 0x02, 0x02, (uint8_t)DEF_USBD_ENDP2_SIZE, (uint8_t)( DEF_USBD_ENDP2_SIZE >> 8 ), 0x00,/* Endpoint descriptor */0x07, 0x05, 0x83, 0x02, (uint8_t)DEF_USBD_ENDP3_SIZE, (uint8_t)( DEF_USBD_ENDP3_SIZE >> 8 ), 0x00,
};/* Language Descriptor 语言描述符*/
const uint8_t MyLangDescr[] = {0x04, 0x03, 0x09, 0x04
};/* Manufacturer Descriptor 厂商描述符*/
const uint8_t MyManuInfo[] = {0x0E, 0x03, 'w', 0, 'c', 0, 'h', 0, '.', 0, 'c', 0, 'n', 0
};/* Product Information 产品描述符*/
const uint8_t MyProdInfo[] = {0x16, 0x03, 'U', 0x00, 'S', 0x00, 'B', 0x00, ' ', 0x00, 'S', 0x00, 'e', 0x00, \'r', 0x00, 'i', 0x00, 'a', 0x00, 'l', 0x00
};/* Serial Number Information 序列号描述符*/
const uint8_t MySerNumInfo[] = {0x16, 0x03, '2', 0, 'K', 0, '2', 0, '3', 0, '4', 0, '5', 0, \'6', 0, '7', 0, '8', 0, '9', 0
};
USB数据发送

这个发送函数因为一次最大只能发64字节,因此加入了分包发送机制。

void app_usbd_tx(uint8_t *pBuf, uint16_t len) {if(len <= 0) {return;}if(len < 64) {USBD_INT_DISENABLE();usbd_ctl.Tx_Flag = 1;usbd_ctl.USB_Up_IngFlag = 0x01;usbd_ctl.USB_Up_TimeOut = 0x00;USBFS_Endp3_DataUp(pBuf, len);USBD_INT_ENABLE();while(1 == usbd_ctl.USB_Up_IngFlag) {if(usbd_ctl.USB_Up_TimeOut >= DEF_USB_UP_TIMEOUT) {usbd_ctl.USB_Up_IngFlag = 0x00;USBFS_Endp_Busy[DEF_UEP3] = 0;usbd_ctl.Tx_Flag = 0;usbd_ctl.Tx_DealPtr = 0;PRINT("usb tx timeout 1......\r\n");}}}else{uint16_t len_integer = len >> 6;     //需要发送的数据包数uint16_t len_remainder = len & 63;   //对64取余剩余数据量while(len_integer > 0) {USBD_INT_DISENABLE();usbd_ctl.USB_Up_IngFlag = 0x01;usbd_ctl.USB_Up_TimeOut = 0x00;USBFS_Endp3_DataUp(&pBuf[usbd_ctl.Tx_DealPtr], USB_FS_PACK_LEN);USBD_INT_ENABLE();while(1 == usbd_ctl.USB_Up_IngFlag) {if(usbd_ctl.USB_Up_TimeOut >= DEF_USB_UP_TIMEOUT) {usbd_ctl.USB_Up_IngFlag = 0x00;USBFS_Endp_Busy[DEF_UEP3] = 0;usbd_ctl.Tx_DealPtr = 0;PRINT("usb tx timeout 2......\r\n");}}usbd_ctl.Tx_DealPtr += USB_FS_PACK_LEN;len_integer--;}if(0 == len_remainder) {USBD_INT_DISENABLE();usbd_ctl.Tx_Flag = 1;usbd_ctl.USB_Up_IngFlag = 0x01;usbd_ctl.USB_Up_TimeOut = 0x00;USBFS_Endp3_DataUp(pBuf, 0);USBD_INT_ENABLE();while(1 == usbd_ctl.USB_Up_IngFlag) {if(usbd_ctl.USB_Up_TimeOut >= DEF_USB_UP_TIMEOUT) {usbd_ctl.USB_Up_IngFlag = 0x00;USBFS_Endp_Busy[DEF_UEP3] = 0;usbd_ctl.Tx_Flag = 0;usbd_ctl.Tx_DealPtr = 0;PRINT("usb tx timeout 3......\r\n");}}}else{USBD_INT_DISENABLE();usbd_ctl.Tx_Flag = 1;usbd_ctl.USB_Up_IngFlag = 0x01;usbd_ctl.USB_Up_TimeOut = 0x00;USBFS_Endp3_DataUp(&pBuf[usbd_ctl.Tx_DealPtr], len_remainder);USBD_INT_ENABLE();while(1 == usbd_ctl.USB_Up_IngFlag) {if(usbd_ctl.USB_Up_TimeOut >= DEF_USB_UP_TIMEOUT) {usbd_ctl.USB_Up_IngFlag = 0x00;USBFS_Endp_Busy[DEF_UEP3] = 0;usbd_ctl.Tx_Flag = 0;usbd_ctl.Tx_DealPtr = 0;PRINT("usb tx timeout 4......\r\n");}}}}
}
USB初始化

沁恒USB时钟需要配置48M,ch32x035主频恰好是48兆因此不需要设置

在初始化时主要修改端点DMA地址配置

配置USB中断优先级并使能

void app_usbd_Interrupts_Config(void) {NVIC_InitTypeDef NVIC_InitStructure = {0};NVIC_InitStructure.NVIC_IRQChannel = USBFS_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}

http://www.dtcms.com/a/478081.html

相关文章:

  • 成都哪里可以做网站涿州网站建设天峰
  • 最新MPAS跨尺度、可变分辨率模式实践技术应用及典型案例分析
  • DSP EDMA3使用
  • 做网站在哪里租服务器家用电脑做网站服务器
  • 第四篇《通信的“世界语“:为什么网络需要HTTP、FTP、DNS等协议?》
  • Helm 与 Ansible 深度对比解析文档
  • 网站域名使用费多少集客crm
  • 阜新市建设小学网站wordpress php 5.2.17
  • 2025年--Lc182--sql(排序和分组)--Java版
  • 生产环境下,前端项目为什么要部署
  • 免费图片素材网站推荐怎样建立一个免费的网站
  • [论文阅读] AI | PynguinML——破解ML库自动化测试难题,覆盖率最高提升63.9%
  • 【HarmonyOS AI赋能】AI字幕AICaption详解
  • 极海APM32F107V6 + 合宙Air780E
  • 做欧洲电商看哪个网站网站建设开发案例教程视频教程
  • C++11(可变参数模板、新的类功能和STL中的一些变化)
  • 医疗运营管理系统编程可靠性、安全性与动态性融合路径
  • 昂瑞微——以创新驱动未来,用芯连接世界
  • 网站承建商有哪些济南seo外贸网站建设
  • Flink1.20 CEP【水位线异常原因深度分析】
  • 30个酷炫HTML+CSS特效源码
  • vtkGaussianBlurPass代码解析
  • 网站制作过程合理的步骤是福州网站推广优化
  • 牛客算法基础noob71 学生综合评估系统
  • 如何清除 Yarn 缓存 ?
  • 做听书网站怎么做用动易建设网站教程
  • 东丽开发区做网站公司响应式网站源码下载
  • RabbitMQ为什么使用AMQP协议
  • 阜新本地网站建设平台百度竞价推广价格
  • Linux 系统启动过程