CH582 USB Device CherryUSB

CH582是一款优秀的芯片,它有着极高的性价比,拥有两个USB(HD)和BLE5,本偏文章将介绍如何将CH582的USB移植到一个优秀的开源项目:CherryUSB。

第一步:需要了解CH582的USB外设,我们直接上寄存器文档,了解CH582的USB寄存器即可。其实CH571,CH572,CH573,CH581,CH582,CH583用的基本是一个USBIP,CH579用的是另一个USBIP,但是它们都很相似。CH571,CH572,CH573与CH581,CH582,CH583的USBIP差别在于端点数目,前三者5个端点,后三者是8个端点,其余都是一样的。CH579的USBIP在中断处理上有不一样的地方,这个我们后面再说。

 我们先看全局寄存器。

1、USB控制寄存器(R8_USB_CTRL)

 

这个寄存器我们主要关注红色框框出来的位,文档里面将这些位的功能都有了比较详细的描述,这里我们就解释一下RB_UC_INT_BUSY 这个bit,使能这个bit以后,如果传输完成中断标志未清0,设备对主机会自动回应NAK,表示繁忙。如果不使能这个bit,例如我们中断服务函数中将ep0的发送端点设置为ACK,表示主机再向控制端点发送in包的时候,数据会被主机拿走,但是这个时候还没退出中断服务函数,传输完成中断标志未清0,其实是在忙的状态,理应该回复主机NAK,使能这个bit,在此种情况就可以自动向主机回复NAK。

2、中断使能寄存器

中断使能寄存器,顾名思义,就是控制开启哪些中断,我们主要关注红框框出来的几个bit,分别问 SUSPEND TRANSFER BUS_RST ,协议栈设计的是传输完成中断,只有ACK了才进中断,所有NAK中断不需要,SOF(起始帧中断)也不需要(可以开但是用不到)。

3、设备地址寄存器

这个寄存器存放的是设备的地址,在USB总线复位的时候,这个寄存器需要被清0,因为主机在枚举初期会以0地址来和设备通讯,在设备接收到设置地址(set_address)命令的时候,我们不能立马把地址填写到这个寄存器,因为那个时候控制传输还没完成(set_address : [setup]  --  [nodata] -- [status phase:in]),控制传输分为三个阶段,建立阶段,数据阶段(可有可无),状态阶段。设置地址这条下发的时候,地址是被放在setup包里面一起下发给设备的,主机还会下发一个in包(状态阶段 以0地址下发)来表示控制传输完成,所以我们需要在状态阶段以后,再将地址写入到设备地址寄存器中,下一次主机与设备通讯的时候就会用新地址了。

 4、杂项状态寄存器

 这个寄存器没啥好说的,值需要关注bit2,用来判断一下是挂起还是唤醒

5、中断标志寄存器

 我们只需要关注红框标记出来的bit,因为前文我们已经说到了,设计的时候只开了三个中断。

6、中断状态寄存器

这个寄存器的每一个bit我们都需要关注,文档中都已经解释的很清楚了。

文档中这段话解释了这个USBIP不太常规的地方,在设计USB中断函数的时候要注意以下描述的内内容,这个需要结合代码,后面再说。

 7、接收长度寄存器

这个寄存器就是存放的当前传输端点接收数据的长度,但是当EP0收到setup包的时候,这个寄存器不会是8字节,前文提到过setup令牌不会影响到这个寄存器。

8、设备物理端口控制寄存器

我们需要关注红框框出来的bit,禁用下拉和使能物理端口。

9、端点模式控制寄存器((R8_UEP4_1_MOD为例)和缓冲区模式

上图上半部分是使能端点发送和接收的一些控制,下半部分描述了一些比较奇怪的东西,不知道为什么这个IP要设计成这样,端点4和端点0的DMA缓冲区有关联,也就是配置了端点0的dma缓冲区地址就会关联性的配置端点4的dma缓冲区地址。搞不懂为什么要这样设计。结合其他端点缓冲区描述一起看一下,如下图:

本身是可以直接将用户的数据buffer地址直接给到dma地址寄存器的,这样能比较好的发挥dma的性能,如果端点是单向端点是可以的,因为配置dma地址的时候只有一个:R16_UEPn_DMA寄存器

没有去区分是in还是out,这个IP设计的如果端点是双向端点,那么out端点的dma地址就是R16_UEPn_DMA的值,但是in端点的dma地址就是R16_UEPn_DMA+64。如果开启双向端点就不能直接指定dma地址为用户buffer地址了,加上本身端点就不多,我们就设计成对所有端点默认开启双向,然后静态分配一块内存供协议栈来中转,将用户的buffer复制到这块内存,dma地址寄存器不去修改(初始化的时候固定为静态分配的这块内存的地址,注意没有用到双缓冲模式)。

10、端点发送长度寄存器

 端点发送数据的时候,此寄存器需要填写待发送数据的长度。

11、端点控制寄存器

这些bit我们都需要注意,着重注意一下红色框框出来的bit,因为在设计端点发送这个函数的时候,同步端点与其他端点有点差别,其实就是同步端点发送的时候不需要期待主机ACK,如果将其当作普通端点来处理,会出现无法进入发送成功中断的情况,这个IP还有一个奇怪的地方,端点0和端点4不支持同步触发位的自动翻转,但是其他端点支持。所以我们在设计中断服务函数的时候要注意发送成功以后需要对0和4端点进行手动的同步触发位翻转。

看到这里,设备寄存器也就基本没了,(沁恒这个IP用起来还是挺方便的,毕竟寄存器比较简单),ch582有两个usb,考虑到官方没有将寄存器写成结构体表的形式,对于切换usb外设来说不是很方便,这里根据文档将寄存器表写出来,如下:

typedef struct
{
    __IO uint8_t USB_CTRL; /*!< 0x40008000 */
    union {
        __IO uint8_t UDEV_CTRL;  /*!< 0x40008001 */
        __IO uint8_t UHOST_CTRL; /*!< 0x40008001 */
    };
    __IO uint8_t USB_INT_EN;  /*!< 0x40008002 */
    __IO uint8_t USB_DEV_AD;  /*!< 0x40008003 */
    __IO uint8_t USB_STATUS0; /*!< 0x40008004 */
    __IO uint8_t USB_MIS_ST;  /*!< 0x40008005 */
    __IO uint8_t USB_INT_FG;  /*!< 0x40008006 */
    __IO uint8_t USB_INT_ST;  /*!< 0x40008007 */
    __IO uint8_t USB_RX_LEN;  /*!< 0x40008008 */
    __IO uint8_t Reserve1;    /*!< 0x40008009 */
    __IO uint8_t Reserve2;    /*!< 0x4000800a */
    __IO uint8_t Reserve3;    /*!< 0x4000800b */
    __IO uint8_t UEP4_1_MOD;  /*!< 0x4000800c */
    union {
        __IO uint8_t UEP2_3_MOD; /*!< 0x4000800d */
        __IO uint8_t UH_EP_MOD;  /*!< 0x4000800d */
    };
    __IO uint8_t UEP567_MOD; /*!< 0x4000800e */
    __IO uint8_t Reserve4;   /*!< 0x4000800f */
    __IO uint16_t UEP0_DMA;  /*!< 0x40008010 */
    __IO uint16_t Reserve5;  /*!< 0x40008012 */
    __IO uint16_t UEP1_DMA;  /*!< 0x40008014 */
    __IO uint16_t Reserve6;  /*!< 0x40008016 */
    union {
        __IO uint16_t UEP2_DMA;  /*!< 0x40008018 */
        __IO uint16_t UH_RX_DMA; /*!< 0x40008018 */
    };
    __IO uint16_t Reserve7; /*!< 0x4000801a */
    union {
        __IO uint16_t UEP3_DMA;  /*!< 0x4000801c */
        __IO uint16_t UH_TX_DMA; /*!< 0x4000801c */
    };
    __IO uint16_t Reserve8;  /*!< 0x4000801e */
    __IO uint8_t UEP0_T_LEN; /*!< 0x40008020 */
    __IO uint8_t Reserve9;   /*!< 0x40008021 */
    __IO uint8_t UEP0_CTRL;  /*!< 0x40008022 */
    __IO uint8_t Reserve10;  /*!< 0x40008023 */
    __IO uint8_t UEP1_T_LEN; /*!< 0x40008024 */
    __IO uint8_t Reserve11;  /*!< 0x40008025 */
    union {
        __IO uint8_t UEP1_CTRL; /*!< 0x40008026 */
        __IO uint8_t UH_SETUP;  /*!< 0x40008026 */
    };
    __IO uint8_t Reserve12; /*!< 0x40008027 */
    union {
        __IO uint8_t UEP2_T_LEN; /*!< 0x40008028 */
        __IO uint8_t UH_EP_PID;  /*!< 0x40008028 */
    };
    __IO uint8_t Reserve13; /*!< 0x40008029 */
    union {
        __IO uint8_t UEP2_CTRL;  /*!< 0x4000802a */
        __IO uint8_t UH_RX_CTRL; /*!< 0x4000802a */
    };
    __IO uint8_t Reserve14; /*!< 0x4000802b */
    union {
        __IO uint8_t UEP3_T_LEN; /*!< 0x4000802c */
        __IO uint8_t UH_TX_LEN;  /*!< 0x4000802c */
    };
    __IO uint8_t Reserve15; /*!< 0x4000802d */
    union {
        __IO uint8_t UEP3_CTRL;  /*!< 0x4000802e */
        __IO uint8_t UH_TX_CTRL; /*!< 0x4000802e */
    };
    __IO uint8_t Reserve16;     /*!< 0x4000802f */
    __IO uint8_t UEP4_T_LEN;    /*!< 0x40008030 */
    __IO uint8_t Reserve17;     /*!< 0x40008031 */
    __IO uint8_t UEP4_CTRL;     /*!< 0x40008032 */
    __IO uint8_t Reserve18[33]; /*!< 0x40008033 */
    __IO uint16_t UEP5_DMA;     /*!< 0x40008054 */
    __IO uint16_t Reserve19;    /*!< 0x40008056 */
    __IO uint16_t UEP6_DMA;     /*!< 0x40008058 */
    __IO uint16_t Reserve20;    /*!< 0x4000805a */
    __IO uint16_t UEP7_DMA;     /*!< 0x4000805c */
    __IO uint8_t Reserve21[6];  /*!< 0x4000805e */
    __IO uint8_t UEP5_T_LEN;    /*!< 0x40008064 */
    __IO uint8_t Reserve22;     /*!< 0x40008065 */
    __IO uint8_t UEP5_CTRL;     /*!< 0x40008066 */
    __IO uint8_t Reserve23;     /*!< 0x40008067 */
    __IO uint8_t UEP6_T_LEN;    /*!< 0x40008068 */
    __IO uint8_t Reserve24;     /*!< 0x40008069 */
    __IO uint8_t UEP6_CTRL;     /*!< 0x4000806a */
    __IO uint8_t Reserve25;     /*!< 0x4000806b */
    __IO uint8_t UEP7_T_LEN;    /*!< 0x4000806c */
    __IO uint8_t Reserve26;     /*!< 0x4000806d */
    __IO uint8_t UEP7_CTRL;     /*!< 0x4000806e */
} USB_FS_TypeDef;

接下来我们看CherryUSB部分。

第二步:我们需要大致了解CherryUSB的port api,下面列出并且简要介绍每个api需要设计成什么样的作用。

int usb_dc_init(void)

int usb_dc_deinit(void)

int usbd_set_address(const uint8_t addr)

int usbd_ep_open(const struct usbd_endpoint_cfg *ep_cfg)

int usbd_ep_close(const uint8_t ep)

int usbd_ep_start_write(const uint8_t ep, const uint8_t *data, uint32_t data_len)

int usbd_ep_start_read(const uint8_t ep, uint8_t *data, uint32_t data_len)

int usbd_ep_set_stall(const uint8_t ep)

int usbd_ep_clear_stall(const uint8_t ep)

int usbd_ep_is_stalled(const uint8_t ep, uint8_t *stalled)

void USBD_IRQHandler(void)

以上就是我们移植索要编写的所有函数。

1、int usb_dc_init(void)  usb device control init,usb设备控制驱动程序初始化,我们需要在这个函数里面完成usb外设的初始化,中断使能等操作,同时为每个端点的dma静态分配一段内存。

2、int usb_dc_deinit(void),usb设备控制驱动程序去初始化,其实这个函数可写也可不写,写的话就是关闭中断,清除usb控制寄存器等。

3、usbd_set_address(const uint8_t addr),设置地址,这个函数我们设计成若传入的地址不是0,将其保存下来,在设置地址命令的状态阶段完成以后将其填入地址寄存器。

4、int usbd_ep_open(const struct usbd_endpoint_cfg *ep_cfg),这个函数就是打开端点,这里其实我们设计的时候在int usb_dc_init(void)已经将所有端点都打开了,因为考虑到IP有一个dma缓冲区的限制,默认所有端点都打开,且都是双向端点,所以在usbd_ep_open这个函数中我们设计成配置端点的使能标志和最大包长(供协议栈处理用,不涉及硬件配置)。

5、int usbd_ep_close(const uint8_t ep),这个函数就是关闭端点,设计成清除端点的使能标志。

6、int usbd_ep_start_write(const uint8_t ep, const uint8_t *data, uint32_t data_len),端点开始写入数据,设计成不限制传入数据的长度,在中断里面软件分包继续发送。

7、int usbd_ep_start_read(const uint8_t ep, uint8_t *data, uint32_t data_len),端点开始读取,这个函数的意思是准备好接收缓冲区,等待主机发送数据,因为中断out设计的是out完成中断,也就是一旦进入out回调了,那么数据就已经在我们事先准备好的接收缓冲区里面了,跟out中断还是有区别的,out中断是进入out回调后需要用户调用一次ep_read将数据从fifo里面读取出来 ,这个ep_read是在进入回调之后才调用,而out完成是在进入回调之前就要调用ep_start_read来准备好接收缓冲区。

8、int usbd_ep_set_stall(const uint8_t ep),在传输出现错误的时候,我们需要向主机回复stall。

9、int usbd_ep_clear_stall(const uint8_t ep), 这个函数会在端点请求的clear feature中被调用,用于清除端点stall。

10、int usbd_ep_is_stalled(const uint8_t ep, uint8_t *stalled),其实这个函数协议栈里面目前没有用到,直接返回0即可。

11、USBD_IRQHandler(void),usb中断处理函数,后面代码说。

第三步:开始编写porting文件

1、首先定义两个USB外设的基地址,这里默认使用USB0,基地址为0x40008000u

 2、静态分配一些内存,因为要兼容CH571,CH572,CH573,所以默认的端点数目为5个,当然EP_NUMS在CH58x上面可以修改为8。

 3、配合协议栈进行处理的一些变量

 4、usb_dc_init编写

第一部分是将dma缓冲区的地址给到前面ep_info的一个指针,方便后续处理的编写。

第二部分是端点使能以及端点dma地址的配置。

第三部分是将所有端点的状态设置为NAK,并且能开启自动翻转的开启自动翻转,将设备地址复位为0,开启上拉,开启忙自动回复NAK,使能dma。

第四部分根据基地址来判断是哪个USB外设,复用对应的USB数据线引脚,清除中断flag,使能硬件端口,开启前文提到的三个中断。最后一个 usb_dc_low_level_init();需要用户自己去实现,这里主要就是开启PFIC的USB中断。

5、usb_dc_deinit 没有实现这个函数。

6、usbd_set_address 编写,不做过多描述,前文已经说明了。 

7、usbd_ep_open

8、usbd_ep_close

9、usbd_ep_start_write

需要注意的就是同步端点和普通端点的有区别

 

10、usbd_ep_start_read

这里的端点的dma地址已经在dc_init里面设置好了,所以在设计这个函数的时候,只需要把用户的buffer地址记录下来,在中断里面将数据复制到用户的buffer即可。

 

11、usbd_ep_set_stall

12、usbd_ep_clear_stall

 

13、usbd_ep_is_stalled

14、USBD_IRQHandler

我们先看大体框架:

接着我们看传输完成中断:

上文阅读文档时提到,在有in或者out和setup同时存在的情况下,应该优先处理前者,处理完成以后清除中断标志再处理后者。

我们先看端点0的in中断:

首先我们需要明白,端点0的in中断有哪些情况,第一种就是ep0成功发送数据以后会产生in中断,第二种是主机发送in包产生的状态阶段(实际上是设备发送了一个0长度数据包)

setup(host->dev)    data(可有可无)  status phase(in)

setup(dev->host)    data  status phase(out)

所以在设计这一部分的时候,我们判断了一下当前setup的方向,如果是get,说明是上一次发送数据成功产生的in中断,这种情况我们需要翻转同步标识位(注意同步标识位在进setup中断的时候被复位成DATA1,因为setup包是以DATA0下发的,下一个不管是in包还是out包都将是DATA1)。

这里还有一个注意的点就是需要手动将端点状态设置为NAK,否则会一直中断。

接下来就是协议栈的一些处理,基本都是相同的,直接看代码应该就能看懂。

如果是set,看下图,我们可以判断一下当前的setup得请求是否是设置地址,如果是设置地址,说明状态阶段已经完成,我们可以将地址填入到设备地址寄存器,并且将ep0发送状态设置为NAK,ep0接收设置为ACK,以便能接收下一次的setup包。如果不是设置地址,也是状态阶段,但是不需要做别的操作,只需要将ep0发送状态设置为NAK,ep0接收设置为ACK,以便能接收下一次的setup包。

(其实这里也可以直接判断地址是否大于0)。到这里端点0的in我们就讲完了。

接下来我们看其他端点的in中断,看下图:

其实注意的地方一个是ep4需要手动翻转同步标识符,另一个就是需要判断一下是否能进用户的回调函数,因为当发送的包长大于端点的最大包长的时候,我们是在中断里面分包继续发送的。

接着我们看端点0的out中断,看下图:

需要注意的就是要手动翻转同步标识符,如果是0长度数据包,要使能端点接收,准备接收下一次的setup包。

其他端点的out中断,端点4需要手动翻转同步标识符。在一开始的时候检查是否同步,不同步的包可以丢弃。

到这里in和out中断看完了,接着我们看setup中断,看下图:

接下来看reset中断,见下图:

基本就是复位地址,调用协议栈的resthandler,然后使能端点0接收,以便能成功接收到setup包。

接下来是suspend中断,见下图:

到此,移植全部结束。详细代码请见CherryUSB主分支。

https://github.com/sakumisu/CherryUSB

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值