USB设备

SpinalHDL库中存在一个USB设备控制器。

用几个要点总结支持的功能:

  • 实现了允许CPU配置和管理端点

  • 存储端点状态和事务描述符的内部RAM

  • 多达6个端点(几乎没有额外开销)

  • 支持全速USB主机(12Mbps)

  • 在Linux上使用自己的驱动程序进行测试(https://github.com/SpinalHDL/linux/blob/dev/drivers/usb/gadget/udc/spinal_udc.c)

  • 用于配置的Bmb内存接口

  • 内部物理层需要一个时钟,该时钟需为12 Mhz的倍数,至少48 Mhz

  • 控制器频率不受限制

  • 无需外部物理层

Linux小工具经过测试且功能正常:

  • 串行连接

  • 以太网连接

  • 大容量存储(ArtyA7 Linux上约为8 Mbps)

部署:

架构

控制器由以下部分组成:

  • 一小部分控制寄存器

  • 一个用于存储端点状态、传输描述符和端点0配置数据的内部RAM。

每个端点的描述符链表时用于处理USB出入(IN/OUT)事务和数据。

端点0也像所有其他端点一样管理出入USB的传输事务,但也会有一些额外的硬件来管理设置(SETUP)事务:

  • 它的链表在每次设置事务时都会被清除

  • 来自设置(SETUP)事务的数据存储在固定位置(SETUP_DATA)

  • 它有一个用于设置(SETUP)事务的特定中断标志

寄存器

请注意,控制器的所有寄存器和存储器只能以32位字的访问方式进行访问,不支持字节访问。

帧FRAME (0xFF00)

名称

类型

描述

usbFrameId

RO

31-0

当前USB帧的ID

地址ADDRESS (0xFF04)

名称

类型

描述

address

WO

6-0

设备将仅侦听具有指定地址的令牌,该字段在USB复位事件发生时自动清除

enable

WO

8

如果置1,启用USB地址过滤

trigger

WO

9

在下一个EP0 IN令牌完成时置位enable(见上文),在任何EP0完成后由硬件清零

这里的想法是在EP0上收到USB SET_ADDRESS的设置(setup)数据包前,保持整个寄存器清零。此时,您可以设置地址和触发字段,然后向EP0提供IN零长度描述符以完成SET_ADDRESS序列。控制器将在该描述符完成时自动打开地址过滤。

中断INTERRUPT (0xFF08)

该寄存器的每个位都可以通过写入 ‘1’ 来清除。读取该寄存器将返回当前中断状态。

名称

类型

描述

endpoints

W1C

15-0

当端点产生中断时拉高

reset

W1C

16

当USB复位发生时拉高

ep0Setup

W1C

17

当端点0收到设置请求时拉高

suspend

W1C

18

当发生USB挂起时拉高

resume

W1C

19

当USB发生恢复时拉高

disconnect

W1C

20

当USB断开连接时拉高

暂停HALT (0xFF0C)

该寄存器允许将单个端点置于休眠状态,以确保CPU操作的原子性,从而允许在端点寄存器和描述符上执行读/修改/写操作。如果USB主机在暂停启用且端点启用的情况下寻址给定端点,那么外设将返回NAK。

名称

类型

描述

endpointId

WO

3-0

您想要进入休眠状态的目标端点

enable

WO

4

当设置暂停时为活动状态,当清除时端点解除暂停。

effective enable

RO

5

设置使能后,需要等待硬件本身设置该位,以保证原子性

配置CONFIG (0xFF10)

名称

类型

描述

pullupSet

SO

0

写入 ‘1’ 以使能dp引脚上的USB设备上拉

pullupClear

SO

1

interruptEnableSet

SO

2

写入 ‘1’ 让当前和未来的中断发生

interruptEnableClear

SO

3

信息INFO (0xFF20)

名称

类型

描述

ramSize

RO

3-0

内部RAM将有 (1 << this) 字节

端点ENDPOINTS (0x0000 - 0x003F)

端点状态存储在内部RAM的开头,每个端点状态有一个32位字。

名称

类型

描述

enable

RW

0

如果不设置,端点将忽略所有流量

stall

RW

1

如果设置,端点将始终返回STALL状态

nack

RW

2

如果设置,端点将始终返回NACK状态

dataPhase

RW

3

指定使用的出入数据PID。 ‘0’ => DATA0。该字段也由控制器更新。

head

RW

15-4

指定当前描述符头(链表)。0 => 空列表,字节地址 = this << 4

isochronous

RW

16

maxPacketSize

RW

31-22

要获得端点响应,您需要:

  • 将其使能标志设置为1

那么有几种情况: -要么设置了stall或nack标志,所以控制器将始终响应相应的响应 -要么,对于EP0设置请求,控制器不会使用描述符,而是会将数据写入SETUP_DATA寄存器和ACK -要么你有一个空链表 (head==0),在这种情况下它将响应NACK -要么你至少有一个由head指向的描述符,在这种情况下,它将执行该描述符,并在一切顺利时进行ACK

设置数据SETUP_DATA (0x0040 - 0x0047)

当端点0接收到SETUP事务时,该事务的数据将存储在该位置。

描述符

描述符允许指定一个端点需要如何处理出入(IN/OUT)事务的数据阶段。它们存储在内部 RAM 中,可以通过链表链接在一起,并且需要在16字节边界上对齐

名称

描述

offset

0

15-0

指定当前传输进度(以字节为单位)

code

0

19-16

0xF => 进行中,0x0 => 成功

next

1

15-4

指向下一个描述符 0 => 无,字节地址 = this << 4

length

1

31-16

分配给数据字段的字节数

方向

2

16

‘0’ => 输出,’1’ => 输入

interrupt

2

17

如果置位,描述符完成时将产生中断。

completionOnFull

2

18

通常,描述符补全只会在USB传输小于maxPacketSize时发生。但如果置位了该字段,那么当描述符被填满时也被视为事件已完成。(offset == length)

data1OnCompletion

2

19

描述符完成时强制端点dataPhase为DATA1

data

请注意,如果控制器接收到IN/OUT与描述符IN/OUT不匹配的帧,那么该帧将被忽略。

此外,要初始化描述符,CPU应将代码字段设置为0xF

用法

import spinal.core._
import spinal.core.sim._
import spinal.lib.bus.bmb.BmbParameter
import spinal.lib.com.usb.phy.UsbDevicePhyNative
import spinal.lib.com.usb.sim.UsbLsFsPhyAbstractIoAgent
import spinal.lib.com.usb.udc.{UsbDeviceCtrl, UsbDeviceCtrlParameter}


case class UsbDeviceTop() extends Component {
  val ctrlCd = ClockDomain.external("ctrlCd", frequency = FixedFrequency(100 MHz))
  val phyCd = ClockDomain.external("phyCd", frequency = FixedFrequency(48 MHz))

  val ctrl = ctrlCd on new UsbDeviceCtrl(
    p = UsbDeviceCtrlParameter(
      addressWidth = 14
    ),
    bmbParameter = BmbParameter(
      addressWidth = UsbDeviceCtrl.ctrlAddressWidth,
      dataWidth = 32,
      sourceWidth = 0,
      contextWidth = 0,
      lengthWidth = 2
    )
  )

  val phy = phyCd on new UsbDevicePhyNative(sim = true)
  ctrl.io.phy.cc(ctrlCd, phyCd) <> phy.io.ctrl

  val bmb = ctrl.io.ctrl.toIo()
  val usb = phy.io.usb.toIo()
  val power = phy.io.power.toIo()
  val pullup = phy.io.pullup.toIo()
  val interrupts = ctrl.io.interrupt.toIo()
}


object UsbDeviceGen extends App{
  SpinalVerilog(new UsbDeviceTop())
}