本章教程为大家讲解制作一个STM32H7的例子所需的最基本API函数,对于一些常用的API函数,一定要熟练掌握这些函数都是实现了什么功能,不常用的函数有个了解即可,用到的时候再去学。
16.1 初学者重要提示
16.2 那些是必备的API
16.3 源文件stm32h7xx_hal.c
16.4 stm32h7xx_hal_rcc.c
16.5 stm32h7xx_hal_cortex.c
16.6 总结
这里我们通过一个简单的初始化流程来了解STM32H7的工程模板所必备的库文件和API:
第1步:系统上电复位,进入启动文件startup_stm32h743xx.s,在这个文件里面执行复位中断服务程序。
第2步:进入到main函数就可以开始用户应用程序编程了。在这个函数里面要做几个重要的初始化,依次是:
前面的两步完成后,就可以开始做用户需要的按键、串口等方面的初始化和应用代码的实现了。这里把我们需要学习的几个库文件整理出来,依次有:
其中startup_stm32h743xx.s和system_stm32h7xx.c已经在第13章为大家讲解过,这里不再赘述。而MPU和Cache涉及到的文件core_cm7.h在第23和24章为大家讲解。本章教程重点为大家讲解文件stm32h7xx_hal.c、stm32h7xx_hal_cortex.c和sm32h7xx_hal_rcc.c。
这个文件比较杂,像基准电压大小配置,EXTI配置,IO补偿配置等都在这个文件里面设置。学习这个文件注意事项:
函数原型:
HAL_StatusTypeDef HAL_Init(void)
{
/* 设置中断优先级分组 */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* 使用滴答定时器做为默认时基,配置为1ms滴答,另外系统上电后默认使用的HIS时钟 */
if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
return HAL_ERROR;
}
/* 初始化底层硬件 */
HAL_MspInit();
/* 返回函数状态 */
return HAL_OK;
}
函数描述:
此函数用于初始化HAL库,此函数主要实现如下功能:
函数参数:
注意事项:
使用举例:
此函数的使用比较简单,上电后优先调用即可。
函数原型:
HAL_StatusTypeDef HAL_DeInit(void)
{
/* 复位所有外设 */__set_PRIMASK
__HAL_RCC_AHB3_FORCE_RESET();
__HAL_RCC_AHB3_RELEASE_RESET();
/* 省略未写 */
__HAL_RCC_APB4_FORCE_RESET();
__HAL_RCC_APB4_RELEASE_RESET();
/* 复位底层硬件初始化 */
HAL_MspDeInit();
/* 返回值 */
return HAL_OK;
}
函数描述:
此函数用于复位HAL库和滴答时钟。
使用举例:
此函数的使用比较简单,需要调用的时候直接调用即可。
函数原型:
__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;
}
函数描述:
此函数用于初始化滴答时钟,此函数相关问题如下:
函数参数:
使用举例:
此函数由HAL_Init调用,无需用户操作,除非需要重定义。
调用了函数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)
函数描述:
这些函数就比较简单了,下面把这些函数实现的功能做个简单的说明:
注意事项:
使用举例:
这些函数都比较简单,这里就不举例了。需要的时候,直接调用即可。
函数原型:
void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling)
函数描述:
此函数用于配置STM32H7内部电压基准大小。
输出基准是2.048 V,条件是VDDA >= 2.4V。
输出基准是2.5 V,条件是VDDA >= 2.8V。
输出基准是1.5 V,条件是VDDA >= 1.8V。
输出基准是1.8 V,条件是VDDA >= 2.1V。
函数原型:
void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling)
void HAL_SYSCFG_VREFBUF_TrimmingConfig(uint32_t TrimmingValue)
HAL_StatusTypeDef HAL_SYSCFG_EnableVREFBUF(void)
void HAL_SYSCFG_DisableVREFBUF(void)
函数描述:
此函数用于配置STM32H7内部电压基准是否在芯片内部与VREF+引脚接通。
此函数用于内部电压基准的校准调节。
函数原型:
void HAL_SYSCFG_AnalogSwitchConfig(uint32_t SYSCFG_AnalogSwitch , uint32_t SYSCFG_SwitchState )
函数描述:
函数原型:
void HAL_SYSCFG_EnableBOOST(void)
void HAL_SYSCFG_DisableBOOST(void)
函数描述:
函数原型:
void HAL_SYSCFG_CM7BootAddConfig(uint32_t BootRegister, uint32_t BootAddress)
函数描述:
用于配置BOOT = 0或者BOOT = 1时的启动地址,详情可以看第13章的13.4小节。
函数原型:
void HAL_EnableCompensationCell(void)
void HAL_DisableCompensationCell(void)
void HAL_SYSCFG_EnableIOSpeedOptimize(void)
void HAL_SYSCFG_DisableIOSpeedOptimize(void)
void HAL_SYSCFG_CompensationCodeSelect(uint32_t SYSCFG_CompCode)
void HAL_SYSCFG_CompensationCodeConfig(uint32_t SYSCFG_PMOSCode, uint32_t SYSCFG_NMOSCode )
函数描述:
分别用于使能或者禁止IO补偿,只有在供电电压范围是2.4V到3.6V之间时,使用此功能才有意义。
分别用于优化IO速度或者禁止优化,不过仅在供电电压低于2.5V时可用,高于2.5V是不可以使用的,另外使用这个功能的前提是用户使能了PRODUCT_BELOW_25V(是可选字节配置选项里面的一个bit)。
IO补偿单元的选择,函数形参可以是SYSCFG_CELL_CODE,即寄存器SYSCFG_CCVR,也可以是SYSCFG_REGISTER_CODE,即寄存器SYSCFG_CCCR。
用于设置GPIO内部构造中NMOS和PMOS的补偿值,两个形参的范围都是0-15。根据用户调用函数HAL_SYSCFG_CompensationCodeSelect选择的寄存器,这里仅有一个形参的设置是有效的。
低功耗和EXTI相关的函数暂时不做讲解,后面章节用到时候再说明。
这个文件主要是实现内部和外部时钟(HSE、HSI、LSE、CSI、LSI、HSI48、PLL、CSS、MCO)以及总线时钟(SYSCLK、AHB3、 AHB1、AHB2、AHB4、APB3、APB1L、APB1H、APB2、 APB4)的配置。
学习这个文件注意事项:
RCC局限性:
使能了外设时钟后,不能立即操作对应的寄存器,要加延迟。不同外设延迟不同:
当前HAL库的解决方案是在使能了外设时钟后,再搞一个读操作,算是当做延迟用。
比如下面使能GPIOA的时钟:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
UNUSED(tmpreg); \
} while(0)
1、正确理解HCLK1,2,3,4以及PCLK1,2,3,4对应哪些总线的时钟,下面一张图可以说明问题:
由上图可以得出:
HCLK1,2,3,4对应的是AHB总线AHB1,AHB2,AHB3和AHB4时钟。
PCLK1、2、3、4对应的是APB总线APB1,APB2,APB3和APB4时钟。
2、内部和外部时钟配置:
高速内部RC振荡器,可以直接或者通过PLL倍频后做系统时钟源。缺点是精度差些,即使经过校准。
低功耗内部RC振荡器,作用同HSI,不过主要用于低功耗。
低速内部时钟,主要用于独立看门狗和RTC的时钟源。
高速外部晶振,可接4 - 48MHz的晶振,可以直接或者通过PLL倍频后做系统时钟源,也可以做RTC的是时钟源。
低速外部晶振,主要用于RTC。
时钟安全系统,一旦使能后,如果HSE启动失败(不管是直接作为系统时钟源还是通过PLL输出后做系统时钟源),系统时钟将切换到HSI。如果使能了中断的话,将进入不可屏蔽中断NMI。
可以在PA8引脚输出HSI, LSE, HSE, PLL1(PLL1_Q)或HSI48 时钟。
可以在PC9引脚输出HSE, PLL2(PLL2_P), SYSCLK, LSI, CSI或PLL1(PLL1_P) 时钟。
主锁相环PLL1,用于给CPU和部分外设提供时钟。
专用锁相环PLL2和PLL3,用于给部分外设提供时钟。
3、System, AHB 和 APB总线时钟配置:
函数原型:
void HAL_RCC_DeInit(void)
{
/* Set HSION bit */
SET_BIT(RCC->CR, RCC_CR_HSION);
/* Reset CFGR register */
CLEAR_REG(RCC->CFGR);
/* 省略未写 */
/* Reset HSEBYP bit */
CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP);
/* Disable all interrupts */
CLEAR_REG(RCC->CICR);
}
函数描述:
此函数用于RCC复位函数,主要实现如下功能:
注意事项:
使用举例:
此函数的使用比较简单,需要调用的时候直接调用即可。
函数原型:
函数描述:
通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了HSE、HSI、CSI、LSI、HSI48、LSE和PLL。
函数参数:
/**
* @brief RCC Internal/External Oscillator (HSE, HSI, CSI, 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;
uint32_t HSI48State;
uint32_t CSIState;
uint32_t CSICalibrationValue;
RCC_PLLInitTypeDef PLL;
}RCC_OscInitTypeDef;
注意事项:
使用举例:
RCC_OscInitTypeDef RCC_OscInitStruct;
HAL_StatusTypeDef ret = HAL_OK;
/* 使能HSE,并选择HSE作为PLL时钟源 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 5;
RCC_OscInitStruct.PLL.PLLN = 160;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
if(ret != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
函数原型:
函数描述:
通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了CPU、AHB和APB的所有总线时钟,即HCLK,SYSTICK,D1PCLK1,PCLK1,PCLK2和D3PCLK1时钟。这里的D1PCLK1其实就是PCLK3,而D3PCLK1是PCLK4。
函数参数:
注意事项:
使用举例:
RCC_ClkInitTypeDef RCC_ClkInitStruct;
HAL_StatusTypeDef ret = HAL_OK;
/*
选择PLL的输出作为系统时钟
配置RCC_CLOCKTYPE_SYSCLK系统时钟
配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线
配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线
配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线
配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线
*/
RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 |
RCC_CLOCKTYPE_PCLK1 | \
RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
/* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
if(ret != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
函数原型:
void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* Check the parameters */
assert_param(IS_RCC_MCO(RCC_MCOx));
assert_param(IS_RCC_MCODIV(RCC_MCODiv));
/* RCC_MCO1 */
if(RCC_MCOx == RCC_MCO1)
{
/* 省略未写 */
}
else
{
/* 省略未写 */
}
}
函数描述:
此函数的作用是配置MCO1(PA8引脚)和MCO2(PC9引脚)的时钟输出以及选择的时钟源,通过下面的截图可以很好的说明此函数的作用:
函数参数:
MCO1可选择的时钟源如下:
使用举例:
此函数的使用比较简单,需要在MCO引脚输出时钟的时候,直接调用即可。
函数原型:
void HAL_RCC_EnableCSS(void)
uint32_t HAL_RCC_GetSysClockFreq(void)
uint32_t HAL_RCC_GetHCLKFreq(void)
uint32_t HAL_RCC_GetPCLK1Freq(void)
uint32_t HAL_RCC_GetPCLK2Freq(void)
void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t *pFLatency)
剩下的这些函数都比较简单了,我们这里就不做讲解了。
这个库文件主要功能是NVIC,MPU和Systick的配置。此文件有个臃肿的地方,里面的API其实就是将ARM的CMSIS库各种API重新封装了一遍。这么做的好处是保证了HAL的API都是以字母HAL开头。
更多NVIC相关知识需要大家看第21章节。
更多SysTick相关知识需要大家看第22章节。
更多MPU相关知识需要大家看第23章节。
函数原型:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
void HAL_NVIC_SystemReset(void)
uint32_t HAL_NVIC_GetPriorityGrouping(void)
void HAL_NVIC_GetPriority(IRQn_Type IRQn, uint32_t PriorityGroup, uint32_t *pPreemptPriority, uint32_t *pSubPriority)
void HAL_NVIC_SetPendingIRQ(IRQn_Type IRQn)
uint32_t HAL_NVIC_GetPendingIRQ(IRQn_Type IRQn)
void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn)
uint32_t HAL_NVIC_GetActive(IRQn_Type IRQn)
这些函数的操作就比较容易了,这里就不做讲解了,用到时调用即可。
函数原型:
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource)
void HAL_SYSTICK_IRQHandler(void)
__weak void HAL_SYSTICK_Callback(void)
这些函数的操作就比较容易了,这里就不做讲解了,用到时调用即可。
函数原型:
void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init)
{
/* 部分省略未写 */
/* Set the Region number */
MPU->RNR = MPU_Init->Number;
if ((MPU_Init->Enable) != RESET)
{
/* 部分省略未写 */
MPU->RBAR = MPU_Init->BaseAddress;
MPU->RASR = ((uint32_t)MPU_Init->DisableExec << MPU_RASR_XN_Pos) |
((uint32_t)MPU_Init->AccessPermission << MPU_RASR_AP_Pos) |
((uint32_t)MPU_Init->TypeExtField << MPU_RASR_TEX_Pos) |
((uint32_t)MPU_Init->IsShareable << MPU_RASR_S_Pos) |
((uint32_t)MPU_Init->IsCacheable << MPU_RASR_C_Pos) |
((uint32_t)MPU_Init->IsBufferable << MPU_RASR_B_Pos) |
((uint32_t)MPU_Init->SubRegionDisable << MPU_RASR_SRD_Pos) |
((uint32_t)MPU_Init->Size << MPU_RASR_SIZE_Pos) |
((uint32_t)MPU_Init->Enable << MPU_RASR_ENABLE_Pos);
}
else
{
MPU->RBAR = 0x00;
MPU->RASR = 0x00;
}
}
此函数在本教程第23章有专门的讲解。
本章节就为大家讲解这么多,对于一些常用的函数,望大家务必要掌握。