2025-06-03 5:20 AM
Hi all.
I have a small problem understanding the timer 16 of the STM32G030K6T6 microcontroller. According to the data sheet, it should use the TIMPCLK clock, which corresponds to the PCLK clock.
My system clock is supplied by the HSI (SYSCLK = 16 MHz). I have set my AHB prescaler to 4 and the APB prescaler to 1, so HCLK = PCLK = 4 MHz. Since my APB prescaler is equal to 1, the timer clock should be TIMPCLK = PCLK = 4 MHz.
I then implemented a delay function with timer 16. So that the counter runs up in 1 us steps, I have to set the prescaler of the timer to 15 instead of 3. This means that the timer 16 still has a 16 MHz signal as an input signal.
Now my question is: Have I misunderstood something here, is the data sheet wrong or is my code not correct? I have attached the quotes from the data sheet and my code. I hope you can help me.
Datasheet RM0454 Rev 5:
"Timer clock
The timer clock TIMPCLK is derived from PCLK (used for APB) as follows:
1. If the APB prescaler is set to 1, TIMPCLK frequency is equal to PCLK frequency.
2. Otherwise, the TIMPCLK frequency is set to twice the PCLK frequency."
"The peripherals are clocked with the clocks from the bus they are attached to (HCLK for
AHB, PCLK for APB) except:
• TIMx, with these clock sources to select from:
– TIMPCLK (selectable for all timers) running at PCLK frequency if the APB
prescaler division factor is set to 1, or at twice the PCLK frequency otherwise"
My code:
/**
******************************************************************************
* @file : main.c
* @author : Johannes S.
* @brief : Main program body
* @microcontroller : STM32G030K6T6
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include <main.h>
#include <stdint.h>
#include <StartUp.h>
/* Private typedef -----------------------------------------------------------*/
// Enum for the colors of the RGB LED
typedef enum {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
COLOR_INVALID
} LED_Color;
// Enum for GPIO Output Control
typedef enum {
HIGH,
LOW,
TOGGLE,
GPIO_State_INVALID
} GPIO_State;
/* Private function ---------------------------------------------------------*/
void LED_RGB(LED_Color color, uint8_t state);
void GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t Pin, uint8_t state);
void Button_1_SetInterrupt(void);
void GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t Pin);
#if !defined(__SOFT_FP__) && defined(__ARM_FP)
#warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif
/**
* Main function
*/
int main(void)
{
// Setup Microcontroller
Flash_Init(); // Set flash wait cycles
PWR_MODULE_Init(); // Set Internal voltage regulator
SystemClock_Config(); // Configure Clocks
GPIO_Init(); // Configure GPIO
TIM16_us_Tick(); // Configure Timer 16 for delay function
// Test RGB LED
LED_RGB(COLOR_RED, HIGH);
LED_RGB(COLOR_BLUE, HIGH);
LED_RGB(COLOR_GREEN, HIGH);
// Configure Button No. 1 as interrupt input
Button_1_SetInterrupt();
// Turn red and blue led off - to better recognize flashing green led
LED_RGB(COLOR_RED, LOW);
LED_RGB(COLOR_BLUE, LOW);
// measure_timer_duration();
// While loop - main loop
while(1)
{
// Test LED blink
delay_ms(3000);
LED_RGB(COLOR_GREEN, TOGGLE);
}
}
/**
* Function for controlling the RGB LED
*/
void LED_RGB(LED_Color color, GPIO_State state)
{
uint32_t pin;
GPIO_TypeDef *LED_GPIO;
// Color assignment based on Enum
switch (color) {
case COLOR_RED:
pin = LED_RGB_Red_Pin;
LED_GPIO = LED_RGB_Red_GPIO_Port;
break;
case COLOR_GREEN:
pin = LED_RGB_Green_Pin;
LED_GPIO = LED_RGB_Green_GPIO_Port;
break;
case COLOR_BLUE:
pin = LED_RGB_Blue_Pin;
LED_GPIO = LED_RGB_Blue_GPIO_Port;
break;
default:
return; // Invalid color, do nothing
}
// Switch on or off based on the status
switch (state) {
case HIGH:
GPIO_WritePin(LED_GPIO, pin, 1); // turn LED on
break;
case LOW:
GPIO_WritePin(LED_GPIO, pin, 0); // turn LED off
break;
case TOGGLE:
GPIO_TogglePin(LED_GPIO, pin); // toggle LED
break;
default:
return; // Invalid color, do nothing
}
}
/**
* Function for controlling a GPIO output
*/
void GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t Pin, uint8_t state) {
if (state) {
GPIOx->BSRR = Pin; // set GPIO high (Bit Set Reset Register - BSRR)
} else {
GPIOx->BRR = Pin; // set GPIO low (Bit Reset Register - BRR)
}
}
/**
* Toggle GPIO Outputs
*/
void GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t Pin) {
GPIOx->ODR ^= Pin;
}
/**
* Declare button 1 (PC6) as interrupt
*/
void Button_1_SetInterrupt(void)
{
// SYSCFG (System Configuration Controller) - Activate clock
// Required for: “Flag pending interrupts from each interrupt line”
// See: SYSCFG interrupt line 7 status register (SYSCFG_ITLINE7) - page 196
RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // Activate Clock for SYSCFG
// Connect pin PC6 to EXTI6 (SYSCFG Configuration)
EXTI->EXTICR[1] = 0x00000000; // Reset Register
EXTI->EXTICR[1] = 0x02 << 16; // Connect EXTI with PC6
EXTI->FTSR1 = 0x1UL << 6; // Interupt triggerd with falling edge
// Activate interrupt
// See en.stm32u0-system-extended-interrupt-event-controller-exti.pdf page 6
// EXTI_FTSR1 output is AND linked with EXTI_IMR1 -> therefore interrupt must be activated here
EXTI->IMR1 |= EXTI_IMR1_IM6; // Unmask interrupt (activate)
// Activate interrupt and set priority in NVIC (Nested vectored interrupt controller)
// Configuration for EXTI4_15
NVIC_SetPriority(EXTI4_15_IRQn, 1); // Set priority to 1 (low)
NVIC_EnableIRQ(EXTI4_15_IRQn); // Activate interrupt in NVIC
}
/**
* Interrupt service routine for button 1 (PC6)
*/
void EXTI4_15_IRQHandler(void)
{
LED_RGB(COLOR_BLUE, TOGGLE); // Toggle blue LED
EXTI->FPR1 |= 0x1UL << 6; // Delete interrupt pending register (otherwise interrupt is triggered again immediately)
}
void TIM16_us_Tick(void)
{
// Uses PCLK Clock = 16 MHz as source
// -> According to the data sheet and with the AHB prescaler of 2, this should actually be 8 MHz.
// However, the input frequency of the timer seems to be independent of the AHB prescaler.
// Enable TIM16 Clock
RCC->APBENR2 |= (0x1UL << 17);
// Set CR1 register
TIM16->CR1 &= ~TIM_CR1_UIFREMAP;
TIM16->CR1 |= TIM_CR1_ARPE;
// Set PSC register
TIM16->PSC = 15; // Set prescaler to 15 -> 1us task for timer (f_timer = f_pclk / (1+prescaler))
// Set ARR register
TIM16->ARR = 0xFFFF; // Maximum value (optional, depending on application)
// Activate Timer
//TIM16->CR1 |= TIM_CR1_CEN;
}
void delay_ms(uint16_t ms) {
// Timer-Setup: Prescaler and ARR are already configured,
// that the timer counts with 1 µs resolution.
TIM16->CR1 |= TIM_CR1_CEN; // start Timer
for (uint16_t i = 0; i < ms; i++) {
TIM16->CNT = 0; // Reset counter to 0
TIM16->SR &= ~TIM_SR_UIF; // Reset update flag (UIF)
//while (TIM16->CNT != 0); // Wait until the counter has been set to 0
//TIM16->CR1 |= TIM_CR1_CEN; // start Timer
while (TIM16->CNT < 1000); // wait 1000 µs = 1 ms
//TIM16->CR1 &= ~TIM_CR1_CEN; // stop Timer
}
TIM16->CR1 &= ~TIM_CR1_CEN; // stop Timer
// Edit later so that function is leaner and more accurate
}
/**
* Function to check timer
*/
void measure_timer_duration(void) {
TIM16->CNT = 0;
TIM16->CR1 |= TIM_CR1_CEN;
for (volatile int i = 0; i < 1000000; ++i); // Short known waiting time
TIM16->CR1 &= ~TIM_CR1_CEN;
uint32_t count = TIM16->CNT;
}
/**
******************************************************************************
* @author J.Stuermann
* @file StartUp.h
* @brief Header file for StartUp.c which sets Register for SMT32G030K6T6 for
* Start Up routine.
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef STARTUP_H_
#define STARTUP_H_
// Constants
// Functions
void Flash_Init(void);
void PWR_MODULE_Init(void);
void SystemClock_Config(void);
void GPIO_Init(void);
#endif /* STARTUP_H_ */
/**
******************************************************************************
* @author J.Stuermann
* @file StartUp.h
* @brief Header file for StartUp.c which sets Register for SMT32G030K6T6 for
* Start Up routine.
******************************************************************************
*/
/**
* Procedure:
* 1. Set flash wait cycles
* 2. Set internal voltage regulator
* 3. Configure System Clock
* 4. Configure GPIOs
* 5.
* 6.
*
*/
/* Includes ------------------------------------------------------------------*/
#include "StartUp.h"
/* Functions -----------------------------------------------------------------*/
/**
* Set flash wait cycles
*/
void Flash_Init(void) {
// Set flash wait cycles based on the system clock frequency.
// 0 Wait-State for <24 MHz
// 1 Wait-State für <48 MHz
// 2 Wait-State für <64 MHz
FLASH->ACR &= ~0x7UL; // Set Wait-State = 0
}
/**
* Set internal voltage regulator to “Range 1” -> 1.2 V is required for system clock up to 64 MHz
*/
void PWR_MODULE_Init(void)
{
// Reset
PWR->CR1 = 0x00000208;
// Voltage scaling range - Range 1 - for 64 Mhz System Frequency
PWR->CR1 = (PWR->CR1 & ~PWR_CR1_VOS_Msk) | PWR_CR1_VOS_0;
// Low power run - off
PWR->CR1 &= ~PWR_CR1_LPR;
}
/**
* System Clock Configuration
* System Clock = 16 MHz
* AHB Clock = 4 MHz
* APB Clock = 4 MHz
*/
void SystemClock_Config(void)
{
// 1. Select internal clock (HSI) with a frequency of 16 MHz
// Clock control register (RCC_CR)
RCC->CR |= RCC_CR_HSION; // turn HSI on
while (!(RCC->CR & RCC_CR_HSIRDY)); // Wait until HSI is stable
RCC->CR &= ~RCC_CR_HSIDIV_Msk; // HSIDIV = 1
RCC->CR &= ~RCC_CR_PLLON; // PLL Clock disable
// Select HSISYS as System Clock
// Set APB and AHB prescaler and disable clock output
// Clock configuration register (RCC_CFGR)
RCC->CFGR &= RCC_CFGR_SWS_HSISYS; // Select HSISYS as System Clock and disable clock output
RCC->CFGR &= ~RCC_CFGR_PPRE_Msk; // Reset APB prescaler = 1
RCC->CFGR &= ~RCC_CFGR_HPRE_Msk; // Reset AHB prescaler = 1
RCC->CFGR |= 0b1001 << RCC_CFGR_HPRE_Pos; // Set AHB prescaler = 4
// MCO clock settings:
// Output: SYSCLK
// Prescaler: 128 to improve measurement (1000 not possible)
RCC->CFGR &= ~RCC_CFGR_MCOSEL_Msk; // Reset MCOSEL (clock selector)
RCC->CFGR |= RCC_CFGR_MCOSEL_0; // choose SYSCLK
RCC->CFGR &= ~RCC_CFGR_MCOPRE_Msk; // Reset MCOPRE (Prescaler)
RCC->CFGR |= (0x7UL << RCC_CFGR_MCOPRE_Pos); // Set Prescaler to 128
}
/**
* Configure GPIOs
*/
void GPIO_Init(void)
{
// 1. activate GPIO Clocks
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Activate GPIOA clock (for SPI, external interrupts, etc.)
RCC->IOPENR |= RCC_IOPENR_GPIOBEN; // Activate GPIOB Clock, if required
RCC->IOPENR |= RCC_IOPENR_GPIOCEN; // Activate GPIOC Clock, if required
// Declare GPIOA pin PA10 as input - for later external interrupt - switch next to connector
GPIOA->MODER &= ~GPIO_MODER_MODE10; // Configure PA10 as input
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD10; // Neither pull-up nor pull-down for PA10
// Declare GPIOA pin PC6 as input - for later external interrupt - Additional switch
GPIOC->MODER &= ~GPIO_MODER_MODE6; // Configure PAC6 as input
GPIOC->PUPDR &= ~GPIO_PUPDR_PUPD6; // Neither pull-up nor pull-down for PC6
// GPIO pin PB4 as output for RGB LED (red)
GPIOB->MODER &= ~GPIO_MODER_MODE4; // Reset Port
GPIOB->MODER |= GPIO_MODER_MODE4_0; // Define Pin as Output
GPIOB->OTYPER &= ~GPIO_OTYPER_OT4; // Push-Pull output
GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEED4; // Reset Speed Setting
GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED4_1; // Set to High Speed (second fastest)
// GPIO pin PB6 as output for RGB LED (blue)
GPIOB->MODER &= ~GPIO_MODER_MODE6; // Reset Port
GPIOB->MODER |= GPIO_MODER_MODE6_0; // Define Pin as Output
GPIOB->OTYPER &= ~GPIO_OTYPER_OT6; // Push-Pull output
GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEED6; // Reset Speed Setting
GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED6_1; // Set to High Speed (second fastest)
// GPIO pin PB5 as output for RGB LED (green)
GPIOB->MODER &= ~GPIO_MODER_MODE5; // Reset Port
GPIOB->MODER |= GPIO_MODER_MODE5_0; // Define Pin as Output
GPIOB->OTYPER &= ~GPIO_OTYPER_OT5; // Push-Pull output
GPIOB->OSPEEDR &= ~GPIO_OSPEEDR_OSPEED5; // Reset Speed Setting
GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED5_1; // Set to High Speed (second fastest)
// Define GPIO pin PA9 as MCO -> MCO - SYSCLK clock output
GPIOA->MODER &= ~GPIO_MODER_MODE9; // Reset Port
GPIOA->MODER |= GPIO_MODER_MODE9_1; // Define Pin as alternate function mode
GPIOA->OTYPER &= ~GPIO_OTYPER_OT9; // Push-Pull output
GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEED9; // Reset Speed Setting
GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED9_1; // Set to High Speed (second fastest)
GPIOA->AFR[2] &= GPIO_AFRH_AFSEL9; // Reset alternate function register für PA9.
// The reset value is also directly correct here to select MCO.
// In the “normal” data sheet there is a table showing which number stands for
// which alternative function, here AF0 stands for MCO
}
2025-06-03 5:47 AM
The code is certainly not ideal, but your assumptions are correct. What makes you think it's at the wrong frequency? Start the timer and do a HAL_Delay(100) or something and calculate the CNT before/after to get the frequency. Set prescaler so it doesn't overflow.
2025-06-03 7:44 AM
First of all, thank you for your quick response.
I would like to further adapt or improve the code. In general, I don't want to use any ready-made libraries, as I am doing this for learning purposes. For this reason, I don't want to use the HAL function.
“What makes you think it's at the wrong frequency?” -> The code works as written; the LED flashes every 3 seconds. However, to my understanding, this should not be the case, as the correct prescaler value for the timer would be 3, assuming the input frequency is 4 MHz. However, I have set the prescaler value to 15 – the code works, which surprises me a little and, to be honest, I don't understand.
If it helps, I could also upload screenshots of the register values in debug mode, but these also match the values in the code.
2025-06-03 7:51 AM
Hi @JohannesSt ,
At this point I don't have answers to your question, but I am curious: how did you write the original post? In particular, did you emphasise the text in bold manually, or did you use some program to do that for you?
Thanks,
JW
2025-06-03 8:18 AM
> RCC->CFGR &= RCC_CFGR_SWS_HSISYS;
This one doesn't look right.
> I could also upload screenshots of the register values
Probably worth doing.
Consider only writing to RCC registers once as HAL does, instead of doing it piece-meal. You may be passing through invalid configurations.
2025-06-03 8:22 AM
Sorry, but boys , that for learning omit learn HAL first and then dont understand, waste own and others time.
TIM1SEL[1:0] value Clock source
00 | PCLK (default) |
01 | SYSCLK |
10 | HSI16 |
11 | PLLQ (if configured) |
2025-06-03 8:30 AM
2025-06-03 8:51 AM
2025-06-03 9:20 AM
Hi @waclawek.jan ,
i editet the written text manually over the toolbar, for my code i used the "insert code" function:
Hope this answers your question? :)
2025-06-03 9:34 AM
First thank you for you advise, i willl do this in the future.
Here a sreenshot of the RCC CFGR register:
MCOPRE = 0x7 -> sets prescaler for the MCO output to 128 -> correct
MCOSEL = 0x1 -> Set SYSCLK for MCO output -> correct
MCO2PRE and MCO2SEL these two parameters are not adjusted
PPRE = 0x0 -> APB prescale = 1 -> correct
HPRE = 0x9 -> AHB prescaler = 4 -> correct
SWS = 0x0 -> SYSCLK = HSISYS (read only - therefore correct)
SW = 0x0 -> SYSCLK = HSISYS -> correct
So the RCC CFGR register seems to be correct in my point of view - Nevertheless, I will follow your advice on writing the registers in the future :)
Here is the screenshot for timer 16 - at the time before the delay function is called.
Does this help? :)