USB是很常用的接口,目前大多数的设备都是USB接口的,比如鼠标、键盘、USB摄像
头等,在实际开发中也常常遇到USB接口的设备,本章就来学习一下如何使能Linux内核自带的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。
如果按照接口类型划分的话USB就要分为很多种了,最常见的就是USB A插头和插座,如下图所示:
使用过JLINK调试器的朋友应该还见过USB B插头和插座,USB B插头和插座如下图所示:
USB插头在不断的缩小,由此产生了Mini USB接口,Mini USB插头和插座如下图所示:
比Mini USB更小的就是Micro USB接口了,以前的智能手机基本都是Micro USB接口的,Micro USB插头和插座如下图所示:
现在最流行的就是USB Typec了,正点原子的STM32MP1开发板使用的是USB Typec接口,USB Typec插头和插座如下图所示:
先以Mini USB为例讲解一下USB的基本电气属性。Mini USB线一般都是一头为USB A插头,一头为Mini USB插头。一共有四个触点,也就是4根线,这四根线的顺序如下图所示:
如上图所示,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是主从结构的,也就是分为主机和从机两部分,一般主机叫做Host,从机叫做Device。主机通过USB A插座来连接外部的设备,比如电脑作为主机,对外提供USB A插座,可以通过USB线来连接一些USB设备,比如声卡、手机等。因此电脑带的USB A插座数量就决定了能外接多少个USB设备,如果不够用的话可以购买USB集线器来扩展电脑的USB插口,USB集线器也叫做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的拓扑结构就是一个分层的金字塔形,如下图所示:
上图中可以看出从Root Hub开始,一共有7层,金字塔顶部是Root Hub,这个是USB控制器内部的。图中的Hub就是连接的USB集线器,Func就是具体的USB设备。
USB主机和从机之间的通信通过管道(Pipe)来完成,管道是一个逻辑概念,任何一个USB设备一旦上电就会存在一个管道,也就是默认管道,USB主机通过管道来获取从机的描述符、配置等信息。在主机端管道其实就是一组缓冲区,用来存放主机数据,在设备端管道对应一个特定的端点。
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模式:
支持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线一头是USB A插座,一头是Typec插头,将TypeC插头插入机器的TypeC口上,需要连接的USB设备插到另一端的USB A插座上,比如U盘。TypeC的CC引脚检查到USB设备已经接入,就会建立USB设备为主模式,机器就知道自己要做为一个主机,用来连接外部的从机设备(U 盘)。
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(英语:Port Physical Layer),中文可称之为端口物理层,是一个对OSI模型物理层的共同简称。PHY控制器主要是提供两个端口,端口1已经规定分配给USB Host控制器,端口2可以分配给USB OTG和USB Host。
此控制器是支持OTG功能,它的内部特性如下所示:
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控制器这是一个主机控制器,此控制器由EHCI控制器和OHCI控制器组成。USBH控制器只能做主机模式。
这里简单提一下OHCI、UHCI、EHCI和xHCI,这四个是用来描述USB控制器规格
的,区别如下:
通俗来讲,OHCI就是FS模式,也就是低速模式,EHCI是HS模式,也就是高速模式。
STM32MP157有两个USB2接口,所以可以直接使用Mini USB或者Micro USB。但是目前USB TypeC接口非常普及,为了方便使用,正点原子STM32MP157开发板上的USB接口采用了TypeC。
接下来就看一下USB TypeC接口(根据USB协议,也叫做USB3.1接口)电气属性,由于TypeC功能比较复杂,比如支持PD充电、显示、音频等,这里只讲解一下TypeC的数据
通信部分。首先来看一下TypeC的接口定义,这里只看母头的引脚定义,如下图所示:
从上图中可以看出,标准的USB TypeC有24根线,分为上下两部分,明显比前面讲的Mini USB要多不少引脚,这些引脚按照功能分类:
仔细看上图可看出,上下两排引脚是对应的,比如(A1,B1)、(A2,B2)等,一直到(A12,B12)。这是因为USB3.1要有能够正反插,因此上下两排引脚肯定要是一样的,这样才能在正反插的时候都能正常工作。
上图是TypeC接口标准的24P母头引脚,在进行TypeC母头选型的时候会发现也有16P甚至6P封装的接口。比如正点原子STM32MP157开发板上使用的就是16P的TypeC母座,16P 的母座引脚结构如下图所示:
上图就是16P的TypeC引脚示意图,从左到右依次是1-16 脚,注意1,2 脚、3,4 脚、13,14 脚和15,16脚离得很近,看起来像是一个引脚,其实他们是两个引脚。16P对应的引脚功能如下图所示:
可以看出,相比于标准24P引脚定义,16P的母座少了8根USB3.1高速差分收发数据线。
USB2.0根据数据传输方向定义了HOST/Device/OTG这三种类型的设备角色,其中OTG既可以做HOST也可以做Device,USB2.0的OTG设备接口通过ID线来区分HOST还是Device。在TypeC领域,也有类似的概念,但是名字变了:
正点原子的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原理图,STM32MP1使用FE2.1这个HUB芯片将STM32MP1的USB2扩展成了7路HOST接口,其中一路供4G模块使用,因此就剩下了6个通用的USB A插座,原理图如下图所示:
上图中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接口如下图所示:
注意,使用FE2.1扩展出来的7路USB接口只能用作HOST!
正点原子的STM32MP1开发板上还有一路USB OTG接口,使用STM32MP1的USB OTG接口。此路USB OTG既可以作为主机(HOST),也可以作为从机(DEVICE),从而实现完整的OTG功能。V1.4版本以前的底板上TypeC芯片使用STUSB1600,V1.5及以后版本的底板使用FUSB302MPX这颗芯片。
现在购买的话都是V1.5以后的版本了,开发板的USB OTG是使用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设备使用描述符来向主机报告自己的相关属性信息,常用的描述符如下图所示:
依次来看一下上图中这5个描述符的含义。
设备描述符用于描述USB设备的一般信息,USB设备只有一个设备描述符。设备描述符里面记录了设备的USB版本号、设备类型、VID(厂商 ID)、PID(产品 ID)、设备序列号等。设备描述符结构如下图所示:
设备描述符的bNumConfigurations域定义了一个USB设备的配置描述符数量,一个USB设备至少有一个配置描述符。配置描述符描述了设备可提供的接口(Interface)数量、配置编号、供电信息等,配置描述符结构如下图所示:
字符串描述符是可选的,字符串描述符用于描述一些方便阅读的信息,比如制造商、设备名称等。如果一个设备没有字符串描述符,那么其他描述符中和字符串有关的索引值都必须为0,字符串描述符结构如下图所示:
wLANGID[0]~wLANGID[x]指明了设备支持的语言 , 具体含义要查阅文档《USB_LANGIDs.pdf》。
主机会再次根据自己所需的语言向设备请求字符串描述符,这次主机会指明要得到的字符串索引值和语言。设备返回Unicode编码的字符串描述符,结构如下图所示:
配置描述符中指定了该配置下的接口数量,配置可以提供一个或多个接口,接口描述符用于描述接口属性。接口描述符中一般记录接口编号、接口对应的端点数量、接口所述的类等,接口描述符结构如下图所示:
接口描述符定义了其端点数量,端点是设备与主机之间进行数据传输的逻辑接口,除了端点0是双向端口,其他的端口都是单向的。端点描述符描述了树传输类型、方向、数据包大小、端点号等信息,端点描述符结构如下图所示:
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令牌包结构,首先是SYNC同步域,包同步域为00000001,也就是连续7个0,后面跟一个1,如果是高速设备的话就是31个0后面跟一个1。紧跟着是PID,这里是SETUP包,为0XB4,是0XB4的原因如下:
PID后面跟着地址域(ADDR)和端点(ENDP),为目标设备的地址和端点号。CRC5域是5位CRC值,是ADDR和ENDP这两个域的校验值。最后就是包结束域(EOP),标记本数据包结束。其他令牌包的结构和SETUP基本类似,只是SOF令包中间没有ADDR和ENDP这两个域,而是只有一个11位的帧号域。
数据包结构如下图所示:
数据包比较简单,同样的,数据包从SYNC同步域开始,然后紧跟着是PID,这里就是DATA0,PID值为0XC3。接下来就是具体的数据,数据完了以后就是16位的CRC校验值,最后是EOP。
握手包结构如下图所示:
上图是ACK握手包,很简单,首先是SYNC同步域,然后就是ACK包的PID,为0X4B,最后就是EOP。其他的NAK、STALL、NYET和ERR握手包结构都是一样的,只是其中的PID不同而已。
在端点描述符中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令牌包。
就以批量写为例简单介绍一下批量传输过程:
批量读的过程刚好相反:
这里的中断传输并不是传统意义上的硬件中断,而是一种保持一定频率的传输,中断传输适用于传输数据量小、具有周期性并且要求响应速度快的数据,比如键盘、鼠标等。中断的端点会在端点描述符中报告自己的查询时间间隔,对于时间要求严格的设备可以采用中断传输。
当USB设备与USB主机连接以后主机就会对USB设备进行枚举,通过枚举来获取设备的
描述符信息,主机得到这些信息以后就知道该加载什么样的驱动、如何进行通信等。USB 枚举过程如下:
接下来就开始编写USB HOST的驱动。USB子系统是一个标准和复杂的接口,所以驱动基本不用写,都是内核里有现成的,只需要在设备树提供对应的设备节点即可。
之前的学习有说到USBH是只能主机模式,要编写USB HOST就用USBH控制器即可。ST官方的STM3MP157C-DK2开发板已经配置好了USBH的节点信息,所以直接参考此节点即可,这样开发板就能够使用 USB Host 模式。
打开“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控制器,一些通用配置,打开 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 = <®11>;
10 vdda1v8-supply = <®18>;
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节点里。
最后在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设备树重新启动开发板。就会有以下输出所示:
如果有上图这些输出信息,说明usb host驱动加载到内核了。
首先做一下USB HOST试验,也就是正点原子的STM32MP1开发板做USB主机,然后外接USB设备,比如USB鼠标键盘、USB 转TTL串口线、U盘等设备。Linux内核已经集成了大量的USB设备驱动,尤其是常见的USB鼠标键盘、U盘等,本节就来学习一下如何使能Linux内核常见的USB设备驱动。
USB鼠标键盘属于HID设备,内核已经集成了相应的驱动,ST官方提供的linux内核默认已经使能了USB鼠标键盘驱动,但是还要学习一下如何手动使能这些驱动。输入“make
menuconfig”,打开linux内核配置界面,首先打开HID驱动,按照如下路径到相应的配置项目:
-> Device Drivers -> HID support -> HID bus support -> <*> Generic HID driver //使能通用 HID 驱动 |
使能后结果如下图所示:
接下来需要使能USB键盘和鼠标驱动,配置路径如下:
-> Device Drivers -> HID support -> USB HID support -> <*> USB HID transport layer //USB 键盘鼠标等 HID 设备驱动 |
使能后如下图所示:
可以将光标放到上图中“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”这两个配置项的时候也不要觉得教程这里写错了。
完成以后重新编译linux内核并且使用得到的 uImage启动开发板。启动以后插入USB鼠标,会有如下图所示的提示信息:
从上图可以看出,系统检测到了鼠标,如果成功驱动的话就会在/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.”这样提示,如下图所示:
接上键盘,然后根据上图中的提示,按下键盘上的Enter(回车)键即可使能LCD屏幕控制台,然后就可以输入各种命令来执行相应的操作,如下图所示:
ST提供的Linux内核默认也已经使能了U盘驱动,因此可以直接插上去使用。但是还是需要学习一下如何手动配置Linux内核,使能U盘驱动。
U盘使用SCSI协议,因此要先使能Linux内核中的SCSI协议,配置路径如下:
-> Device Drivers -> SCSI device support -> <*> SCSI disk support //选中此选项 |
结果如下图所示:
还需要使能USB Mass Storage,也就是USB 接口的大容量存储设备,配置路径如下:
-> Device Drivers -> USB support -> USB Gadget Support -> USB Gadget functions configurable through configfs -> [*] Mass storage //选中 |
结果如下图所示:
准备好一个U盘,注意U盘要为FAT32格式的!NTFS和exFAT由于版权问题所以在Linux下支持的不完善,操作的话可能会有问题,比如只能读,不能写或者无法识别等。准备好以后将U盘插入到开发板USB HUB扩展出来的HOST接口上,此时会输出如下图所示信息:
从上图可以看出,系统检测到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盘就能正常读写操作了,直接对/mnt/usb_disk目录进行操作就行了。如果要拔出U盘要执行一个sync命令进行同步,然后在使用unmount进行U盘卸载,命令如下所示:
sync //同步 cd / //如果处于/mnt/usb_disk 目录的话先退出来,否则卸载的时候提示设//备忙,导致卸载失败,切记! umount /mnt/usb_disk //卸载 |
STUSB1600是ST出的一款TypeC 芯片,也是ST官方开发板搭配STM32MP1的。STUSB1600的驱动设备树编写参考文档“Documentation/devicetree/bindings/usb/st,typec-stusb.txt”。此文档描述了STUSB1600 设备相关信息。
进入到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。
首先先去配置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 };
控制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中断引脚为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 文件去启动开发板。
在上一小节已经完成了一部分的设备树配置,由于只是改了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”,如下图所示:
接着配置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覆盖掉,拷贝结果如下图所示:
拷贝完成后,进入内核的配置选项图形界面,配置路径如下:
-> Device Drivers -> USB support -> USB Type-C Support -> USB Type-C Port Controller Manager -> <*> Fairchild FUSB302 Type-C chip driver //选中 |
如下图所示:
重新编译设备树和内核,使用新的设备树stm32mp157d-atk.dtb和内核uImage去启动开发
板,有如下图打印信息,说明加载驱动成功。如图所示:
系统重启成功以后就可以正常使用USB_OTG接口,OTG既可以做主机,也可以做从机,做主机的话测试方法和之前的测试一模一样,直接在正点原子的USB_OTG接口上使用Typec OTG线接入鼠标键盘、U盘等设备。USB_OTG接口如下所示:
OTG从机就是将开发板作为一个USB设备连接到其他的主机上,这里来做两个USB从机实验:模拟U盘以及USB声卡。
模拟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 //大容量存储 |
结果如下图所示:
这里需要将驱动编译为模块!使用的时候直接输入命令加载驱动模块即可。配置好
以后执行下面这些命令重新编译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接口的外置声卡,一般电脑内部都自带了声卡,但是内部自带的声卡效果相对来说比较差,不能满足很多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 |
配置如下图所示:
注意,这里也是编译为驱动模块,配置完成以后重新编译内核、编译模块,会得到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”设备,如下图所示:
上图中的“AC Interface”就是开发板模拟出来的USB声卡,设置Windows,选择音频输出使用“AC Interface”,Windows10设置如下图所示:
一切设置好以后就可以从开发板上听到电脑输出的声音,此时开发板就完全是一个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就可以了。