到目前为止,我们已经对设备、配置、接口与端点有了初步的概念,这对于进一步理解“如何修改相应描述符的基本原理”有着非凡的意义。如果需要开发基于STM32单片机的USB设备,那么具体在哪里修改描述符呢?应该如何修改描述符呢?我们首先了解一下STM32单片机USB固件库的文件结构。
在进行项目开发时,工程师应该养成参考官方例程(文档)的好习惯,尤其是在学习某一项全新技术或应用时,会省事很多。有人可能会反驳:那不就知其然不知其所以然了吗?当然不是!项目开发最讲究效率,你想深入探究可以,但切记不可什么事都从头开始做起。对于厂商发布的USB固件库(例程)而言,为了方便后续按照我们的需求实现相应的功能,不可避免地需要对其进行更改,而为了最终实现数据发送与接收功能,USB固件库包含了一些文件,我们需要初步了解它们各自的用途以及可以更改之处。
前面已经提过,当使用STM32单片机控制某个外设时,需要添加对应的头文件与源文件,USB控制器自然也不例外,但由于USB控制器比较复杂,其不像其他标准外设那样基本上一个源文件对应一个文件,而是对应包含多个文件的固件库。为方便用户更容易地使用USB固件库,其被划分为 内核 与 用户接口 两层,前者管理“使用USB控制器与USB标准协议的”直接传输(遵循USB 2.0规范全速标准,且与前述STM32F10x系列单片机标准外设固件库是分离的),也是最接近USB硬件控制器的那一层,而后者则提供了 内核层 与 应用层 之间的完整接口,通常也是进行USB设备固件开发时需要修改的那一层,图12.1给出了STM32F10x系列单片机USB应用的层次结构。
我们先来简单介绍一下与内核层相关的文件,如表12.1所示。
图12.1 STM32F10x系列单片机USB应用的层次结构
表12.1 与内核层相关的文件
usb_regs.h(.c)是直接与USB硬件控制器对接的文件,它的意义等同于stm32f10x_gpio.h(.c)与GPIO外设的关系。也就是说,usb_regs.h(.c)把与USB控制器相关的寄存器地址及对寄存器的常用操作定义成了宏或函数,这样在进行设备固件开发时就不需要直接对寄存器进行操作了,也就是前文提到的硬件抽象层。usb_mem.h(.c)完成的工作比较单一,从(往)端点缓冲区读出(写入)数据的传输管理,相当于超市货物上架与下架的操作。usb_int.h(.c)包含了正确处理数据传输的中断服务程序,其中包含了CTR_HP与CTR_LP两个函数。usb_core.h(.c)主要用于处理底层的协议管理,后续我们会结合USB控制器详尽讨论。
与内核层相关的文件通常不需要修改,但后续我们仍然会结合USB 2.0规范与STM32单片机硬件控制器来深入剖析它们,这里只需要了解一下usb_type.h头文件即可。按照官方发布的固件库说明文档,它提供了固件库中使用的主要数据类型,也就是说,它与以往介绍过的stdint.h文件存在的意义相同,即保证固件库的独立性,其源代码如清单12.1所示。
清单12.1 usb_type.h头文件
从清单12.1中可以看到,usb_type.h头文件中本身并没多少代码,其只是定义了一个枚举类型而已,无法达到保证固件库独立性的目的(usb_conf.h中没有定义数据类型)。实际上,USB固件库的数据类型定义与前述STM32F10x系列单片机的标准固件库是完全一样的,因为核心文件core_cm3.h已经包含了stdint.h头文件,可以直接使用其中定义的数据类型。
在进行USB设备开发时经常会修改应用接口层,与应用接口层相关的文件如表12.2所示。
表12.2 与应用接口层相关的文件
我们前面提过,内核层中的usb_int.h(.c)定义了CTR_HP与CTR_LP两个中断服务函数,而这两个函数的调用是通过usb_istr.h(.c)完成的(见图12.1)。usb_conf.h则是USB配置文件,包括端点的使用数量、发送与接收缓冲区地址等,其实就相当于对货架的配置。
usb_prop.h(.c)是理解USB协议非常重要的文件,它与内核层文件对接。我们前面提到过,内核层文件是不需要修改的,它实现了响应主机命令的功能。例如,在总线枚举过程中响应获取描述符、分配设备地址、为设备选择配置等命令。但是为了方便用户使用,内核层在响应主机命令时提供了一些“允许用户自定义行为”的机会。例如,当主机给设备分配地址(或其他场合)时,用户可能会想做些什么。这些“允许用户自定义行为的机会”具体表现为一个个的空函数,你可以根据自己的需求去实现(当然,也可以什么都不用做),而usb_prop.h(.c)就汇总了所有“允许用户自定义行为”的空函数。
当主机往(从)设备的每个端点发送(读取)数据后,设备需要进行相应的处理(相当于仓库给超市上下架货物后需要实现的操作),而该处理工作就是在usb_endp.c源文件中实现的。usb_pwr.h(.c)主要用于打开或关闭USB控制器的电源,usb_desc.h(.c)则包含了所有描述符常量数据的定义。
USB固件库涉及的文件比较多,现阶段的你可能会感到非常无助,但目前只需要有这些概念即可,后续还会详细讨论,不要忘了我们介绍USB固件库的目的: 找到USB描述符所在的文件并对其进行修改 。很明显,表12.2告诉我们它们在usb_desc.h (.c)文件中。先来熟悉一下usb_desc.c源文件,其完整的源代码如清单12.2所示。
清单12.2 usb_desc.c源文件
usb_desc.c源文件中所有的源代码将会在接下来几章中详细讨论,我们暂时不用关注细节,先粗略观察一下,就会发现其中定义的都是一些数组,并且大多数是以 const 关键字修饰的。也就是说,在USB设备运行期间,与描述符相关的数据总是不会改变的,正如同在超市正常营业且顾客正在愉快购物的时候,你不会对超市进行装修一样。Joystick_StringSerial数组代表设备序列号字符串,之所以没有使用 const 关键字修饰,是因为需要根据每一片STM32单片机的唯一96位ID号进行修改,但是在主机对设备完成总线枚举之后,Joystick_StringSerial数组中的数据也会是固定不变的。
usb_desc.c源文件中涉及的常量宏被定义在相应的usb_desc.h头文件中,相应的源代码如清单12.3所示。
清单12.3 usb_desc.h头文件
那么usb_desc.c源文件中定义的数组中保存的各项数据到底代表什么意思呢?下面从设备描述符开始讲解。