STM32F429第四篇之跑马灯程序详解
文章目录
- 前言
- 硬件
- 软件
- 编写代码
- 主程序
- 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);
}
上述代码可以大致分成以下五个部分:
- 使能PWR时钟。
- 设置调压器输出的电压级别。
- 配置时钟源相关参数。
- 开启Over-Driver功能。
- 配置系统时钟相关参数。
其中,第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;
总共分成四个语句,如下:
- 因为功能复位寄存器有两个(AFRL和AFRH)。其中AFRH对应端口引脚的高8位,AFRL对应端口引脚低8位。所以,根据position是大于等于8或者小于7来获取对应复用功能寄存器的数值,且保存在temp中。
- 将引脚的寄存器对应位清零。
- 因为高8位与低8位在上一步已经区分。所以此时,高8位应该以第8位为基点。即第8位等同于第0位,就9位等同于第1位,以此类推。
position & (uint32_t)0x07U
相当于将大于8的position减去8,小于8的position不变。 - 因为每个引脚对应的复位寄存器是4个二进制位,
((uint32_t)(position & (uint32_t)0x07U) * 4U))
,将处理过的偏移量*4。即第0偏移到0,第1位偏移到4,从而对应其在寄存器中的二进制对应位。 (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。- 最后将上一步计算结果取反,且与temp相位与。则,将引脚对应的复位功能寄存器对应位清0,其余位不变。
- 因为高8位与低8位在上一步已经区分。所以此时,高8位应该以第8位为基点。即第8位等同于第0位,就9位等同于第1位,以此类推。
- 该步骤和上一步处理方式基本相同 。即将复用功能数据写入到复位功能寄存器对应的temp二进制位中。
- 最后,将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个部分:
- HSE设置
- HSI设置
- LSI设置
- LSE设置
- PLL设置
其中,以HSE设置为典型,详细介绍该函数实现过程。HSE设置大致可以分成三个部分:
- 检测参数数据。
- 条件判断:若HSE用作系统时钟,则不可以禁用HSE。
- 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;
}
其中两个判断条件分别为:
- HSE已经启动。
- 将要进制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__)
的参数由两部分组成:
- 高三位,对应其所在的寄存器
- 低五位,对应其在寄存器的二进制位
通过该宏就可以根据标志位得知系统的状态。
最后,看一下设置HSE状态,该部分大致可以分成两个部分:
- 设置HSE的新状态写入寄存器。
- 等待写入的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状态大致可以分成两类:
- 打开HSE
- 关闭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部分基本结束。其余四个部分与其基本一致,不再详细展开,只是其中几点需要特别注意为:
- HSI 需要设置微调信息。
- 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)))
若想理解该宏的意思,需要了解的有:
- MODIFY_REG宏定义
- RCC_CR_HSITRIM
- 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)))
可以大致分成两个步骤:
- 通过CLEARMASK将REG对应位清零。
- 在将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;
}
这部分代码比较简单,比较难理解的地方在上面一节已经提到了。所以,本部分代码不再详细展开,只是添加了中文备注,如上所示。
时钟初始化总共分成六个部分:
- 提高Flash延时时间
- HCLK设置
- SYSCLK设置
- 降低Flash延时时间
- PCLK1设置
- PCLK2设置
需要注意的是,每个不同的模块对于分频数和倍频数设置的限制是不同的。所以,为了得到想要的主频,还需要自己配合不同模块的限制。另外一点是关于Flash延时时间的设置,若想要提高Flash延时时间,则先进行Flash时间设置,再进行主频设置。若想要降低Flash延时时间,则需要先进行主频设置,再进行Flash延时时间设置。这是因为宽裕的Flash延时时间是更加安全的。
最后,关于SYSCLK源设置还需要注意,在设置之前,需要保证SYSCLK源是处于启动状态。
systick
这部分内容请参考博客<STM32F429第十篇之systick>
STM32F429第四篇之跑马灯程序详解相关推荐
- android跑马灯效果横向,Android自定义View实现纵向跑马灯效果详解
首先看看效果图(录制的gif有点卡,真实的效果还是很流畅的) 实现思路 通过上面的gif图可以得出结论,其实它就是同时绘制两条文本信息,然后通过动画不断的改变两条文本信息距离顶部的高度,以此来实现滚动 ...
- java 走马灯程序,详解微信小程序实现跑马灯效果(附完整代码)
在微信小程序 里实现跑马灯效果,类似滚动字幕或者滚动广告之类的,使用简单的CSS样式控制,没用到JS,效果如下图: Wxml代码: 一个人活着就是为了让更多的人更好的活着! Wxss代码: /*首页跑 ...
- 单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础
上篇文章 单片机入门学习四 STM32单片机学习一 跑马灯程序和创建工程 仅介绍了入门程序及其编译运行过程,下面开始对stm32的一些基础知识做一个记录. 1.stm32f103zet6(上篇问题3 ...
- 【STM32F429开发板用户手册】第18章 STM32F429的GPIO应用之跑马灯
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 第18章 STM32F429的GPIO应用之跑马灯 本 ...
- ARM上裸奔的跑马灯程序
今天用优龙ST2410测试了一下在不进系统的情况下裸奔跑马灯程序.首先总结下在ARM板上裸奔程序的几种方法:既然要在ARM上裸奔,首先地让ARM板不进系统,方法是上电复位后选BIOS的最后一项:7 : ...
- Proteus仿真STM32F103R6的寄存器版跑马灯程序(存储器宏定义)
跑马灯程序,存储器映像.用宏定义,增强程序的可读性.. 头文件: #ifndef __STM32F10x_H #define __STM32F10x_H#define PERIPH_BASE (0X4 ...
- Proteus仿真STM32F103R6的寄存器版跑马灯程序
STM32最简单的外设莫过于IO口的高低电平控制了, Proteus仿真STM32F103R6的跑马灯程序. 一.原理图 二.跑马灯C程序 // 粗略延时函数 static void delay(un ...
- 02 ARM11 时钟初始化后的跑马灯程序
2019独角兽企业重金招聘Python工程师标准>>> .text .globl _start _start:ldr r0, =0x70000000orr r0, r0, #0x13 ...
- 第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。
开场白: 上一节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式.这节在上一节的驱动程序基础上,开始讲跑马灯程序.我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧.它是基于鸿哥的 ...
- arm 跑马灯 linux,02 ARM11 时钟初始化后的跑马灯程序
.text .globl _start _start: ldr r0, =0x70000000 orr r0, r0, #0x13 mcr p15, 0, r0, c15, c2, 4 ldr r0, ...
最新文章
- 第十六届全国大学生智能汽车竞赛-航天智慧物流创意组 线下选拔赛赛题发布!
- 两家外企的长三角仓库,活儿竟全被中国机器人承包了
- jupyter代码字体大小_你可能并不知道这样定制炫酷的jupyter主题
- GPU高效通信算法-Ring Allreduce
- 给定一个数值,输出符合中国人习惯的读法--记一道笔试题
- Java程序编译和运行的过程
- IO-03. 求整数均值
- Linux服务器的那些性能参数指标
- 计算机检索高考投档线,投档分数线是什么意思 低于投档线会被录取吗
- 【MLDL】logistics regression理解
- c语言利用查表法画正弦波,嵌入式C语言查表法在项目中的应用
- NotebookApp] 302 GET /?token=be0e8107dd84eab831a957b640602e5157b5336b15e7fa61 (127.0.0.1) 1.000000ms
- 上传计算机桌面文件图标不见,关于桌面上图标都不见了这类问题的解决方法
- error: C++ preprocessor “/lib/cpp“ fails sanity check错误解决方法
- android热成像模块,Arduino制作简易热成像装置
- 德国“红绿灯”组合敲定组阁方案,默克尔继任者是他
- ClouderaManager6.3 单机搭建
- 设计线性相位高通FIR滤波器
- Zynq-7000系列Quad-SPI I/O接口简介
- 作为过来人的我是如何写博客的?