ARM 教程 第一部分 时钟





5.00/5 (11投票s)
这是基于ARM CPU架构的系列文章的第一篇。
引言
ARM时钟方案是一个非常复杂但灵活的系统,允许开发人员通过一系列时钟源、时钟多路复用器和分频器来控制处理器的各个部分,将信号沿一系列总线传播到各种外设。
对于大多数其他处理器,系统时钟持续活跃;但在ARM生态系统中,每个时钟都可以独立开关以节省电量。这样,未使用的外设就不会计时,因此不会占用宝贵的资源。
本文的示例代码和讨论将围绕STM32 NUCLEO-64开发板展开,该板搭载STM32F411RE处理器。该板由STMicroelectronics制造,是一款32位Cortex-M4 MCU。ARM系列处理器在广泛的领域提供解决方案,以满足性能、功耗和成本要求。
STM32F411RE处理器是一款32位Cortex-M4微控制器,最大时钟速度为100MHz,需要1.7V至3.6V电源,包含512KB闪存和128KB SRAM。外设包括81个I/O端口(其中77个支持5V电压)、3个I2C接口、3个USARTS、5个SPI、一个OTG USB、16通道ADC、11个定时器、DMA和CRC。
请注意,虽然设备供电为3.3V,但有些引脚支持5V电压。详细信息请参阅STM32F411RE数据手册中的“引脚排列和引脚描述”部分。这仅在引脚配置为输入时相关,当配置为输出时,引脚输出3.3V电平。
我与STMicroelectronics没有任何关联,也不收取任何报酬,我选择这个系列的设备只是因为它们价格便宜,IDE免费且没有限制,而且我是一个抠门的人。

要跟随代码示例,您需要下表中列出的项目。任何M4系列的ARM处理器都应该适用于这些示例,但提供的示例是在搭载STM32F411RE处理器的NUCLEO-64开发板上编写和测试的。该板板载ST-LINK编程器/调试器,因此可以直接从STM32CubeIDE使用。(13美元,这价格不赖。)
所需物品 | 来源 |
NUCLEO-64开发板,STM32F411RE(可选) | digikey.com |
STM32CubeIDE for STM32(可选) | st.com |
STM32F411RE参考手册 | mouser.com |
STM32F411RE数据手册 | mouser.com |
在讨论时钟系统之前,有必要简要介绍一下高级微控制器总线架构(AMBA)。AMBA是一种开放标准的片上互连规范,用于系统级芯片(SoC)设计中功能模块的连接和管理。如图2所示,STM32F411RE包含AHB、APB1和APB2总线。
AHB上的简单事务包括地址阶段和随后的数据阶段(没有等待状态:只有两个总线周期),并且具有64到1024位的宽总线宽度。
“APB专为低带宽控制访问设计,例如系统外设上的寄存器接口。该总线具有与AHB相似的地址和数据阶段,但信号列表大大简化,复杂度较低(例如,没有突发传输)。此外,它是一个专为低频系统设计的接口,具有低位宽(32位)。”

有各种时钟源可用于通过系统时钟多路复用器驱动系统时钟。时钟多路复用器选择多个输入之一,并将该信号转发到前面描述的AHB总线之一。图3中的时钟配置图来自STM32CubeIDE
,用于配置STM32F411RE
的时钟系统。这是一个非常好的工具,可以学习时钟系统的工作原理,但使用HAL创建项目时引入的硬件抽象层(HAL)增加了大量冗余代码,在大多数情况下是不必要的。
SYSCLK
-SYSCLK
是主系统时钟,来源于HSI时钟、HSE时钟或PLL时钟HCLK
- AHB总线(AHB1或AHB2)的时钟信号PCLK1
- APB1分频器后的APB1源PCLK2
- APB2分频器后的APB2源

主系统时钟SYSCLK
可以由三种来源之一驱动;请参阅图3中的系统时钟多路复用器
- 高速内部(HSI)时钟,对于
STM32F411RE
来说,它是一个16MHz RC振荡器电路。 - 高速外部(HSE)时钟,
STM32F411RE
具有一个精确稳定的8MHz外部晶体振荡器时钟。 - 锁相环(PLL),可变输出频率,具体取决于处理器,但对于
STM32F411RE
处理器,最大频率为100MHz。
还有两个低速时钟源,我可能会在另一篇文章中提及。这些时钟源驱动实时时钟(RTC)和独立看门狗(IWDG)子系统。(参见图3。)
- LSI - 32KHz,低速内部时钟
- LSE - 32.768KHz,低速外部时钟
选择时钟源
将HSE和HSI设置为系统时钟源是一个相当简单的过程,只需几个步骤即可完成,而PLL时钟由于乘法器和各种分频器而复杂得多。

要将HSE时钟设置为系统时钟,请设置时钟控制寄存器中的HSEON(位16)。如果HSE时钟源是需要单个引脚的类型,则应使用OSC_IN
引脚并在CR寄存器中设置HSEBYP(位18)。一旦设置了HSERDY
位,表示HSE振荡器稳定,则设置时钟配置寄存器中的系统时钟切换位(位0)以选择HSE系统时钟源。
// Set the HSEON, then wait for HSERDY
RCC->CR |= (1 < < 16);
while(!(RCC->CR & (1 < < 17)));
// System clock switch to HSE
RCC->CFGR |= 0x01;

要将HSI时钟设置为系统时钟,请设置HSION
(位0)并等待HSIRDY
(位1)被设置,表示HSI振荡器稳定。一旦设置了HSIRDY
位,时钟配置寄存器中的切换位(位0)将被复位以选择HSI作为系统时钟源。
// Set the HSION bit and wait for HSIRDY
RCC->CR |= 0x01;
while(!(RCC->CR & (1 < < 2)));
// System clock switch to HSI
RCC->CFGR &= ~0x03;
将PLL编程为系统时钟源相当复杂,需要仔细考虑将由哪个源驱动以及要输出的频率。PLL电路的源可以是HSI或HSE时钟,并且需要相应地进行预分频,因为主PLL的输入必须等于1。例如,如果我们将PLL源设置为HSI,则M分频器必须设置为16。(参见图3 [A]。)
在将PLL设置为更高频率时,另一个关键因素是闪存延迟等待状态。由于内存通常比MPU慢,因此会引入等待状态,以便MPU能够与内存通信。(参见表1。)
在提供的示例中,PLL频率设置为48MHz,这实际上不需要等待状态,但添加它只是为了演示如何实现。

// Set SYSCLK/HCLK to 48MHz, APB1 to 24MHz and APB2 to 48MHz
// Enable HSE as our source clock and wait for it to settle
RCC->CR |= (1 < < 16);
while (!(RCC->CR & (1 < < 17)))
// Disable PLL (PLLON), this must be done while we configure PLL settings
RCC->CR &= ~(1 < < 24);
// PLL Config settings
RCC->PLLCFGR |= (1 < < 22); // Designate HSE as source
RCC->PLLCFGR &= ~(0x1f); // Clear M field
RCC->PLLCFGR |= (0x08); // M / 8
RCC->PLLCFGR &= ~(0x1FF < < 6); // Clear N field
RCC->PLLCFGR |= (192 < < 6); // N * 192 = 48MHz
RCC->PLLCFGR &= ~(0x03 < < 16); // Clear P field
RCC->PLLCFGR |= (0x01 < < 16); // P / 4
RCC->PLLCFGR &= ~(0x0f < < 24); // Clear Q field
RCC->PLLCFGR |= (0x04 < < 24); // Q / 4
// Enable PLL and wait to settle
RCC->CR |= (1 < < 24);
while (!(RCC->CR & (1 < < 25)))
/*
The voltage scaling is adjusted to fHCLK frequency as follows:
- Scale 3 for fHCLK ≤ 64 MHz
- Scale 2 for 64 MHz < < fHCLK ≤ 84 MHz
- Scale 1 for 84 MHz < < fHCLK ≤ 100 MHz
*/
PWR->CR |= (0x01 < < 14); // Scale 3
/*
Flash Latency - To correctly read data from Flash memory,
the number of wait states (LATENCY) must be correctly programmed in the
Flash access control register (FLASH_ACR) according to the
frequency of the CPU clock (HCLK) and the supply voltage of the device.
- enable Prefetch (bit 8)
- enable Instruction cache (bit 9)
- enable Data Cache (bit 10)
- Set wait state to 1 (Refer to Table 1)
*/
FLASH->ACR = (1 < < 8) | (1 < < 9) | (1 < < 10) | (2 < < 0);
RCC->CFGR &= ~(0x03); // Clear System clock switch
RCC->CFGR |= 0x02; // Switch to PLL
RCC->CFGR &= ~(0x0F < < 4); // HCLK Prescale / 1
RCC->CFGR &= ~(0x07 < < 10); // APB1 Prescale clear
RCC->CFGR |= (0x04 < < 10); // APB1 Prescale / 2
RCC->CFGR &= ~(0x07 < < 13); // APB2 Prescale / 1
下表摘自参考手册,显示了在各种频率和功率等级下所需的各种等待状态。我花了一些时间才弄清楚,当我将频率设置为最大值时,还需要添加等待状态才能使其正常工作。

编程分频器
根据AMBA规范(修订版2.0),“AHB作为高性能系统骨干总线。AHB支持处理器、片上存储器和片外外部存储器接口与低功耗外设宏单元功能的有效连接。AHB还指定确保在高效设计流程中使用综合和自动化测试技术时易于使用。”
SYSCLK可以通过编程AHB分频器来分频以提供CPU时钟(HCLK)频率。该值可以是1-512的2的幂,对于STM32F411RE
,最大频率可以达到100MHz。
列表4中的代码是一个编程AHB分频器的例程。由于这是演示代码,仅作为概念验证编写,而不是用于生产,因此没有进行错误检查或验证。
void SetAHBPrescale(uint8_t val)
{
// Valid values are from 1 to 512 in power of 2.
// Output maximum of 100MHz
// Bits 4-7 determine AHB prescale value
RCC->CFGR &= ~(0x0F < < 4);
RCC->CFGR |= ((val & 0x0f) < < 4);
}
根据AMBA规范(修订版2.0),AMBA APB经过优化,可最大限度地降低功耗并减少接口复杂性,以支持外设功能。APB可与任一版本的系统总线结合使用。
STM32F411RE
有两个APB总线;一个低速APB1和一个高速APB2,它们通过一对AHB到APB桥从AHB总线传播。(参见图2。)图2还显示了两个DMA通道使用APB桥直接与各种外设通信。
列表5中的代码是编程APB分频器的例程。可以看出,这个过程相当简单,只需设置各3位即可。
void SetAPB1Prescale(uint8_t val)
{
// Valid values are from 1 to 512 in power of 2.
// Output maximum of 50MHz
// Bits 10-12 determine APB1 Low speed prescale value
RCC->CFGR &= ~(0x07 < < 10);
RCC->CFGR |= ((val & 0x07) < < 10);
}
void SetAPB2Prescale(uint8_t val)
{
// Valid values are from 1 to 16 in power of 2.
// Output maximum of 100MHz
// Bits 13-15 determine APB2 High speed prescale value
RCC->CFGR &= ~(0x07 < < 13);
RCC->CFGR |= ((val & 0x07) < < 13);
}
将时钟输出到GPIO引脚
我使用过的大多数微控制器都提供了一种将时钟输出到I/O引脚的方法,ARM也不例外。提供了两个引脚,允许程序员将几种时钟源之一输出到一个或两个芯片提供的引脚,作为额外福利,它们还为输出提供了分频器。有关每个引脚提供的时钟输出的描述,请参阅参考手册。
列表6中提供的示例代码将MCO1设置为将OutType
指定的时钟输出到QPIOA引脚8。
void InitClkOutput(OutType typ)
{
/*
* OutType
* HSI = 00
* LSI = 01
* HSE = 10
* PLL = 11
*/
// GPIOAEN: IO port A clock enable
RCC->AHB1ENR |= 1;
// Set Alternate function mode for PA8
GPIOA->MODER |= 0x00020000;
// Refer to the datasheet, Table 9 Page 150
// AFRH AF00 = MCO1
GPIOA->AFR[1] &= ~0x0f;
/*
* MCO1: Microcontroller clock output 1 and MCO1PRE: MCO1 prescaler.
* According to manual it is highly recommended that these values be
* set before enabling external oscillators. If the type is not PLL
* then set the appropriate bit(s).
*/
if (typ > 0)
RCC->CFGR |= (typ < < 21);
// No prescale, at 48MHz most scopes will work.
RCC->CFGR &= ~(0x07 < < 24);
}
另一个很棒的资源是DigiKey,他们制作了一系列关于STM32
和NUCLEO
生态系统的视频,非常适合初学者。我从Microchip世界过来,那里的SAM系列处理器是基于ARM的,所以跳到STMicroelectronics并不是一个巨大的飞跃,但足以让我花点时间适应。该系列视频的第一部分向用户介绍了IDE以及如何使用HAL框架创建项目。STM32和Nucleo入门第一部分:STM32CubeIDE和Blinky简介 – Digi-Key。我不是框架的忠实粉丝,但当我开始时,我会按照我的意愿配置外设,然后查看生成的代码,编译并运行它以查看预期结果。然后我创建一个空项目并使用基本的CMSIS语言进行编码。
结论
作为ARM平台的新手,从Arduino(Atmel/MicroChip)转过来是一次学习经历。我研究得越多,就越喜欢ARM系列处理器。自撰写本文以来,我订购了各种其他处理器,例如:Cortex-M0 STM32F030K6T6芯片、Cortex-M3 Blue Pill开发板和NUCLEO-144 Cortex-M7开发板。我订购了STM32F030K6T6芯片并制作了一个电路板来安装它们,尝试使用我很久以前买的一台返工机焊接它们。它虽然不漂亮,但能用,随着时间的推移我会做得更好。
在我的研究过程中,我还观看了许多视频,阅读了许多文章,并查阅了各种网站上的大量帖子,并在参考部分和整篇文章中提供了一些更好的文章和视频。我希望它能像帮助我一样帮助大家。
参考文献
- ARM架构系列 wikipedia.com
- ARM处理器:A、R和M类别及其具体特点 sirinsoftware.com
- NUCLEO-F411RE,digikey digikey.com
- STM32F411RE数据手册下载 mouser.com
- STM32CubeIDE STM32集成开发环境, st.com
- STM32和Nucleo入门第一部分:STM32CubeIDE和Blinky简介 - Digi-Key, youtube.com
- STM32CubeProg 适用于所有STM32的STM32CubeProgrammer软件, st.com
- STM32F411微处理器, st.com
- STM32CubeIDE简介 st.com #60:锁相环电路和频率合成基础 youtube.com
- ARM产品系列 arm.com
- stm32cube-mcu-package github.com
- LPC1768:SysTick定时器 exploreembedded.com
- #7 ARM微控制器教程 - 使用GNU库将时钟更改为48Mhz youtube.com
- 高级微控制器总线架构:简介,2019年6月2日,Stephen St. Michael allaboutcircuits.com
历史
- 2023年2月6日:初始版本