最近,我在使用STM32CUBEMX这个软件生成HAL库的模板,并学习STM32单片机的USB Device外设功能以及相关协议栈,发现了HAL的一些特点,现在记录下来。
(1)"函数的本质是一个数据处理器"这句话的理解。
首先理解一下寄存器的作用,首先要明白操作那个寄存器就是操作硬件,也就是读写寄存器就是实现了STM32单片机功能的控制。
首先看一下以下代码:
cpp
typedef struct
{
__IO uint32_t GOTGCTL; /*!< USB_OTG Control and Status Register Address offset: 000h */
__IO uint32_t GOTGINT; /*!< USB_OTG Interrupt Register Address offset: 004h */
__IO uint32_t GAHBCFG; /*!< Core AHB Configuration Register Address offset: 008h */
__IO uint32_t GUSBCFG; /*!< Core USB Configuration Register Address offset: 00Ch */
__IO uint32_t GRSTCTL; /*!< Core Reset Register Address offset: 010h */
__IO uint32_t GINTSTS; /*!< Core Interrupt Register Address offset: 014h */
__IO uint32_t GINTMSK; /*!< Core Interrupt Mask Register Address offset: 018h */
__IO uint32_t GRXSTSR; /*!< Receive Sts Q Read Register Address offset: 01Ch */
__IO uint32_t GRXSTSP; /*!< Receive Sts Q Read & POP Register Address offset: 020h */
__IO uint32_t GRXFSIZ; /*!< Receive FIFO Size Register Address offset: 024h */
__IO uint32_t DIEPTXF0_HNPTXFSIZ; /*!< EP0 / Non Periodic Tx FIFO Size Register Address offset: 028h */
__IO uint32_t HNPTXSTS; /*!< Non Periodic Tx FIFO/Queue Sts reg Address offset: 02Ch */
uint32_t Reserved30[2]; /*!< Reserved 030h*/
__IO uint32_t GCCFG; /*!< General Purpose IO Register Address offset: 038h */
__IO uint32_t CID; /*!< User ID Register Address offset: 03Ch */
uint32_t Reserved40[48]; /*!< Reserved 040h-0FFh */
__IO uint32_t HPTXFSIZ; /*!< Host Periodic Tx FIFO Size Reg Address offset: 100h */
__IO uint32_t DIEPTXF[0x0F]; /*!< dev Periodic Transmit FIFO Address offset: 0x104 */
} USB_OTG_GlobalTypeDef;
cpp
#define USB_OTG_FS_PERIPH_BASE 0x50000000U
cpp
#define USB_OTG_FS ((USB_OTG_GlobalTypeDef *)USB_OTG_FS_PERIPH_BASE)
其中第一个类型表示USB_OTG的寄存器组数据,第二个宏表示寄存器组首地址值,最终定义了一个宏"USB_OTG_FS"代表整个寄存器组的地址。到这里数据已经有了,而在HAL库的源文件"stm32f1xx_ll_usb.c"中定义的API函数的参数列表中都有USB_OTG_GlobalTypeDef *类型的参数。这样调用这些API函数的时候就实现了参数的传递,也就在函数内部实现了对寄存器组的控制。
cpp
HAL_StatusTypeDef USB_CoreInit(USB_OTG_GlobalTypeDef *USBx, USB_OTG_CfgTypeDef cfg);
HAL_StatusTypeDef USB_EnableGlobalInt(USB_OTG_GlobalTypeDef *USBx);
HAL_StatusTypeDef USB_DisableGlobalInt(USB_OTG_GlobalTypeDef *USBx);
...
static HAL_StatusTypeDef USB_CoreReset(USB_OTG_GlobalTypeDef *USBx);
真正验证了"函数其实是一个数据处理器"这句话,而且更进一步"HAL库中API函数组其实是指针变间接处理同一个自定义结构体类型数据的数据处理器"。
在其他HAL库中也有相似的用法,所以我们需要做的就是定义这个结构体类型的数据(寄存器组已经定义了,无需定义,硬件上自然存在),然后在需要的时候,调用这些API函数完成参数传参。函数内部就自动完成对这些结构体类型数据的访问了,只不过是以指针间接访问的方式实现的。
真的很难想象,HAL库每一个源文件中少则十几个,多册几十个函数居然都是操作寄存器组,那可想而知,寄存器虽然没那么多,但是操作它的花样还是很多的,主要原因是每一个寄存器都有32bit,寄存器内部各个bit位代表不同硬件操作。
当然了,其他类似的自定义结构体类型是内存变量的情况,函数参数为自定义结构体类型指针变量,函数内部实现对自定义结构体变量内部成员的读写访问,一般不会涉及到位操作,只是简单内存变量的读写访问。
(2)关于架构的思想。
我现在有一个感受就是:我已经看了好多linux源码程序了,c语言的各种用法也都遇到过,熟悉了,为什么切换到STM32单片机的程序就感觉无从下手,不知道从哪里看呢?
我现在的答案是:缺乏架构思想,也就是那些源文件是在顶层,哪些顶层源文件中的函数调用底层源文件中的函数。
首先,最顶层的是唯一的main.c,因为其他函数都是从main.c中的main()开始调用的。
然后是adc.c、can.c、tim.c等STM32CUBEMX软件自动生成的外设源文件,每一个源文件中定义了第一条论述中的自定义结构体类型变量实体。然后调用API函数实现简单的初始化、去初始化等操作,让外设处于准备工作状态。这一层如果是复杂的外设例如USB、以太网等,可能需要调用一些协议栈,这就是我接下来要说的了
然后是协议栈,中间层、RTOS操作系统等这些纯软件的操作。这一层一般不涉及到硬件操作,具有一定可移植性。
最后,HAL库层(硬件抽象层),这一层是软件的最底层,主要就是操作寄存器组实现各个功能控制,主要用到了位运算。
再往下,就是硬件层了,属于纯数字电路、模拟电路等就不论述了。
注意:以上只是简单的说明,并不固定死的,也并不规定不能跨层访问,只是大致的分层等。到这里可能有人要问了,那业务功能层在那一层呢?我们都知道业务功能层是实现具体的客户需求,属于应用层的范畴,他应该在高于第二层或者与第二层平行的关系。因为应用层主要是实现客户需求功能会调用到中间层(去操纵内存变量,内存变量代表业务逻辑)、或者HAL库层(去操作寄存器)。