USB设备
SpinalHDL库中存在一个USB设备控制器。
A few bullet points to summarize support:
实现了允许CPU配置和管理端点
存储端点状态和事务描述符的内部RAM
Up to 16 endpoints (for virtually no price)
Support USB host full speed (12 Mbps)
在Linux上使用自己的驱动程序进行测试(https://github.com/SpinalHDL/linux/blob/dev/drivers/usb/gadget/udc/spinal_udc.c)
Bmb memory interface for the configuration
内部物理层需要一个时钟,该时钟需为12 Mhz的倍数,至少48 Mhz
控制器频率不受限制
无需外部物理层
Linux小工具经过测试且功能正常:
串行连接
以太网连接
大容量存储(ArtyA7 Linux上约为8 Mbps)
部署:
https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/digilent/ArtyA7SmpLinux
https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/radiona/ulx3s/smp
架构
控制器由以下部分组成:
一小部分控制寄存器
一个用于存储端点状态、传输描述符和端点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 |
The device will only listen at tokens with the specified address This field is automatically cleared on usb reset events |
enable |
WO |
8 |
如果置1,启用USB地址过滤 |
trigger |
WO |
9 |
Set the enable (see above) on the next EP0 IN token completion Cleared by the hardware after any EP0 completion |
The idea here is to keep the whole register cleared until a USB SET_ADDRESS setup packet is received on EP0. At that moment, you can set the address and the trigger field, then provide the IN zero length descriptor to EP0 to finalize the SET_ADDRESS sequence. The controller will then automatically turn on the address filtering at the completion of that descriptor.
中断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)
The endpoints status are stored at the beginning of the internal ram over one 32 bits word each.
名称 |
类型 |
位 |
描述 |
---|---|---|---|
enable |
RW |
0 |
If not set, the endpoint will ignore all the traffic |
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不匹配的帧,那么该帧将被忽略。
Also, to initialize a descriptor, the CPU should set the code field to 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())
}