STM32F429第四篇之跑马灯程序详解

前言

本文以上篇博文<STM32F429第三篇之GPIO的模板工程构建>构建的项目为历程,讲解在HAL库中如何操作控制GPIO的输出,以及STM32编程的步骤。

本文主要参考文献为:

  • 正点原子.STM32F429开发指南——HAL库版本
  • STM32F429xx中文数据手册——DocID024030 Rev 4
  • RM0090 参考手册——文档 ID 018909 第 4 版
  • RM0090 Reference manual——RM0090 Rev 18
  • Cortex-M3 权威指南

本文更新顺序:

  • 20200911——更新LED驱动程序部分,以及HAL库讲解中的GPIO部分。
  • 20201020——更新主程序中的RCC时钟初始化部分,延时函数初始化部分。
  • 20201023——更新RCC时钟初始化部分和RCC部分。
  • 20201104——更新振荡器初始化程序部分。
  • 20201106——更新了时钟初始化部分。

硬件

在这里插入图片描述

软件

编写代码

在该程序中,主要需要编写两个部分的代码:

  • 主程序
  • led初始化程序

下面分别讲解:

主程序

#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{

    HAL_Init();                     //初始化HAL库    
	Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
   	
	delay_init(180);                //初始化延时函数
   	LED_Init();                     //初始化LED
    
	while(1)
    {
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); 	//LED0对应引脚PB1拉低,亮
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);   //LED1对应引脚PB0拉高,灭
        delay_ms(500);										//延时500ms
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET); //LED0对应引脚PB1拉高,灭
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET); //LED1对应引脚PB0拉低,亮
        delay_ms(500);                                      //延时500ms

    }
	
}


主程序很简单,可以概括为以下几个步骤:

  • HAL库初始化
  • RCC时钟初始化
  • 相关外设初始化
    • 延时函数初始化
    • LED相关的GPIO初始化
  • while循环
HAL库初始化

HAL初始化部分比较复杂,涉及的内容比较多,这部分的详细讲解以后补上。这里大致说明其所实现的功能有4个方面:

  • 初始化FLASH部分。使能FLASH的预存,数据缓存,指令缓存。
  • 设置NVIC组的优先级为4;
  • 将systick(系统定时器)作为time base的时钟源,且将其配置为1ms。
  • Msp初始化。

其源程序如下:

/**
  * @brief  This function is used to initialize the HAL Library; it must be the first 
  *         instruction to be executed in the main program (before to call any other
  *         HAL function), it performs the following:
  *           Configure the Flash prefetch, instruction and Data caches.
  *           Configures the SysTick to generate an interrupt each 1 millisecond,
  *           which is clocked by the HSI (at this stage, the clock is not yet
  *           configured and thus the system is running from the internal HSI at 16 MHz).
  *           Set NVIC Group Priority to 4.
  *           Calls the HAL_MspInit() callback function defined in user file 
  *           "stm32f4xx_hal_msp.c" to do the global low level hardware initialization 
  *            
  * @note   SysTick is used as time base for the HAL_Delay() function, the application
  *         need to ensure that the SysTick time base is always set to 1 millisecond
  *         to have correct HAL operation.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch, Instruction cache, Data cache */ 
#if (INSTRUCTION_CACHE_ENABLE != 0U)
   __HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE != 0U)
   __HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */

#if (PREFETCH_ENABLE != 0U)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);
  
  /* Init the low level hardware */
  HAL_MspInit();
  
  /* Return function status */
  return HAL_OK;
}
RCC时钟初始化

RCC时钟初始化主要通过函数Stm32_Clock_Init实现,需要注意的是,该函数并非是HAL库官方提供的函数,而是由正点原子实现。其源程序如下:

void Stm32_Clock_Init(u32 plln, u32 pllm, u32 pllp, u32 pllq)
{
    HAL_StatusTypeDef ret = HAL_OK;

    /***********************************1.使能PWR时钟*****************************************************/
    __HAL_RCC_PWR_CLK_ENABLE();

    /***********************************2.设置调压器输出电压级别*******************************************/
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);	//设置调压器输出电压级别1

    /***********************************3.配置时钟源相关参数**********************************************/
    RCC_OscInitTypeDef RCC_OscInitStructure;
    RCC_OscInitStructure.OscillatorType = RCC_OSCILLATORTYPE_HSE;   	//振荡器类型为HSE(外部高速振荡器)
    RCC_OscInitStructure.HSEState = RCC_HSE_ON;                      	//打开HSE
    RCC_OscInitStructure.PLL.PLLState = RCC_PLL_ON;						//打开PLL
    RCC_OscInitStructure.PLL.PLLSource = RCC_PLLSOURCE_HSE;				//PLL时钟源选择HSE
    RCC_OscInitStructure.PLL.PLLM = pllm; 								//主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
    RCC_OscInitStructure.PLL.PLLN = plln; 								//主PLL倍频系数(PLL倍频),取值范围:64~432.
    RCC_OscInitStructure.PLL.PLLP = pllp; 								//系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
    RCC_OscInitStructure.PLL.PLLQ = pllq; 								//USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
    ret = HAL_RCC_OscConfig(&RCC_OscInitStructure);						//振荡器参数初始化
    if(ret != HAL_OK) while(1);

    /***********************************4.开启over-driver功能**********************************************/
    ret = HAL_PWREx_EnableOverDrive();														//开启Over-Driver功能
    if(ret != HAL_OK) while(1);

    /***********************************5.配置系统时钟相关参**********************************************/
    RCC_ClkInitTypeDef RCC_ClkInitStructure;
    //选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2
    RCC_ClkInitStructure.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStructure.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;					//设置系统时钟时钟源为PLL
    RCC_ClkInitStructure.AHBCLKDivider = RCC_SYSCLK_DIV1;							//AHB分频系数为1
    RCC_ClkInitStructure.APB1CLKDivider = RCC_HCLK_DIV4; 							//APB1分频系数为4
    RCC_ClkInitStructure.APB2CLKDivider = RCC_HCLK_DIV2; 							//APB2分频系数为2
    ret = HAL_RCC_ClockConfig(&RCC_ClkInitStructure, FLASH_LATENCY_5);				//同时设置FLASH延时周期为5WS,也就是6个CPU周期。
    if(ret != HAL_OK) while(1);
}

上述代码可以大致分成以下五个部分:

  1. 使能PWR时钟。
  2. 设置调压器输出的电压级别。
  3. 配置时钟源相关参数。
  4. 开启Over-Driver功能。
  5. 配置系统时钟相关参数。

其中,第1,2,4步骤与PWR模块有关系。此处不再详细解释。第1步之所以需要使能PWR时钟,是因为在第2步和第4步都需要电源相关的配置。
我们确定电源电压和HCLK时钟频率之后,电压几倍VOS,Over-Driver功能和FLASH的延时Latency参数是固定的。VOS的参数含义官方解释为:
在这里插入图片描述

通过查询数据手册可以得到:
在这里插入图片描述
通过以上官方文档可以了解,若需要将STM32F429运行在最高时钟频率180MHz,则需要选择电源级别为1,且打开 超载(Over-Driver) 功能。而上述的第1,2,4步骤即实现该功能,让ARM可以运行在频率180MHz处。
步骤3和步骤5是通过HAL库中RCC功能实现时钟分配,关于此处使用到的结构体和函数的用法可以参考博客<STM32F429第八篇之stm32f4xx_hal_rcc>。关于RCC时钟配置的更多信息,可以参考博客<STM32F429第七篇之RCC(复位与时钟)>

Flash等待周期可以通过下表确定:
在这里插入图片描述
一般地,我们开发板工作在3.3V,180MHz的环境下,因此可知,等待周期为5WS(6CPU周期)。

延时函数初始化
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init ( u8 SYSCLK )
{
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
    u32 reload;
#endif



    HAL_SYSTICK_CLKSourceConfig ( SYSTICK_CLKSOURCE_HCLK );		//SysTick频率为HCLK
    fac_us = SYSCLK;											//不论是否使用OS,fac_us都需要使用


#if SYSTEM_SUPPORT_OS 											//如果需要支持OS.
    reload = SYSCLK;					    					//每秒钟的计数次数 单位为K
    reload *= 1000000 / delay_ostickspersec;					//根据delay_ostickspersec设定溢出时间
    //reload为24位寄存器,最大值:16777216,在180M下,约合0.745s左右
    fac_ms = 1000 / delay_ostickspersec;						//代表OS可以延时的最少单位
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;					//开启SYSTICK中断
    SysTick->LOAD = reload; 									//每1/OS_TICKS_PER_SEC秒中断一次
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; 					//开启SYSTICK
#else
#endif
}

LED驱动

头文件

#ifndef _LED_H
#define _LED_H

extern void LED_Init(void);

#endif

C文件

#include "led.h"
#include "stm32f4xx.h"

void LED_Init(void)
{

    __HAL_RCC_GPIOB_CLK_ENABLE();           //开启GPIOB时钟
	
	GPIO_InitTypeDef GPIO_Initure;
    GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; //PB1,0
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
	
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);	//PB0置1,默认初始化后灯灭
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);	//PB1置1,默认初始化后灯灭
}

led驱动程序主要实现以下几个功能:

  • 开启GPIOB的时钟。
  • 对led相关的引脚PB0和PB1进行功能初始化。
  • 初始化LED的初始状态为熄灭状态。

其主要的初始化步骤本质上为GPIO的初始化步骤,其官方的步骤可以参考博客<STM32F429第六篇之stm32f4xx_hal_gpio>中的使用方法 节。

HAL库详解

GPIO

通过编写代码部分,我们可以总结出,该例程关于GPIO部分主要使用了两个HAL库函数:

  • HAL_GPIO_Init();//GPIO初始化程序
  • HAL_GPIO_WritePin();//GPIO位操作

关于两个函数的使用方法,可以参考博客<STM32F429第六篇之stm32f4xx_hal_gpio>中 函数 节。

GPIO初始化
/**
  * @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));
    assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
    //此处没有检查所有的参数

    /* Configure the port pins */
	//进入循环,GPIO_NUMBER为16,表示端口有16个引脚
    for(position = 0U; position < GPIO_NUMBER; position++)
    {
        /* Get the IO position */
        ioposition = ((uint32_t)0x01U) << position;								//当前处理数据位的位置
        /* Get the current IO position */
        iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;					//获取当前位的PIN值是否置1

        if(iocurrent == ioposition)															//当前数据数据位需要初始化
        {
            /*--------------------- GPIO Mode Configuration ------------------------*/
            /* In case of Alternate function mode selection */
            //判断引脚是否为复用功能,若是,将该引脚对应的复用功能写入。
            if((GPIO_Init->Mode == GPIO_MODE_AF_PP) || (GPIO_Init->Mode == GPIO_MODE_AF_OD))
            {
                /* 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];				//AFR数组有两个元素,分别对应AFRL和AFRH。position取值为0-15,其中0-7对应AFRL,8-15对应AFRH。

                //因为已经区分成两个元素。所以,第一步,取低三位的二进制数即可。(position & (uint32_t)0x07U),例如,第3位和第11位的处理方法相同
                //position的取值为[0,15]。其对应的AFRL/AFRH的二进制数位为position*4。每个数位占有4个寄存器AFRL/AFRH二进制位。
                //例如,当position=3时。对应AFRL[15:12]。即(0xF<<(3*4))对应二进制位0000 1111 0000 0000。其中数据1的位置恰好对应AFRL[15:12]。
                //最后,再将结果进行取反,相位与。即将寄存器AFRL/AFRH的对应二进制位清0。其余位不变。
                temp &= ~((uint32_t)0xFU << ((uint32_t)(position & (uint32_t)0x07U) * 4U)) ;
                //和上面执行相同。将GPIO_Init->Alternate的数值写入寄存器AFRL/AFRH的对应二进制位。
                temp |= ((uint32_t)(GPIO_Init->Alternate) << (((uint32_t)position & (uint32_t)0x07U) * 4U));
                GPIOx->AFR[position >> 3U] = temp;				//将改变后的值,复原到AFR寄存器。
            }

            /* Configure IO Direction mode (Input, Output, Alternate or Analog) */
            //将引脚的对应模式写入
            temp = GPIOx->MODER;
            temp &= ~(GPIO_MODER_MODER0 << (position * 2U));				//先将对应位清零,GPIO_MODER_MODER0=3(0b11)
            temp |= ((GPIO_Init->Mode & GPIO_MODE) << (position * 2U));	//再将将对应位置1,GPIO_MODE=3(0b11)
            GPIOx->MODER = temp;

            /* In case of Output or Alternate function mode selection */
            //判断是否为输出或者复用功能。若是,则需要设置引脚的类型与速度。
            if((GPIO_Init->Mode == GPIO_MODE_OUTPUT_PP) || (GPIO_Init->Mode == GPIO_MODE_AF_PP) ||
                    (GPIO_Init->Mode == GPIO_MODE_OUTPUT_OD) || (GPIO_Init->Mode == GPIO_MODE_AF_OD))
            {
                //写入速度
                /* 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) ;												// GPIO_OTYPER_OT_0=0x00000001U(0b0000 0001)
                temp |= (((GPIO_Init->Mode & GPIO_OUTPUT_TYPE) >> 4U) << position);	// GPIO_OUTPUT_TYPE=0x00000010U(0b0001 0000)
                GPIOx->OTYPER = temp;
            }

			//写入上拉,下拉还是浮空功能
            /* 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;

            /*--------------------- EXTI Mode Configuration ------------------------*/
            /* Configure the External Interrupt or event for the current IO */
            if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
            {
                /* Enable SYSCFG Clock */
                __HAL_RCC_SYSCFG_CLK_ENABLE();

                temp = SYSCFG->EXTICR[position >> 2U];
                temp &= ~(((uint32_t)0x0FU) << (4U * (position & 0x03U)));
                temp |= ((uint32_t)(GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));
                SYSCFG->EXTICR[position >> 2U] = temp;

                /* Clear EXTI line configuration */
                temp = EXTI->IMR;
                temp &= ~((uint32_t)iocurrent);
                if((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
                {
                    temp |= iocurrent;
                }
                EXTI->IMR = temp;

                temp = EXTI->EMR;
                temp &= ~((uint32_t)iocurrent);
                if((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
                {
                    temp |= iocurrent;
                }
                EXTI->EMR = temp;

                /* Clear Rising Falling edge configuration */
                temp = EXTI->RTSR;
                temp &= ~((uint32_t)iocurrent);
                if((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
                {
                    temp |= iocurrent;
                }
                EXTI->RTSR = temp;

                temp = EXTI->FTSR;
                temp &= ~((uint32_t)iocurrent);
                if((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
                {
                    temp |= iocurrent;
                }
                EXTI->FTSR = temp;
            }
        }
    }
}

通过上文源程序可知,GPIO初始化共分成两个部分:

  • GPIO模式初始化
  • EXIT模式初始化

本文只涉及到第一部分,所以第二部分略过不谈。

在GPIO初始化的函数总体思路是:

  • 调用一次函数,初始化循环一组端口所有位。
  • 因为一组端口有16个引脚,所以,函数循环16次, 依次判断该引脚Pin对应位是否置1。
  • 若当前引脚对应位置1,那么对该位进行初始化,否则,跳过该位,循环至下一个引脚。
  • 直到16个引脚循环结束。

在GPIO模式初始化部分,基本流程如下所示:

  • 判断该引脚是否为复用功能。若是,将复用功能对应数据写入复用功能寄存器AFRL/AFRH。
  • 将引脚对应的模式(Mode)写入GPIO端口模式寄存器(MODER)。
  • 判断该引脚是否为输出或者复用功能。若是,则将引脚的速度和输出类型分别写入GPIO输出速度寄存器(OSPEEDR)和GPIO端口输出类型寄存器(OTYPER)。
  • 将引脚对应的上拉/下拉功能写入GPIO端口上拉/下拉寄存器(PUPDR)。

其中,写入寄存器一般分成两个步骤:

  • 将引脚对应寄存器数据位清零。
  • 将引脚对应寄存器数据位写入数据。

以复用功能寄存器为例,其源程序如下:

temp = GPIOx->AFR[position >> 3U];				
temp &= ~((uint32_t)0xFU << ((uint32_t)(position & (uint32_t)0x07U) * 4U)) ;
temp |= ((uint32_t)(GPIO_Init->Alternate) << (((uint32_t)position & (uint32_t)0x07U) * 4U));
GPIOx->AFR[position >> 3U] = temp;				

总共分成四个语句,如下:

  1. 因为功能复位寄存器有两个(AFRL和AFRH)。其中AFRH对应端口引脚的高8位,AFRL对应端口引脚低8位。所以,根据position是大于等于8或者小于7来获取对应复用功能寄存器的数值,且保存在temp中。
  2. 将引脚的寄存器对应位清零。
    1. 因为高8位与低8位在上一步已经区分。所以此时,高8位应该以第8位为基点。即第8位等同于第0位,就9位等同于第1位,以此类推。 position & (uint32_t)0x07U相当于将大于8的position减去8,小于8的position不变。
    2. 因为每个引脚对应的复位寄存器是4个二进制位,((uint32_t)(position & (uint32_t)0x07U) * 4U)),将处理过的偏移量*4。即第0偏移到0,第1位偏移到4,从而对应其在寄存器中的二进制对应位。
    3. (uint32_t)0xFU << ((uint32_t)(position & (uint32_t)0x07U) * 4U)。因为0xF 二进制形式为 0b1111,即四个二进制位为1,其余所有位为0。所以,将其左移上一步计算出的偏移量,即将引脚对应的复用功能寄存器位置1,其余位置0。假设position为3的情况下,此时,计算结果二进制形式为 0x 0000 0000 0000 0000 0000 1111 0000 0000
    4. 最后将上一步计算结果取反,且与temp相位与。则,将引脚对应的复位功能寄存器对应位清0,其余位不变。
  3. 该步骤和上一步处理方式基本相同 。即将复用功能数据写入到复位功能寄存器对应的temp二进制位中。
  4. 最后,将temp写入AFR寄存器中。
GPIO位操作
/**
  * @brief  Sets or clears the selected data port bit.
  *
  * @note   This function uses GPIOx_BSRR register to allow atomic read/modify
  *         accesses. In this way, there is no risk of an IRQ occurring between
  *         the read and the modify access.
  *
  * @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 be written.
  *          This parameter can be one of GPIO_PIN_x where x can be (0..15).
  * @param  PinState: specifies the value to be written to the selected bit.
  *          This parameter can be one of the GPIO_PinState enum values:
  *            @arg GPIO_PIN_RESET: to clear the port pin
  *            @arg GPIO_PIN_SET: to set the port pin
  * @retval None
  */
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if(PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
  }
}

该函数比较简单,即判断是需要清零还是置位。

  • 若是需要置位,将GPIO端口置位/复位寄存器(BSRR)对应位置1.
  • 若是需要清零,将GPIO端口置位/复位寄存器(BSRR)对应位+16置1.

RCC

通过编写代码部分,我们可以总结出,该例程关于RCC部分主要使用了两个HAL库函数:

  • HAL_RCC_OscConfig();//初始化振荡器相关参数
  • HAL_RCC_ClockConfig();//初始化系统时钟

关于此两个函数的使用方法,可以参考博客<STM32F429第八篇之stm32f4xx_hal_rcc>

另外还有一个外设初始化的宏,如:

  • __HAL_RCC_PWR_CLK_ENABLE();
  • ___HAL_RCC_GPIOB_CLK_ENABLE();

关于外设初始化宏部分比较简单,此处不再详细展开,可以参考博客<STM32F429第九篇之stm32f4xx_hal_rcc_ex>。

下面重点分析两个函数的源代码。

振荡器参数设置
__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef  *RCC_OscInitStruct)
{
    uint32_t tickstart = 0U;

    /* Check the parameters */
    assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));//检测振荡器类型
    /*------------------------------- HSE Configuration ------------------------*/
    if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)//判断是否有HSE
    {
        /* Check the parameters */
		/********************1.检测参数状态**************************************/
        assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));//判断HSE状态
        /* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */
		/********************2.条件判断:若HSE用作系统时钟,则不可以禁用HSE**************************************/
        if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) || \
                ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))//判断HSE是否已经直接或者间接用于系统时钟
        {
            if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))//若HSE用做系统时钟,则不可以禁用HSE。
            {
                return HAL_ERROR;
            }//若HSE已经成为系统时钟,且并非禁用HSE,则无须任何操作。
        }
		/********************3.设置HSE状态**************************************/
        else//HSE并未直接或者间接用做系统时钟
        {
            /* Set the new HSE configuration ---------------------------------------*/
            __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);//将HSE状态直接写入寄存器

            /* Check the HSE State */
            if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)//若设置的状态不是关闭HSE
            {
                /* Get Start Tick*/
                tickstart = HAL_GetTick();//获得开始时间

                /* Wait till HSE is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)//HSE准备结束就退出循环
                {
                    if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)//等待时间超过等待最大值(100)
                    {
                        return HAL_TIMEOUT;//返回超时
                    }
                }
            }
            else//若设置的状态为关闭HSE
            {
                /* Get Start Tick*/
                tickstart = HAL_GetTick();

                /* Wait till HSE is bypassed or disabled */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
                {
                    if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
                    {
                        return HAL_TIMEOUT;
                    }
                }
            }
        }
    }
    /*----------------------------- HSI Configuration --------------------------*/
    if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSI) == RCC_OSCILLATORTYPE_HSI)//判断是否有HSI
    {
        /* Check the parameters */
        assert_param(IS_RCC_HSI(RCC_OscInitStruct->HSIState));//两种状态RCC_HSI_OFF或者RCC_HSI_ON,此处存疑。
        assert_param(IS_RCC_CALIBRATION_VALUE(RCC_OscInitStruct->HSICalibrationValue));//值小于等于0x1F

        /* Check if HSI is used as system clock or as PLL source when PLL is selected as system clock */
        if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSI) || \
                ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL)&& ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSI)))//判断HSI是否已经直接或者间接用于系统时钟
        {
            /* When HSI is used as system clock it will not disabled */
            if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) != RESET) && (RCC_OscInitStruct->HSIState != RCC_HSI_ON))//HSI已经直接或者间接作为系统时钟,不可以清零。
            {
                return HAL_ERROR;
            }
            /* Otherwise, just the calibration is allowed */
            else//当系统时钟已经直接或者间接使用HSI,而HSI尚未打开或者设置状态不是RCC_HSI_ON
            {
                /* Adjusts the Internal High Speed oscillator (HSI) calibration value.*/
                __HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(RCC_OscInitStruct->HSICalibrationValue);//设置内部高速时钟的微调
            }
        }
        else//若HSI并没有直接或者间接用于系统时钟。
        {
            /* Check the HSI State */
            if((RCC_OscInitStruct->HSIState) != RCC_HSI_OFF)//需要使能RCC
            {
                /* Enable the Internal High Speed oscillator (HSI). */
                __HAL_RCC_HSI_ENABLE();//通过改变RCC_CR寄存器使能HSION

                /* Get Start Tick*/
                tickstart = HAL_GetTick();//得到当前时间

                /* Wait till HSI is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET)//等待HSI启动
                {
                    if((HAL_GetTick() - tickstart ) > HSI_TIMEOUT_VALUE)//若是启动时间超时,则返回错误
                    {
                        return HAL_TIMEOUT;
                    }
                }

                /* Adjusts the Internal High Speed oscillator (HSI) calibration value.*/
                __HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(RCC_OscInitStruct->HSICalibrationValue);//设置HSI的微调
            }
            else//需要禁用RCC
            {
                /* Disable the Internal High Speed oscillator (HSI). */
                __HAL_RCC_HSI_DISABLE();//禁用RCC

                /* Get Start Tick*/
                tickstart = HAL_GetTick();//获取当前时间

                /* Wait till HSI is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) != RESET)//等待HSI禁用
                {
                    if((HAL_GetTick() - tickstart ) > HSI_TIMEOUT_VALUE)
                    {
                        return HAL_TIMEOUT;
                    }
                }
            }
        }
    }
    /*------------------------------ LSI Configuration -------------------------*/
    if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_LSI) == RCC_OSCILLATORTYPE_LSI)//判断是否有LSI
    {
        /* Check the parameters */
        assert_param(IS_RCC_LSI(RCC_OscInitStruct->LSIState));//判断LSI状态为RCC_LSI_OFF或RCC_LSI_ON

        /* Check the LSI State */
        if((RCC_OscInitStruct->LSIState) != RCC_LSI_OFF)//要使能LSI
        {
            /* Enable the Internal Low Speed oscillator (LSI). */
            __HAL_RCC_LSI_ENABLE();//通过改变RCC_CSR使能LSI

            /* Get Start Tick*/
            tickstart = HAL_GetTick();//得到当前时间值

            /* Wait till LSI is ready */
            while(__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET)//等待LSI准备好
            {
                if((HAL_GetTick() - tickstart ) > LSI_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;//超时返回错误
                }
            }
        }
        else//要禁止LSI
        {
            /* Disable the Internal Low Speed oscillator (LSI). */
            __HAL_RCC_LSI_DISABLE();//禁止LSI

            /* Get Start Tick*/
            tickstart = HAL_GetTick();

            /* Wait till LSI is ready */
            while(__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) != RESET)//等待LSI准备好
            {
                if((HAL_GetTick() - tickstart ) > LSI_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;//超时返回错误
                }
            }
        }
    }
    /*------------------------------ LSE Configuration -------------------------*/
    if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_LSE) == RCC_OSCILLATORTYPE_LSE)//判断是否有LSE
    {
        /* Check the parameters */
        assert_param(IS_RCC_LSE(RCC_OscInitStruct->LSEState));//LSE三种状态:RCC_LSE_OFF,RCC_LSE_ON,RCC_LSE_BYPASS

        /* Enable Power Clock*/
        __HAL_RCC_PWR_CLK_ENABLE();//因为要对寄存器RCC_PWR进行操作,所以对该时钟进行使能。

        /* Enable write access to Backup domain */
        PWR->CR |= PWR_CR_DBP;	//使能RTC以及RTC备份寄存器和备份SRAM的访问								

        /* Wait for Backup domain Write protection enable */
        tickstart = HAL_GetTick();//记录当前时间

        while((PWR->CR & PWR_CR_DBP) == RESET)//等待写入信息生效
        {
            if((HAL_GetTick() - tickstart ) > RCC_DBP_TIMEOUT_VALUE)
            {
                return HAL_TIMEOUT;
            }
        }

        /* Set the new LSE configuration -----------------------------------------*/
        __HAL_RCC_LSE_CONFIG(RCC_OscInitStruct->LSEState);//直接设置LSE状态
        /* Check the LSE State */
        if((RCC_OscInitStruct->LSEState) != RCC_LSE_OFF)//等待LSE状态生效
        {
            /* Get Start Tick*/
            tickstart = HAL_GetTick();

            /* Wait till LSE is ready */
            while(__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET)
            {
                if((HAL_GetTick() - tickstart ) > RCC_LSE_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
        else
        {
            /* Get Start Tick*/
            tickstart = HAL_GetTick();

            /* Wait till LSE is ready */
            while(__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) != RESET)
            {
                if((HAL_GetTick() - tickstart ) > RCC_LSE_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
    }
    /*-------------------------------- PLL Configuration -----------------------*/
    /* Check the parameters */
    assert_param(IS_RCC_PLL(RCC_OscInitStruct->PLL.PLLState));//检测PLL状态:RCC_PLL_NONE,RCC_PLL_OFF,RCC_PLL_ON
    if ((RCC_OscInitStruct->PLL.PLLState) != RCC_PLL_NONE)//判断是否需要设置PLL
    {
        /* Check if the PLL is used as system clock or not */
        if(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL)//若PLL不是系统时钟
        {
            if((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_ON)//需要将PLL设置为使能
            {
                /* Check the parameters */
                assert_param(IS_RCC_PLLSOURCE(RCC_OscInitStruct->PLL.PLLSource));//RCC_PLLSOURCE_HSI或者RCC_PLLSOURCE_HSE
                assert_param(IS_RCC_PLLM_VALUE(RCC_OscInitStruct->PLL.PLLM));//0~63
                assert_param(IS_RCC_PLLN_VALUE(RCC_OscInitStruct->PLL.PLLN));//50~432
                assert_param(IS_RCC_PLLP_VALUE(RCC_OscInitStruct->PLL.PLLP));//2,4,6,8
                assert_param(IS_RCC_PLLQ_VALUE(RCC_OscInitStruct->PLL.PLLQ));//4~15

                /* Disable the main PLL. */
                __HAL_RCC_PLL_DISABLE();//禁止PLL

                /* Get Start Tick*/
                tickstart = HAL_GetTick();//记录当前时间

                /* Wait till PLL is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)//等待设置生效
                {
                    if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
                    {
                        return HAL_TIMEOUT;
                    }
                }

                /* Configure the main PLL clock source, multiplication and division factors. */
                WRITE_REG(RCC->PLLCFGR, (RCC_OscInitStruct->PLL.PLLSource                                            | \
                                         RCC_OscInitStruct->PLL.PLLM                                                 | \
                                         (RCC_OscInitStruct->PLL.PLLN << POSITION_VAL(RCC_PLLCFGR_PLLN))             | \
                                         (((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U) << POSITION_VAL(RCC_PLLCFGR_PLLP)) | \
                                         (RCC_OscInitStruct->PLL.PLLQ << POSITION_VAL(RCC_PLLCFGR_PLLQ))));//配置PLL信息
                /* Enable the main PLL. */
                __HAL_RCC_PLL_ENABLE();//将PLL打开

                /* Get Start Tick*/
                tickstart = HAL_GetTick();

                /* Wait till PLL is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)//等待PLL设置生效
                {
                    if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
                    {
                        return HAL_TIMEOUT;
                    }
                }
            }
            else
            {
                /* Disable the main PLL. */
                __HAL_RCC_PLL_DISABLE();//禁止PLL

                /* Get Start Tick*/
                tickstart = HAL_GetTick();

                /* Wait till PLL is ready */
                while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)//等待PLL设置生效
                {
                    if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
                    {
                        return HAL_TIMEOUT;
                    }
                }
            }
        }
        else//若PLL已经成为系统时钟,则PLL的状态只能为RCC_PLL_NONE,否则报错
        {
            return HAL_ERROR;
        }
    }
    return HAL_OK;
}

该函数总体分成5个部分:

  1. HSE设置
  2. HSI设置
  3. LSI设置
  4. LSE设置
  5. PLL设置

其中,以HSE设置为典型,详细介绍该函数实现过程。HSE设置大致可以分成三个部分:

  1. 检测参数数据。
  2. 条件判断:若HSE用作系统时钟,则不可以禁用HSE。
  3. HSE并没有用作系统时钟 ,则设置HSE的状态。

在判断HSE是否直接或者间接用于系统时钟的时候,源代码如下:

if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) ||\
      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))	

第一行的含义是:判断系统时钟为RCC_CFGR_SWS_HSE。
第二行的含义是:判断系统时钟为RCC_CFGR_SWS_PLL且PLL的时钟源为HSE。
所以,综合以上两行语句,则表示HSE直接或者间接用于系统时钟。

其中,宏__HAL_RCC_GET_SYSCLK_SOURCE()的定义为:

/** @brief  Macro to get the clock source used as system clock.
  * @retval The clock source used as system clock. The returned value can be one
  *         of the following:
  *              - RCC_SYSCLKSOURCE_STATUS_HSI: HSI used as system clock.
  *              - RCC_SYSCLKSOURCE_STATUS_HSE: HSE used as system clock.
  *              - RCC_SYSCLKSOURCE_STATUS_PLLCLK: PLL used as system clock.
  *              - RCC_SYSCLKSOURCE_STATUS_PLLRCLK: PLLR used as system clock.
  */     
#define __HAL_RCC_GET_SYSCLK_SOURCE() ((uint32_t)(RCC->CFGR & RCC_CFGR_SWS))

若当HSE直接或者间接用做系统时钟时,则不可以禁用HSE,如下图参考手册所示:
在这里插入图片描述
这个条件由以下程序给出判定:

if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))//若HSE用做系统时钟,则不可以禁用HSE。
{
	return HAL_ERROR;
}

其中两个判断条件分别为:

  1. HSE已经启动。
  2. 将要进制HSE。

其中,第二条语句比较简单,这里不再详述。第一条语句的宏定义为:

/** @brief  Check RCC flag is set or not.
  * @param  __FLAG__: specifies the flag to check.
  *         This parameter can be one of the following values:
  *            @arg RCC_FLAG_HSIRDY: HSI oscillator clock ready.
  *            @arg RCC_FLAG_HSERDY: HSE oscillator clock ready.
  *            @arg RCC_FLAG_PLLRDY: Main PLL clock ready.
  *            @arg RCC_FLAG_PLLI2SRDY: PLLI2S clock ready.
  *            @arg RCC_FLAG_LSERDY: LSE oscillator clock ready.
  *            @arg RCC_FLAG_LSIRDY: LSI oscillator clock ready.
  *            @arg RCC_FLAG_BORRST: POR/PDR or BOR reset.
  *            @arg RCC_FLAG_PINRST: Pin reset.
  *            @arg RCC_FLAG_PORRST: POR/PDR reset.
  *            @arg RCC_FLAG_SFTRST: Software reset.
  *            @arg RCC_FLAG_IWDGRST: Independent Watchdog reset.
  *            @arg RCC_FLAG_WWDGRST: Window Watchdog reset.
  *            @arg RCC_FLAG_LPWRRST: Low Power reset.
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define RCC_FLAG_MASK  ((uint8_t)0x1FU)
#define __HAL_RCC_GET_FLAG(__FLAG__) (((((((__FLAG__) >> 5U) == 1U)? RCC->CR :((((__FLAG__) >> 5U) == 2U) ? RCC->BDCR :((((__FLAG__) >> 5U) == 3U)? RCC->CSR :RCC->CIR))) & ((uint32_t)1U << ((__FLAG__) & RCC_FLAG_MASK)))!= 0U)? 1U : 0U)

/**
  * @}
  */

这条语句很难读懂,是因为应用了很多三目运算符,可以将上述定义用条件语句改写为:

int __HAL_RCC_GET_FLAG(uint8_t __FLAG__)
{
    flag = __FLAG__ >> 5U;//将__FLAG__右移5位,为新的判断条件。
    uint32_t x = 0;//x用于存储对应的寄存器数据
	//根据__FLAG__高3位数据的不同,来选择对应的寄存器
    if(flag == 1)
    {
        x = RCC->CR;
    }
    else if (flag == 2)
    {
        x = RCC->BDCR;
    }
    else if(flag == 3)
    {
        x = RCC->CSR;
    }
    else
    {
        x = RCC->CIR;
    }
    di5wei = __FLAG__ & 0x1FU;	//获取低5位,低5位是用来确定是寄存器的哪一个二进制位。
    y = 1 << di5wei;//将1移动到对应的二进制位
    if(x & y == 0)//返回值为1或者0
        return 0;
    else
        return 1;
}

根据以上代码可以分析出,宏__HAL_RCC_GET_FLAG(__FLAG__)的参数由两部分组成:

  1. 高三位,对应其所在的寄存器
  2. 低五位,对应其在寄存器的二进制位

通过该宏就可以根据标志位得知系统的状态。

最后,看一下设置HSE状态,该部分大致可以分成两个部分:

  1. 设置HSE的新状态写入寄存器。
  2. 等待写入的HSE状态有效。

写入寄存器的宏定义为:

/**
  * @brief  Macro to configure the External High Speed oscillator (HSE).
  * @note   Transition HSE Bypass to HSE On and HSE On to HSE Bypass are not supported by this macro. 
  *         User should request a transition to HSE Off first and then HSE On or HSE Bypass.
  * @note   After enabling the HSE (RCC_HSE_ON or RCC_HSE_Bypass), the application
  *         software should wait on HSERDY flag to be set indicating that HSE clock
  *         is stable and can be used to clock the PLL and/or system clock.
  * @note   HSE state can not be changed if it is used directly or through the
  *         PLL as system clock. In this case, you have to select another source
  *         of the system clock then change the HSE state (ex. disable it).
  * @note   The HSE is stopped by hardware when entering STOP and STANDBY modes.  
  * @note   This function reset the CSSON bit, so if the clock security system(CSS)
  *         was previously enabled you have to enable it again after calling this
  *         function.    
  * @param  __STATE__: specifies the new state of the HSE.
  *         This parameter can be one of the following values:
  *            @arg RCC_HSE_OFF: turn OFF the HSE oscillator, HSERDY flag goes low after
  *                              6 HSE oscillator clock cycles.
  *            @arg RCC_HSE_ON: turn ON the HSE oscillator.
  *            @arg RCC_HSE_BYPASS: HSE oscillator bypassed with external clock.
  */
#define __HAL_RCC_HSE_CONFIG(__STATE__) (*(__IO uint8_t *) RCC_CR_BYTE2_ADDRESS = (__STATE__))

此处比较简单,不再详述。

因为写入的HSE状态大致可以分成两类:

  1. 打开HSE
  2. 关闭HSE

以打开HSE为例,源代码为:

if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
{
    /* Get Start Tick*/
    tickstart = HAL_GetTick();//记录当前时间

    /* Wait till HSE is ready */
    while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)//判断是否生效
    {
        if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)//用当前时间减去第一次记录的时间,计算消耗时间
        {
            return HAL_TIMEOUT;
        }
    }
}

若上述代码所示,记录当前的时间。若设置并未生效,则循环等待且等待时间超过最大等待时间,则报错 HAL_TIMEOUT。至此HSE部分基本结束。其余四个部分与其基本一致,不再详细展开,只是其中几点需要特别注意为:

  1. HSI 需要设置微调信息。
  2. PLL设置需要写入分频与倍频的参数。需要注意的是,需要首先关闭PLL,然后将PLL倍频与分频的参数写入,最后在将PLL打开。

下面重点讲述HSI设置微调信息的过程,其对应的源码为:

__HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(RCC_OscInitStruct->HSICalibrationValue);

则宏定义为:

/** @brief  Macro to adjust the Internal High Speed oscillator (HSI) calibration value.
  * @note   The calibration is used to compensate for the variations in voltage
  *         and temperature that influence the frequency of the internal HSI RC.
  * @param  __HSICalibrationValue__: specifies the calibration trimming value.
  *         (default is RCC_HSICALIBRATION_DEFAULT).
  *         This parameter must be a number between 0 and 0x1F.
  */
#define __HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(__HSICalibrationValue__) (MODIFY_REG(RCC->CR,\
        RCC_CR_HSITRIM, (uint32_t)(__HSICalibrationValue__) << POSITION_VAL(RCC_CR_HSITRIM)))

若想理解该宏的意思,需要了解的有:

  1. MODIFY_REG宏定义
  2. RCC_CR_HSITRIM
  3. POSITION_VAL宏定义

其中,第二点最简单,源代码为:

#define  RCC_CR_HSITRIM                      ((uint32_t)0x000000F8U)

RCC_CR_HSITRIM 为HSI微调偏移量在CR中的掩码,换句话说, RCC_CR_HSITRIM中二进制1对应的数据位就是RCC_CR寄存器中HSI微调偏移量对应的数据位。

MODIFY_REG宏定义如下所示:

#define MODIFY_REG(REG, CLEARMASK, SETMASK)  WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))

可以大致分成两个步骤:

  1. 通过CLEARMASK将REG对应位清零。
  2. 在将SETMASK对应的二进位置一。

POSITION_VAL宏定义最难理解,其对应的宏定义为

#define POSITION_VAL(VAL)     (__CLZ(__RBIT(VAL)))	

其中__RBIT()__CLA()都对应着汇编指令,可以通过查询keil帮助文档或者<权威指南>来获得指令的意思如下:

__RBIT()//ARM 32位数据汇编指令,将数据的二进制值进行反转。
__CLA()//ARM 32位数据汇编指令,计算二进制数前导零的个数

在这里插入图片描述
注意:

两个指令只适用于32位整形。

其中,__RBIT()即将二进制数最高位和最低位调换位置,次高位和次低位调换位置,以此类推。__CLA(),计算从最高位到第一个0,该二进制数中有多少个零。所以,POSITION_VAL即求取该32位二进制数从从最位开始,到第一个1,共有多少个0.

综上所述,可以理解

#define __HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(__HSICalibrationValue__) (MODIFY_REG(RCC->CR,\
        RCC_CR_HSITRIM, (uint32_t)(__HSICalibrationValue__) << POSITION_VAL(RCC_CR_HSITRIM)))

程序的含义就是先将CR寄存器对应HSI微调部分进行清零,然后将微调数据__HSICalibrationValue__左移适当位置,写入对应的寄存器位中。

时钟初始化
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef  *RCC_ClkInitStruct, uint32_t FLatency)
{
    uint32_t tickstart = 0U;

    /* Check the parameters */
    assert_param(IS_RCC_CLOCKTYPE(RCC_ClkInitStruct->ClockType));//IS_RCC_CLOCKTYPE(CLK) ((1U <= (CLK)) && ((CLK) <= 15U))
    assert_param(IS_FLASH_LATENCY(FLatency));//FLASH_LATENCY_0-FLASH_LATENCY_15

    /* To correctly read data from FLASH memory, the number of wait states (LATENCY)
      must be correctly programmed according to the frequency of the CPU clock
      (HCLK) and the supply voltage of the device. */

    /**************************************1.调高Flash延时时间 *******************************************/
    /* Increasing the number of wait states because of higher CPU frequency */
    if(FLatency > (FLASH->ACR & FLASH_ACR_LATENCY))//若设定值比原有值大
    {
        /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
        __HAL_FLASH_SET_LATENCY(FLatency);//将设定值写入寄存器

        /* Check that the new number of wait states is taken into account to access the Flash
        memory by reading the FLASH_ACR register */
        if((FLASH->ACR & FLASH_ACR_LATENCY) != FLatency)//检查写入值是否有效
        {
            return HAL_ERROR;
        }
    }
    /**************************************2.HCLK设置 *******************************************/
    /*-------------------------- HCLK Configuration --------------------------*/
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_HCLK) == RCC_CLOCKTYPE_HCLK)
    {
        assert_param(IS_RCC_HCLK(RCC_ClkInitStruct->AHBCLKDivider));//分频数可以为1,2,4,8,16,64,128,256,512。注意没有32分频
        MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);//将分频数写入对应的寄存器
    }

    /**************************************3.SYSCLK设置 *******************************************/
    /*------------------------- SYSCLK Configuration ---------------------------*/
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_SYSCLK) == RCC_CLOCKTYPE_SYSCLK)
    {
        assert_param(IS_RCC_SYSCLKSOURCE(RCC_ClkInitStruct->SYSCLKSource));//检测范围

        /* HSE is selected as System Clock Source */
        if(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_HSE)//若HSE作为系统时钟
        {
            /*3.1检测选择系统时钟是否处于禁用状态,需要注意的是设置PLL源的时候没有这个步骤*/
            /* Check the HSE ready flag */
            if(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)//若HSE处于禁用状态,则返回错误HAL_ERROR
            {
                return HAL_ERROR;
            }
        }
        /* PLL is selected as System Clock Source */
        else if((RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK)   ||
                (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLRCLK))
        {
            /* Check the PLL ready flag */
            if(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)//若PLL处于禁用状态,则返回错误HAL_ERROR
            {
                return HAL_ERROR;
            }
        }
        /* HSI is selected as System Clock Source */
        else
        {
            /* Check the HSI ready flag */
            if(__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET)//若HSI处于禁用状态,则返回错误HAL_ERROR
            {
                return HAL_ERROR;
            }
        }

        //若选择的系统时钟处于启动状态,则使其生效。
        __HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);
        /* Get Start Tick*/
        tickstart = HAL_GetTick();//开始计时,记录当前的时间

        //判断设置是否生效,若等待时间超时,返回错误HAL_TIMEOUT
        if(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_HSE)
        {
            while (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_HSE)
            {
                if((HAL_GetTick() - tickstart ) > CLOCKSWITCH_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
        else if(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK)
        {
            while (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_PLLCLK)
            {
                if((HAL_GetTick() - tickstart ) > CLOCKSWITCH_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
        else if(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLRCLK)
        {
            while (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_PLLRCLK)
            {
                if((HAL_GetTick() - tickstart ) > CLOCKSWITCH_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
        else
        {
            while(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_HSI)
            {
                if((HAL_GetTick() - tickstart ) > CLOCKSWITCH_TIMEOUT_VALUE)
                {
                    return HAL_TIMEOUT;
                }
            }
        }
    }

    /**************************************4.降低Flash延时时间 *******************************************/
    /* Decreasing the number of wait states because of lower CPU frequency */
    if(FLatency < (FLASH->ACR & FLASH_ACR_LATENCY))
    {
        /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
        __HAL_FLASH_SET_LATENCY(FLatency);

        /* Check that the new number of wait states is taken into account to access the Flash
        memory by reading the FLASH_ACR register */
        if((FLASH->ACR & FLASH_ACR_LATENCY) != FLatency)
        {
            return HAL_ERROR;
        }
    }

    /**************************************5.PCLK1设置 *******************************************/
    /*-------------------------- PCLK1 Configuration ---------------------------*/
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
    {
        assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));//1,2,4,8,16
        MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);//设置APB1分频数
    }
    
	/**************************************6.PCLK2设置 *******************************************/
    /*-------------------------- PCLK2 Configuration ---------------------------*/
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
    {
        assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB2CLKDivider));//1,2,4,8,16
        MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct->APB2CLKDivider) << 3U));//设置APB2分频数
    }

    /* Configure the source of time base considering new system clocks settings*/
    HAL_InitTick (TICK_INT_PRIORITY);

    return HAL_OK;
}

这部分代码比较简单,比较难理解的地方在上面一节已经提到了。所以,本部分代码不再详细展开,只是添加了中文备注,如上所示。

时钟初始化总共分成六个部分:

  1. 提高Flash延时时间
  2. HCLK设置
  3. SYSCLK设置
  4. 降低Flash延时时间
  5. PCLK1设置
  6. PCLK2设置

需要注意的是,每个不同的模块对于分频数和倍频数设置的限制是不同的。所以,为了得到想要的主频,还需要自己配合不同模块的限制。另外一点是关于Flash延时时间的设置,若想要提高Flash延时时间,则先进行Flash时间设置,再进行主频设置。若想要降低Flash延时时间,则需要先进行主频设置,再进行Flash延时时间设置。这是因为宽裕的Flash延时时间是更加安全的。
最后,关于SYSCLK源设置还需要注意,在设置之前,需要保证SYSCLK源是处于启动状态。

systick

这部分内容请参考博客<STM32F429第十篇之systick>

  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值