Click here to Skip to main content
15,879,095 members
Articles / Programming Languages / C++

ARM Tutorial Part 1 Clocks

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
6 Feb 2023CPOL11 min read 10.1K   83   17   7
This is the first in a series of articles based on the ARM CPU architecture.
This article discusses clock configuration, prescalers, and GPIO clock output, facilitating understanding of ARM processors.

Introduction

The ARM clock scheme is a very complex; but, flexible system allowing the developer to control various sections of the processor using a collection of: clock sources, clock multiplexers and prescalers propagating the signal along a series of buses to the various peripherals.

With most other processors, the system clock is continuously active; but in the ARM ecosystem, each clock can be independently switched on and off to conserve power. In this way, peripherals that are not being used are not clocked and therefore not drawing valuable resources.

The example code and this article's discussion will center around an STM32 NUCLEO-64 board featuring the STM32F411RE processor. The board is manufactured by STMicroelectonics and is a 32-bit Cortex-M4 MCU. The ARM family of processors provide solutions in a wide range of areas that address performance, power and cost requirements.

The STM32F411RE processor is a 32bit Cortex-M4 microcontroller with a maximum clock speed of 100MHz that requires a 1.7V to 3.6V supply, contains 512KB flash and 128KB SRAM. Peripherals include 81 I/O ports, of which 77 are 5V tolerant, 3 I2C interfaces, 3 USARTS, 5 SPI, an OTG USB, 16 channels ADC, 11 Timers, DMA and CRC.

Be aware that while the power supplied to the device is 3.3V some pins are 5V tolerant. Refer to the STM32F411RE datasheet in the section "Pinouts and pin description" for details. This is pertinent only when the pin is configured as input, when configured as output the pin outputs at the 3.3V level.

I am in no way affiliated with or receive any compensation from STMicroelectronis, I only chose this family of devices because they were inexpensive, the IDE was free, with no restrictions, and I'm a cheap bastard.

Figure 1: NUCLEO-64 featuring the STM32F411RE processor

To follow along with the code examples, you will need the items listed in the following table. Any ARM processor in the M4 family should work for these examples but the examples provided were written and tested on the NUCLEO-64 board that has an STM32F411RE processor. The board has an ST-LINK programmer/debugger on board so it can be used directly from the STM32CubeIDE. (For $13, that's not a bad deal.)

Gear you might want to download

What's needed From Where
NUCLEO-64 board, STM32F411RE (Optional) digikey.com
STM32CubeIDE for STM32 (Optional) st.com
STM32F411RE Reference Manual mouser.com
STM32F411RE Datasheet mouser.com

Prior to discussing the clock system, a brief introduction to the Advanced Microcontroller Bus Architecture (AMBA) is necessary. AMBA is an open-standard, on-chip interconnect specification for the connection and management of functional blocks in system-on-a-chip (SoC) designs. As can be seem in Figure 2, the STM32F411RE contains and AHB, APB1 and APB2 buses.

A simple transaction on the AHB consists of an address phase and a subsequent data phase (without wait states: only two bus-cycles) and has a large bus width that can be 64 to 1024 bits.

"APB is designed for low bandwidth control accesses, for example, register interfaces on system peripherals. This bus has an address and data phase similar to AHB, but a much reduced, low complexity signal list (for example, no bursts). Furthermore, it is an interface designed for a low frequency system with a low bit width (32 bits)."

Figure 2. STM32F411xC/xE block diagram

There are various clock sources that may be used to drive the System clock via a System Clock Mux. A clock mux selects one of several inputs and propagates that signal forward to one of the AHB bus, described previously. The clock configuration chart in Figure 3 is taken from the STM32CubeIDE and is used to configure the clock system for the STM32F411RE. It's a very nice utility to learn how the clock system works but the Hardware Abstraction Layer (HAL) that's introduced when creating a project with HAL adds a lot of bulk that in most cases is not needed.

  • SYSCLK - The SYSCLK is the main system clock derived from either the HSI clock, HSE clock, or from the PLL clock
  • HCLK - The clock signal for the AHB bus (AHB1 or AHB2)
  • PCLK1 - APB1 source after the APB1 prescaler
  • PCLK2 - APB2 source after the APB2 prescaler
Figure 3. Clock configuration, source STM32CubeIDE

The main system clock SYSCLK can be driven by one of three sources; refer to the System Clock Mux in Figure 3:

  • High Speed Internal (HSI) Clock that in the case of the STM32F411RE is a 16MHz RC Oscillator circuit.
  • High Speed External (HSE) clock, the STM32F411RE has an external 8MHz oscillator clock that is accurate and stable.
  • Phase Locked Loop (PLL), Variable output frequency that is processor specific, but for the STM32F411RE processor has a maximum frequency of 100MH.

There are two more low speed clock sources that I may mention in another article. These clock sources drive the Real Time Clock (RTC) and Independent Watchdog (IWDG) sub-systems. (Refer to Figure 3.)

  • LSI - 32KHz, Low speed internal clock
  • LSE - 32.768KHz, Low speed external clock

Selecting the Clock Source

Setting the HSE and HSI as the system clock source is a fairly simple process and requires only a couple of steps to perform, as opposed to the PLL clock which is far more complicated because of the multiplier and various prescalers.

Figure 4. RCC clock control register (RCC_CR)

To set the HSE clock as system clock, set the HSEON (bit 16) in the Clock Control register. If the HSE clock source is of the type that requires a single pin, then the OSC_IN pin should be used and the HSEBYP (bit 18) set in the CR register. Once the HSERDY bit is set, indicating that the HSE oscillator is stable, the System Clock Switch bit (bit 0) in the Clock Configuration register is set to select HSE system clock source.

C++
// Set the HSEON, then wait for HSERDY
RCC->CR |= (1 < < 16);
while(!(RCC->CR & (1 < < 17)));

// System clock switch to HSE
RCC->CFGR |= 0x01;
Listing 1. Programming HSE as source for SYSCLK
Figure 5. RCC clock configuration register (RCC_CFGR)

To set the HSI clock as the system clock, set the HSION (bit 0) and wait for the HSIRDY (bit 1) to be set indicating that the HSI oscillator is stable. Once the HSIRDY bit is set, the Switch bit (bit 0) in the Clock Configuration register is reset to select HSI as the system clock source.

C++
// Set the HSION bit and wait for HSIRDY
RCC->CR |= 0x01;
while(!(RCC->CR & (1 < < 2)));

// System clock switch to HSI
RCC->CFGR &= ~0x03;    
Listing 2. Programming HSI as source for SYSCLK

To program the PLL as the system clock source is fairly complicated and takes some thought as to what source will drive it and the frequency that is to be output. The source to the PLL circuit can either be the HSI or HSE clocks and will need to be prescaled accordingly, as the input to the Main PLL must be equal to 1. As an example, if we set the PLL source as HSI, the M divider must be set to 16. (Refer to Figure 3 [A].)

Another factor that is critical when setting the PLL to higher frequencies is the Flash Latency wait states. Because memory is generally slower than the MPU, wait states are introduced so that the MPU will be able to communicate with memory. (Refer to Table 1.)

In the example in provided the PLL frequency is set to 48MHz, which doesn't really require wait states but was added to show how it could be accomplished.

Figure 6. RCC PLL configuration register (RCC_PLLCFGR)
C++
// 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    
Listing 3. PLL as source for 48MHz System Clock frequency.

The table below was copied from the Reference Manual and shows the various wait states that are required at various frequencies and power ratings. It took me a while to figure out that when I set the frequency to its maximum value that I also needed to add wait states for it to work.

Table 1. Number of wait states according to CPU clock (HCLK) frequency

Programming the Prescalers

According to the AMBA Specification (Rev 2.0), "The AHB acts as the high-performance system backbone bus. AHB supports the efficient connection of processors, on-chip memories and off-chip external memory interfaces with low-power peripheral macrocell functions. AHB is also specified to ensure ease of use in an efficient design flow using synthesis and automated test techniques."

SYSCLK may be scaled to provide the CPU clock (HCLK) frequency by programming the AHB Prescaler. This value can be from 1-512 in powers of 2 and in the case of the STM32F411RE can have a maximum frequency of 100MHz.

The code in Listing 4 is a routine that programs the AHB Prescaler. Since this is demo code and only written as a POF and not produced for production, no error checking or validation is done.

C++
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);
}
Listing 4. Code to program the AHB Prescaler

According to the AMBA Specification (Rev 2.0), AMBA APB is optimized for minimal power consumption and reduced interface complexity to support peripheral functions. APB can be used in conjunction with either version of the system bus.

The STM32F411RE has two APB buses; a low speed APB1 and a high speed APB2 that propagate from the AHB bus through a pair of AHB-to-APB bridges. (Refer to Figure 2.) Figure 2 also shows that the two DMA channels use the APB bridges to communicate directly with the various peripherals.

The code in Listing 5 are routines that program the APB Prescalers. As can be seen, the process is fairly simple, only a set of 3 bits each needs to be set.

C++
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);
}
Listing 4. Code to program the AHB Prescaler

Outputting the Clock to a GPIO Pin

Most Microcontrollers that I have worked with provide a way to output the clock to a I/O pin and ARM is no exception. Two pins are provided to allow the programmer to output one of several clock sources to one or both of the pins provided on the chip and as a bonus, they provide a prescaler for the output. For a description of what clocks are provided for output on each pin, refer to the Reference Manual.

The example code provided in Listing 6 sets the MCO1 to output clock specified by OutType to QPIOA pin 8.

C++
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);
}
Listing 6. Code to program MCO1 to output to PA8

Another great resource is DigiKey, they have created a series of videos on the STM32 and NUCLEO ecosystems that are perfect for beginners. I'm coming over from the Microchip world where the SAM series of processors are ARM based so it wasn't a giant leap to STMicroelectronics but it was enough that it took a little time to adjust. Starting with the first video in the series introduces the user to the IDE and creating a project using the HAL framework. Getting Started with STM32 and Nucleo Part 1: Introduction to STM32CubeIDE and Blinky – Digi-Key. I'm not a big fan of frameworks but when I started, I would configure the peripheral like I wanted it and then look at the code generated, compile and run it to see what to expect. Then I created an empty project and coded it using basic CMSIS language.

Conclusion

Being new to the ARM platform and coming from the Arduino (Atmel/MicroChip) has been a learning experience. The more research I did, the more I grew to like the ARM family of processors. Since writing this article, I have ordered various other processors such as the; Cortex-M0 STM32F030K6T6 chips, Cortex-M3 Blue Pill board, and a NUCLEO-144 Cortex-M7 board. I ordered the STM32F030K6T6 chips and created a board to mount them to attempt to solder them using a rework machine I bought a while back. It ain't pretty, but it works and with time I will get better.

Also while doing my research, I viewed a lot of videos, read a lot of articles and went through a lot of posts on various sites and have provided some of the better articles and videos in the reference section and throughout the article. I hope it helps y'all as much as it helped me.

References

  • ARM architecture family wikipedia.com
  • The ARM Processors: A, R, and M Categories and Their Specifics sirinsoftware.com
  • NUCLEO-F411RE, digikey digikey.com
  • STM32F411RE Datasheet download mouser.com
  • STM32CubeIDE Integrated Development Environment for STM32, st.com
  • Getting Started with STM32 and Nucleo Part 1: Introduction to STM32CubeIDE and Blinky - Digi-Key, youtube.com
  • STM32CubeProg STM32CubeProgrammer software for all STM32, st.com
  • STM32F411 Micro Processor, st.com
  • Introduction to STM32CubeIDE st.com #60: Basics of Phase Locked Loop Circuits and Frequency Synthesis youtube.com
  • ARM Product families arm.com
  • stm32cube-mcu-package github.com
  • LPC1768: SysTick Timer exploreembedded.com
  • #7 ARM Microcontroller Tutorial - Changing the Clock to 48Mhz Using the GNU Library youtube.com
  • The Advanced Microcontroller Bus Architecture: An Introduction, June 02, 2019 by Stephen St. Michael allaboutcircuits.com

History

  • 6th February, 2023: Initial version
This article was originally posted at https://JaxCoder.com

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired
United States United States
Currently enjoying retirement and working on projects without pressure, deadlines or any kind of management.

Comments and Discussions

 
GeneralNice. Pin
jeron117-Feb-23 4:50
jeron117-Feb-23 4:50 
GeneralRe: Nice. Pin
Mike Hankey17-Feb-23 5:03
mveMike Hankey17-Feb-23 5:03 
GeneralRe: Nice. Pin
jeron117-Feb-23 5:19
jeron117-Feb-23 5:19 
QuestionCortex M Pin
Member Negio5-Feb-23 7:12
Member Negio5-Feb-23 7:12 
AnswerRe: Cortex M Pin
Mike Hankey5-Feb-23 8:32
mveMike Hankey5-Feb-23 8:32 
GeneralMy vote of 5 Pin
CPallini5-Feb-23 6:31
mveCPallini5-Feb-23 6:31 
Very nice!
GeneralRe: My vote of 5 Pin
Mike Hankey5-Feb-23 8:33
mveMike Hankey5-Feb-23 8:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.