Hello,
I'm tyring to program a STM32F4 (with ARM F4) board to generate a sinusoidal PWM, that is a sequence of pulses with variable width that, once filtered by a LPF, gives a sinusoid.
I was told the best way to get it is by generating a sequence of single pulses synchronized to timer by interrupts.
I've tried several solutions but I'm not good at managing interrupts.
Do you have any idea how to achieve this goal? Can you attach a piece of code where it's explained how to do it?
Thanks in advance! ;)
Mara
You can't manage them, or you don't understand the concept? Can your teachers/instructors not take responsibility for adequately explaining things?
Yes, you can use PWM mode, you can create a table of values to program into the CCRx for the channel you're using at each Update interrupt of the timer.
If the value of the Period is 10000-1, then the sine table you create will need to be 5000 + 5000*sin(angle), a table with 10 degree increments would consist of 36 entries. At each interrupt you'd output the next value, incrementing a counter, and wrapping it based on the size of the table.
ie
void TIMx_IRQHandler(void) // Adapt for timer and channel under consideration { static int index = 0; if (TIM_GetITStatus(TIMx, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIMx, TIM_IT_Update); TIMx->CCRx = sinetbl[index]; index = (index + 1) % 36; } }
Thank you for your answer!
Well, I know the theory about interrupts, but I've never used them before so I don't know how to manage them in my code.
I have two more questions for you:
1) Which function do I have to call to generate the sequence of pulses?
(HAL_TIM_OnePulse_Start, HAL_TIM_Base_Start_IT, HAL_TIM_PWM_Start)
2) How can I connect the execution of the function to the interrupt?
Forgive my incompetence, but that's my first time of hardware programming.
Thanks, Mara
What board are you using?
STM32F401RE
It looks like you have a STM32F401RE-Nucleo board. You can download at:
www.st.com/.../PF259243
at the bottom of the page is a read button. It provides a few projects for your board as well as other STMF4 Boards. The more expensive boards have the most project examples. I am looking at a Nucleo board the STM32F153RE. It has one project called TIM_DMA which creates a output PWM signal that varies its pulses since the DMA continuously writes to TIM1 Capture Compare Register 3.
Description in readme file This example provides a description of how to use DMA with TIM1 Update request to transfer Data from memory to TIM1 Capture Compare Register 3 (CCR3).
The following configuration values are used in this example:
- TIM1CLK = SystemCoreClock - Counter repetition = 3 - Prescaler = 0 - TIM1 counter clock = SystemCoreClock - SystemCoreClock is set to 64 MHz for STM32F1xx
The objective is to configure TIM1 channel 3 to generate complementary PWM (Pulse Width Modulation) signal with a frequency equal to 17.57 KHz, and a variable duty cycle that is changed by the DMA after a specific number of Update DMA request.
The number of this repetitive requests is defined by the TIM1 Repetition counter, each 4 Update Requests, the TIM1 Channel 3 Duty Cycle changes to the next new value defined by the aCCValue_Buffer.
The PWM waveform can be displayed using an oscilloscope.
The examples provided for the STM32F401RE-Nucleo do not include a PWM project. But if you download the STM32F4 Cube software there are also examples for the Other STM32F4 boards included. You can browse the other projects and take a look at PWM examples. Especially look at the EVAL board projects.
With DMA
// NUCLEO-F401RE DMA Driven PWM - Not good for school or work assignments. // Copyright (C) 2015 sourcer32@gmail.com, All Rights Reserved #include "stm32f4xx.h" #define PWM_ELEMENTS 100 const u32 PWM_Buffer[PWM_ELEMENTS] = { // Sine Table 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 50, 53, 56, 58, 61, 63, 66, 68, 70, 72, 75, 77, 79, 80, 82, 84, 86, 87, 89, 90, 91, 92, 94, 95, 96, 96, 97, 98, 98, 99, 99, 99, 99,100, 99, 99, 99, 99, 98, 98, 97, 96, 96, 95, 94, 92, 91, 90, 89, 87, 86, 84, 82, 80, 79, 77, 75, 72, 70, 68, 66, 63, 61, 58, 56, 53, 50, 48, 45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3 }; /**************************************************************************************/ void RCC_Configuration(void) { /* GPIOA clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); /* DMA1 clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); /* TIM2 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); } /**************************************************************************************/ void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /* GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Connect TIM2 pins to AF */ GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_TIM2); // PA5 TIM2_CH1 } /**************************************************************************************/ void DMA_Configuration(void) { DMA_InitTypeDef DMA_InitStruct; /* TIM2_UP - DMA1, Channel 3, Stream 1 */ DMA_InitStruct.DMA_Channel = DMA_Channel_3; DMA_InitStruct.DMA_BufferSize = PWM_ELEMENTS; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&PWM_Buffer[0]; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA1_Stream1, &DMA_InitStruct); /* Enabling DMA */ DMA_Cmd(DMA1_Stream1, ENABLE); } /**************************************************************************************/ void TIM2_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_TimeBaseStructure.TIM_Prescaler = (SystemCoreClock / 10000) - 1; // 10 KHz tick TIM_TimeBaseStructure.TIM_Period = 100-1; // 0..99, 100 Hz period TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* Channel 1 configuration (PA.5) */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 50; // 50% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; TIM_OC1Init(TIM2, &TIM_OCInitStructure); /* Associating DMA and TIM2 Update (DMA1 Channel 3, Stream 1) */ TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE); /* Turning on TIM2 and PWM outputs */ TIM_Cmd(TIM2, ENABLE); } /**************************************************************************************/ int main(void) { RCC_Configuration(); GPIO_Configuration(); DMA_Configuration(); TIM2_Configuration(); while(1); } /**************************************************************************************/ #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif /**************************************************************************************/
With IRQ
// NUCLEO-F401RE IRQ Driven PWM - Not good for school or work assignments. // Copyright (C) 2015 sourcer32@gmail.com, All Rights Reserved #include "stm32f4xx.h" #define PWM_ELEMENTS 100 const u32 PWM_Buffer[PWM_ELEMENTS] = { // Sine Table 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 50, 53, 56, 58, 61, 63, 66, 68, 70, 72, 75, 77, 79, 80, 82, 84, 86, 87, 89, 90, 91, 92, 94, 95, 96, 96, 97, 98, 98, 99, 99, 99, 99,100, 99, 99, 99, 99, 98, 98, 97, 96, 96, 95, 94, 92, 91, 90, 89, 87, 86, 84, 82, 80, 79, 77, 75, 72, 70, 68, 66, 63, 61, 58, 56, 53, 50, 48, 45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3 }; /**************************************************************************************/ void RCC_Configuration(void) { /* GPIOA clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); /* TIM2 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); } /**************************************************************************************/ void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /* GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Connect TIM2 pins to AF */ GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_TIM2); // PA5 TIM2_CH1 } /**************************************************************************************/ void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); } /**************************************************************************************/ void TIM2_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_TimeBaseStructure.TIM_Prescaler = (SystemCoreClock / 10000) - 1; // 10 KHz tick TIM_TimeBaseStructure.TIM_Period = 100-1; // 0..99, 100 Hz period TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* Channel 1 configuration (PA.5) */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 50; // 50% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; TIM_OC1Init(TIM2, &TIM_OCInitStructure); /* Enable TIM2 Update Interrupt */ TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); /* Turning on TIM2 and PWM outputs */ TIM_Cmd(TIM2, ENABLE); } /**************************************************************************************/ void TIM2_IRQHandler(void) // Per Keil forum example, modified to suit { static int index = 0; if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); TIM2->CCR1 = PWM_Buffer[index]; index = (index + 1) % PWM_ELEMENTS; } } /**************************************************************************************/ int main(void) { RCC_Configuration(); GPIO_Configuration(); NVIC_Configuration(); TIM2_Configuration(); while(1); } /**************************************************************************************/ #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif /**************************************************************************************/
Thanks for your help!
I'm using the second code, the one with IRQ, but I got 26 errors because a lot of functions and structures you've used are unknown to Keil, like:
TIM_IT_Update TIM_Cmd RCC_AHB1PeriphClockCmd NVIC_InitStructure ... and many more
What library did you use? I can't find none of these functions. Did you program the code for the STM32F401RE?
Just another question ... how can I modify that code if I want to introduce another two phases at 120 and 240 degrees to drive a brushless motor?
"What library did you use? I can't find none of these functions."
I thought Google was available almost world-wide.
I got all the STM32F4 driver libraries I've found on the ST website, but none of them got the functions you've used.
Thsnks for the Code Clive.
It uses the ST Standard Peripheral Library (SPL)
The Red Download button at the bottom www.st.com/.../PF257901
You'd have to make sure the project called out the 401, and configure the PLL correctly in the system_stm32f4xx.c
There are project templates for MDK4, for MDK5 you will probably need to install the Cortex Legacy Pack
The 401C Discovery firmware library may be a shorter jump www.st.com/.../PF259428
Hello Clive,
I'm replacing the functions you've used for the F4 library's functions, so I can better understand what you meant to do.
But there is one thing I can't still figure it out.
Why did you define the "TIM2_IRQHandler(void)" function if you din't even call it in the main part?
Do I have to call it just before the "while(1)" cycle?
Thank you, Mara
It's an Interrupt, it's called by the processor when the TIM2 Update Interrupt asserts. The NVIC/TIM configuration enable this, and the vector table of interrupt handlers are listed in startup_stm32f4xx.s direct execution to the routine.
It will be called outside the flow of regular code, you don't need to explicitly call it. This is how interrupts work. Please refer to Computer Science and Micro-Controller texts if you need foundational understanding.
I'd recommend one of Joseph Yiu's books on the Cortex-Mx series parts, and there are of course ARM's Technical Reference Manuals (TRM) which describe the cores in significant detail.