正点原子嵌入式linux驱动开发——Linux USB驱动

USB是很常用的接口,目前大多数的设备都是USB接口的,比如鼠标、键盘、USB摄像
头等,在实际开发中也常常遇到USB接口的设备,本章就来学习一下如何使能Linux内核自带的USB驱动。这里不会具体学习USB的驱动开发。

USB接口简介

什么是USB

USB全称为Universal Serial Bus,翻译过来就是通用串行总线。由英特尔与众多电脑公司提出来,用于规范电脑与外部设备的连接与通讯。目前USB接口已经得到了大范围的应用,已经是电脑、手机等终端设备的必配接口,甚至取代了大量的其他接口。比如最新的智能手机均采用USB TypeC取到了传统的3.5mm耳机接口,苹果最新的MacBook只有 USB TypeC接口,至于其他的HDMI、网口等均可以通过USB TypeC扩展坞来扩展。

按照大版本划分,USB目前可以划分为USB1.0、USB2.0、USB3.0以及正在即将到来的USB4.0。

  • USB1.0:USB规范于1995年第一次发布,由Inter、IBM、Microsoft等公司组成的USB-IF(USB Implement Forum)组织提出。USB-IF在1996年正式发布USB1.0,理论速度为1.5Mbps。1998 年USBIF在USB1.0的基础上提出了USB1.1规范。
  • USB2.0:USB2.0依旧由Inter、IBM、Microsoft等公司提出并发布,USB2.0分为两个版本:Full-Speed和High-Speed,也就是全速(FS)和高速(HS)。USB2.0 FS的速度为12Mbps,USB2.0 HS速度为480Mbps。目前大多数单片机以及低端Cortex-A芯片配置的都是USB2.0接口,比如STM32MP157。USB2.0全面兼容USB1.0标准
  • USB3.0:USB3.0同样由Inter等公司发起的,USB3.0最大理论传输速度为5.0Gbps,USB3.0引入了全双工数据传输,USB2.0的480Mbps为半双工。USB3.0中两根线用于发送数据,另外两根用于接收数据。在USB3.0的基础上又提出了USB3.1、USB3.2等规范,USB3.1理论传输速度提升到了10Gbps,USB3.2理论传输速度为20Gbps。为了规范 USB3.0标准的命名,USB-IF公布了最新的USB命名规范,原来的USB3.0和USB3.1命名将不会采用,所有的3.0 版本的USB都命名为 USB3.2,以前的USB3.0、USB3.1和USB3.2分别叫做USB3.2 Gen1、USB3.2 Gen2、USB3.2 Gen 2X2。
  • USB4.0:目前还在标准定制中,目前还没有设备搭载,据说是在Inter的雷电3接口上改进而来。USB4.0的速度将提升到了40Gbps,最高支持100W的供电能力,只需要一根线就可以完成数据传输与供电,极大的简化了设备之间的链接线数,期待USB4.0设备上市

如果按照接口类型划分的话USB就要分为很多种了,最常见的就是USB A插头和插座,如下图所示:

USB A插头和插座

使用过JLINK调试器的朋友应该还见过USB B插头和插座,USB B插头和插座如下图所示:

USB B插头和插座

USB插头在不断的缩小,由此产生了Mini USB接口,Mini USB插头和插座如下图所示:

Mini USB插头和插座

比Mini USB更小的就是Micro USB接口了,以前的智能手机基本都是Micro USB接口的,Micro USB插头和插座如下图所示:

Micro USB插头和插座

现在最流行的就是USB Typec了,正点原子的STM32MP1开发板使用的是USB Typec接口,USB Typec插头和插座如下图所示:

USB Typec插头和插座

USB电气特性

Mini USB电气属性

先以Mini USB为例讲解一下USB的基本电气属性。Mini USB线一般都是一头为USB A插头,一头为Mini USB插头。一共有四个触点,也就是4根线,这四根线的顺序如下图所示:

USB A插头线序

如上图所示,USB A插头从左到右线序依次为 1,2,3,4,第1根线为VBUS,电压为5V,第2根线为D-,第3根线为D+,第4根线为GND。USB采用差分信号来传输数据,因此有D-和D+两根差分信号线。仔细观察的话会发现USB A插头的1和4这两个触点比较
长,2和3这两个触点比较短。1和4分别为VBUS和GND,也就是供电引脚,当插入 USB的时候会先供电,然后再接通数据线。拔出的时候先断开数据线,然后再断开电源线

再观察一下Mini USB插头,会发现Mini USB插头有5个触点,也就是5根线,线序
从左往右依次是1-5。第1根线为VCC(5V),第2根线为D-,第3根线为D+,第4根线为ID,第5根线为GND。可以看出Mini USB插头相比USB A插头多了一个ID线,这个ID线用于
实现OTG功能,通过ID线来判断当前连接的是主设备(HOST)还是从设备(SLAVE)

USB是一种支持热插拔的总线接口,使用差分线(D-和 D+)来传输数据,USB支持两种供电模式:总线供电和自供电,总线供电就是由USB接口为外部设备供电,在USB2.0下,总线供电最大可以提供500mA的电流。

USB拓补结构

USB是主从结构的,也就是分为主机和从机两部分,一般主机叫做Host,从机叫做Device。主机通过USB A插座来连接外部的设备,比如电脑作为主机,对外提供USB A插座,可以通过USB线来连接一些USB设备,比如声卡、手机等。因此电脑带的USB A插座数量就决定了能外接多少个USB设备,如果不够用的话可以购买USB集线器来扩展电脑的USB插口,USB集线器也叫做USB HUB,USB HUB如下图所示:

USB HUB

上图是一个一拖四的USB HUB,也就是将一个USB接口扩展为4个。主机一般会带几个原生的USB主控制器,比如STM32MP1就有两个原生的USB主控制器,因此STM32MP1
对外提供两个USB接口
,这两个接口肯定不够用,正点原子的STM32MP1开发板上有6个HOST接口,六路都是USB2通过USB HUB芯片扩展出来的,稍后会讲解其原理图。

虽然可以对原生的USB口数量进行扩展,但是不能对原生USB口的带宽进行扩展,比如STM32MP1的两个原生USB口都是USB2.0的,带宽最大为480Mbps,因此接到下面的所有USB设备总带宽最大为480Mbps。

USB只能主机与设备之间进行数据通信,USB 主机与主机、设备与设备之间是不能通信的。因此两个正常通信的USB接口之间必定有一个主机,一个设备。为此使用了不同的插头和插座来区分主机与设备,比如主机提供 USB A插座,从机提供Mini USB、Micro USB 等插座。在一个USB系统中,仅有一个USB主机,但是可以有多个USB设备,包括USB功能设备和USB HUB,最多支持127个设备一个USB主控制器支持128个地址,地址0是默认地址,只有在设备枚举的时候才会使用,地址0不会分配给任何一个设备。所以一个USB主控制器最多可以分配127个地址。整个USB的拓扑结构就是一个分层的金字塔形,如下图所示:

USB金字塔拓补

上图中可以看出从Root Hub开始,一共有7层,金字塔顶部是Root Hub,这个是USB控制器内部的。图中的Hub就是连接的USB集线器,Func就是具体的USB设备。

USB主机和从机之间的通信通过管道(Pipe)来完成,管道是一个逻辑概念,任何一个USB设备一旦上电就会存在一个管道,也就是默认管道,USB主机通过管道来获取从机的描述符、配置等信息。在主机端管道其实就是一组缓冲区,用来存放主机数据,在设备端管道对应一个特定的端点

USB OTG

USB分为HOST(主机)和从机(或DEVICE),有些设备可能有时候需要做HOST,有时候又需要做DEVICE,配两个USB口当然可以实现,但是太浪费资源了。如果一个USB接口既可以做HOST又可以做DEVICE那就太好了,使用起来就方便很多。为此,USB OTG 应运而生,OTG是On-The-Go的缩写,支持USB OTG功能的USB接口既可以做HOST,也可以做DEVICE。为了区分当前的工作状态,这里就引入了ID线这个概念,前面讲解USB电气属性的时候已经说过了,Mini USB插头有5根线,其中一条就是ID线。ID线的高低电平表示USB口工作在HOST还是DEVICE模式

  • ID=1:OTG设备工作在从机模式。
  • ID=0:OTG设备工作在主机模式。

支持OTG模式的USB接口一般都是Mini USB、Micro USB 等这些带有ID线的接口。正点原子的STM32MP1开发板OTG模式是使用USB Type C做接口,没有ID线USB Type C有自己的识别方法,稍后会讲解TypeC接口电气属性。正点原子的STM32MP157开发板USB_OTG连接到了STM32MP1的USB1接口上。如果要使用OTG的主机模式,那么就需要一根OTG线,TypeC接口的OTG线如下图所示:

Typec OTG线

可以看出,TypeC OTG线一头是USB A插座,一头是Typec插头,将TypeC插头插入机器的TypeC口上,需要连接的USB设备插到另一端的USB A插座上,比如U盘。TypeC的CC引脚检查到USB设备已经接入,就会建立USB设备为主模式,机器就知道自己要做为一个主机,用来连接外部的从机设备(U 盘)。

STM32MP1 USB接口简介

STM32MP157提供了两个USB2.0接口,这两个USB接口都是支持高速模式,也就是480Mbit/S,这两个USB接口都内置了高速PHY。其中USB2支持OTG功能,正点原子STM32MP157开发板上的USB OTG接口就是链接到USB2接口上的。USB1接口连接了一个HUB芯片,实现了USB Host接口扩展

STM32MP1内部集成了三个跟USB相关的控制器名字分别为:USB OTG控制器、USB Host控制器和USB HS PHY控制器,提供一个简称方便书写:OTG、USBH和PHY。接着分析一下这三个控制器是如何工作的。

PHY控制器

PHY(英语:Port Physical Layer),中文可称之为端口物理层,是一个对OSI模型物理层的共同简称。PHY控制器主要是提供两个端口,端口1已经规定分配给USB Host控制器,端口2可以分配给USB OTG和USB Host

OTG控制器

此控制器是支持OTG功能,它的内部特性如下所示:

  1. 此控制器有一个独立的内核USB OTG HS。
  2. 该控制器支持HS、FS和LS模式,不管是主机还是从机模式都支持HS/FS/LS,硬件支持OTG信号、会话请求协议和主机协商协议,支持8个双向端点,还支持PHY接口。
  3. 内嵌一个DMA。
  4. 支持片上全速PHY、连接外部全速PHY的I2C接口和连接外部高速PHY的ULPL接口。
  5. 具有采用高级FIFO控制的4 KB专用RAM。

OTG控制器有两个模式:正常模式(normal mode)和低功耗模式(low power mode)。OTG控制器都可以运行在高速模式(HS 480Mbps)、全速模式(LS 12Mbps)和低速模式(1.5Mbps)。正常模式下每个OTG控制器都可以工作在主机(HOST)或从机(DEVICE)模式下,每个USB控制器都有其对应的接口。低功耗模式顾名思义就是为了节省功耗,USB2.0 协议中要求,设备在上行端口检测到空闲状态以后就可以进入挂起状态。在从机(DEVICE)模式下,端口停止活动3ms以后OTG控制器内核进入挂起状态。在主机(HOST)模式下,OTG控制器内核不会自动进入挂起状态,但是可以通过软件设置。不管是本地还是远端的USB主从机都可以通过产生唤醒序列来重新开始USB通信。

USBH控制器

USBH控制器这是一个主机控制器,此控制器由EHCI控制器和OHCI控制器组成。USBH控制器只能做主机模式

这里简单提一下OHCI、UHCI、EHCI和xHCI,这四个是用来描述USB控制器规格
的,区别如下:

  • OHCI:全称为Open Host Controller Interface,这是一种USB控制器标准,厂商在设计USB控制器的时候需要遵循此标准,用于USB1.1标准。OHCI不仅仅用于USB,也支持一些其他的接口,比如苹果的Firewire等,OHCI由于硬件比较难,所以软件要求就降低了,软件相对来说比较简单。OHCI主要用于非X86的USB,比如扩展卡、嵌入式USB控制器
  • UHCI:全称是Universal Host Controller Interface,UHCI是Inter主导的一个用于USB1.0/1.1的标准,与OHCI不兼容。与OHCI相比UHCI硬件要求低,但是软件要求相应就高了,因此硬件成本上就比较低。
  • EHCI:全称是Enhanced Host Controller Interface,是Inter主导的一个用于USB2.0的USB控制器标准。EHCI仅提供USB2.0的高速功能,至于全速和低速功能就由OHCI或UHCI来提供。
  • xHCI:全称是eXtensible Host Controller Interface,是目前最流行的USB3.0控制器标准,在速度、能效和虚拟化等方面比前三个都有较大的提高。xHCI支持所有速度种类的USB设备,xHCI出现的目的就是为了替换前面三个

通俗来讲,OHCI就是FS模式,也就是低速模式,EHCI是HS模式,也就是高速模式

USB Typec电气属性

USB TypeC接口引脚定义

STM32MP157有两个USB2接口,所以可以直接使用Mini USB或者Micro USB。但是目前USB TypeC接口非常普及,为了方便使用,正点原子STM32MP157开发板上的USB接口采用了TypeC。

接下来就看一下USB TypeC接口(根据USB协议,也叫做USB3.1接口)电气属性,由于TypeC功能比较复杂,比如支持PD充电、显示、音频等,这里只讲解一下TypeC的数据
通信部分
。首先来看一下TypeC的接口定义,这里只看母头的引脚定义,如下图所示:

标准USB TypeC母头引脚

从上图中可以看出,标准的USB TypeC有24根线,分为上下两部分,明显比前面讲的Mini USB要多不少引脚,这些引脚按照功能分类:

  • A1、A12、B1、B12:这4个引脚为GND。
  • A2、A3、B2、B3:这4个引脚是USB3.1特有的,为超高速差分发送信号线,这个是USB2.0和USB1.0所没有的。
  • A10、A11、B10、B11:这4个引脚也是USB3.1特有的,为超高速差分接收信号线,这个也是USB2.0和USB1.0所没有的。
  • A4、A9、B4、B9:VBUS引脚,这个USB2.0也有。
  • A5、B5:CC1和CC2这2个引脚为USB3.1特有的配置通道引脚。Type-C的插座中有两个CC脚,USB通信中的角色检测都是通过CC脚进行的。对于TypeC插头或者线缆正常只有一个CC引脚,两个端口连接在一起之后,只存在一个CC引脚连接,通过检测哪一个CC有连接,就可以判断连接的方向。如果USB线缆中有需供电的器件,其中一个CC引脚将作为VCONN供电。
    对于正点原子linux驱动教程里的USB章节来说,主要使用CC1和CC2引脚来实现USB OTG功能。
  • A8、B8:SBU1和SBU2引脚,这两个并不是USB信号,而是用作其他功能的,比如TypeC作为DP接口使用的时候SBU用作音频传输通道。
  • A6、A7、B6、B7:这4个引脚就是USB2.0的D+和D-,因为USB3.1要兼容USB2.0和USB1.0接口,所以必须要有D+和D-。

仔细看上图可看出,上下两排引脚是对应的,比如(A1,B1)、(A2,B2)等,一直到(A12,B12)。这是因为USB3.1要有能够正反插,因此上下两排引脚肯定要是一样的,这样才能在正反插的时候都能正常工作

上图是TypeC接口标准的24P母头引脚,在进行TypeC母头选型的时候会发现也有16P甚至6P封装的接口。比如正点原子STM32MP157开发板上使用的就是16P的TypeC母座,16P 的母座引脚结构如下图所示:

16PTypeC引脚示意图

上图就是16P的TypeC引脚示意图,从左到右依次是1-16 脚,注意1,2 脚、3,4 脚、13,14 脚和15,16脚离得很近,看起来像是一个引脚,其实他们是两个引脚。16P对应的引脚功能如下图所示:

16P母座引脚定义

可以看出,相比于标准24P引脚定义,16P的母座少了8根USB3.1高速差分收发数据线

USB TypeC的Data Role

USB2.0根据数据传输方向定义了HOST/Device/OTG这三种类型的设备角色,其中OTG既可以做HOST也可以做Device,USB2.0的OTG设备接口通过ID线来区分HOST还是Device。在TypeC领域,也有类似的概念,但是名字变了:

  1. DFP:DFP全称是Downstream Facing Port,也就是下行端口,可以理解为HOST,DFP提供VBUS、VCONN,可以接收数据。
  2. UFP:UFP全称是Upstream Facing Port,也就是上行端口,可以理解为Device,UFP 从VBUS中取电源,UFP设备也可以传输数据,比如U盘,键盘鼠标等。
  3. DRP:DRP全称是Dual Role Port,也就是双角色端口,可以理解为OTG,DRP既可以做DFP,也可以做UFP。也可以在DFP和UFP之间进行动态切换。这个切换过程就用到了CC引脚,具体识别过程比较复杂,这里就不讲解了。如果要在USB TypeC接口上实现DRP功能,那么就需要使用到外置的TypeC芯片!正点原子STM32MP157开发板使用了STUSB1600或FUSB302MPX这两种TypeC芯片。这两种芯片都有一个IIC配置接口,也就是需要编写驱动程序,否则的话DRP功能无法实现

硬件原理图分析

正点原子的STM32MP1开发板USB部分原理图可以分为两部分:USB HUB以及USB OTG

USB TypeC本来是给USB3.1准备的,单位了方便使用,所以正点原子STM32MP157开发板也使用了TypeC接口,但是本质上还是USB2.0协议,所以不要看到TypeC就以为支持USB3.0!

另外,使用TypeC接口实现OTG功能的话就需要外界TypeC芯片!通过专用的TypeC芯片来控制CC引脚实现USB的主从切换。V1.3版本以前的底板使用STUSB1600这颗TypeC芯片,V1.5版本以后的底板都使用FUSB302MPX。

依次来看一下这两部分的硬件原理图。

USB HUB原理图分析

首先来看一下USB HUB原理图,STM32MP1使用FE2.1这个HUB芯片将STM32MP1的USB2扩展成了7路HOST接口,其中一路供4G模块使用,因此就剩下了6个通用的USB A插座,原理图如下图所示:

USB HUB原理图

上图中U21就是USB HUB芯片FE2.1,FE2.1是一款符合USB2.0标准的USB HUB芯片,支持一拖七扩展,可以将一路USB扩展为7路USB HOST接口。这里将STM32MP1的USB1扩展出了7路USB HOST接口,分别为HUB_DP1/DM1、HUB_DP2/DM2、HUB_DP3/DM3、HUB_DP4/DM4、HUB_DP5/DM5、HUB_DP6/DM6和HUB_DP7/DM7。其中HUB_DP7/DM7用于4G模块,因此对外提供的只有六个USB HOST接口,这三个USB HOST接口如下图所示:

扩展的USB HOST

注意,使用FE2.1扩展出来的7路USB接口只能用作HOST!

USB OTG原理图分析

正点原子的STM32MP1开发板上还有一路USB OTG接口,使用STM32MP1的USB OTG接口。此路USB OTG既可以作为主机(HOST),也可以作为从机(DEVICE),从而实现完整的OTG功能。V1.4版本以前的底板上TypeC芯片使用STUSB1600,V1.5及以后版本的底板使用FUSB302MPX这颗芯片

现在购买的话都是V1.5以后的版本了,开发板的USB OTG是使用FUSB302MPX做控制。原理图如下图所示:

FUSB302MPX

FUSB302PMX也是负责控制切换主机和从机模式的,MT9700HT5是负载开关,用来控制VBUS输出,当OTG_PWR_CTRL输出高电平的时候OUT引脚就输出5V电压,也就是VBUS变为5V。当OTG_PWR_CTRL输出低电平的时候OUT输出0V,相当于VBUS关闭。

所以当开发板上的TypeC接口作为主设备的时候,OTG_PWR_CTRL要输出高电平,VBUS输出5V,为外部USB设备供电。当TypeC接口作为从设备的时候,OTG_PWR_CTRL输出低电平OTG_PWR_CTRL对应的GPIO 引脚为PZ6

USB协议简介

USB描述符

USB描述符就是用来描述USB信息的,描述符就是一串按照一定规则构建的字符串,USB设备使用描述符来向主机报告自己的相关属性信息,常用的描述符如下图所示:

USB设备常用描述符

依次来看一下上图中这5个描述符的含义。

设备描述符

设备描述符用于描述USB设备的一般信息,USB设备只有一个设备描述符。设备描述符里面记录了设备的USB版本号、设备类型、VID(厂商 ID)、PID(产品 ID)、设备序列号等。设备描述符结构如下图所示:

设备描述符结构

配置描述符

设备描述符的bNumConfigurations域定义了一个USB设备的配置描述符数量,一个USB设备至少有一个配置描述符。配置描述符描述了设备可提供的接口(Interface)数量、配置编号、供电信息等,配置描述符结构如下图所示:

配置描述符结构

字符串描述符

字符串描述符是可选的,字符串描述符用于描述一些方便阅读的信息,比如制造商、设备名称等。如果一个设备没有字符串描述符,那么其他描述符中和字符串有关的索引值都必须为0,字符串描述符结构如下图所示:

字符串描述符结构

wLANGID[0]~wLANGID[x]指明了设备支持的语言 , 具体含义要查阅文档《USB_LANGIDs.pdf》。

主机会再次根据自己所需的语言向设备请求字符串描述符,这次主机会指明要得到的字符串索引值和语言。设备返回Unicode编码的字符串描述符,结构如下图所示:

Unicode编码的字符串描述符结构

接口描述符

配置描述符中指定了该配置下的接口数量,配置可以提供一个或多个接口,接口描述符用于描述接口属性。接口描述符中一般记录接口编号、接口对应的端点数量、接口所述的类等,接口描述符结构如下图所示:

接口描述符结构

端口描述符

接口描述符定义了其端点数量,端点是设备与主机之间进行数据传输的逻辑接口,除了端点0是双向端口,其他的端口都是单向的。端点描述符描述了树传输类型、方向、数据包大小、端点号等信息,端点描述符结构如下图所示:

端点描述符结构

USB数据包类型

USB是串行通信,需要一位一位的去传输数据,USB传输的时候先将原始数据进行打包,所以USB中传输的基本单元就是数据包。根据用途的不同,USB协议定义了4种不同的包结构:令牌(Token)包、数据(Data)包、握手(Handshake)包和特殊(Special)包。这四种包通过包标识符PID来区分,PID共有8位,USB协议使用低4位PID3-PID0,另外的高四位PID7-PID4是PID3-PID0的取反,传输顺序是PID0、PID1、PID2、PID3…PID7令牌包的PID1-0为01,数据包的PID1-0 为11,握手包的PID1-0为10,特殊包的PID1-0为00。每种类型的包又有多种具体的包,如下图所示:

数据包结构

一个完整的包分为多个域,所有的数据包都是以同步域(SYNC)开始,后面紧跟着包标识符(PID),最终都以包结束(EOP)信号结束。不同的数据包中间位域不同,一般有包目标地址(ADDR)、包目标端点(ENDP)、数据、帧索引、CRC等,这个要具体数据包具体分析。接下来简单看一下这些数据包的结构。

令牌包

令牌包结构如下图所示:

SETUP令牌包

上图是一个SETUP令牌包结构,首先是SYNC同步域,包同步域为00000001,也就是连续7个0,后面跟一个1,如果是高速设备的话就是31个0后面跟一个1紧跟着是PID,这里是SETUP包,为0XB4,是0XB4的原因如下:

  1. ETUP包的PID3-PID0 为1101,因此对应的PID7~PID4就是0010。
  2. PID传输顺序为PID0、PID1、PID2…PID7,因此按照传输顺序排列的话此处的PID就是10110100=0XB4,并不是0X2D。

PID后面跟着地址域(ADDR)和端点(ENDP),为目标设备的地址和端点号CRC5域是5位CRC值,是ADDR和ENDP这两个域的校验值。最后就是包结束域(EOP),标记本数据包结束。其他令牌包的结构和SETUP基本类似,只是SOF令包中间没有ADDR和ENDP这两个域,而是只有一个11位的帧号域

数据包

数据包结构如下图所示:

数据包

数据包比较简单,同样的,数据包从SYNC同步域开始,然后紧跟着是PID,这里就是DATA0,PID值为0XC3。接下来就是具体的数据,数据完了以后就是16位的CRC校验值,最后是EOP

握手包

握手包结构如下图所示:

ACK握手包

上图是ACK握手包,很简单,首先是SYNC同步域,然后就是ACK包的PID,为0X4B,最后就是EOP。其他的NAK、STALL、NYET和ERR握手包结构都是一样的,只是其中的PID不同而已。

USB传输类型

在端点描述符中bmAttributes指定了端点的传输类型,一共有4种,本节来看一下这四种传输类型的区别。

控制传输

控制传输一般用于特定的请求,比如枚举过程就是全部由控制传输来完成的,比如获取描述符、设置地址、设置配置等。控制传输分为三个阶段:建立阶段(SETUP)、数据阶(DATA)和状态阶段(STATUS),其中数据阶段是可选的。建立阶段使用SETUP令牌包,SETUP使用DATA0包。数据阶段是 0 个、1个或多个输入(IN)/输出(OUT)事务,数据阶段的所有输入事务必须是同一个方向的,比如都为IN或都为OUT。数据阶段的第一个数据包必须是DATA1,每次正确传输以后就在DATA0和DATA1之间进行切换。数据阶段完成以后就是状态阶段,状态阶段的传输方向要和数据阶段相反,比如数据阶段为IN的话状态阶段就要为OUT,状态阶段使用DATA1包。比如一个读控制传输格式如下图所示:

读控制传输阶段

同步传输

同步传输用于周期性、低时延、数据量大的场合,比如音视频传输,这些场合对于时延要求很高,但是不要求数据100%正确,允许有少量的错误。因此,同步传输没有握手阶段,即使数据传输出错了也不会重传。

批量传输

批量传输就是用于大批量传输大块数据的,这些数据对实时性没有要求,比如MSD类设备(存储设备),U盘之类的。批量传输分为批量读(输入)和批量写(输出),如果是批量读的话第一阶段就是IN令牌包,如果是批量写那么第一阶段就是OUT令牌包

就以批量写为例简单介绍一下批量传输过程:

  1. 主机发出OUT令牌包,令牌包里面包含了设备地址、端点等信息。
  2. 如果OUT令牌包正确的话,也就是设备地址和端点号匹配,主机就会向设备发送一个数据(DATA)包,发送完成以后主机进入接收模式,等待设备返回握手包,一切都正确的话设备就会向主机返回一个ACK握手信号。

批量读的过程刚好相反:

  1. 主机发出IN令牌包,令牌包里面包含了设备地址、端点等信息。发送完成以后主机就进入到数据接收状态,等待设备返回数据。
  2. 如果IN令牌包正确的话,设备就会将一个DATA包放到总线上发送给主机。主机收到这个DATA包以后就会向设备发送一个ACK握手信号。

中断传输

这里的中断传输并不是传统意义上的硬件中断,而是一种保持一定频率的传输,中断传输适用于传输数据量小、具有周期性并且要求响应速度快的数据,比如键盘、鼠标等。中断的端点会在端点描述符中报告自己的查询时间间隔,对于时间要求严格的设备可以采用中断传输

USB枚举

当USB设备与USB主机连接以后主机就会对USB设备进行枚举,通过枚举来获取设备的
描述符信息,主机得到这些信息以后就知道该加载什么样的驱动、如何进行通信等
。USB 枚举过程如下:

  1. 第一回合,当USB主机检测到USB设备插入以后机会发出总线复位信号来复位设备。USB设备复位完成以后地址为0,主机向地址0的端点0发送数据,请求设备的描述符。设备得到请求以后就会按照主机的要求将设备描述符发送给主机,主机得到设备发送过来的设备描述符以后,如果确认无误就会向设备返回一个确认数据包(ACK)。
  2. 第二回合,主机再次复位设备,进入地址设置阶段。主机向地址0的端点0发送设置地址请求数据包,新的设备地址就包含在这个数据包中,因此没有数据过程。设备进入状态过程,等待主机请求状态返回,收到以后设备就会向主机发送一个0字节状态数据包,表明设备已经设置好地址了,主机收到这个0字节状态数据包以后会返回一个确认包(ACK)。设备收到主机发送的ACK包以后就会使用这个新的设备地址,至此设备就得到了一个唯一的地址。
  3. 第三回合,主机向新的设备地址端点0发送请求设备描述符数据包,这一次主机要获取整个设备描述符,一共是18个字节。
  4. 和第3步类似,接下来依次获取配置描述符、配置集合、字符串描述符等等。

STM32MP1 USB HOST驱动编写

接下来就开始编写USB HOST的驱动。USB子系统是一个标准和复杂的接口,所以驱动基本不用写,都是内核里有现成的,只需要在设备树提供对应的设备节点即可

之前的学习有说到USBH是只能主机模式,要编写USB HOST就用USBH控制器即可。ST官方的STM3MP157C-DK2开发板已经配置好了USBH的节点信息,所以直接参考此节点即可,这样开发板就能够使用 USB Host 模式。

USBH控制器节点信息

打开“stm32mp151.dtsi”文件,找到USBH两个控制器的节点信息,名字分别为“usbh_ohci”和“usbh_ehci”。如下示例代码所示:

示例代码 50.4.1 USBH 下两个控制器节点信息
1  usbh_ohci: usbh-ohci@5800c000 {
2      compatible = "generic-ohci";
3      reg = <0x5800c000 0x1000>;
4      clocks = <&rcc USBH>;
5      resets = <&rcc USBH_R>;
6      interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
7      status = "disabled";
8  };
9
10 usbh_ehci: usbh-ehci@5800d000 {
11     compatible = "generic-ehci";
12     reg = <0x5800d000 0x1000>;
13     clocks = <&rcc USBH>;
14     resets = <&rcc USBH_R>;
15     interrupts-extended = <&exti 43 IRQ_TYPE_LEVEL_HIGH>;
16     companion = <&usbh_ohci>;
17     power-domains = <&pd_core>;
18     wakeup-source;
19     status = "disabled";
20 };

从上面的代码可以知道USBH是支持USB2.0和USB1.1的。使用USB2.0就要配置usbh_ehci节点,使用USB1.1就要配置usbh_ohci 节点。这两个节点的信息是不需要修改的,这是STM32MP1一些通用配置信息。需要做的就是在stm32mp157d-atk.dts文件中追加对应属性信息,然后status属性值改为“okay”,还要配置USBH使用哪个PHY端口。根据两个节点的compatible属性找到对应的驱动路径为:drivers/usb/host/ohci-platform.c和drivers/usb/host/ehci-platform.c。

配置PHY控制器

先了解一下PHY控制器,一些通用配置,打开 stm32mp151.dtsi文件,找的如下内容所示:

示例代码 50.4.2 usbphyc 控制器节点信息
1  usbphyc: usbphyc@5a006000 {
2      #address-cells = <1>;
3      #size-cells = <0>;
4      #clock-cells = <0>;
5      compatible = "st,stm32mp1-usbphyc";
6      reg = <0x5a006000 0x1000>;
7      clocks = <&rcc USBPHY_K>;
8      resets = <&rcc USBPHY_R>;
9      vdda1v1-supply = <&reg11>;
10     vdda1v8-supply = <&reg18>;
11     status = "disabled";
12
13     usbphyc_port0: usb-phy@0 {
14         #phy-cells = <0>;
15         reg = <0>;
16     };
17
18     usbphyc_port1: usb-phy@1 {
19         #phy-cells = <1>;
20         reg = <1>;
21     };
22 };

示例代码50.4.1.2 usbphyc节点就是STM32MP1的USB PHY。已经知道PHY控制器有两个端口,刚好usbphyc节点里有两个子节点名字分别为:usbphyc_port0和usbphyc_port1,这两个子节点就是PHY控制器的两个端口,其中usbphyc_port0只能分配给USB Host。注意:“#phy-cells”属性和“#gpio-cells”属性作用是一样的,如果#phy-cells为1,表示一个cell,此cell表示端口做USBH的PHY端口还是OTG的PHY端口,0表示做OTG的PHY端口,1表示做USBH的PHY端口

打开“stm32mp15xx-dkx.dtsi”文件找到“usb_phy_tuning”节点,把此节点的内容拷贝到stm32mp157d-atk.dts的根目录下,拷贝内容如下所示:

示例代码 50.4.3 添加的 usb_phy_tuning 节点信息
1  usb_phy_tuning: usb-phy-tuning {
2      st,hs-dc-level = <2>;
3      st,fs-rftime-tuning;
4      st,hs-rftime-reduction;
5      st,hs-current-trim = <15>;
6      st,hs-impedance-trim = <1>;
7      st,squelch-level = <3>;
8      st,hs-rx-offset = <2>;
9      st,no-lsfs-sc;
10 };

usb_phy_tuning此节点负责调整PHY的配置,对于此节点的属性内容感兴趣的可以去看Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.yaml文件 。 接着还是在stm32mp157d-atk.dts文件中使能usbphyc以及向usbphyc_port0节点追加的内容,要修改的如下所示:

示例代码 50.4.4 使能 usbphyc 和追加 usbphyc_port0 的内容
1 &usbphyc {
2     status = "okay";
3 };
4
5 &usbphyc_port0 {
6     phy-supply = <&v3v3>;
7     st,phy-tuning = <&usb_phy_tuning>;
8 };

第2行,这里把usbphyc的status属性修改为“okay”,使能usbphyc。

第6行,给usbphyc_port0节点追加phy-supply属性,添加一个电源管理属性。

第7行,把要修改的PHY配置,添加到usbphyc_port0节点里。

配置usbh_ehci

最后在stm32mp157d-atk.dts文件,使能usbh_ehci和指定PHY端口。

示例代码 50.4.5 追加 usbh_ehci 节点内容
1 &usbh_ehci {
2     phys = <&usbphyc_port0>;
3     status = "okay";
4 };

这里的内容很简单,就是修改status属性为“okay”和指定使用usbphyc_port0端口。修改完就重新编译设备树,使用新的stm32mp157d-atk.dtb设备树重新启动开发板。就会有以下输出所示:

usbh-echi打印信息
如果有上图这些输出信息,说明usb host驱动加载到内核了。

Linux内核自带Host实验

USB鼠标键盘测试

首先做一下USB HOST试验,也就是正点原子的STM32MP1开发板做USB主机,然后外接USB设备,比如USB鼠标键盘、USB 转TTL串口线、U盘等设备。Linux内核已经集成了大量的USB设备驱动,尤其是常见的USB鼠标键盘、U盘等,本节就来学习一下如何使能Linux内核常见的USB设备驱动。

USB鼠标键盘驱动使能

USB鼠标键盘属于HID设备,内核已经集成了相应的驱动,ST官方提供的linux内核默认已经使能了USB鼠标键盘驱动,但是还要学习一下如何手动使能这些驱动。输入“make
menuconfig”,打开linux内核配置界面,首先打开HID驱动,按照如下路径到相应的配置项目:

-> Device Drivers
-> HID support
-> HID bus support
-> <*> Generic HID driver //使能通用 HID 驱动

使能后结果如下图所示:

使能HID驱动

接下来需要使能USB键盘和鼠标驱动,配置路径如下:

-> Device Drivers
-> HID support
-> USB HID support
-> <*> USB HID transport layer //USB 键盘鼠标等 HID 设备驱动

使能后如下图所示:

使能USB鼠标键盘驱动

可以将光标放到上图中“USB HID Transport layer”这一行,然后按下“?”键打开对应的帮助信息就可以看到对于这个配置项的描述,简单总结一下:

此选项对应配置项就是CONFIG_USB_HID,也就是USB接口的HID设备。如果要使用USB接口的keyboards(键盘)、mice(鼠标)、joysticks(摇杆)、graphic tablets(绘图板)等其他的HID设备,那么就需要选中“USB HID Transport layer”。但是要注意一点,此驱动和HIDBP(Boot Protocol)键盘、鼠标的驱动不能一起使用!所以要是在网上查阅linux内核USB键盘鼠标驱动的时候,发现推荐使用“USB HIDBP Keyboard (simple Boot) support”和“USB HIDBP Mouse (simple Boot) support”这两个配置项的时候也不要觉得教程这里写错了。

测试USB鼠标和键盘

完成以后重新编译linux内核并且使用得到的 uImage启动开发板。启动以后插入USB鼠标,会有如下图所示的提示信息:

USB鼠标log信息

从上图可以看出,系统检测到了鼠标,如果成功驱动的话就会在/dev/input目录下生成一个名为eventX(X=0,1,2,3…)的文件,这个就是前面讲的输入子系统,鼠标和键盘都是作为输入子系统设备的。教程中这里对应的就是/dev/input/event1这个设备,使用如下命令查看鼠标的原始输入值,结果如下图:

鼠标原始输入值

上图就是鼠标作为输入子系统设备的原始输入值,这里就不去分析了,在移植GUI图形库以后就可以直接使用鼠标,比如QT等。

最后再来测试一下USB键盘,屏幕已经驱动起来了,所以可以直接将屏幕作为终端,然后接上键盘直接输入命令来进行各种操作。首先将屏幕设置为控制台,打开开发板根文件系统中的/etc/inittab文件,然后在里面加入下面这一行:

tty1::askfirst:-/bin/sh

完成以后重启开发板,此时屏幕就会作为终端控制台,会有“Please press Enter to activate
this console.”这样提示,如下图所示:

LCD屏幕作为终端

接上键盘,然后根据上图中的提示,按下键盘上的Enter(回车)键即可使能LCD屏幕控制台,然后就可以输入各种命令来执行相应的操作,如下图所示:

键盘操作终端

U盘实验

ST提供的Linux内核默认也已经使能了U盘驱动,因此可以直接插上去使用。但是还是需要学习一下如何手动配置Linux内核,使能U盘驱动。

使能U盘驱动

U盘使用SCSI协议,因此要先使能Linux内核中的SCSI协议,配置路径如下:

-> Device Drivers
-> SCSI device support
-> <*> SCSI disk support //选中此选项

结果如下图所示:

使能SCSI

还需要使能USB Mass Storage,也就是USB 接口的大容量存储设备,配置路径如下:

-> Device Drivers
-> USB support
-> USB Gadget Support
-> USB Gadget functions configurable through configfs
-> [*] Mass storage //选中

结果如下图所示:

使能USB大容量存储设备

U盘测试

准备好一个U盘,注意U盘要为FAT32格式的!NTFS和exFAT由于版权问题所以在Linux下支持的不完善,操作的话可能会有问题,比如只能读,不能写或者无法识别等。准备好以后将U盘插入到开发板USB HUB扩展出来的HOST接口上,此时会输出如下图所示信息:

U盘log信息

从上图可以看出,系统检测到U盘插入,大小为16GB对应的设备文件为/dev/sda和/dev/sda1,可以查看一下/dev目录下有没有sda和sda1这两个文件。/dev/sda是整个U盘,/dev/sda1是U盘的第一个分区,一般使用U盘的时候都是只有一个分区。要想访问U盘需要先对U盘进行挂载,理论上挂载到任意一个目录下都可以,这里可以创建一个/mnt/usb_disk目录,然后将U盘挂到/mnt/usb_disk目录下,命令如下:

mkdir /mnt/usb_disk -p //创建目录
mount /dev/sda1 /mnt/usb_disk/ -t vfat -o iocharset=utf8 //挂载

-t指定挂载所使用的文件系统类型,这里设置为vfat,也就是FAT文件系统,“-o iocharset”
设置硬盘编码格式为utf8,否则的话U盘里面的中文会显示乱码!

挂载成功以后进入到/mnt/usb_disk目录下,输入ls命令查看U盘文件,如下图所示:

U盘文件

至此U盘就能正常读写操作了,直接对/mnt/usb_disk目录进行操作就行了。如果要拔出U盘要执行一个sync命令进行同步,然后在使用unmount进行U盘卸载,命令如下所示:

sync //同步
cd / //如果处于/mnt/usb_disk 目录的话先退出来,否则卸载的时候提示设//备忙,导致卸载失败,切记!
umount /mnt/usb_disk //卸载

Linux内核自带USB OTG实验

STUSB1600设备树编写

STUSB1600是ST出的一款TypeC 芯片,也是ST官方开发板搭配STM32MP1的。STUSB1600的驱动设备树编写参考文档“Documentation/devicetree/bindings/usb/st,typec-stusb.txt”。此文档描述了STUSB1600 设备相关信息。

USB OTG控制器节点信息

进入到Linux内核源码目录,打开arch/arm/boot/dts/stm32mp151.dtsi 设备树文件,在这个设备树文件有一个usbotg_hs节点,此节点就是USB OTG控制器节点,内容如下:

示例代码 50.6.1.1 usbotg_hs 控制器
1  usbotg_hs: usb-otg@49000000 {
2      compatible = "st,stm32mp1-hsotg", "snps,dwc2";
3      reg = <0x49000000 0x10000>;
4      clocks = <&rcc USBO_K>;
5      clock-names = "otg";
6      resets = <&rcc USBO_R>;
7      reset-names = "dwc2";
8      interrupts-extended = <&exti 44 IRQ_TYPE_LEVEL_HIGH>;
9      g-rx-fifo-size = <512>;
10     g-np-tx-fifo-size = <32>;
11     g-tx-fifo-size = <256 16 16 16 16 16 16 16>;
12     dr_mode = "otg";
13     usb33d-supply = <&usb33>;
14     power-domains = <&pd_core>;
15     wakeup-source;
16     status = "disabled";
17 };

示例代码50.6.1.1中的usbotg_hs节点不需要修改,这里只是查看一下usbotg_hs完整节点信息。根据第2行的compatible属性就是可以找到STM32MP1的usbotg_hs驱动源文件,驱动文件名为drivers/usb/dwc2/params.c。第12行,dr_mode属性用来配置控制器做otg功能还是host功能,不配置此属性默认为otg功能。第16行的status属性为disabled,所以usbotg_hs默认关闭的,如果要使能就是要修改status属性为 okay、配置一个PHY端口和设置使用那个控制器去控制usbotg_hs

使能usbotg_hs节点

首先先去配置PHY接口,在stm32mp157d-atk.dts文件中追加usbphyc_port1节点,内容如下所示:

示例代码 50.6.1.2 usbphyc_port1 节点配置
1 &usbphyc_port1 {
2     phy-supply = <&vdd_usb>;
3     st,phy-tuning = <&usb_phy_tuning>;
4 };

注意:要先使能usbphyc节点,这里在USB HOST实验已经使能就不用配置。

接着去追加usbotg_hs的相关属性信息。追加的内容如下所示:

示例代码 50.6.1.3 usbotg_hs 节点内容
1  &usbotg_hs {
2      phys = <&usbphyc_port1 0>;
3      phy-names = "usb2-phy";
4      usb-role-switch;
5      status = "okay";
6
7      port {
8      usbotg_hs_ep: endpoint {
9          remote-endpoint = <&con_usbotg_hs_ep>;
10         };
11     };
12 };

第2行,配置usbotg_hs的PHY接口,这里0表示为OTG USB的PHY端口。

第5行,把status属性改为okay,使能usbotg_hs。

第7-11行,添加了一个port节点,第9行指定usbotg_hs 节点使用con_usbotg_hs_ep做控制器,con_usbotg_hs_ep会在STUSB1600节点里创建。

最后还需要添加vdd_usb电源节点,因为示例代码50.6.1.2中的usbphyc_port1节点需要用到vdd_usb节点,在stm32mp157d-atk.dts中添加vdd_usb电源节点,在根节点下添加内容如下

示例代码 50.6.1.4 vdd_usb 电源节点
1 vdd_usb: regulator-vdd-usb {
2     compatible = "regulator-fixed";
3     regulator-name = "vdd_usb";
4     regulator-min-microvolt = <3300000>;
5     regulator-max-microvolt = <3300000>;
6     regulator-always-on;
7     regulator-boot-on;
8 };

使能I2C1节点

控制Typec的芯片是STUSB1600,此芯片是使用I2C协议和CPU进行通讯,所以要使能I2C1。把示例代码50.6.1.4拷贝到stm32mp157d-atk.dts文件里,代码如下所示:

示例代码 50.6.1.5 使能 i2c1 节点
1 &i2c1 {
2     pinctrl-names = "default", "sleep";
3     pinctrl-0 = <&i2c1_pins_b>;
4     pinctrl-1 = <&i2c1_pins_sleep_b>;
5     status = "okay";
6 };

正点原子STM32MP157开发板I2C1_SCL和I2C1_SDA这两个引脚为PF14和PF15。这两个引脚的pinctrl配置已经在stm32mp15-pinctrl.dtsi文件里面提供了,如果使用的其他引脚需要自行配置。

配置STUSB1600节点

先配置STUSB1600的中断引脚电气属性和电源管理,STUSB1600中断引脚为PG2,在stm32mp15-pinctrl.dtsi文件中输入如下内容:

示例代码 50.6.1.6 stusb1600 中断电气属性
1 stusb1600_pins_b: stusb1600-0 {
2     pins {
3         pinmux = <STM32_PINMUX('G', 2, ANALOG)>;
4         bias-pull-up;
5     };
6 };

把STUSB1600的中断引脚改为内部上拉。因为USB OTG需要5V电源管理,前面没有此配置,所以要一个5V的电源配置,在根节点下添加如下示例代码所示:

示例代码 50.6.1.7 5V 的 vin 节点
1 vin: regulator-vin {
2     compatible = "regulator-fixed";
3     regulator-name = "vin";
4     regulator-min-microvolt = <5000000>;
5     regulator-max-microvolt = <5000000>;
6     regulator-always-on;
7     regulator-boot-on;
8 };

接下来配置STUSB1600节点,此节点属于I2C1的子节点。STUSB1600的配置内容如下所示:

示例代码 50.6.1.8 stusb1600 节点信息
1  stusb1600@28 {
2      compatible = "st,stusb1600";
3      reg = <0x28>;
4      interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
5      interrupt-parent = <&gpiog>;
6      pinctrl-names = "default";
7      pinctrl-0 = <&stusb1600_pins_b>;
8      status = "okay";
9      vdd-supply = <&vin>;
10
11     connector {
12         compatible = "usb-c-connector";
13         label = "USB-C";
14         power-role = "dual";
15         power-opmode = "default";
16
17         port {
18             con_usbotg_hs_ep: endpoint {
19                 remote-endpoint = <&usbotg_hs_ep>;
20             };
21         };
22     };
23 };

示例代码50.6.1.7代码中可以分为两个部分:第1-9行属于控制STUSB1600芯片相关属性,第11-22行属于Typec端口相关属性。

第2行,compatible的属性值为“st,stusb1600”,在Linux源码目录下,搜索此属性值,会找到stusb1600的驱动源码为drivers/usb/typec/typec_stusb.c。

第3行,reg属性值为“0x28”,stusb1600芯片通讯地址为0x28。

第4-5行,设置中断相关配置,中断触发为下降沿触发。

第6-7行,设置中断的电气属性。

第9行,设置电源配置。

第12-15行,可以查看Documentation/devicetree/bindings/connector/usb-connector.txt 文件。

第17-20行,定义了一个con_usbotg_hs_ep端口,同时指定此端口连接到usbotg_hs节点的usbotg_hs_ep端口。

重新编译设备树,使用新的stm32mp157d-atk.dtb 文件去启动开发板。

FUSB302驱动移植

在上一小节已经完成了一部分的设备树配置,由于只是改了USB TypeC的芯片,所以只需要把STUSB1600相关的部分改为FUSB302 即可其实就是上一小节前面的1-3小点不用修改,只需要改第4小点。

首先配置FUSB302的中断电气属性,配置如下所示:

示例代码 50.6.2.1 fusb302 中断电气属性
1 fusb302_pins_a: fusb302-0 {
2     pins {
3         pinmux = <STM32_PINMUX('G', 2, ANALOG)>;
4         bias-pull-up;
5     };
6 };

在 stm32mp157d-atk.dts文件中添加头文件“dt-bindings/usb/pd.h”,如下图所示:

添加“dt-bindings/usb/pd.h”头文件

接着配置FUSB302节点,配置的代码如下所示:

示例代码 50.6.2.2 fusb302 节点信息
1  fusb302@22 {
2      compatible = "fcs,fusb302","fairchild,fusb302";
3      reg = <0x22>;
4      pinctrl-names = "default";
5      pinctrl-0 = <&fusb302_pins_a>;
6      int-n-gpios = <&gpiog 2 GPIO_ACTIVE_HIGH>;
7      vbus-5v-gpios = <&gpioz 6 GPIO_ACTIVE_HIGH>;
8      status = "okay";
9
10     connector {
11         compatible = "usb-c-connector";
12         label = "USB-C";
13         power-role = "dual";
14         power-opmode = "default";
15
16         try-power-role = "sink";
17         source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
18         sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
19         PDO_VAR(3000, 12000, 3000)
20         PDO_PPS_APDO(3000, 11000, 3000)>;
21         op-sink-microwatt = <10000000>;
22         port {
23             con_usbotg_hs_ep: endpoint {
24                 remote-endpoint = <&usbotg_hs_ep>;
25             };
26         };
27     };
28 };

fusb302的设备树没有使用电源管理,而是用PZ6 引脚去控制电压。其它的属性和stusb1600一样。

最后就是去改驱动了,Linux内核提供的fusb302驱动是没读取connector相关的属性,所以此驱动是不能使用。在github里找到了可用的fusb302驱动,改动的部分是如何读取connector节点和使用PZ6引脚控制电压。想看如何修改的,可以用文件对比软件,就能知道修改了那些内容。将25_fusb302目录的fusb302.c和fusb302.h文件拷贝到linux源码下的drivers/usb/typec/tcpm目录里,注意要把原来的fusb302.c覆盖掉,拷贝结果如下图所示:

拷贝fusb302驱动的tcpm目录

拷贝完成后,进入内核的配置选项图形界面,配置路径如下:

-> Device Drivers
-> USB support
-> USB Type-C Support
-> USB Type-C Port Controller Manager
-> <*> Fairchild FUSB302 Type-C chip driver //选中

如下图所示:

FUSB302的驱动使能

重新编译设备树和内核,使用新的设备树stm32mp157d-atk.dtb和内核uImage去启动开发
板,有如下图打印信息,说明加载驱动成功。如图所示:

加载FUSB302驱动打印信息

OTG主机实验

系统重启成功以后就可以正常使用USB_OTG接口,OTG既可以做主机,也可以做从机,做主机的话测试方法和之前的测试一模一样,直接在正点原子的USB_OTG接口上使用Typec OTG线接入鼠标键盘、U盘等设备。USB_OTG接口如下所示:

USB_OTG接口

OTG从机实验

OTG从机就是将开发板作为一个USB设备连接到其他的主机上,这里来做两个USB从机实验:模拟U盘以及USB声卡。

模拟U盘实验

模拟U盘实验就是将开发板当做一个U盘,可以将开发板上的U盘或者TF卡挂载到PC上,首先需要配置Linux,配置路径如下:

-> Device Drivers
-> USB support
-> USB Gadget Support
-> < > USB Gadget functions configurable through configfs //不要编译进内核
-> USB Gadget precomposed configurations ( [=m])
-> Mass Storage Gadget //大容量存储

结果如下图所示:

USB Gadget中大容量存储设备驱动

这里需要将驱动编译为模块!使用的时候直接输入命令加载驱动模块即可。配置好
以后执行下面这些命令重新编译Linux内核、编译模块:

make uImage LOADADDR=0XC2000040 -j32
make modules -j32

编译完成后会得到3个.ko内核模块文件,对应路径为:

drivers/usb/gadget/libcomposite.ko
drivers/usb/gadget/function/usb_f_mass_storage.ko
drivers/usb/gadget/legacy/g_mass_storage.ko

将上述三个.ko 模块拷贝到开发板根文件系统/lib/modules/5.4.31目录下,如图所示:

内核模块拷贝到根文件系统

拷贝完成以后使用新编译出来的uImage启动开发板,在开发板上插入一个U盘,记住这个U盘对应的设备文件,比如这里是/dev/sda 和/dev/sda1,以后要将/dev/sda1挂载到PC上,也就是把/dev/sda1作为模拟U盘的存储区域

使用TypeC数据线将开发板和电脑连接,连接好以后依次加载libcomposite.ko、usb_f_mass_storage.ko和g_mass_storage.ko这三个驱动文件,顺序不能错了!命令如下:

depmod
modprobe libcomposite.ko
modprobe usb_f_mass_storage.ko
modprobe g_mass_storage.ko file=/dev/sda1 removable=1

加载g_mass_storage.ko的时候使用file参数指定使用的大容量存储设备,这里使用U盘对应的/dev/sda1。如果加载成功的话电脑就会出现一个U盘,这个U盘就是开发板模拟的,可以直接在电脑上对这个U盘进行读写,实际上就是操作插在开发板上的U盘。操作完成后要退出需要执行如下命令:

rmmod g_mass_storage.ko
rmmod usb_f_mass_storage.ko
rmmod libcomposite.ko

注意!不要将开发板上的EMMC或者NAND作为模拟U盘的存储区域,因为linux下EMMC和NAND使用的文件系统一般都是EXT3/EXT4和UBIFS,这些文件系统类型和windows下的不兼容,如果挂载的话就会在windows下提示要格式化U盘!

USB声卡实验

USB声卡就是USB接口的外置声卡,一般电脑内部都自带了声卡,但是内部自带的声卡效果相对来说比较差,不能满足很多HIFI玩家的需求。USB声卡通过USB接口来传递音频数据,具体的ADC和DAC过程由声卡完成,摆脱了电脑主板体积的限制,外置USB声卡就可以做的很好。STM32MP1开发板板载了音频解码芯片,因此可以将STM32MP1开发板作为一个外置USB声卡,配置Linux内核,配置路径如下:

-> Device Drivers
-> USB suppor
-> USB Gadget Support
-> USB Gadget precomposed configurations
-> Audio Gadget //选中音频,编译为模块
-> UAC 1.0 //选中
-> [*] UAC 1.0 (Legacy) //选中 UAC

配置如下图所示:

使能USB Gadget下的音频驱动

注意,这里也是编译为驱动模块,配置完成以后重新编译内核、编译模块,会得到3个驱动模块文件,模块文件路径如下:

drivers/usb/gadget/libcomposite.ko
drivers/usb/gadget/function/usb_f_uac1_legacy.ko
drivers/usb/gadget/legacy/g_audio.ko

将上述三个.ko模块文件拷贝到开发板根文件系统/lib/modules/5.4.31目录下,拷贝完成以后使用新编译出来的uImage启动开发板,首先要按照之前学习的音频驱动的方法配置STM32MP1的声卡,保证声卡播放正常!使用Typec数据线将开发板与电脑连接起来,最后依次加载 libcomposite.ko、usb_f_uac1_legacy.ko和g_audio.ko这三个驱动模块,命令如下:

depmod
modprobe libcomposite.ko
modprobe usb_f_uac1_legacy.ko
modprobe g_audio.ko

加载完成以后稍等一会虚拟出一个USB声卡,打开电脑的设备管理器,选择“声音、视频和游戏控制器”,会发现有一个名为“AC Interface”设备,如下图所示:

模拟的USB声卡

上图中的“AC Interface”就是开发板模拟出来的USB声卡,设置Windows,选择音频输出使用“AC Interface”,Windows10设置如下图所示:

声卡输出设置为AC Interface

一切设置好以后就可以从开发板上听到电脑输出的声音,此时开发板就完全是一个USB声
卡设备了。

关于USB驱动就讲解到这里,本章并没有深入到USB驱动具体编写方式,只是对USB的协议做了简单的介绍,后面讲解了一下Linux内核自带的USB HOST和DEVICE驱动的使用,Linux 内核已经集成了大量的USB设备驱动,至于其他特殊的就需要具体情况具体分析了。

总结

本章的学习重点,主要就是对USB设备做了一个大致的了解,然后对USB的传输协议大致做了介绍,这一部分可以多看看,如果之后想做项目有要用到USB设备的话这一部分要多琢磨一下。

然后就是具体的STM32MP157开发板的USB驱动的使用。

对于USB HOST来说,这一部分需要在设备树中(.dts),在根节点下添加usb_phy_tuning节点(可以直接从stm32mp15xx-dkx.dtsi复制),然后在设备树中添加usbphyc和usbphyc_port0的节点,之后在添加usbh_ehci节点。

以上就配置完成了,之后要具体使用USB驱动设备的话,就进入Linux内核里面取使能对应设备就可以了。

至于USB OTG的驱动,因为我这边新买的肯定是最新的,就是FUSB302芯片。在设备树里面追加usbphyc_port1节点,然后再加一个usbotg_hs节点,然后在根节点加上usbphyc_port1所需要的vdd_usb节点;之后添加i2c1节点并使能;最后配置fusb302的电气属性,在i2c1节点下添加fusb302节点。最后再修改一下驱动,把提供的驱动文件拷贝到drivers/usb/typec/tcpm目录里面,最后进入内核配置使能FUSB302配置就可以了。

OTG主机实验和HOST是一样的;从机就需要先进入内核配置使能,然后需要“make modules”并按照前面写的复制.ko到/lib/modules/5.4.31然后按照顺序来modprobe,最后不用了就rmmod就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/180737.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023-11-04 LeetCode每日一题(数组中两个数的最大异或值)

2023-11-04每日一题 一、题目编号 421. 数组中两个数的最大异或值二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数数组 nums &#xff0c;返回 nums[i] XOR nums[j] 的最大运算结果&#xff0c;其中 0 ≤ i ≤ j < n 。 示例 1&#xff1a; 示例 2&…

【笔记】单片机卡死的八大原因和解决方法

在微控制器上&#xff0c;程序卡住&#xff08;即停止执行&#xff09;可能有多种原因。下面我将列举一些常见的原因&#xff0c;并提供一些可能导致程序卡住的示例情况。请注意&#xff0c;这里只是一些示例&#xff0c;并不能穷尽所有可能的情况。 1. 死循环&#xff08;Infi…

Nignx安装负载均衡动静分离以及Linux前端项目部署将域名映射到特定IP地址

目录 一、nginx简介 1.1 定义 1.2 背景 1.3 作用 二、nginx搭载负载均衡提供前后分离后台接口数据 2.1 nginx安装 2.1.1 下载依赖 2.1.2 下载并解压安装包 2.1.3 安装nginx 2.1.4 启动nginx服务 2.2 tomcat负载均衡 2.2.1 负载均衡所需服务器准备 2.2.2 配置修改 …

2022年12月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 列表L1中全是整数&#xff0c;小明想将其中所有奇数都增加1&#xff0c;偶数不变&#xff0c;于是编写了如下图所示的代…

驱动开发11-2 编写SPI驱动程序-点亮数码管

驱动程序 #include <linux/init.h> #include <linux/module.h> #include <linux/spi/spi.h>int m74hc595_probe(struct spi_device *spi) {printk("%s:%d\n",__FILE__,__LINE__);char buf[]{0XF,0X6D};spi_write(spi,buf,sizeof(buf));return 0; …

阿里云推出AI编程工具“通义灵码“;生成式 AI 入门教程 2

&#x1f989; AI新闻 &#x1f680; 阿里云推出AI编程工具"通义灵码"&#xff0c;支持多种语言及实时续写功能 摘要&#xff1a;阿里云推出了一款名为"通义灵码"的AI编程工具&#xff0c;支持多种主流编程语言&#xff0c;包括Java、Python、Go等。该工…

Redis Sentinel 哨兵模式

Sentinel 哨兵模式 Redis Sentinel 官网 Redis 的 Sentinel 文档 -- Redis中国用户组&#xff08;CRUG&#xff09; Sentinel Redis 命令参考&#xff08;红色&#xff09; Sentinel 通过监控的方式获取主机的工作状态是否正常&#xff0c;当主机发生故障时&#xff0c; Senti…

十年JAVA搬砖路——Linux搭建Ldap服务器。

1.安装命令 yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel2.启动ldap systemctl start slapd systemctl enable slapd3.修改密码 slappasswd Aa123456获得返回的密码加密密码串&#xff1a; {SSHA}DkSw0…

RPC 原理详解

文章目录 什么是 RPCRPC 基本原理RPC核心功能服务寻址数据编解码网络传输一次RPC的调用过程 实践基于HTTP协议的RPC基于TCP协议的RPC 什么是 RPC RPC&#xff08;Remote Procedure Call&#xff09;&#xff0c;即远程过程调用&#xff0c;它允许像调用本地服务一样调用远程服…

Python基础入门例程37-NP37 不低于与不超过(运算符)

最近的博文&#xff1a; Python基础入门例程36-NP36 谁的数字大&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程35-NP35 朋友的年龄是否相等&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程34-NP34 除法与取模运算&#xff08;运算符&#xff09;…

07.Diffusion Model概述

文章目录 Diffusion Model原理Reverse ProcessDenoise模块Forward Process(Diffusion Process) 文字生成图片by Diffusion Model文字生成图像的常见套路Text EncoderFrchet Inception DistanceContrastive Language-Image Pre-Training(CLIP) DecoderGeneration Model 部分截图…

Fourier分析导论——第3章——Fourier级数的收敛性(E.M. Stein R. Shakarchi)

第 3 章 Fourier级数的收敛性(Convergence of Fourier Series) The sine and cosine series, by which one can represent an arbitrary function in a given interval, enjoy among other remarkable properties that of being convergent. This property did not escape…

【Midjourney入门教程1】Midjourney的注册、订阅

文章目录 前言一、Midjourney是什么二、Midjourney注册三、新建自己的服务器四、开通订阅 前言 AI绘画即指人工智能绘画&#xff0c;是一种计算机生成绘画的方式。是AIGC应用领域内的一大分支。 AI绘画主要分为两个部分&#xff0c;一个是对图像的分析与判断&#xff0c;即“…

行业观察:数字化企业需要什么样的数据中心

伴随着数字经济在中国乃至全球的高速发展&#xff0c;数字化转型已经成为广大企业的必经之路。而作为数字经济的核心基础设施&#xff0c;数据中心充当了接收、处理、存储与转发数据流的“中枢大脑”&#xff0c;对驱动数字经济发展和企业数字化转型起到了极为关键的重要作用。…

路由器基础(十一):ACL 配置

访问控制列表 (Access Control List,ACL) 是目前使用最多的访问控制实现技术。访问控制列表是路由器接口的指令列表&#xff0c;用来控制端口进出的数据包。ACL适用于所有的被路由协议&#xff0c;如IP、IPX、AppleTalk 等。访问控制列表可以分为基本访问控制列表和高级访问控制…

第10章_创建和管理表

文章目录 1 基础知识1.1 一条数据存储的过程1.2 标识符命名规则1.3 MySQL中的数据类型 2 创建和管理数据库2.1 创建数据库2.2 使用数据库2.3 修改数据库2.4 删除数据库代码演示 3 创建表3.1 创建方式13.2 创建方式23.3 查看数据表结构代码演示 4 修改表4.1 追加一个列4.2 修改一…

shell学习脚本05(小滴课堂)

可以对海量的数据进行提取。 -v对提取的内容进行取反。 -n显示出行号。 -w精确匹配&#xff1a; -i:忽略大小写&#xff1a; -E正则匹配&#xff1a; cut命令&#xff1a; -d指定分隔符&#xff0c;-f指定截取区域&#xff1a; 截取第一列到第三列&#xff1a; 截取第二列到最…

在Python中添加Selenium Web Driver等待

本文将介绍在Python中在Selenium Web驱动程序中添加等待的示例。 Python Selenium Web 驱动程序等待 大多数 Web 应用程序都使用 AJAX 技术。 因此&#xff0c;网页上存在的不同元素需要不同的时间间隔才能完全上传&#xff0c;因为硒在网页上存在之前无法找到任何文本。 我们…

系列六、Mybatis的一级缓存

一、概述 Mybatis一级缓存的作用域是同一个SqlSession&#xff0c;在同一个SqlSession中执行两次相同的查询&#xff0c;第一次执行完毕后&#xff0c;Mybatis会将查询到的数据缓存起来&#xff08;缓存到内存中&#xff09;&#xff0c; 第二次执行相同的查询时&#xff0c;会…

【ML】分类问题

分类问题 classification&#xff1a;根据已知样本特征&#xff0c;判断输入样本属于哪种已知样本类。 常用入门案例&#xff1a;垃圾邮件检测、图像分类、手写数字识别、考试通过预测。 分类问题和回归问题的明显区别&#xff1a; 分类问题的结果是非连续型标签&#xff0c…