文章目录

  • 前言
  • 硬件
  • 软件
    • 编写代码
      • 主程序
        • HAL库初始化
        • RCC时钟初始化
        • 延时函数初始化
      • LED驱动
    • HAL库详解
      • GPIO
        • GPIO初始化
        • GPIO位操作
      • RCC
        • 振荡器参数设置
        • 时钟初始化
      • systick

前言

本文以上篇博文<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);   //设置时钟,180Mhzdelay_init(180);                //初始化延时函数LED_Init();                     //初始化LEDwhile(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);                                      //延时500msHAL_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;                        //打开HSERCC_OscInitStructure.PLL.PLLState = RCC_PLL_ON;                     //打开PLLRCC_OscInitStructure.PLL.PLLSource = RCC_PLLSOURCE_HSE;             //PLL时钟源选择HSERCC_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和PCLK2RCC_ClkInitStructure.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);RCC_ClkInitStructure.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                    //设置系统时钟时钟源为PLLRCC_ClkInitStructure.AHBCLKDivider = RCC_SYSCLK_DIV1;                           //AHB分频系数为1RCC_ClkInitStructure.APB1CLKDivider = RCC_HCLK_DIV4;                            //APB1分频系数为4RCC_ClkInitStructure.APB2CLKDivider = RCC_HCLK_DIV2;                           //APB2分频系数为2ret = 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;
#endifHAL_SYSTICK_CLKSourceConfig ( SYSTICK_CLKSOURCE_HCLK );       //SysTick频率为HCLKfac_us = SYSCLK;                                           //不论是否使用OS,fac_us都需要使用#if SYSTEM_SUPPORT_OS                                             //如果需要支持OS.reload = SYSCLK;                                            //每秒钟的计数次数 单位为Kreload *= 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_Hextern 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,0GPIO_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值是否置1if(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_ONif ((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_HSEassert_param(IS_RCC_PLLM_VALUE(RCC_OscInitStruct->PLL.PLLM));//0~63assert_param(IS_RCC_PLLN_VALUE(RCC_OscInitStruct->PLL.PLLN));//50~432assert_param(IS_RCC_PLLP_VALUE(RCC_OscInitStruct->PLL.PLLP));//2,4,6,8assert_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或者0return 0;elsereturn 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 Flashmemory 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_TIMEOUTif(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 Flashmemory 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,16MODIFY_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,16MODIFY_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>

STM32F429第四篇之跑马灯程序详解相关推荐

  1. android跑马灯效果横向,Android自定义View实现纵向跑马灯效果详解

    首先看看效果图(录制的gif有点卡,真实的效果还是很流畅的) 实现思路 通过上面的gif图可以得出结论,其实它就是同时绘制两条文本信息,然后通过动画不断的改变两条文本信息距离顶部的高度,以此来实现滚动 ...

  2. java 走马灯程序,详解微信小程序实现跑马灯效果(附完整代码)

    在微信小程序 里实现跑马灯效果,类似滚动字幕或者滚动广告之类的,使用简单的CSS样式控制,没用到JS,效果如下图: Wxml代码: 一个人活着就是为了让更多的人更好的活着! Wxss代码: /*首页跑 ...

  3. 单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础

    上篇文章 单片机入门学习四 STM32单片机学习一 跑马灯程序和创建工程 仅介绍了入门程序及其编译运行过程,下面开始对stm32的一些基础知识做一个记录. 1.stm32f103zet6(上篇问题3 ...

  4. 【STM32F429开发板用户手册】第18章 STM32F429的GPIO应用之跑马灯

    最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 第18章       STM32F429的GPIO应用之跑马灯 本 ...

  5. ARM上裸奔的跑马灯程序

    今天用优龙ST2410测试了一下在不进系统的情况下裸奔跑马灯程序.首先总结下在ARM板上裸奔程序的几种方法:既然要在ARM上裸奔,首先地让ARM板不进系统,方法是上电复位后选BIOS的最后一项:7 : ...

  6. Proteus仿真STM32F103R6的寄存器版跑马灯程序(存储器宏定义)

    跑马灯程序,存储器映像.用宏定义,增强程序的可读性.. 头文件: #ifndef __STM32F10x_H #define __STM32F10x_H#define PERIPH_BASE (0X4 ...

  7. Proteus仿真STM32F103R6的寄存器版跑马灯程序

    STM32最简单的外设莫过于IO口的高低电平控制了, Proteus仿真STM32F103R6的跑马灯程序. 一.原理图 二.跑马灯C程序 // 粗略延时函数 static void delay(un ...

  8. 02 ARM11 时钟初始化后的跑马灯程序

    2019独角兽企业重金招聘Python工程师标准>>> .text .globl _start _start:ldr r0, =0x70000000orr r0, r0, #0x13 ...

  9. 第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

    开场白: 上一节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式.这节在上一节的驱动程序基础上,开始讲跑马灯程序.我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧.它是基于鸿哥的 ...

  10. arm 跑马灯 linux,02 ARM11 时钟初始化后的跑马灯程序

    .text .globl _start _start: ldr r0, =0x70000000 orr r0, r0, #0x13 mcr p15, 0, r0, c15, c2, 4 ldr r0, ...

最新文章

  1. 第十六届全国大学生智能汽车竞赛-航天智慧物流创意组 线下选拔赛赛题发布!
  2. 两家外企的长三角仓库,活儿竟全被中国机器人承包了
  3. jupyter代码字体大小_你可能并不知道这样定制炫酷的jupyter主题
  4. GPU高效通信算法-Ring Allreduce
  5. 给定一个数值,输出符合中国人习惯的读法--记一道笔试题
  6. Java程序编译和运行的过程
  7. IO-03. 求整数均值
  8. Linux服务器的那些性能参数指标
  9. 计算机检索高考投档线,投档分数线是什么意思 低于投档线会被录取吗
  10. 【MLDL】logistics regression理解
  11. c语言利用查表法画正弦波,嵌入式C语言查表法在项目中的应用
  12. NotebookApp] 302 GET /?token=be0e8107dd84eab831a957b640602e5157b5336b15e7fa61 (127.0.0.1) 1.000000ms
  13. 上传计算机桌面文件图标不见,关于桌面上图标都不见了这类问题的解决方法
  14. error: C++ preprocessor “/lib/cpp“ fails sanity check错误解决方法
  15. android热成像模块,Arduino制作简易热成像装置
  16. 德国“红绿灯”组合敲定组阁方案,默克尔继任者是他
  17. ClouderaManager6.3 单机搭建
  18. 设计线性相位高通FIR滤波器
  19. Zynq-7000系列Quad-SPI I/O接口简介
  20. 作为过来人的我是如何写博客的?

热门文章

  1. 软件体系结构 复习题
  2. 【转载】RPG或SLG游戏在线地图编辑器
  3. webstorm 破解方法(100%好使)
  4. 数据库工具DBeaver
  5. 2018中级职称计算机题库,2018年中级通信工程师传输与接入考试试题
  6. 人口matlab数学模型,基于MATLAB构建人口数学模型研究二胎开放对中国人口的影响...
  7. javascript服务端编程
  8. PHP 将文字转化mp3文件
  9. Go语言实战的知识图谱
  10. 韩顺平--Java坦克大战