Com

一、UART

  1. 简介

    可以使用UART协议来发出和接收RS232 / RS485帧。

    有一个没有奇偶校验并拥有一个停止位的8位帧的例子:

    uart

  2. 总线定义

    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)
        }
    }
    
  3. 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设备

  1. 简介

    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

  2. 架构(Architecture)

    控制器由以下部分组成:

    • 少数控制寄存器

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

    每个端点的描述符链表是为了处理USB的出入任务和数据。

    端点0也会像其他端点一样处理出入USB的交换任务但也会有一些额外的硬件来处理SETUP(设置)任务:

    • 它的链表在每个设置任务上被清除

    • 设置任务的数据存储在一个固定的位置(SETUP_DATA)

    • 它对设置任务有一个特定的中断标志

  3. 寄存器(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任务时, 该任务的数据将存储在该位置

  1. 描述符(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.

  1. 使用(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())
    }