对于初学者来说,有时候知其然而不知其所以然,换句话来讲,知道这个东西是这样子操作,但是不知道为啥这么做。虽然老话有讲,不管黑猫白猫,捉到老鼠就是好猫,但是某些时候我们还是得需要知道为啥这么干。
举个栗子,配置一个GPIO去控制一个LED灯。
寄存器版本的代码:
这里就涉及两个与GPIO相关的寄存器:GPIOx_CRL,GPIOx_ODR。通过注释我们也知道了这两个寄存器的功能。
为什么这么赋值呢?下面就先看一下这两个寄存器的详情。
首先是端口配置低寄存器(GPIOx_CRL),因为我们配置的GPIO口为PB5,所以是看端口配置低寄存器。假如是GPIO8~GPIO15,我们就需要查看GPIOx_CRH。
我们是用PB5来控制LED灯,所以说模式要设置为推挽输出模式,速度可以设置为50MHz。所以说我们对Bit[23:20]赋值0x3。
通过上面,已经把PB5设置好了,这里就相当于我们理解中的初始化成功。初始化成功后,那么就是对该GPIO口的控制了,让它输出高电平还是低电平,这里使用端口输出数据寄存器(GPIOx_ODR)去实现了。
对ODR5位置1或者清0就是控制GPIO输出高电平还是低电平了,具体根据原理图查看才知道,赋值0还是1能让LED亮。当然我们的LED灯设计是低电平点亮,高电平熄灭。初始化情况下,还是赋值1,让LED处于熄灭状态。
以上就是寄存器版本对GPIO口配置,我们可以很清晰的看到相对底层的操作。
我们再来看一下HAL库对GPIO的操作。
很明显,HAL库函数就是有封装了,标准库同样也是一样的。初始化一个外设,老掉牙的套路:定义一个外设结构体,并且对其成员进行赋值,然后调用HAL_XXX_Init函数完成。
GPIO初始化结构体哪里找呢?就在stm32f1xx_hal_gpio.h中已经定义好了,具体如下:
Pin就是引脚号,Mode就是模式设置,Pull就是上下拉设置(F1的话,输出模式不用设置),Speed就是速度设置。
我们是用PB5去控制灯,和寄存器版本一样的配置,模式是设置推挽输出,速度设置50MHz。
那么如何赋值?其实上面已经有图了,这里还是简单提一下吧。
gpio_init_struct.Mode= GPIO_MODE_OUTPUT_PP;怎么知道“GPIO_MODE_OUTPUT_PP”含义呢?如下图所示:
就是使用最为常用的操作“Goto Definition”去走到底查看这个宏代表的值,那么通过上图就可以知道“GPIO_MODE_OUTPUT_PP”为1。
同样的,查看“GPIO_SPEED_FREQ_HIGH”含义呢?如下图所示:
这个宏相对上一个宏再多跳转了一级,我们要以获得值为前提,所以说还没有见到我们熟悉的数字时,就需要一直“Goto Definition”,最终我们发现该宏是为3。
到这里还没有看到寄存器的操作,但是看到这个0x3的出现,我们现在已经有点底了。前面也说了对结构体成员赋值后,就可以调用初始化函数,这时候同样的Goto Definition。函数如下(经过处理):
解析过程如上图,经过漫长的兜兜转转,最终你会发现HAL库的实际依然是操作寄存器,对GPIOB_CRL进行0x3<< 20操作,这就和我们前面寄存器操作是一致的。
另外还有一个控制输出高低电平的函数,如下:
这里更加清楚,直接调用寄存器操作。这里直接操作GPIOx_BSRR寄存器,那么使用GPIOx_ODR寄存器也是可以。
总结一下,只要你用几次goto definition,你就会发现HAL库函数的实际就是操作寄存器,标准库也是一样。
HAL库,就是将硬件抽象化,相对的,对寄存器相关操作掌握能力就要求不高。在开发效率上,HAL库优势更明显,但是执行效率并不可观。一位技术大咖就认为:初始化用HAL库,方便快捷;其他地方可以使用寄存器操作,简单直接。所以说,想要技术变得贼溜,HAL库、寄存器一起搞起。
|