Com
一、UART
简介
可以使用UART协议来发出和接收RS232 / RS485帧。
有一个没有奇偶校验并拥有一个停止位的8位帧的例子:
总线定义
case class Uart() extends Bundle with IMasterSlave { val txd = Bool() // Used to emit frames val rxd = Bool() // Used to receive frames override def asMaster(): Unit = { out(txd) in(rxd) } }
UartCtrl
库中实现了一个Uart控制器。这个控制器的特性是使用一个采样窗口来读取
rxd
引脚, 然后使用多数投票制来过滤它的值。| IO口名 | 方向 | 类型 | 描述 | | :—-: | :—-: | :————: | :———————————————–: | | config | in | UartCtrlConfig | 用于设置控制器的时钟分频器/奇偶校验/停止/数据长度 | | write | slave | Stream[Bits] | 用于请求进行帧交换传输的流端口 | | read | master | Flow[Bits] | 用于接收解码帧的流端口 | | write | master | Uart | 与实际实现的连接接口 |
控制器可以通过一个
UartCtrlGenerics
配置对象实例化:
属性 | 类型 | 描述 |
---|---|---|
dataWidthMax | Int | 帧内的最大位数 |
clockDividerWidth | Int | 内部时钟分频器的位宽 |
preSamplingSize | Int | 指定有多少samplingTick在一个UART波特的起始处下降 |
samplingSize | Int | 指定有多少samplingTick用于采样UART波特中段的rxd 值 |
postSamplingSize | Int | 指定在一个UART波特值的末尾有多少个samplingTick被丢弃 |
二、USB设备
简介
SpinalHDL库中有一个USB设备控制器。在以下几个要点中, 它可以设置为:
它允许CPU配置和管理端点
存储端点状态和事务描述符的内部RAM
多达16个端点(几乎没有额外开销)
支持USB主机全速运行(12Mbps)
在linux上使用自己的驱动程序进行测试(https://github.com/SpinalHDL/linux/blob/dev/drivers/usb/gadget/udc/spinal_udc.c)
用于配置的Bmb内存接口
内部需要一个频率是12Mhz的倍数的时钟, 至少为48Mhz
控制器频率不受限制
不需要外部phy
Linux小工具测试和功能:
串行连接
以太网连接
大容量存储(在ArtyA7 linux上达到8mbps)
部署:
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
架构(Architecture)
控制器由以下部分组成:
少数控制寄存器
一个用来存储端点状态的内部RAM, 一个传输描述符合端点0配置数据。
每个端点的描述符链表是为了处理USB的出入任务和数据。
端点0也会像其他端点一样处理出入USB的交换任务但也会有一些额外的硬件来处理SETUP(设置)任务:
它的链表在每个设置任务上被清除
设置任务的数据存储在一个固定的位置(SETUP_DATA)
它对设置任务有一个特定的中断标志
寄存器(Registers)
注意, 控制器的所有寄存器和内存只能以32位的字访问, 不支持字节访问。
帧FRAME (0xFF00)
名称 | 类型 | 比特 | 描述 |
---|---|---|---|
usbFrameId | RO | 31-0 | 目前的usb帧的id |
地址ADDRESS (0xFF04)
名称 | 类型 | 比特 | 描述 |
---|---|---|---|
address | WO | 6-0 | 该USB设备只会被特定地址的令牌(token)所控制。其字段会被usb重置任务自动清除 |
enable | WO | 8 | 如果设置, 启用USB地址过滤 |
trigger | WO | 9 | 设置下一个EP0 IN令牌使能(见上文)。在任何EP0完成后由硬件清除 |
这里的想法是保持整个寄存器清空, 直到EP0上接收到USB SET_ADDRESS设置包。此时, 用户可以设置地址和触发器字段, 然后向EP0提供IN零长度描述符, 以结束SET_ADDRESS序列。然后, 控制器将在描述符完成时自动打开地址过滤。
中断INTERRUPT (0xFF08)
这个寄存器的所有位都可以通过写入“1”来清除。
名称 | 类型 | 比特 | 描述 |
---|---|---|---|
endpoints | RC | 15-0 | 当端点产生中断时拉高 |
reset | RC | 16 | 当USB复位发生时拉高 |
ep0Setup | RC | 17 | 当端点0收到配置请求时拉高 |
suspend | RC | 18 | 当端点悬挂时拉高 |
resume | RC | 19 | 当端点恢复时拉高 |
disconnect | RC | 20 | 当端点连接中断时拉高 |
- 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 | 若不设置, 则该端点忽略所有的流量(traffic) |
stall | RW | 1 | 若设置了, 端点将始终返回STALL状态 |
nack | RW | 2 | 若设置了, 端点将始终返回NACK状态 |
dataPhase | RW | 3 | 指定使用的IO数据PID。' 0 ' = > DATA0。这个字段也由控制器更新。 |
head | RW | 15-4 | 指定当前描述符头部(链表)0 => empty list, byte address = this << 4 |
isochronous | RW | 16 | 指定当前描述符头部(链表)0 => empty list, byte address = this << 4 |
maxPacketSize | RW | 31-22 |
为了获得一个端点响应需要:设置其使能标志位为1.
还有其他一些例子:要么用户有停滞或纳标记集,因此,控制器总是有相应的响应;要么EP0设置请求,控制器不会使用描述符,但将数据写入SETUP_DATA寄存器和ACK;要么用户有一个空链表(head==0)并响应NACK;要么用户至少有一个描述符由头部指出,在这种情况下将执行和ACK。
SETUP_DATA (0x0040 - 0x0047)
当端点0接收SETUP任务时, 该任务的数据将存储在该位置
描述符(Descriptors)
描述符允许指定端点需要如何处理IO传输任务的数据阶段。它们存储在内部ram中, 可以通过它们的链表链接在一起, 并且需要在16字节的边界上对齐.
名称 | 字 | 比特 | 述 |
---|---|---|---|
offset | 0 | 15-0 | 指定当前传输进度(以字节为位) |
code | 0 | 19-16 | 0xF => in progress, 0x0 =>success |
next | 1 | 15-4 | 指定下一个传输符 0 => nothing, byte address = this <<4 |
length | 1 | 31-16 | 为数据字段分配的字数 |
direction | 2 | 16 | ‘0’ => OUT, ‘1’ =>IN |
interrupt | 2 | 17 | 如果设置了, 则描述符的完成将生成一个断。 |
completionOnFull | 2 | 18 | 通常, 描述符补全只发生在USB传输小于maxPacketSize时。但是如果设置了这个字段, 那么当描述符被填满时也被认为事件已完成(抵消= =长度) |
data1OnCompletion | 2 | 19 | 强制端点dataphaseDATA1 |
data | ... | ... |
注意, 如果控制器接收到一个帧, 其中IO不匹配描述符IO, 该帧将被忽略。
另外, 要初始化描述符, CPU应该将代码字段设置为0xF.
使用(usage)
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()) }