HAL笔记


以下所有笔记均基于安富莱V6板

配置文件

为防止递归包含使用如下宏定义进行头文件保护

#ifndef __STM32F4xx_HAL_CONF_H 
#define __STM32F4xx_HAL_CONF_H

// 在这里定义你的配置

#endif /* __STM32F4xx_HAL_CONF_H */

递归包含是指在头文件中包含其他头文件时,可能会出现循环依赖的情况。例如,头文件A包含头文件B,而头文件B又包含头文件A,这样就形成了递归包含。
递归包含可能会导致以下问题:

  1. 编译错误:当头文件A包含头文件B时,编译器会先处理头文件A的内容,然后再处理头文件B的内容。如果头文件B中又包含了头文件A,就会产生循环依赖,编译器无法解析这种情况,导致编译错误。
  2. 重复定义:如果递归包含导致某个符号在多个头文件中被定义多次,就会导致重复定义的错误。这会导致链接错误,使得程序无法正常编译和运行。

在配置文件中对于HAL库模块的启用使用了宏定义,如:

/* ########################## Module Selection ############################## */  
/**  
* @brief This is the list of modules to be used in the HAL driver  
*/  
#define HAL_MODULE_ENABLED  
  
/* #define HAL_CRYP_MODULE_ENABLED */  
#define HAL_ADC_MODULE_ENABLED  
#define HAL_CAN_MODULE_ENABLED  
/* #define HAL_CRC_MODULE_ENABLED */  
/* #define HAL_CAN_LEGACY_MODULE_ENABLED */

注释掉的即为未启用的模块,在配置文件的后面,如果你启用了该模块,那么会包含相对应的头文件

#ifdef HAL_RCC_MODULE_ENABLED  
#include "stm32f4xx_hal_rcc.h"  
#endif /* HAL_RCC_MODULE_ENABLED */  
  
#ifdef HAL_GPIO_MODULE_ENABLED  
#include "stm32f4xx_hal_gpio.h"  
#endif /* HAL_GPIO_MODULE_ENABLED */  
  
#ifdef HAL_EXTI_MODULE_ENABLED  
#include "stm32f4xx_hal_exti.h"  
#endif /* HAL_EXTI_MODULE_ENABLED */  
  
#ifdef HAL_DMA_MODULE_ENABLED  
#include "stm32f4xx_hal_dma.h"  
#endif /* HAL_DMA_MODULE_ENABLED */  
  
#ifdef HAL_CORTEX_MODULE_ENABLED  
#include "stm32f4xx_hal_cortex.h"  
#endif /* HAL_CORTEX_MODULE_ENABLED */  
  
#ifdef HAL_ADC_MODULE_ENABLED  
#include "stm32f4xx_hal_adc.h"  
#endif /* HAL_ADC_MODULE_ENABLED */  
  
#ifdef HAL_CAN_MODULE_ENABLED  
#include "stm32f4xx_hal_can.h"  
#endif /* HAL_CAN_MODULE_ENABLED */  
  
#ifdef HAL_CAN_LEGACY_MODULE_ENABLED  
#include "stm32f4xx_hal_can_legacy.h"  
#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */  
  
#ifdef HAL_CRC_MODULE_ENABLED  
#include "stm32f4xx_hal_crc.h"  
#endif /* HAL_CRC_MODULE_ENABLED */  
  
#ifdef HAL_CRYP_MODULE_ENABLED  
#include "stm32f4xx_hal_cryp.h"  
#endif /* HAL_CRYP_MODULE_ENABLED */  
  
#ifdef HAL_DMA2D_MODULE_ENABLED  
#include "stm32f4xx_hal_dma2d.h"  
#endif /* HAL_DMA2D_MODULE_ENABLED */  
  
#ifdef HAL_DAC_MODULE_ENABLED  
#include "stm32f4xx_hal_dac.h"  
#endif /* HAL_DAC_MODULE_ENABLED */  
......

在配置文件中包含了对晶振频率的设定,需和外界保持一致。

/* ########################## HSE/HSI Values adaptation ##################### */  
/**  
* @brief Adjust the value of External High Speed oscillator (HSE) used in your application.  
* This value is used by the RCC HAL module to compute the system frequency  
* (when HSE is used as system clock source, directly or through the PLL).  
*/  
#if !defined (HSE_VALUE)  
#define HSE_VALUE 25000000U /*!< Value of the External oscillator in Hz */  
#endif /* HSE_VALUE */  
  
#if !defined (HSE_STARTUP_TIMEOUT)  
#define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */  
#endif /* HSE_STARTUP_TIMEOUT */  
  
/**  
* @brief Internal High Speed oscillator (HSI) value.  
* This value is used by the RCC HAL module to compute the system frequency  
* (when HSI is used as system clock source, directly or through the PLL).  
*/  
#if !defined (HSI_VALUE)  
#define HSI_VALUE ((uint32_t)16000000U) /*!< Value of the Internal oscillator in Hz*/  
#endif /* HSI_VALUE */  
  
/**  
* @brief Internal Low Speed oscillator (LSI) value.  
*/  
#if !defined (LSI_VALUE)  
#define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/  
#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz  
The real value may vary depending on the variations  
in voltage and temperature.*/  
/**  
* @brief External Low Speed oscillator (LSE) value.  
*/  
#if !defined (LSE_VALUE)  
#define LSE_VALUE 32768U /*!< Value of the External Low Speed oscillator in Hz */  
#endif /* LSE_VALUE */  
  
#if !defined (LSE_STARTUP_TIMEOUT)  
#define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */  
#endif /* LSE_STARTUP_TIMEOUT */  
  
/**  
* @brief External clock source for I2S peripheral  
* This value is used by the I2S HAL module to compute the I2S clock source  
* frequency, this source is inserted directly through I2S_CKIN pad.  
*/  
#if !defined (EXTERNAL_CLOCK_VALUE)  
#define EXTERNAL_CLOCK_VALUE 12288000U /*!< Value of the External audio frequency in Hz*/  
#endif /* EXTERNAL_CLOCK_VALUE */  
  
/* Tip: To avoid modifying this file each time you need to use different HSE,  
=== you can define the HSE value in your toolchain compiler preprocessor. */

需要注意的是,对于启动时的超时时间(XX_STARTUP_TIMEOUT)建议改为5000(5s),防止启动失败时出问题。

以下是HAL系统配置选项

/* ########################### System Configuration ######################### */  
/**  
* @brief This is the HAL system configuration section  
*/  
#define VDD_VALUE 3300U /*!< Value of VDD in mv */  
#define TICK_INT_PRIORITY 15U /*!< tick interrupt priority */  
#define USE_RTOS 0U  
#define PREFETCH_ENABLE 1U  
#define INSTRUCTION_CACHE_ENABLE 1U  
#define DATA_CACHE_ENABLE 1U

比较重要的是TICK_INT_PRIORITY,这个是设置滴答中断优先级的,15是最低优先级。
目前HAL库还不支持RTOS宏定义配置

在配置文件的最后,是关于断言功能的

#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 */

其主要作用是用来判断函数形参是否有效。默认情况下是关闭的。
用一个HAL库函数举例

===============================================================================  
##### IO operation functions #####  
===============================================================================
@endverbatim  
* @{  
*/  
  
/**  
* @brief Reads the specified input port pin.  
* @param GPIOx where x can be (A..K) to select the GPIO peripheral for STM32F429X device or  
* x can be (A..I) to select the GPIO peripheral for STM32F40XX and STM32F427X devices.  
* @param GPIO_Pin specifies the port bit to read.  
* This parameter can be GPIO_PIN_x where x can be (0..15).  
* @retval The input port pin value.  
*/  
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)  
{  
GPIO_PinState bitstatus;  
  
/* Check the parameters */  
assert_param(IS_GPIO_PIN(GPIO_Pin));  
  
if((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)  
{  
bitstatus = GPIO_PIN_SET;  
}  
else  
{  
bitstatus = GPIO_PIN_RESET;  
}  
return bitstatus;  
}

对于HAL的各个API都用到了断言功能,但是即使使能了断言,仍然需要自己定义函数

void assert_failed(uint8_t* file, uint32_t line)

下面给出一个参考

/*
ST 库函数使用了 C 编译器的断言功能,如果定义了 USE_FULL_ASSERT ,那么所有的 ST 库函数将检查函数形参是否正确。如果不正确将调用是否正确。如果不正确将调用 assert_failed() assert_failed() 函数函数,这个函数是一个死循环,便于用户检查代码。
LINE__ 表示源代码行号。 关键字__FILE__表示源代码文件名。
断言功能使能后将增大代码大小,推荐用户仅在调试时使能,在正式发布软件是禁止。断言功能使能后将增大代码大小,推荐用户仅在调试时使能,在正式发布软件是禁止。 用户可以选择是否使能用户可以选择是否使能STST固件库的断言供能。使能断言的方法有两种:固件库的断言供能。
使能断言的方法有两种:(1) 在在CC编译器的预定义宏选项中定义编译器的预定义宏选项中定义USE_FULL_ASSERTUSE_FULL_ASSERT。。 (2) 在本文件取消在本文件取消"#define USE_FULL_ASSERT 1""#define USE_FULL_ASSERT 1"行的注释。 */
void assert_failed(uint8_t* file, uint32_t line){ 
/* 用户可以添加自己的代码报告源代码文件名和代码行号,比如将错误文件和行号打印到串口
printf("Wrong parameters value: file %s on line %d\\rr\\n", file, line) */
/* 这是一个死循环,断言失败时程序会在此处死机,以便于用户查错这是一个死循环,断言失败时程序会在此处死机,以便于用户查错 */
while (1) {  
  }
}

GPIO

gpio.h/.c

似乎这里只有一个函数“ void MX_GPIO_Init()”

/** Configure pins as  
* Analog  
* Input  
* Output  
* EVENT_OUT  
* EXTI  
* Free pins are configured automatically as Analog (this feature is enabled through  
* the Code Generation settings)  
*/  
void MX_GPIO_Init(void)  
{  
  
GPIO_InitTypeDef GPIO_InitStruct = {0};  
  
/* GPIO Ports Clock Enable */  
__HAL_RCC_GPIOE_CLK_ENABLE();  
__HAL_RCC_GPIOI_CLK_ENABLE();  
__HAL_RCC_GPIOC_CLK_ENABLE();  
__HAL_RCC_GPIOF_CLK_ENABLE();  
__HAL_RCC_GPIOH_CLK_ENABLE();  
__HAL_RCC_GPIOA_CLK_ENABLE();  
__HAL_RCC_GPIOB_CLK_ENABLE();  
__HAL_RCC_GPIOJ_CLK_ENABLE();  
__HAL_RCC_GPIOG_CLK_ENABLE();  
__HAL_RCC_GPIOD_CLK_ENABLE();  
__HAL_RCC_GPIOK_CLK_ENABLE();  
  
/*Configure GPIO pins : PE2 PE3 PE4 PE5  
PE6 PE7 PE8 PE9  
PE10 PE11 PE12 PE13  
PE14 PE15 PE0 PE1 */  
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5  
|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9  
|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13  
|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;  
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;  
GPIO_InitStruct.Pull = GPIO_NOPULL;  
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);  
  
/*Configure GPIO pins : PI8 PI10 PI11 PI12  
PI13 PI14 PI15 PI0  
PI1 PI2 PI3 PI4  
PI5 PI6 PI7 */  
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12  
|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0  
|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4  
|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;  
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;  
GPIO_InitStruct.Pull = GPIO_NOPULL;  
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);  
  
}

这里只摘取了部分代码,后面的基本都是相同的

GPIO_InitTypeDef结构体一般用于配置GPIO引脚的初始化参数。这个结构体包含了多个成员变量,用来设置引脚的模式、速度、上拉/下拉设置等。

在这行代码中,GPIO_InitStruct变量被初始化为0,这意味着所有的成员变量都被设置为0。这样做是为了确保在后续的代码中,如果没有特别指定某个成员变量的值,那么它们的默认值都是0。
随后开启GPIO的时钟并开始配置引脚,这里的每一个GPIO_PIN之前都用宏定义定义了地址,使用或位操作符将多个引脚组合在一起。
这里统一设置为了模拟模式和浮空状态,随后进入HAL_GPIO_INIT函数进行初始化

/**  
* @brief Initializes the GPIOx peripheral according to the specified parameters in the GPIO_Init.  
* @param GPIOx where x can be (A..K) to select the GPIO peripheral for STM32F429X device or  
* x can be (A..I) to select the GPIO peripheral for STM32F40XX and STM32F427X devices.  
* @param GPIO_Init pointer to a GPIO_InitTypeDef structure that contains  
* the configuration information for the specified GPIO peripheral.  
* @retval None  
*/  
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)  
{  
uint32_t position;  
uint32_t ioposition = 0x00U;  
uint32_t iocurrent = 0x00U;  
uint32_t temp = 0x00U;  
  
/* Check the parameters */  
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));  
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));  
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));  
  
/* Configure the port pins */  
for(position = 0U; position < GPIO_NUMBER; position++)  
{  
/* Get the IO position */  
ioposition = 0x01U << position;  
/* Get the current IO position */  
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;  
  
if(iocurrent == ioposition)  
{  
/*--------------------- GPIO Mode Configuration ------------------------*/  
/* In case of Output or Alternate function mode selection */  
if(((GPIO_Init->Mode & GPIO_MODE) == MODE_OUTPUT) || \  
(GPIO_Init->Mode & GPIO_MODE) == MODE_AF)  
{  
/* Check the Speed parameter */  
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));  
/* Configure the IO Speed */  
temp = GPIOx->OSPEEDR;  
temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2U));  
temp |= (GPIO_Init->Speed << (position * 2U));  
GPIOx->OSPEEDR = temp;  
  
/* Configure the IO Output Type */  
temp = GPIOx->OTYPER;  
temp &= ~(GPIO_OTYPER_OT_0 << position) ;  
temp |= (((GPIO_Init->Mode & OUTPUT_TYPE) >> OUTPUT_TYPE_Pos) << position);  
GPIOx->OTYPER = temp;  
}  
  
if((GPIO_Init->Mode & GPIO_MODE) != MODE_ANALOG)  
{  
/* Check the parameters */  
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));  
  
/* Activate the Pull-up or Pull down resistor for the current IO */  
temp = GPIOx->PUPDR;  
temp &= ~(GPIO_PUPDR_PUPDR0 << (position * 2U));  
temp |= ((GPIO_Init->Pull) << (position * 2U));  
GPIOx->PUPDR = temp;  
}  
  
/* In case of Alternate function mode selection */  
if((GPIO_Init->Mode & GPIO_MODE) == MODE_AF)  
{  
/* Check the Alternate function parameter */  
assert_param(IS_GPIO_AF(GPIO_Init->Alternate));  
/* Configure Alternate function mapped with the current IO */  
temp = GPIOx->AFR[position >> 3U];  
temp &= ~(0xFU << ((uint32_t)(position & 0x07U) * 4U)) ;  
temp |= ((uint32_t)(GPIO_Init->Alternate) << (((uint32_t)position & 0x07U) * 4U));  
GPIOx->AFR[position >> 3U] = temp;  
}  
  
/* Configure IO Direction mode (Input, Output, Alternate or Analog) */  
temp = GPIOx->MODER;  
temp &= ~(GPIO_MODER_MODER0 << (position * 2U));  
temp |= ((GPIO_Init->Mode & GPIO_MODE) << (position * 2U));  
GPIOx->MODER = temp;  
  
/*--------------------- EXTI Mode Configuration ------------------------*/  
/* Configure the External Interrupt or event for the current IO */  
if((GPIO_Init->Mode & EXTI_MODE) != 0x00U)  
{  
/* Enable SYSCFG Clock */  
__HAL_RCC_SYSCFG_CLK_ENABLE();  
  
temp = SYSCFG->EXTICR[position >> 2U];  
temp &= ~(0x0FU << (4U * (position & 0x03U)));  
temp |= ((uint32_t)(GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));  
SYSCFG->EXTICR[position >> 2U] = temp;  
  
/* Clear Rising Falling edge configuration */  
temp = EXTI->RTSR;  
temp &= ~((uint32_t)iocurrent);  
if((GPIO_Init->Mode & TRIGGER_RISING) != 0x00U)  
{  
temp |= iocurrent;  
}  
EXTI->RTSR = temp;  
  
temp = EXTI->FTSR;  
temp &= ~((uint32_t)iocurrent);  
if((GPIO_Init->Mode & TRIGGER_FALLING) != 0x00U)  
{  
temp |= iocurrent;  
}  
EXTI->FTSR = temp;  
  
temp = EXTI->EMR;  
temp &= ~((uint32_t)iocurrent);  
if((GPIO_Init->Mode & EXTI_EVT) != 0x00U)  
{  
temp |= iocurrent;  
}  
EXTI->EMR = temp;  
  
/* Clear EXTI line configuration */  
temp = EXTI->IMR;  
temp &= ~((uint32_t)iocurrent);  
if((GPIO_Init->Mode & EXTI_IT) != 0x00U)  
{  
temp |= iocurrent;  
}  
EXTI->IMR = temp;  
}  
}  
}  
}

以下是GPIO初始化结构体定义

/**  
* @brief GPIO Init structure definition  
*/  
typedef struct  
{  
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.  
This parameter can be any value of @ref GPIO_pins_define */  
  
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.  
This parameter can be a value of @ref GPIO_mode_define */  
  
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.  
This parameter can be a value of @ref GPIO_pull_define */  
  
uint32_t Speed; /*!< Specifies the speed for the selected pins.  
This parameter can be a value of @ref GPIO_speed_define */  
  
uint32_t Alternate; /*!< Peripheral to be connected to the selected pins.  
This parameter can be a value of @ref GPIO_Alternate_function_selection */  
}GPIO_InitTypeDef;

定时器

预加载

STM32的定时器预加载功能是指在定时器的计数器中设置一个预加载值,当计数器的值达到这个预设值时,可以触发一个事件或中断。这个功能在STM32微控制器的定时器操作中非常有用,尤其是在需要精确控制时间事件时。

重复周期计数器

在STM32微控制器中,一个重复周期计数器(Repeat Counter)通常是指定时器中的一个功能,它允许定时器在计数达到预设值后自动重置并重新开始计数。这种功能对于创建周期性事件非常有用,如周期性中断或定期检查某个条件。

使用重复周期计数器的基本步骤如下:

  1. 定时器配置:首先设置定时器的基本参数,包括时钟源、预分频值(用于确定计数器的计数速度)和自动重载值(ARR,用于设定计数器达到该值时重置)。

  2. 设置重复计数器(如果可用):某些STM32定时器模型中含有一个专门的重复计数器(REP寄存器),可以用来设置定时器在自动重载后重复计数的次数。如果设置为N,则定时器将完成N+1个周期后产生一个更新事件(包括中断,如果已启用)。

  3. 中断配置(可选):如果需要在每个周期结束时执行特定的操作,可以配置并启用更新事件中断(UIE)。

  4. 启动定时器:最后,启动定时器以开始计数。

工作模式

  1. 单脉冲模式(Single-Shot Mode):在这种模式下,HRTIM在每次启动时只生成一个脉冲。这种模式通常用于精确控制时间长度的单次事件。

  2. 重复模式(Repetitive Mode):HRTIM会不断重复生成波形。这种模式适用于需要连续产生周期性波形的应用。

  3. 连续模式(Continuous Mode):在连续模式下,HRTIM持续运行,不断循环计数。这种模式通常用于持续跟踪时间或产生连续的波形。

  4. 比较模式(Compare Mode):HRTIM可以在计数器值达到预设值时生成一个事件。这种模式适用于需要在特定时间点执行操作的应用。

  5. 捕获模式(Capture Mode):在捕获模式下,HRTIM能够捕获并记录外部事件(如输入信号的边沿)发生时的计数器值,这对于测量外部事件的时间非常有用。

  6. 死区时间管理(Dead-Time Management):用于PWM(脉宽调制)应用,以防止H桥类型驱动器中的上下桥臂同时导通。

  7. 波形模式(Waveform Mode):这是一种高级模式,用于生成复杂的波形。在这种模式下,可以使用多个HRTIM通道并配置不同的事件和操作。

  8. 突发模式(Burst Mode):允许HRTIM在特定条件下自动启动和停止,实现对周期和脉冲的精确控制。

ADC


文章作者: Harry Zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Harry Zhang !
  目录