STM32 – Clock code examples

These code examples will show the clock configuration at a pure register level rather than using HAL drivers. The HAL (Hardware Abstraction Layer) is an ST library to make configuring the device easier and while this can make your life easier it can also make it really hard. Especially if you don’t understand what is happening in the code or how the system architecture and registers work.

This code will be based on the SMT32L152RC but as most arm cortex processors are similar, it should trnslate accros most. Either way, I think this will gave a good intro into the SMT32 clcok set up at a base level. The example is kept simple and is lacking is error hanlding etc – that sfor you to add, I just want to keep this simple.

Please see the overview and register page for details.

Project Set up

I am using the Atollic  IDE which is now free from ST (who bought it). It’s an Eclipse-based IDE.

  1. Click ‘New’
  2. Click ‘C Project’
  3. Select ‘Embedded C project’
  4. Add your project Name

Screenshot for 'New C Project''

Next, you need to select your Processor or development board. In this case, we just want the processor as I don’t want it to reconfigure anything.

  1. Click ‘STM31L’
  2. Click ‘MCUs’
  3. Scroll down and select STM32L152RC
  4. Click Next

MCU Selection

You will need to select a Debug probe. If you are using one of the discovery boards or Nucleo boards then it has an ST-Link built in which allows for programming and debugging the processor. If not you will need to buy and set up one.

  1. Select ‘ST-LINK’ from the drop-down list
  2. Click ‘Finished’

Select debug Probe ST-Link

Project Template Overview

Once the project has been created, you will see a number of files already in the file structure that handle the initial setup of the device – this, in this case, is a very minimal setup to ensure the device runs on its default which will be the MSI clock at 2049Mhz.

Project File Structure

In the file structure, you will see the following files in the ‘src’ folder

  • main.c – this is the main or main entry point into the project
  • startup-smt32l152xc.s – this is the ‘boot’ file which sets up some low level such as vector tables and calls the initial clock set up
  • system_stm32l152xx.c – The file contains a function called SystemInit that sets up the clock in its default mode to ensure the device runs on power up.
  • tiny_printf.c – which is a reduced printf library – you can select the full when creating the project or just link it in.

There is also a ‘Drivers’ folder and this contains the headers required for this family of MCUs which we will be using. They are from the CMSIS (Cortex Microcontroller Software Interface Standard) organisation who develop and maintain these files and allow for a standard to be implemented.

There is the main header file ‘stm32l1xx.h’ which will then link to a more specific file for the device. Note, however, that not all #defines are there and often generic #defines are used  – more on this later.

Important: You do need to edit the header file and uncomment the line with your device so that more device-specific #defines are available in your project. Line 21 in this case

/* Uncomment the line below according to the target STM32L device used in your
application
*/

#if !defined (STM32L100xB) && !defined (STM32L100xBA) && !defined (STM32L100xC) && \
!defined (STM32L151xB) && !defined (STM32L151xBA) && !defined (STM32L151xC) && !defined (STM32L151xCA) && !defined (STM32L151xD) && !defined (STM32L151xDX) && !defined (STM32L151xE) && \
!defined (STM32L152xB) && !defined (STM32L152xBA) && !defined (STM32L152xC) && !defined (STM32L152xCA) && !defined (STM32L152xD) && !defined (STM32L152xDX) && !defined (STM32L152xE) && \
!defined (STM32L162xC) && !defined (STM32L162xCA) && !defined (STM32L162xD) && !defined (STM32L162xDX) && !defined (STM32L162xE)
/* #define STM32L100xB */ /*!< STM32L100C6, STM32L100R and STM32L100RB Devices */
/* #define STM32L100xBA */ /*!< STM32L100C6-A, STM32L100R8-A and STM32L100RB-A Devices */
/* #define STM32L100xC */ /*!< STM32L100RC Devices */
/* #define STM32L151xB */ /*!< STM32L151C6, STM32L151R6, STM32L151C8, STM32L151R8, STM32L151V8, STM32L151CB, STM32L151RB and STM32L151VB */
/* #define STM32L151xBA */ /*!< STM32L151C6-A, STM32L151R6-A, STM32L151C8-A, STM32L151R8-A, STM32L151V8-A, STM32L151CB-A, STM32L151RB-A and STM32L151VB-A */
/* #define STM32L151xC */ /*!< STM32L151CC, STM32L151UC, STM32L151RC and STM32L151VC */
/* #define STM32L151xCA */ /*!< STM32L151RC-A, STM32L151VC-A, STM32L151QC and STM32L151ZC */
/* #define STM32L151xD */ /*!< STM32L151QD, STM32L151RD, STM32L151VD & STM32L151ZD */
/* #define STM32L151xDX */ /*!< STM32L151VD-X Devices */
/* #define STM32L151xE */ /*!< STM32L151QE, STM32L151RE, STM32L151VE and STM32L151ZE */
/* #define STM32L152xB */ /*!< STM32L152C6, STM32L152R6, STM32L152C8, STM32L152R8, STM32L152V8, STM32L152CB, STM32L152RB and STM32L152VB */
/* #define STM32L152xBA */ /*!< STM32L152C6-A, STM32L152R6-A, STM32L152C8-A, STM32L152R8-A, STM32L152V8-A, STM32L152CB-A, STM32L152RB-A and STM32L152VB-A */
#define STM32L152xC  // STM32L152CC, STM32L152UC, STM32L152RC and STM32L152VC
/* #define STM32L152xCA */ /*!< STM32L152RC-A, STM32L152VC-A, STM32L152QC and STM32L152ZC */
/* #define STM32L152xD */ /*!< STM32L152QD, STM32L152RD, STM32L152VD and STM32L152ZD */
/* #define STM32L152xDX */ /*!< STM32L152VD-X Devices */
/* #define STM32L152xE */ /*!< STM32L152QE, STM32L152RE, STM32L152VE and STM32L152ZE */
/* #define STM32L162xC */ /*!< STM32L162RC and STM32L162VC */
/* #define STM32L162xCA */ /*!< STM32L162RC-A, STM32L162VC-A, STM32L162QC and STM32L162ZC */
/* #define STM32L162xD */ /*!< STM32L162QD, STM32L162RD, STM32L162VD and STM32L162ZD */
/* #define STM32L162xDX */ /*!< STM32L162VD-X Devices */
/* #define STM32L162xE */ /*!< STM32L162RE, STM32L162VE and STM32L162ZE */
#endif

System_stm32L1xx.c

As mentioned above, this file is called during the ‘boot up’ and before main() is called. It sets up the clock configuration to the default MSI clock at 2Mkz and sets up the vector table.

You should do you initial clock set up here before main is called, else you could write a new initialisation function which you would call in the first line of main().

The Code

For this example, I will leave this file (system_smt32Lxx.c) alone and set up a new initialisation function. It would be better to set up separate files for these, I will keep it simple.

First I will add two empty functions. One to initialise the GPIO which we need to output a clock onto Pin 8 of Port A. This secondary function is called MCO (Master Clock Output).

MCO (Master Clock Output)

Many MCU’s will have a pin that can be set up to output a selected clock onto the pin. This can be used for a variety of reasons but for this example we will use it to measure the clock signal to ensure the clock is configured as expected. On the STM32L1542 this is pin PA8.

In order for this to work, we need to configure the pin for the Alternate Function

PA8 has the following alternate functions:

  1. MCU
  2. USART
  3. COM0 (for LCD)
  4. TIMx_IC3
  5. Event out.

When configuring the GPIO, the first thing is to configure the pin as an alternate function

void initGPIO(void)
{
	GPIOA->MODER ~(GPIO_MODER_MODER8_Msk);
	GPIOA->MODER |= GPIO_MODER_MODER8_1;
}

The first thing to do (best practice) is the clear the bits so that the next OR operation is not compromised. You can do this in a single operation, but this makes it clear what you are doing.
GPIOA is a pointer to the register for Port A. The CMSIS defines the MASK bits for each pin.
MODER is the Ports Pin Mode (either Input, output or alternate function)

GPIO Indicates it’s for a GPIO
MODER Indicates it’s for the Pin function
8 indicates it’s for Pin 8
1 indicates its value, which if we look at the definition is 0x2 << GPIO_MODER_MODER8_Pos, where Pos is the bit position within the 32bit Port Register.

I don’t find this terribly useful do I like to add new definitions to supplement the supplied #defines as follows

// Additional #defines for the STM32L152RC
#define GPIO_MODER_INPUT 		0x0
#define GPIO_MODER_OUTPUT 		0x1
#define GPIO_MODER_ALTERNATE 	        0x2
#define GPIO_MODER_ANALOG		0x3

Now I can still use the position #defines and these more useful #defines as such

void initGPIO(void)
{
  GPIOA->MODER ~(GPIO_MODER_MODER8_Msk);
  GPIOA->MODER |= GPIO_MODER_ALTERNATE << GPIO_MODER_MODER8_Pos;
}

Before these registers will be set, you need to enable the Port clock as follows

void initGPIO(void)
{
  // first enable the Port clock else nothing will work.
  RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
  GPIOA->MODER &= ~(GPIO_MODER_MODER8_Msk);
  GPIOA->MODER |= GPIO_MODER_ALTERNATE << GPIO_MODER_MODER8_Pos;
}

Then we need to tell the MCU which alternate function we are using. Looking at an extract from the datasheet for this chip (you must check this with the datasheet of the chip and not the reference manual)

Alternate Functions table

This shows us all the alternate function and the top row is the Function bit position that switched the function on or off. So for this example, we want to select MCO which is 0x0.

Port A has 16 pins and each alternate function requires 4 bits each, so the registers for the pins are split as follows

AFRL for pins 0 through 7
AFRH for pins 8 through 15

The register is a 2 item array AFR[2] where [0] is the low register and [1] is the high register

As this is pin 8, we need to get the first 4 bits of the High Register.

Again, the #define is not exactly useful so I will supplement with my own #define

#define GPIO_AFR_PA8_MCO		0x0

with the final GPIO code initialisation as follows

void initGPIO(void)
{
  GPIOA->MODER &= ~(GPIO_MODER_MODER8_Msk);
  GPIOA->MODER |= GPIO_MODER_ALTERNATE << GPIO_MODER_MODER8_Pos;
  // Select which Alternate function we need
  GPIOA->AFR[1] &= ~(GPIO_AFRH_AFRH0_Msk); // Reset the bits for that Function
  GPIOA->AFR[1] |= GPIO_AFR_PA8_MCO << GPIO_AFRH_AFRH0_Pos;
}

The last thing is to call our new GPIO initialisation function.

int main(void)
{
  initGPIO();
  int i = 0;

  while (1)
  {
    i++;
  }
  return 0;
}

We won’t see anything on Pin 8 yet, but run the program and check that the registers are set as expected

Clock Initialisation

Now that GPIO is configured, we need to set up the clock MCO setting. Ie Which clock to pump out to PA8. For the first run, we will assign SYSCLK to the MCO pin so that we can verify what the main clock is running.

In the debug Register Tab (SFR), go to GPIOA and open MODER. You should see after the code has run, that MODER8 changes from 0x0 to 0x2, indicating that PIN 8 has been set to an alternate function.

Debug Register for MODER

Now we need to tell the MCU which clock we want to output.

This is done in the RCC_CFGR (Configuration) register and there is a #define supplied for this. In the initClock function add

void initClock(void)
{
    RCC->CFGR &= ~(RCC_CFGR_MCOSEL_Msk); // Reset the MCO Selection bits
    RCC->CFGR |= RCC_CFGR_MCOSEL_SYSCLK; // Select SYSCLK for output
}

and call the InitClock in Main()

int main(void)
{
  initClock();
  initGPIO();

That should compile and run – now we need to be able to see the clock output. Hopefully, you have either an Oscilloscope, frequency counter or a logic analyser. A multimeter won’t work for this. I’m going to use my Saleae Logic Analyser to log the view from, but note that this logic analyser cannot sample more than 2Mhz (just) so so we need to either use a good oscilloscope or pull a different trick which we will do soon.

2.083 Mhz clock pulse

If you don’t have a way to visualise this then step through your code and check the register values for the clock registers -if they are set then it should all work. I would not recommend buying a cheap frequency counter. I’ve not tried them, but its unlikely you will get an accurate or even close reading which will just be confusing.

Right, now that we can visualise the SYSTEM clock, let’s have a play with the other clock settings. If you have not had a look at the Clock overview page or a good look at the reference manual then please do as this will make more sense.

First, let’s just change the current clock (the MSI Clock) to 65.536khz

The MSI clock frequency setting is managed in the ICSCR register. Once again, the #defines for the range are generic and hence I will add my own defines as follows

#define RCC_ICSCR_MSIRANGE_65536_kHz	0x0
#define RCC_ICSCR_MSIRANGE_131072_kHz	0x1
#define RCC_ICSCR_MSIRANGE_262144_kHz	0x2
#define RCC_ICSCR_MSIRANGE_524288_kHz 	0x3
#define RCC_ICSCR_MSIRANGE_1048_MHz		0x4
#define RCC_ICSCR_MSIRANGE_2097_MHz 	0x5
#define RCC_ICSCR_MSIRANGE_4194MHz		0x6

Then in the initClock() function, I can simply switch between the frequency options.

Note: As with all internal clocks, the Frequency will almost never by 100% accurate. You do need to use an external crystal or resonator in order to get more accurate frequencies.

Add this to the initClock() function.

// Change MSI to 65536Khz
RCC->ICSCR &= ~(RCC_ICSCR_MSIRANGE_Msk);
RCC->ICSCR |= RCC_ICSCR_MSIRANGE_65536_kHz << RCC_ICSCR_MSIRANGE_Pos;

In order to change to any of the other frequencies, just select a different #define.

Next, we can change from the MSI clock to the High-Speed Internal (HSI) clock. This is 16Mhz for the SMT32L152RC MCU. When we set this, the output on the MCI pin (PA8) should be 16Mhz, but the logic analyser I’m using to test the frequency can not sample at that rate, so we will divide the MSO output by 16 to get a 1Mhz reading.

First, let’s get the HSI clock working

We need to switch the clock on and then configure the system to use the HI for the main system clock so add the following (highlighted) code to initClock(). You can comment out the change to the MSI clock as that won’t be needed for now. S

void initClock(void)
{
  RCC->CFGR &= ~(RCC_CFGR_MCOSEL_Msk); // Reset the MCO Selection bits
  RCC->CFGR |= RCC_CFGR_MCOSEL_SYSCLK; // Select SYSCLK for output

  // Change MSI to 65536Khz
  //RCC->ICSCR &= ~(RCC_ICSCR_MSIRANGE_Msk);
  //RCC->ICSCR |= RCC_ICSCR_MSIRANGE_65536_kHz << RCC_ICSCR_MSIRANGE_Pos;

  // switch the clock on
  RCC->CR |= RCC_CR_HSION;
  // wait for the clock to be ready before moving on
  while (!(RCC->CR & RCC_CR_HSIRDY));

}

Note: the while() loop is not good practice in this case. There is no exit should the clock not be ready. Ideally, you should handle the error or situation in some way.

Once the clock is ready, we can tell the processor to use it, which is done in the CFGR register so add the following code

// Configure the processor to use the clock
  RCC->CFGR &= ~(RCC_CFGR_SW_Msk); // Reset the current setting
  RCC->CFGR |= RCC_CFGR_SW_HSI; // Set a new value

So that’s the System Clock set up, but as I mentioned before, we can’t see this as the Logic Analyser I’m using can’t sample at that rate, so we need to divide the MCO output. I will set the MCO prescaler to 16 so that we get a 1Mhz pulse out.

Add the code to initClock()

// Divide the MCO output by 16
  RCC->CFGR &= ~(RCC_CFGR_MCOPRE_Msk); // Reset current value
  RCC->CFGR |= RCC_CFGR_MCOPRE_DIV16; // set the prescaler to 16

and when you compile and run you should get a nice 1Mhz clock pulse on PA8

1Mhz Clock Pulse

Now there are still the LSI clocks, but I hope this gives you enough of a flavour of how the clocks work and how to set them up using the registers. If or when you use the HAL framework or other 3rd party code, you will have an easier time debugging the system.