本篇笔记主要介绍 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,这样就可以找到我们需要的缓存地址,从而读取到主机发给设备的数据,然后进行相应的处理。
设备发送同理。
设计资源 培训 开发板 精华推荐
- 用于电池充电指示器的 NCP301LSN34T1 3.4V 电压检测器的典型应用
- KIT33975AEWEVBE: 评估套件 - 33975,MSDI,32mA抑制唤醒
- [我憨了] 无线PPM解密狗- 基于萝丽三和ATmega32u4
- AM2G-0507SH30Z 7.2V 2 瓦 DC/DC 转换器的典型应用
- ESP32台灯
- LT1117CM 低压差负电源稳压器的典型应用
- DC782A-S,LTC2255IUH 演示板,高速 ADC,VDD = +3.0V,125 Msps,14 位 10MHz <艾因< 170MHz
- ANAVI Macro Pad 2:带背光的开源可编程两键机械键盘
- REF43 ±2.5V 低功耗精密电压基准的典型应用
- 【训练营】灯条控制器