以下所有笔记均基于安富莱V6板
配置文件
为防止递归包含使用如下宏定义进行头文件保护
#ifndef __STM32F4xx_HAL_CONF_H
#define __STM32F4xx_HAL_CONF_H
// 在这里定义你的配置
#endif /* __STM32F4xx_HAL_CONF_H */
递归包含是指在头文件中包含其他头文件时,可能会出现循环依赖的情况。例如,头文件A包含头文件B,而头文件B又包含头文件A,这样就形成了递归包含。
递归包含可能会导致以下问题:
- 编译错误:当头文件A包含头文件B时,编译器会先处理头文件A的内容,然后再处理头文件B的内容。如果头文件B中又包含了头文件A,就会产生循环依赖,编译器无法解析这种情况,导致编译错误。
- 重复定义:如果递归包含导致某个符号在多个头文件中被定义多次,就会导致重复定义的错误。这会导致链接错误,使得程序无法正常编译和运行。
在配置文件中对于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)通常是指定时器中的一个功能,它允许定时器在计数达到预设值后自动重置并重新开始计数。这种功能对于创建周期性事件非常有用,如周期性中断或定期检查某个条件。
使用重复周期计数器的基本步骤如下:
定时器配置:首先设置定时器的基本参数,包括时钟源、预分频值(用于确定计数器的计数速度)和自动重载值(ARR,用于设定计数器达到该值时重置)。
设置重复计数器(如果可用):某些STM32定时器模型中含有一个专门的重复计数器(REP寄存器),可以用来设置定时器在自动重载后重复计数的次数。如果设置为N,则定时器将完成N+1个周期后产生一个更新事件(包括中断,如果已启用)。
中断配置(可选):如果需要在每个周期结束时执行特定的操作,可以配置并启用更新事件中断(UIE)。
启动定时器:最后,启动定时器以开始计数。
工作模式
单脉冲模式(Single-Shot Mode):在这种模式下,HRTIM在每次启动时只生成一个脉冲。这种模式通常用于精确控制时间长度的单次事件。
重复模式(Repetitive Mode):HRTIM会不断重复生成波形。这种模式适用于需要连续产生周期性波形的应用。
连续模式(Continuous Mode):在连续模式下,HRTIM持续运行,不断循环计数。这种模式通常用于持续跟踪时间或产生连续的波形。
比较模式(Compare Mode):HRTIM可以在计数器值达到预设值时生成一个事件。这种模式适用于需要在特定时间点执行操作的应用。
捕获模式(Capture Mode):在捕获模式下,HRTIM能够捕获并记录外部事件(如输入信号的边沿)发生时的计数器值,这对于测量外部事件的时间非常有用。
死区时间管理(Dead-Time Management):用于PWM(脉宽调制)应用,以防止H桥类型驱动器中的上下桥臂同时导通。
波形模式(Waveform Mode):这是一种高级模式,用于生成复杂的波形。在这种模式下,可以使用多个HRTIM通道并配置不同的事件和操作。
突发模式(Burst Mode):允许HRTIM在特定条件下自动启动和停止,实现对周期和脉冲的精确控制。