USB之STM32基础
2023-04-06 来源:elecfans
本篇笔记主要介绍 STM32 相关的知识点,毕竟之后的 CDC 教程是用 STM32 开发的。
为了写这一篇,鱼鹰把 STM32 中文参考手册 USB 相关的从头到尾看了一遍,虽然以前就已经看过了,但这次看,收获又是不同。
不过限于篇幅,鱼鹰不会面面俱到,只介绍和 CDC 相关的一些东西。
要完成 USB 模拟串口(CDC)的实验,STM32 手册是必须细细阅读的,不然代码里面很多操作你是无法看懂的。
其实理解了前面的一些东西,你会发现 STM32 中的 USB 知识和前面的大同小异,毕竟开发芯片的厂家也是按照 USB 标准来实现的,不会差到哪里去。
硬件基础
首先,STM32F103 使用 PA11(USBDM,D-)和 PA12(USBDP,D+)完成数据的收发。但看过前面章节的道友应该知道,全速 USB 在 D+ 引脚是需要有一个上拉电阻的,同时两根数据线需要各自串联一个 22 Ω的电阻。
这就是你需要的硬件基础,如果说你的开发板有 USB 接口,但是没有这些条件,那么你的 USB 接口只能用于供电,无法进行数据传输。
当然,STM32F103 的速度为全速 12 Mbit,换算成字节为 1.5 MB,除去 USB 协议的开销(令牌、打包等),大概能达到 1 MB/s 速度。
鱼鹰在测试给各位道友的 CDC 例程发现只能达到 100 KB 左右,原以为是主机没有及时发送令牌包导致带宽很低,后来发现 USB 设备发出的数据包只有几个字节,而不是最大包 64B,才知道是发送的数据太少了,后来增加发送的数据量(一次往缓冲多写几百个字节),带宽达到了 400~700KB,但离 1MB 还差了点。
通过逻辑分析仪查看才知道,主机发送 IN 令牌包时,设备有可能还没准备好,浪费了带宽,不过在看 STM32 资料中发现,对于批量传输(CDC 使用批量传输),可以使用双缓冲提高传输量,估计用了双缓冲,传输速率能达到 1MB/s,比串口的 115200 Bit/s 快的多,也稳定的多,毕竟人家可是自带了 CRC 校验和数据重传功能的。
软件基础
现在看一看 STM32F103 的 USB 有哪些功能
第一点,支持 USB2.0 全速,而不是 2.0 高速 480Mbit/s。
有 1~8 个(双向)端点,这是能完成组合设备的基础,按照 CDC + DAP 组合设备来说,一共需要 1(控制传输)+ 2(CDC)+1(HID) = 4 个端点的,更不要说再模拟一个 U 盘了。
CRC、NRZI 编解码,这个可以让你不必关心每一位是什么情况,你只需要处理底层给你的字节数据即可。
支持双缓冲,最大程度的利用 USB 的带宽。
支持 USB 挂起和恢复操作,其实还支持设备远程唤醒操作,即由设备发起唤醒请求(比如鼠标移动后唤醒设备)。
后面有一个注意点,就是 USB 和 CAN 共用 512 字节的缓存,也就是说同一时刻只能有一个外设可以工作,当然你可以通过软件在不同时刻使用不同的外设。
可以看看 USB 设备框图,了解一下 USB 是由哪些结构组成的。
为了实现 USB 通信,有以下基础步骤需要完成:
1、打开 Port A 的外设时钟(PA11 和 PA12)
2、打开 USB 时钟(其实还需要设置 USB 时钟频率,一般 SystemInit 会替你完成,当 USB 时钟打开后, PA11 和 PA12 引脚由 USB 接管,不归 GPIO 控制)。
3、打开相应中断(一共有三个中断)
低优先级中断是我们主要关注的,因为 USB 枚举过程就在这个中断完成,所以这个中断必须开启,其他两个就看需求了。
4、配置 USB 寄存器,使 USB 可以正常工作。
5、之后所有的操作都在低优先级中断进行(包括复位、枚举、SOF 检测等)。
以上步骤具体可以看鱼鹰提供的例程实现,不再多说。
USB 寄存器
USB 中有三类寄存器:端点寄存器、通用寄存器、缓冲区描述表,再加上和描述表对应的缓冲区(数据收发缓存区,USB 所有的数据传输都首先要经过这里),我们要做的就是在合适的时候对这些寄存器进行相应的操作即可。
地址 0x 0x4000 5C00 开始为端点寄存器,因为有 8 个(双向)端点,所以有 8 个寄存器管理。
之后的寄存器为通用寄存器,用于管理整个 USB 模块的,具体可查看参考手册。
以上寄存器有些位很特殊,比如可能写 0 有效,写 1 无效,所以有如下要求:
所以以往的读 - 改 - 写不能在这里使用,不然你这边读回了 0,但是硬件修改了变成 1,如果往回写 0 ,那么就把硬件设置的 1 清除了,肯定会有影响,所以针对这种位,需要对不操作的位设置为 1 ,这样就不会意外修改了。
还有可能写 1 翻转,写 0 无效,这时你会发现代码中使用异或(^)来设置需要的位,非常巧妙。
总之,在学习 USB 过程中,可以锻炼你的位操作能力。
上述两类寄存器在参考手册其实是比较详尽的,但缓冲区描述表(描述表的作用就是描述端点发送和接收缓存区的地址和大小)就显得晦涩难懂了,所以这里详细说一下缓冲区描述表(以下表述可能有问题,需要各位自行验证)。
首先,描述表的地址在 0x4000 6000,也就是说前面所说的 512 Byte 的基地址。但是按照参考手册中的描述来看,这个空间大小应该是 512 Byte * 2,这是因为 USB 模块寻址采用 16 位寻址的,而应用程序使用 32 位寻址,也就是说,按照我们的软件角度,空间分布应该是这样的:
低地址的两个字节可以被我们访问(有颜色部分),高地址的两个字节不可访问(但是按照双缓冲描述来看,好像可以访问到,以后在验证一下)。
所以地址范围应该有 1 KB 的空间,但只有一半是可以使用的。
还有一点就是这块空间不仅用于存放 USB 传输的数据,还用来存放缓存区描述表,这个缓冲区描述表可以在这块空间的任何一个位置(上图在缓冲区的最开始位置),只要满足 8 字节对齐即可,毕竟一个端点需要 16 字节记录(这里可能会感到疑惑,为什么一个端点 16 字节,但却是 8 字节对齐,这就是 16 位 和 32 访问的区别,在 USB 寄存器中,USB 模块通过 16 位访问,所以寄存器里面的值都是按照 16 位来保存偏移的)。
这个表的基地址存放在 USB_BTABLE 寄存器中,一般设置为 0,表示这个表放在上述空间的开始处。
根据需要,依次安排描述表。比如 CDC 有三个端点,前 16 个字节安排端点 0,负责描述发送缓存区的地址和大小,接收缓存区的地址和大小(防止接收时溢出)
端点 1 和端点 2 供 CDC 使用,占用 32 字节。所以前 48 字节被描述表占用了,剩下的(1024 – 48)/ 2 就是数据缓冲区了。比如将端点 0 的发送缓冲区地址指向 0x18(相对地址 0x4000 6000 偏移,16 位访问),大小为 64 字节,端点 0 的接收缓存区指向 0x58(寄存器 USB_ADDR0_RX 写入的值,16 位访问),大小为 64 字节(注意这里的值为 16 位寻址,即 USB 模块的寻址,和应用层 32 位寻址不同,两者之间需要转化)。
按理应该像上面分布空间的,但实际上你会发现分布如下:
那么是否可以将端点 0 的缓存地址安排在 0x40006030 位置(即 USB_ADDR0_TX 值为 0x18 而不是上图的 0x30 呢),而不是 0x40006060 呢,这样就不会浪费那些空间了。
因为这个改动会较大,感兴趣的可以尝试一下。
当 USB 模块写入端点 0 的数据时,首先根据 USB_BTABLE 的值找到描述表的位置,然后再根据描述表第一个表项的 USB_ADDR0_RX 找到接收缓冲区的地址,最后写入数据(写入过程中会判断是否超出限制,防止破坏其他缓冲区,这个通过 USB_COUNT0_Rx 判断),当应用程序进行读取上述地址的数据时,因为采用了 32 位访问,所以对 USB_BTABLE 和 USB_ADDR0_RX 偏移地址 x2,这样就可以找到我们需要的缓存地址,从而读取到主机发给设备的数据,然后进行相应的处理。
设备发送同理。