目录
(2)HAL_GPIO_WritePin()和HAl_GPIO_TogglePin()的异同
(1)首先,HAL_GPIO_TogglePin()中的assert_param
1.HAL_GPIO_WritePin()函数
HAL_GPIO_WritePin()函数也是很常用的实现GPIO引脚输出状态变化的库函数。
该函数HAL_GPIO_WritePin()的定义:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin,GPIO_PinState PinState)
{
/*Check the parameter*/
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if (PinState != GPIO_PIN_RESET)
{
GPIOx ->BSRR = (uint32_t)GPIO_Pin; //BSRR寄存器的低16位置位
}
else
{
GPIOx ->BRR = (uint32_t)GPIO_Pin; //BRR寄存器的低16位复位等效于BSRR寄存器的高16位复位
}
}
HAL_GPIO_WritePin()函数有3个参数,前面两个与GPIO_TogglePin()相同;多出的参数为PinState,顾名思义,是引脚状态。PinState的类型是GPIO_PinState,可以在固件库中查看对GPIO_PinState的声明:
typedef enum
{
GPIO_PIN_RESET = 0U,
GPIO_PIN_SET
} GPIO_PinState;
这是一个枚举类型,用typedef关键词将枚举类型定义成GPIO_PinState,其有两个成员:GPIO_PIN_RESET,被赋值为0;另一个是GPIO_PIN_SET,被赋值为1(后续枚举成员的值在前一个枚举值基础上自动+1)。
HAL_GPIO_WritePin()函数前面两个参数用于指定端口和引脚号,第3个参数用于设置该I/O引脚的状态(0或1)。
(1) 细说HAL_GPIO_WritePin()函数
在HAL_GPIO_WritePin()函数的定义中,首先是两行assert_param()的声明语句。if语句的条件是判断传递过来的参数PinState是否不等于0(GPIO_PIN_RESET);如果参数PinState为1(即不等于0),则通过BSRR寄存器的低16位(BS)置位,使得该引脚输出1;如果等于0,则通过BRR寄存器复位该引脚状态,使其输出0。
固件库中的函数控制I/O,是通过操作BSRR和BRR寄存实现的。实际上,也可以直接给ODR寄存器赋值来实现对I/O引脚状态的控制,但相比于操作BSRR和BRR寄存器,这样做效率会低一些。
(2)HAL_GPIO_WritePin()和HAl_GPIO_TogglePin()的异同
HAL_GPIO_WritePin()和HAl_GPIO_TogglePin()都可以实现对I/O引脚状态的控制,但实现的功能有区别。HAL_GPIO_WritePin()可以让I/O输出一个给定的电平(高或低电平);而函数HAl_GPIO_TogglePin()只是让相应的I/O引脚输出状态翻转,至于是输出高电平还是低电平,取决于之前的状态。
2.assert_param语句的功能
在函数HAL_GPIO_TogglePin()中,该语句为:
assert_param(IS_GPIO_PIN(GPIO_Pin));
在HAL_GPIO_WritePin()中,该语句为:
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
实际上assert_param()是一种宏,用于函数的参数检查。括号中是一个表达式为真(有效),则程序会继续执行下去;若表达式为假,则程序就会跳转,去执行可以发出错误信息的代码段。
(1)首先,HAL_GPIO_TogglePin()中的assert_param
assert_param(IS_GPIO_PIN(GPIO_Pin));
其中,GPIO_Pin是HAL_GPIO_TogglePin的参数,用于指定I/O的引脚号,例如PB3,则GPIO_Pin就是无符号数0x0008。
IS_GPIO_PIN()是判断传递过来的GPIO_Pin是否为有效的GPIO引脚。GPIO_Pin是无符号数,GPIO端口通常为16个,GPIO_Pin从右边第1位开始,分别为0x0001、0x0004、0x0008、…、0x8000。也就是说,传递过来的GPIO_Pin应该是不为0并且只有16位的数。查看固件库中关于IS_GPIO_PIN()的声明,stm32g4xx_hal_gpio.h文件中,可知它是由define宏定义的:
# define IS_GPIO_PIN(__PIN__)((((uint32_t)(__PIN__) & GPIO_PIN_MASK) != 0x00U) && (((uint32_t)(__PIN_) & ~GPIO_PIN_MASK) = 0x00U))
其中,__PIN__也是库函数中的宏定义,用来指明引脚号;GPIO_PIN_MASK也是通过宏定义的:
# define GPIO_PIN_MASK (0×0000FTTFU) /*PIN mask for assert test */
在IS_GPIO_PIN(__PIN__)的定义中,当右侧两个表达式同时成立时IS_GPIO_PIN(__PIN__)才为真,这两个表达式通过&&连接。第一个表达式中,(__PIN__)&.GPIO_PIN_MASK)是用于判断传递过来的引脚号是否为GPIO_PIN_MASK为0x0000 FFFF,只要(__PIN__)不为0x0000,与0x0000 FFFF按位“与”的结果就不为0,此时第一个表达式即为真;否则为假。第二个表达式是将传递过来的引脚号和~GPIO_PIN_MASK进行逻辑“与”,只要传递过来的引脚号不多于16位,该表达式(==)就为真;否则表达式为假。两个表达式同时为真时,IS_GPIO_PIN(__PIN__)真;此时assert_param()中的表达式为真,程序会继续执行。否则就会调用assert_failed函数,输出错误信息。
assert_param()的定义在固件库的stm32g4xx_hal_conf.h文件中,其声明如下:
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t *file, uint32_t line);
#else
#define assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */
(2)再看,assert_param()定义
如果USE_FULL_ASSERT已定义过,则会编译下面两条语句:
# define assert_param(expr)((expr)?(void)0U:assert_failed((uint8_t *)__FILE__ , __LINE__))
void assert_failed(uint8_t * file,uint32_t line);
第一句define宏定义中的(void)0U,是空语句的表达式。将assert_param(expr)定义为:
如果表达式expr真,就是空语句(void)0U;如果表达式expr假,则为assert_failed()函数。define宏定义后,就是对assert_failed()的声明。
如果USE_FULL_ASSERT未定义过,则会编译#else中的内容,就是用define宏定义直接将assert_param(expr)定义为空语句(void)0U。
其中,在(void)0U中,“0”后跟个“U”表示此“0”为无符号数。"__FILE__,__LINE__"也是宏定义,用来表示文件和行数。函数assert_failed()在这个定义中只是进行了声明,并没有具体定义。
3.ifdef条件编译
声明中用到了ifdef条件编译。它的定义格式如下:
# ifdef 标识符
程序段1 //标识符被定义过(通常是用#define命令定义的),则编译程序段1
# else //条件编译中可以没有#else部分
程序段2 //否则编译程序段2
# endif