Loading...
 

Zephyr project on STM32

   Zephyr Workbench, a VSCode extension to manage Zephyr on STM32.
It enables users to easily create, develop, and debug Zephyr applications.
Main features:
  • Install host dependencies.
  • Import toolchain and SDK.
  • Create, configure, build and manage apps.
  • Debug STM32.
You can directly download it from the VSCode marketplace
For more details, visit the Zephyr Workbench

System Workbench for STM32


1MHz output compare

Hello,

I would like to ask regarding the highest speed I can toggle using output compare.
When I try to toggle the output with the frequency of 1M Hz, I do not get what I expect.

As attached program, I can get the correct frequency for Channel 1 to 3, but not channel 4. Could you please take a look at it and explain what could be the issue?

I attached the logic analyzer output screen for reference.

I am using Windows 7 64 bit, STM32f4Discovery Board & System Workbench Version: 1.13.1.201701261206

Thank you.

/* Includes ----------------------*/

  1. include “myTimer4.h”


/* Private variables ---------------------*/
TIM_TimeBaseInitTypeDef TIM4_TimeBaseStructure;
TIM_OCInitTypeDef TIM4_OCInitStructure;


static uint16_t TIM4_PrescalerValue = 0;
static uint32_t TIM4_Frequency = 42000000;
static uint16_t uhCapture = 0;

//static uint16_t myTimer = RCC_APB1Periph_TIM4;


__IO uint16_t TIM4_CCR1_Val = 42000;
__IO uint16_t TIM4_CCR2_Val = 4200;
__IO uint16_t TIM4_CCR3_Val = 420;
__IO uint16_t TIM4_CCR4_Val = 42;



/* Private function prototypes -------------------*/
void TIM4_OC_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

/* TIM4 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

/* GPIOC clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

/* GPIOC Configuration: TIM4 CH1 (PD12), TIM4 CH2 (PD13), TIM4 CH2 (PD14) and TIM4 CH4 (PD15) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Connect TIM Channels to AF2 */
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);

/* Enable the TIM4 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* ---------------------------
TIM4 Configuration: Output Compare Toggle Mode:

In this example TIM4 input clock (TIM4CLK) is set to 2 * APB1 clock (PCLK1),
since APB1 prescaler is different from 1.
TIM4CLK = 2 * PCLK1
PCLK1 = HCLK / 4
=> TIM4CLK = HCLK / 2 = SystemCoreClock /2

To get TIM4 counter clock at 42 MHz, the prescaler is computed as follows:
Prescaler = (TIM4CLK / TIM4 counter clock) - 1
Prescaler = ((SystemCoreClock /2) /42 MHz) - 1

CC1 update rate = TIM4 counter clock / TM4_CCR1_Val = 1k Hz
==> So the TIM4 Channel 1 generates a periodic signal with a
frequency equal to 500 Hz.

CC2 update rate = TIM4 counter clock / TM4_CCR2_Val = 10k Hz
==> So the TIM4 Channel 2 generates a periodic signal with a
frequency equal to 5k Hz.

CC3 update rate = TIM4 counter clock / TM4_CCR3_Val = 100k Hz
==> So the TIM4 Channel 3 generates a periodic signal with a
frequency equal to 50k Hz.

CC4 update rate = TIM4 counter clock / TM4_CCR4_Val = 1M Hz
==> So the TIM4 Channel 4 generates a periodic signal with a
frequency equal to 500k Hz.

Note:
SystemCoreClock variable holds HCLK frequency and is defined in system_stm32f4xx.c file.
Each time the core clock (HCLK) changes, user had to call SystemCoreClockUpdate()
function to update SystemCoreClock variable value. Otherwise, any configuration
based on this variable will be incorrect.
--------------------------- */

/* Compute the prescaler value */
TIM4_PrescalerValue = (uint16_t) ((SystemCoreClock / 2) / TIM4_Frequency) - 1;

/* Time base configuration */
TIM4_TimeBaseStructure.TIM_Period = 65535;
TIM4_TimeBaseStructure.TIM_Prescaler = TIM4_PrescalerValue;
TIM4_TimeBaseStructure.TIM_ClockDivision = 0;
TIM4_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

TIM_TimeBaseInit(TIM4, &TIM4_TimeBaseStructure);

/* Output Compare Toggle Mode configuration: Channel1 */
TIM4_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM4_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM4_OCInitStructure.TIM_Pulse = TIM4_CCR1_Val;
TIM4_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM4, &TIM4_OCInitStructure);

TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Disable);

/* Output Compare Toggle Mode configuration: Channel2 */
TIM4_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM4_OCInitStructure.TIM_Pulse = TIM4_CCR2_Val;

TIM_OC2Init(TIM4, &TIM4_OCInitStructure);

TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Disable);

/* Output Compare Toggle Mode configuration: Channel3 */
TIM4_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM4_OCInitStructure.TIM_Pulse = TIM4_CCR3_Val;

TIM_OC3Init(TIM4, &TIM4_OCInitStructure);

TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Disable);

/* Output Compare Toggle Mode configuration: Channel4 */
TIM4_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM4_OCInitStructure.TIM_Pulse = TIM4_CCR4_Val;

TIM_OC4Init(TIM4, &TIM4_OCInitStructure);

TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Disable);

/* TIM enable counter */
TIM_Cmd(TIM4, ENABLE);

/* TIM IT enable */
TIM_ITConfig(TIM4, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);
}

void TIM4_InterruptHandler()
{
if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1 );
uhCapture = TIM_GetCapture1(TIM4);
TIM_SetCompare1(TIM4, uhCapture + TIM4_CCR1_Val );
}

if (TIM_GetITStatus(TIM4, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC2);
uhCapture = TIM_GetCapture2(TIM4);
TIM_SetCompare2(TIM4, uhCapture + TIM4_CCR2_Val);
}

if (TIM_GetITStatus(TIM4, TIM_IT_CC3) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC3);
uhCapture = TIM_GetCapture3(TIM4);
TIM_SetCompare3(TIM4, uhCapture + TIM4_CCR3_Val);
}

if (TIM_GetITStatus(TIM4, TIM_IT_CC4) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC4);
uhCapture = TIM_GetCapture4(TIM4);
TIM_SetCompare4(TIM4, uhCapture + TIM4_CCR4_Val);
}
}


also answered on micromouseonline.com

The problem is that the value you add to the CR4 register is very small. By the time the code has run, the counter has already passed the new compare value and has to go all the way around again before you get a trigger. Thus the output frequency will be about ((42000000/(65536+42))/2 = 320Hz or so.

You are effectively asking the code to update the register within 1us and there are several statements before you even test the OC4 interrupt.

The highest frequency I could get with your code and no optimisation was about 140kHz with TIM4_CCR4_Val = 150. even with higher levels of optimisation and a bit of minor tweaking of the code, I could not get a significantly higher frequency with all four channels running. With only one channel running and all the optimisation, I could get a 385kHz output frequency (interrupts at twice that of course). When you add a second or more channels, sooner or later the interrupts will interfere with each other and you will miss the critical timing for the fastest pulse. there will also be some jitter in the others.

I do not believe it is possible to get the channels of a single timer to output pulse trains at different frequencies without using the interrupt and so your maximum frequency is going to be limited by how fast you can respond to that interrupt.

So – I would expect that, at a clock frequency of 84MHz, you cannot reasonably expect more than about 140kHz from a single channel and then only if the other channels are at much lower frequencies.

If you only care about the frequency and can live with narrow pulses, you can set the timer mode to Timing only:

TIM4_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
And, in the interrupt handler, pulse the pin directly.


if (TIM_GetITStatus(TIM4, TIM_IT_CC4) != RESET) {
TIM_ForcedOC4Config(TIM4,TIM_ForcedAction_Active);
TIM_ClearITPendingBit(TIM4, TIM_IT_CC4);
uhCapture = TIM_GetCapture4(TIM4);
TIM_SetCompare4(TIM4, uhCapture + TIM4_CCR4_Val);
TIM_ForcedOC4Config(TIM4,TIM_ForcedAction_InActive);
}

With only one channel active, I can then get up to 300kHz but enabling more channels will upset it again because the overhead of setting/clearing the pin takes more time and there will be considerable jitter.

I had not looked into this before and, I have to say, I am disappointed that the STM32 timers cannot do better than this. Perhaps I have missed something.


 

Newest Forum Posts

  1. reservation car service Seattle by Jamesprede, 2025-05-01 10:06
  2. Last day: drone bonus by Danielrug, 2025-04-19 16:55
  3. SPI on Nucleo_STMH533RE by higginsa1, 2025-03-25 07:37
  4. SPI on Nucleo_STMH533RE by royjamil, 2025-03-23 11:31
  5. SPI on Nucleo_STMH533RE by higginsa1, 2025-03-23 09:33
  6. Configuring DMA for ADC in SW? by sam.hodgson, 2025-03-04 12:58
  7. Insightful Perspectives on This Subject by davidsycle, 2025-03-04 05:45
  8. Build a project in "release" mode by info@creosrl.it, 2025-02-20 18:12
  9. Build a project in "release" mode by info@creosrl.it, 2025-02-20 17:05
  10. Build a project in "release" mode by tang, 2025-02-20 10:36

Last-Modified Blogs