目录
在前几篇文章中我想把一些基础的部分简单总结一下:首先是前两篇文章,学习一个mcu就要先对它的外设有初步的理解,还有要使用的HAL库,它相当于我们的代码与硬件之间连接的桥梁;这篇博客会总结一下429时钟树的一些知识,还有时钟配置函数;再之后可能还会总结基于SysTick的延时函数、程序执行流程、中断、DMA等。
1.时钟系统
1.1.时钟源
F429有5个时钟源:HSI,HSE,LSI,LSE,PLL(PLL,PLLI2S,PLLSAI)。HSI,HSE,PLL是高速时钟,LSI,LSE是低速时钟。HSE,LSE是外部时钟源。
- LSI是低速内部时钟,32kHz左右RC振荡器,独立看门狗和自动唤醒单元。
- LSE是低速外部时钟,32.768kHz石英晶体,RTC 的时钟源。
- HSE是高速外部时钟,4MHz~26MHz,板子是25M。可以做为系统时钟或PLL输入。
- HSI是高速内部时钟,16MHz,系统时钟或PLL输入。仅需几个微秒就能启动,但是精度差。当HSE故障时,HSI是备用时钟。
- PLL主要作用是对时钟进行倍频,然后把时钟输出到各个功能部件,共有3个PLL:
a) 主PLL由HSE或者HSI提供时钟信号,有两个不同的输出时钟。第一个PLLCLK用于生成高速的系统时钟(最高180MHz)。第二个PLLQ为48M,用于USB OTG FS,随机数发生器和SDIO时钟。
b) PLLI2S在I2S和SAI1上实现高品质音频
c) PLLSAI用于SAI输入时钟,LCD_TFT接口时钟。
1.2.系统时钟SYSCLK计算
主PLL时钟来若自HSE,也就是25MHz的外部晶振,先经过分频系数为M的分频器,再经过倍频系数为N的倍频器,还需要经过分频系数为 P(第一个输出 PLLP)或者 Q(第二个输出 PLLQ)的分频器分频之后,最后生成最终的主PLL时钟。我们设置M=25,N=360,P=2,则生成的高速时钟 PLLP(也就是PLLCLK)为180MHz。下图为在CubeMX中配置主时钟,HSE可以由有源或无源晶振或提供。当使用有源晶振时,时钟从OSC_IN进入,OSC_OUT 悬空;选用无源晶振时,时钟从OSC_IN 和 OSC_OUT进入,并且要配谐振电容。
但是 USB OTG FS的情况比较特殊,必须使用 48M,Q=VCO输出时钟360/48=7.5,出现了小数这明显是错误。野火教程中将N设为336,PLLCLK=VCOCLK/2=168M,USBCLK=336/7=48M,也就是PLLCLK降频了。正点原子教程中选择超频的方法,设N=432,USBCLK=432/9=48M,此时PLLCLK=216MHz。
1.3.AHB和APB总线时钟
AHB总线时钟HCLk由SYSCLK经AHB预分频器分频后得到,分频系数由RCC_CFGR的HPRE 位设置,设为1分频,即HCLK=SYSCLK=180M。AHB上的外设有FSMC,RNG,DCMI,USB OTG FS,USB OTG HS,以太网MAC,DMA,SRAM,Flash,RCC,CRC,GPIO。
APB1总线时钟PCLK1由HCLK经低速APB预分频器得到,分频系数由RCC_CFGR 的PPRE1位设置,配置PCLK1=HCLK/4=45M。总线上的外设有UART2/3/4/5/7/8,DAC,PWR,CAN1/2,I2C1/2/3,I2S2/3,SPI2/3,RTC,TIM2/3/4/5/6/7/12/13/14。
APB2总线时钟PCLK2由HCLK经高速APB2预分频器得到,由RCC_CFGR的PPRE2位设置。PCLK2=HCLK/2=90M。总线上的外设有SPI1/4/5/6,TIM1/8/9/10/11,EXTI,SYSCFG,ADC1/2/3,USART1/6。
1.4.其它时钟
- 独立看门狗时钟源只能是低速LSI,32kHz左右。
- RTC 时钟源可以选择 LSI,LSE,以及HSE分频后的时钟(通过 RCC_BDCR选择),一般选择 LSE,即外部32.768Khz。
- MCO1和MCO2分别是向PA8和PC9输出时钟。MCO1的四个时钟来源分别为:HSI/LSE/HSE/PLL;MCO2的四个时钟来源分别为:HSE/PLL/SYSCLK/PLLI2S,最大不超过100MHz。
- I2S时钟源可以是PLLI2S_R 时钟或外部时钟I2S_CKIN(通过寄存器I2SSRC选择),原子的教程播放音频用的SAI,没用I2S。
- LCD-TFT(LTDC)接口时钟唯一来源是PLLSAI_R。
- SAI1_A的时钟,通过寄存器SAI1ASRC选择内部PLLSAI_Q、PLLI2S_Q或外部I2SCKIN作为时钟。原子教程使用SAI1_A驱动 WM8978,时钟来自PLLSAI_Q。
- LTDC的时钟固定为PLLSAI_R。
- Systick的时钟源可以是AHB时钟HCLK或HCLK/8。
2.时钟配置函数
2.1.SystemInit函数
该函数在system_stm32f4xx.c中实现,在启动文件startup_stm32f429xx.s中被调用。SystemInit主要做了如下四个方面工作:
- FPU设置。
- 复位RCC时钟配置为默认复位值(默认开启HIS),即配置系统时钟为16MHz,没有像标准库的SystemInit一样进行时钟的初始化配置。
- 外部存储器配置。
- 中断向量表地址配置。
void SystemInit(void)
{
/* FPU 设置------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* 复位 RCC 时钟配置为默认配置-----------*/
RCC->CR |= (uint32_t)0x00000001;//打开 HSION 位
RCC->CFGR = 0x00000000;//复位 CFGR 寄存器
RCC->CR &= (uint32_t)0xFEF6FFFF;//复位 HSEON, CSSON and PLLON 位
RCC->PLLCFGR = 0x24003010; //复位寄存器 PLLCFGR
RCC->CR &= (uint32_t)0xFFFBFFFF;//复位 HSEBYP 位
RCC->CIR = 0x00000000;//关闭所有中断
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* 配置中断向量表地址=基地址+偏移地址 ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}
2.2.Stm32_Clock_Init函数
该函数除了配置SYSCLK值外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1和PCLK2的时钟值,是正点原子教程里的时钟初始化函数,在HAL_Init()之后调用。
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStructure;
RCC_ClkInitTypeDef RCC_ClkInitStructure;
__HAL_RCC_PWR_CLK_ENABLE(); //使能PWR时钟
//下面这个设置用来设置调压器输出电压级别,以便在器件未以最大频率工作
//时使性能与功耗实现平衡,此功能只有STM32F42xx和STM32F43xx器件有,
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);//设置调压器输出电压级别1
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);
ret=HAL_PWREx_EnableOverDrive(); //开启Over-Driver功能
if(ret!=HAL_OK) while(1);
//选中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);
}
该函数步骤如下:
- 使能PWR时钟:调用__HAL_RCC_PWR_CLK_ENABLE()。
- 设置调压器输出电压级别:调用__HAL_PWR_VOLTAGESCALING_CONFIG()。
- 选择是否开启Over-Driver功能:调用函数 HAL_PWREx_EnableOverDrive()。
- 配置时钟源相关参数,配置好后调用函数 HAL_RCC_OscConfig()。
- 配置系统时钟源以及AHB,APB1和APB2的分频系数,配置好后调用函数HAL_RCC_ClockConfig()。
2.3.一些汇编指令
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
CPSID I
BX LR
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
CPSIE I
BX LR
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}