首页|资源下载
登录|注册

您现在的位置是:首页 > 技术阅读 >  USB 之 STM32 基础(六)

USB 之 STM32 基础(六)

时间:2022-09-29

来源:公众号【鱼鹰谈单片机】

作者:鱼鹰Osprey

ID   :emOsprey


本篇笔记主要介绍 STM32 相关的知识点,毕竟之后的 CDC 教程是用 STM32开发的。
为了写这一篇,鱼鹰把STM32中文参考手册USB相关的从头到尾看了一遍,虽然以前就已经看过了,但这次看,收获又是不同。
不过限于篇幅,鱼鹰不会面面俱到,只介绍和 CDC 相关的一些东西。
要完成 USB 模拟串口(CDC)的实验,STM32 手册是必须细细阅读的,不然代码里面很多操作你是无法看懂的。
其实理解了前面的一些东西,你会发现 STM32 中的 USB 知识和前面的大同小异,毕竟开发芯片的厂家也是按照 USB 标准来实现的,不会差到哪里去。

硬件基础

首先,STM32F103 使用 PA11(USBDMD-)和PA12(USBDPD+)完成数据的收发。但看过前面章节的道友应该知道,全速 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,这样就可以找到我们需要的缓存地址,从而读取到主机发给设备的数据,然后进行相应的处理。
设备发送同理。
具体实现可参考鱼鹰给出的源代码。


推荐阅读:
嵌入式系统优先级详解
KEIL 调试经验总结
线程CPU使用率到底该如何计算?
许久以后,你会感谢自己写的异常处理代码
终极串口接收方式,极致效率
延时功能进化论(合集)
如何写一个健壮且高效的串口接收程序?
打了多年的单片机调试断点到底应该怎么设置?| 颠覆认知

-THE END-



如果对你有帮助,记得转发分享哦


微信公众号「鱼鹰谈单片机

每周一更单片机知识

长按后前往图中包含的公众号关注


鱼鹰,一个被嵌入式耽误的畅销书作家

个人微信「EmbeddedOsprey

长按后打开对方的名片关注