16.1 初学者重要提示
对于一些常用的函数,大家一定要熟练的掌握都实现了什么功能,比如HAL_Init,HAL_RCC_OscConfig,HAL_RCC_ClockConfig等。最好的办法是把这些函数的源码读一遍。
16.2 那些是必备的API
这里我们通过一个简单的初始化流程来了解STM32F4的工程模板所必备的库文件和API:
第1步:系统上电复位,进入启动文件startup_stm32f429xx.s,在这个文件里面执行复位中断服务程序。
在复位中断服务程序里面执行函数SystemInit,此函数在文件system_stm32f4xx.c里面。
之后是调用编译器封装好的函数,比如用于MDK的启动文件是调用__main,最终进入到main函数。
第2步:进入到main函数就可以开始用户应用程序编程了。在这个函数里面要做几个重要的初始化,依次是:
HAL库初始化函数HAL_Init,需要用到文件stm32f4xx_hal.c。
系统时钟初始化,需要用到库文件stm32f4xx_hal_rcc.c。
前面的两步完成后,就可以开始做用户需要的按键、串口等方面的初始化和应用代码的实现了。这里把我们需要学习的几个库文件整理出来,依次有:
startup_stm32f429xx.s
system_stm32f4xx.c
stm32f4xx_hal.c
stm32f4xx_hal_cortex.c
stm32f4xx_hal_rcc.c
core_cm4.h
其中startup_stm32f429xx.s和system_stm32f4xx.c已经在第13章为大家讲解过,这里不再赘述。。本章教程重点为大家讲解文件stm32f4xx_hal.c、stm32f4xx_hal_cortex.c和sm32f4xx_hal_rcc.c(core_cm4.h文件在后面章节为大家讲解)。
16.3 源文件stm32f4xx_hal.c(重要)
这个文件比较杂,像基准电压大小配置,EXTI配置,IO补偿配置等都在这个文件里面设置。学习这个文件注意事项:
HAL库中各个外设驱动里面的延迟实现是基于此文件提供的时间基准,而这个时间基准既可以使用滴答定时器实现也可以使用通用的定时器实现,默认情况下是用的滴答定时器。
函数HAL_Init里面会调用时间基准初始化函数HAL_InitTick,而调用函数HAL_RCC_ClockConfig也会调用时间基准初始化函数HAL_InitTick。
如果在中断服务程序里面调用延迟函数HAL_Delay要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。
16.3.1 函数HAL_Init
函数原型:
HAL_StatusTypeDef HAL_Init(void)
{
/* 配置Flash的指令预取,指令Cache和数据Cache */
#if (INSTRUCTION_CACHE_ENABLE != 0U)
__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif
#if (DATA_CACHE_ENABLE != 0U)
__HAL_FLASH_DATA_CACHE_ENABLE();
#endif
#if (PREFETCH_ENABLE != 0U)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif
/* 设置中断优先级分组 */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* 使用滴答定时器做为默认时基,配置为1ms滴答,另外系统上电后默认使用的HSI时钟 */
HAL_InitTick(TICK_INT_PRIORITY);
/* 初始化底层硬件 */
HAL_MspInit();
/* 返回HAL_OK */
return HAL_OK;
}
函数描述:
此函数用于初始化HAL库,此函数主要实现如下功能:
设置NVIC优先级分组是4。
设置滴答定时器的每1ms中断一次。
HAL库不像之前的标准库,在系统启动函数SystemInit里面做了RCC初始化,HAL库是没有做的,所以进入到main函数后,系统还在用内部高速时钟HSI,对于F4来说,HSI主频是16MHz。
函数HAL_Init里面调用的HAL_MspInit一般在文件stm32f4xx_hal_msp.c里面做具体实现,主要用于底层初始化。当前此函数也在文件stm32f4xx_hal.c里面,只是做了弱定义。
函数参数:
返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
注意事项:
必须在main函数里面优先调用此函数。
用户务必保证每1ms一次滴答中断。
关于优先级分组的设置可以看第xx章节(注:后面做到这个章节时补上)。
使用举例:
此函数的使用比较简单,上电后优先调用即可。
16.3.2 函数HAL_DeInit
函数原型:
HAL_StatusTypeDef HAL_DeInit(void)
{
/* 复位所有外设 */
__HAL_RCC_APB1_FORCE_RESET();
__HAL_RCC_APB1_RELEASE_RESET();
__HAL_RCC_APB2_FORCE_RESET();
__HAL_RCC_APB2_RELEASE_RESET();
__HAL_RCC_AHB1_FORCE_RESET();
__HAL_RCC_AHB1_RELEASE_RESET();
__HAL_RCC_AHB2_FORCE_RESET();
__HAL_RCC_AHB2_RELEASE_RESET();
__HAL_RCC_AHB3_FORCE_RESET();
__HAL_RCC_AHB3_RELEASE_RESET();
/* 复位底层硬件初始化 */
HAL_MspDeInit();
/* 返回值 */
return HAL_OK;
}
函数描述:
此函数用于复位HAL库和滴答时钟。
复位了APB1,2的时钟以及AHB1,2,3的时钟。
函数HAL_DeInit里面调用的HAL_MspDeInit一般在文件stm32f4xx_hal_msp.c里面做具体实现,主要用于底层初始化,跟函数HAL_Init里面调用的HAL_MspInit是一对。当前此函数也在文件stm32f4xx_hal.c里面,只是做了弱定义。
使用举例:
此函数的使用比较简单,需要调用的时候直接调用即可。
16.3.3 函数HAL_InitTick
函数原型:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
函数描述:
此函数用于初始化滴答时钟,此函数相关问题如下:
此函数有个前缀__weak ,表示弱定义,用户可以重定义。
此函数用于初始化滴答时钟1ms中断一次,并且为滴答中断配置一个用户指定的优先级。
此函数由HAL_Init调用,或者任何其它地方调用函数HAL_RCC_ClockConfig配置RCC的时候也会调用HAL_InitTick。
调用基于此函数实现的HAL_Delay要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。
函数参数:
形参TickPriority用于设置滴答定时器优先级。
返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
使用举例:
此函数由HAL_Init调用,无需用户操作,除非需要重定义。
16.3.4 Systick的相关函数
调用了函数HAL_Init后,Systick相关的函数就可以使用了。这些函数如下:
函数原型:
__weak void HAL_IncTick(void)
__weak uint32_t HAL_GetTick(void)
uint32_t HAL_GetTickPrio(void)
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)
HAL_TickFreqTypeDef HAL_GetTickFreq(void)
__weak void HAL_Delay(uint32_t Delay)
__weak void HAL_SuspendTick(void)
__weak void HAL_ResumeTick(void)
函数描述:
这些函数就比较简单了,下面把这些函数实现的功能做个简单的说明:
函数HAL_IncTick在滴答定时器中断里面被调用,实现一个简单的计数功能,因为一般滴答定时器中断都是配置的1ms,所以计数全局变量uwTick每毫秒加1。
函数HAL_GetTick用于获取全局变量uwTick当前的计数。
函数HAL_GetTickPrio用于获取滴答时钟优先级。
函数HAL_SetTickFreq和HAL_GetTickFreq是一对,前者用于设置滴答中断频率,后再用于获取滴答中断频率。
函数HAL_Delay用于阻塞式延迟,默认单位是ms。
函数HAL_SuspendTick和HAL_ResumeTick是一对,前者用于挂起滴答定时器,后者用于恢复。
注意事项:
函数有个前缀__weak ,表示弱定义,用户可以重定义。
使用举例:
这些函数都比较简单,这里就不举例了。需要的时候,直接调用即可。
16.3.5 低功耗状态下继续使用调试功能
如果希望在睡眠,停机和待机的低功耗模式下继续使用调试功能,调用下面的函数即可:
/* 睡眠模式下继续使用调试功能 */
void HAL_DBGMCU_EnableDBGSleepMode(void)
{
SET_BIT(DBGMCU->CR, DBGMCU_CR_DBG_SLEEP);
}
/* 停机模式下继续使用调试功能 */
void HAL_DBGMCU_EnableDBGStopMode(void)
{
SET_BIT(DBGMCU->CR, DBGMCU_CR_DBG_STOP);
}
/* 待机模式下继续使用调试功能 */
void HAL_DBGMCU_EnableDBGStandbyMode(void)
{
SET_BIT(DBGMCU->CR, DBGMCU_CR_DBG_STANDBY);
}
16.4 源文件stm32f4xx_hal_rcc.c
这个文件主要是实现内部和外部时钟(HSE、HSI、LSE、LSI、PLL、CSS、MCO)以及总线时钟(SYSCLK、AHB1、AHB2、AHB3、APB1)的配置。
学习这个文件注意事项:
1、 系统上电复位后,通过内部高速时钟HSI运行(主频16MHz),Flash工作在0等待周期,所有外设除了SRAM、Flash、JTAG 和 PWR,时钟都是关闭的。
AHB和APB总线无分频,所有挂载这两类总线上的外设都是以HSI频率运行。
所有的GPIO都是模拟模式,除了JTAG相关的几个引脚。
2、 系统上电复位后,用户需要完成以下工作:
选择用于驱动系统时钟的时钟源。
配置系统时钟频率和Flash设置。
配置分频器。
使能外设时钟。
配置外设时钟源,部分外设的时钟可以不来自系统时钟(I2S, RTC, ADC, USB OTG FS/SDIO/RNG)。
RCC局限性:
使能了外设时钟后,不能立即操作对应的寄存器,要加延迟。不同外设延迟不同:
如果是AHB的外设,使能了时钟后,需要等待2个AHB时钟周期才可以操作这个外设的寄存器。
如果是APB的外设,使能了时钟后,需要等待2个APB时钟周期才可以操作这个外设的寄存器。
当前HAL库的解决方案是在使能了外设时钟后,再搞一个读操作,算是当做延迟用。
比如下面使能GPIOA的时钟:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do {
__IO uint32_t tmpreg = 0x00U;
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
/* Delay after an RCC peripheral clock enabling */
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
UNUSED(tmpreg);
} while(0U)
关于时钟方面的知识点补充
1、正确理解PCLK1,2和HCLK对应哪些总线的时钟,下面一张图可以说明问题:
由上图可以得出:
PCLK1、PC对应的是APB总线APB1,APB2时钟。
HCLK对应的AHB总线。
2、内部和外部时钟配置:
HSI (high-speed internal)
高速内部RC振荡器,可以直接或者通过PLL倍频后做系统时钟源。缺点是精度差些,即使经过校准。
LSI (low-speed internal)
低速内部时钟,主要用于独立看门狗和RTC的时钟源。
HSE (high-speed external)
高速外部晶振,可接4 - 26z的晶振,可以直接或者通过PLL倍频后做系统时钟源,也可以做RTC的是时钟源。
LSE (low-speed external)
低速外部晶振,主要用于RTC。
CSS (Clock security system)
时钟安全系统,一旦使能后,如果HSE启动失败(不管是直接作为系统时钟源还是通过PLL输出后做系统时钟源),系统时钟将切换到HSI。如果使能了中断的话,将进入不可屏蔽中断NMI。
MCO1 (micro controller clock output)
可以在PA8引脚输出SYSCLK、PLLI2SCLK、HSE和PLLCLK。
MCO2 (micro controller clock output)
可以在PC9引脚输出LSE、HSE、HSI和PLLCLK。
PLL锁相环,时钟输入来自HSI , HSE 或者CSI
16.4.1 函数HAL_RCC_DeInit
函数原型:
__weak HAL_StatusTypeDef HAL_RCC_DeInit(void)
{
return HAL_OK;
}
函数描述:
文件stm32f4xx_hal_rcc.c里面的此函数是空的(做了__weak弱定义,方便用户再其它文件里面重定义),ST将具体实现放在了stm32f4xx_hal_rcc_ex.c里面的此函数里面
此函数用于RCC复位函数,主要实现如下功能:
HSI 打开作为系统时钟。
HSE和PLL关闭。
AHB, APB1和APB2总线无分频 。
CSS, MCO1 和 MCO2 关闭。
所有中断关闭。
注意事项:
此函数不会修改外设时钟,LSI、LSE和RTC时钟。
使用举例:
此函数的使用比较简单,需要调用的时候直接调用即可。
16.4.2 函数HAL_RCC_OscConfig
函数原型:
函数描述:
通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了HSE、HSI、LSI、LSE和PLL。
函数参数:
函数的形参是RCC_OscInitTypeDef类型结构体变量,这个结构体的定义如下(主要是HSE、HSI、LSI、LSE和PLL的配置变量):
/**
* @brief RCC Internal/External Oscillator (HSE, HSI, LSE and LSI) configuration structure definition
*/
typedef struct
{
uint32_t OscillatorType;
uint32_t HSEState;
uint32_t LSEState;
uint32_t HSIState;
uint32_t HSICalibrationValue;
uint32_t LSIState;
RCC_PLLInitTypeDef PLL;
}RCC_OscInitTypeDef;
注意事项:
LSE Bypass 切换到 LSE On 或者 LSE On切换到 LSE Bypass都不支持,用需要先关闭LSE,然后才可以切换到LSE Bypass 或者LSE On。
HSE Bypass 切换到 HSE On 或者 LSE On切换到 LSE Bypass都不支持,用需要先关闭HSE,然后才可以切换到HSE Bypass 或者HSE On。
使用举例:
RCC_OscInitTypeDef RCC_OscInitStruct;
/* 使能HSE,并选择HSE作为PLL时钟源 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
16.4.3 函数HAL_RCC_ClockConfig
函数原型:
函数描述:
通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了HCLK、SYSCLK、PLCK1和PLCK2。
上一篇:第17章 STM32F429之GPIO的HAL库API
下一篇:第15章 STM32F429的GPIO基础知识(重要)
推荐阅读最新更新时间:2024-11-13 11:45
设计资源 培训 开发板 精华推荐
- 当 LTC2377CMS-16 中启用数字增益压缩时,使用 LT6350 的典型应用被配置为接受 ±10V 输入信号,同时运行一个 5.5V 单电源
- #第三届立创大赛#便携调试用数控电源
- 使用 Infineon Technologies AG 的 TDA 16847G 的参考设计
- NSI45015WT1G 大电流 LED 灯串的典型应用
- MAXREFDES9000:基于DS28E18的远程两线供电及控制I2C和SPI接口传感器
- 使用 ams AG 的 AS1312-BTDT 的参考设计
- 使用 Analog Devices 的 LTC1261CS 的参考设计
- LT3437、14V 至 3.3V 降压转换器具有 100A 空载静态电流
- RK3399屏幕转接小板(7寸MIPI-1024*600)
- DI-91 - 12 W 通用输入 CV 适配器