2022-08-08 02:08 AM
Hello - i'm new to STM32 MCU's (mostly have been using 8bit AVR's or OS based Raspberry Pi). I've been trying to implement a 6-step motor control (making everything from scratch as a learning experience). I picked TIM4 and TIM8 in a master-slave configuration, using complementary outputs of channel 1, 2 & 3 in TIM8. Everything is set so that PWM preloaded registers update happens in a commutation even, which is the TRGO of TIM4.
void motor6Step_Init(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
htim4->Instance->CCR1 = (TIM4_ARR>>2);
htim8->Instance->CCR1 = 0;
htim8->Instance->CCR2 = 0;
htim8->Instance->CCR3 = 0;
htim8->Instance->CR1 &= ~( TIM_CR1_CEN );
htim8->Instance->CNT = 0;
__HAL_TIM_ENABLE_IT(htim8, TIM_IT_UPDATE);
HAL_TIMEx_ConfigCommutEvent_IT(htim8, TIM_TS_ITR2, TIM_COMMUTATION_TRGI);
}
void motor6Step_Start(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
HAL_TIM_Base_Start(htim8);
HAL_TIM_OC_Start_IT(htim4, TIM_CHANNEL_1);
}
void motor6Stop_Stop(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
HAL_TIM_OC_Stop_IT(htim4, TIM_CHANNEL_1);
HAL_TIM_Base_Stop(htim8);
htim4->Instance->CNT = 0;
htim8->Instance->CNT = 0;
}
The code used for steps:
#define TIM8_DISABLE_ALL_CHANNELS \
TIM8->CCER = 0UL; \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0 \
#define TIM8_ENABLE_CHANNELS_STEP1 \
TIM8->CCR1 = TIM8_DUTY; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCER = 0UL | ( TIM_CCER_CC1E | (TIM_CCER_CC2E) | (TIM_CCER_CC2NE) )
#define TIM8_ENABLE_CHANNELS_STEP2 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY; \
TIM8->CCER = 0UL | ( TIM_CCER_CC3E | (TIM_CCER_CC2E) | (TIM_CCER_CC2NE) )
#define TIM8_ENABLE_CHANNELS_STEP3 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY; \
TIM8->CCER = 0UL | ( TIM_CCER_CC3E | (TIM_CCER_CC1E) | (TIM_CCER_CC1NE) )
#define TIM8_ENABLE_CHANNELS_STEP4 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = TIM8_DUTY; \
TIM8->CCR3 = 0; \
TIM8->CCER = 0UL | ( TIM_CCER_CC2E | (TIM_CCER_CC1E) | (TIM_CCER_CC1NE) )
#define TIM8_ENABLE_CHANNELS_STEP5 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = TIM8_DUTY; \
TIM8->CCR3 = 0; \
TIM8->CCER = 0UL | ( TIM_CCER_CC2E | (TIM_CCER_CC3E) | (TIM_CCER_CC3NE) )
#define TIM8_ENABLE_CHANNELS_STEP6 \
TIM8->CCR1 = TIM8_DUTY; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCER = 0UL | ( TIM_CCER_CC1E | (TIM_CCER_CC3E) | (TIM_CCER_CC3NE) )
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM8){
if(counter6Step == previousCounter6Step){
switch(counter6Step){
case 0:
TIM8_DISABLE_ALL_CHANNELS;
break;
case 1:
TIM8_ENABLE_CHANNELS_STEP1;
counter6Step++;
break;
case 2:
TIM8_ENABLE_CHANNELS_STEP2;
counter6Step++;
break;
case 3:
TIM8_ENABLE_CHANNELS_STEP3;
counter6Step++;
break;
case 4:
TIM8_ENABLE_CHANNELS_STEP4;
counter6Step++;
break;
case 5:
TIM8_ENABLE_CHANNELS_STEP5;
counter6Step++;
break;
case 6:
TIM8_ENABLE_CHANNELS_STEP6;
tim4_pscUpdate = 1;
counter6Step=1;
break;
default:
TIM8_DISABLE_ALL_CHANNELS;
counter6Step = 0;
break;
}
}
}
}
void HAL_TIMEx_CommutCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM8){
previousCounter6Step = counter6Step;
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
}
}
The problem i'm experiencing, is that between steps using the same "high-side" PWM, the output goes low after changing the low side - for example:
going from step 2 to step 3, the PWM is correctly genrated on CH3 during step 2, but the outpul stays in the low state during step 3.
Funny thing is if i do a breakpoint in the callback and use the debug as a "step mode" the pwm is generated properly. Maybe anybody experienced something similar in the past?
Solved! Go to Solution.
2022-08-10 11:54 PM
I got it to work - most of the issues were coming from turning off both the CCx and CCxN channels:
If you set both to 0 (both OSSR and OSSI are '1' in my case), then both channels are no longer driven by timer - that caused issues when i just turned it back on. I opted for the option of disabling only CCxN and setting CCR registers to '0'.
I also switched from COM callback to Trigger one. Updated code for 6-step control:
#define TIM8_DISABLE_ALL_CHANNELS \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCER &= ~( TIM_CCER_CC1NE | TIM_CCER_CC2NE | TIM_CCER_CC3NE )
#define TIM8_ENABLE_CHANNELS_STEP1 \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR1 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC3NE); \
TIM8->CCER |= TIM_CCER_CC2NE
#define TIM8_ENABLE_CHANNELS_STEP2 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY
#define TIM8_ENABLE_CHANNELS_STEP3 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC2NE); \
TIM8->CCER |= TIM_CCER_CC1NE
#define TIM8_ENABLE_CHANNELS_STEP4 \
TIM8->CCR1 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR2 = TIM8_DUTY
#define TIM8_ENABLE_CHANNELS_STEP5 \
TIM8->CCR1 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR2 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC1NE); \
TIM8->CCER |= TIM_CCER_CC3NE
#define TIM8_ENABLE_CHANNELS_STEP6 \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR1 = TIM8_DUTY
/**
* @brief Callback for TIM being triggered
* @param htim: timer handle pointer
* @retval None
*/
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM8){
switch(counter6Step){
case 0:
TIM8_DISABLE_ALL_CHANNELS;
break;
case 1:
TIM8_ENABLE_CHANNELS_STEP1;
counter6Step++;
break;
case 2:
TIM8_ENABLE_CHANNELS_STEP2;
counter6Step++;
break;
case 3:
TIM8_ENABLE_CHANNELS_STEP3;
counter6Step++;
break;
case 4:
TIM8_ENABLE_CHANNELS_STEP4;
counter6Step++;
break;
case 5:
TIM8_ENABLE_CHANNELS_STEP5;
counter6Step++;
break;
case 6:
TIM8_ENABLE_CHANNELS_STEP6;
tim4_pscUpdate = 1;
counter6Step=1;
break;
default:
TIM8_DISABLE_ALL_CHANNELS;
counter6Step = 0;
break;
}
}
}
/**
* @brief Callback for TIM Output Compare
* @param htim: timer handle pointer
* @retval None
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM4){
if(tim4_pscUpdate){
uint32_t tim4PscBuff = TIM4->PSC;
if(tim4_pscDir == TIM4_PSC_DECREASE){
if(tim4PscBuff>0){
TIM4->PSC = tim4PscBuff - 1;
} else {
tim4_pscDir = TIM4_PSC_INCREASE;
}
} else {
if(tim4PscBuff<TIM4_PSC){
TIM4->PSC = tim4PscBuff + 1;
} else {
tim4_pscDir = TIM4_PSC_DECREASE;
}
}
tim4_pscUpdate = 0;
}
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
if(!counter6Step)
counter6Step++;
}
}
/**
* @brief 6-step algorithm initialization
* @param htim4: timer handle pointer
* @param htim8: timer handle pointer
* @retval None
*/
void motor6Step_Init(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
htim4->Instance->CCR1 = (TIM4_ARR>>2);
htim8->Instance->CCR1 = 0;
htim8->Instance->CCR2 = 0;
htim8->Instance->CCR3 = 0;
htim8->Instance->CR1 &= ~( TIM_CR1_CEN );
htim8->Instance->CNT = 0;
htim8->Instance->CCER = 0UL | TIM_CCER_CC1E | TIM_CCER_CC2E | TIM_CCER_CC3E;
__HAL_TIM_ENABLE_IT(htim8, TIM_IT_TRIGGER);
}
/**
* @brief 6-step algorithm start
* @param htim4: timer handle pointer
* @param htim8: timer handle pointer
* @retval None
*/
void motor6Step_Start(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
HAL_TIM_Base_Start(htim8);
HAL_TIM_OC_Start_IT(htim4, TIM_CHANNEL_1);
}
My final results:
I hope this will be able to help someone with similar issues in the future.
2022-08-08 06:54 AM
Maybe you should not mix Cube/HAL and register access.
> Everything is set so that PWM preloaded registers update happens in a commutation event
Only the CCxE, CCxNE, CCxP, CCxNP and OCxM bits are loaded upon COM event (if TIMx_CR2.CCPC is set). The TIMx_CCRx registers, if preloaded (OCxPE set), are not loaded upon COM event but upon Update event.
> TIM8->CCER = 0UL | ( TIM_CCER_CC3E | (TIM_CCER_CC1E) | (TIM_CCER_CC1E) )
This does not seem to be correct.
JW
2022-08-10 11:54 PM
I got it to work - most of the issues were coming from turning off both the CCx and CCxN channels:
If you set both to 0 (both OSSR and OSSI are '1' in my case), then both channels are no longer driven by timer - that caused issues when i just turned it back on. I opted for the option of disabling only CCxN and setting CCR registers to '0'.
I also switched from COM callback to Trigger one. Updated code for 6-step control:
#define TIM8_DISABLE_ALL_CHANNELS \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCER &= ~( TIM_CCER_CC1NE | TIM_CCER_CC2NE | TIM_CCER_CC3NE )
#define TIM8_ENABLE_CHANNELS_STEP1 \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR1 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC3NE); \
TIM8->CCER |= TIM_CCER_CC2NE
#define TIM8_ENABLE_CHANNELS_STEP2 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY
#define TIM8_ENABLE_CHANNELS_STEP3 \
TIM8->CCR1 = 0; \
TIM8->CCR2 = 0; \
TIM8->CCR3 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC2NE); \
TIM8->CCER |= TIM_CCER_CC1NE
#define TIM8_ENABLE_CHANNELS_STEP4 \
TIM8->CCR1 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR2 = TIM8_DUTY
#define TIM8_ENABLE_CHANNELS_STEP5 \
TIM8->CCR1 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR2 = TIM8_DUTY; \
TIM8->CCER &= ~(TIM_CCER_CC1NE); \
TIM8->CCER |= TIM_CCER_CC3NE
#define TIM8_ENABLE_CHANNELS_STEP6 \
TIM8->CCR2 = 0; \
TIM8->CCR3 = 0; \
TIM8->CCR1 = TIM8_DUTY
/**
* @brief Callback for TIM being triggered
* @param htim: timer handle pointer
* @retval None
*/
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM8){
switch(counter6Step){
case 0:
TIM8_DISABLE_ALL_CHANNELS;
break;
case 1:
TIM8_ENABLE_CHANNELS_STEP1;
counter6Step++;
break;
case 2:
TIM8_ENABLE_CHANNELS_STEP2;
counter6Step++;
break;
case 3:
TIM8_ENABLE_CHANNELS_STEP3;
counter6Step++;
break;
case 4:
TIM8_ENABLE_CHANNELS_STEP4;
counter6Step++;
break;
case 5:
TIM8_ENABLE_CHANNELS_STEP5;
counter6Step++;
break;
case 6:
TIM8_ENABLE_CHANNELS_STEP6;
tim4_pscUpdate = 1;
counter6Step=1;
break;
default:
TIM8_DISABLE_ALL_CHANNELS;
counter6Step = 0;
break;
}
}
}
/**
* @brief Callback for TIM Output Compare
* @param htim: timer handle pointer
* @retval None
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM4){
if(tim4_pscUpdate){
uint32_t tim4PscBuff = TIM4->PSC;
if(tim4_pscDir == TIM4_PSC_DECREASE){
if(tim4PscBuff>0){
TIM4->PSC = tim4PscBuff - 1;
} else {
tim4_pscDir = TIM4_PSC_INCREASE;
}
} else {
if(tim4PscBuff<TIM4_PSC){
TIM4->PSC = tim4PscBuff + 1;
} else {
tim4_pscDir = TIM4_PSC_DECREASE;
}
}
tim4_pscUpdate = 0;
}
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
if(!counter6Step)
counter6Step++;
}
}
/**
* @brief 6-step algorithm initialization
* @param htim4: timer handle pointer
* @param htim8: timer handle pointer
* @retval None
*/
void motor6Step_Init(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
htim4->Instance->CCR1 = (TIM4_ARR>>2);
htim8->Instance->CCR1 = 0;
htim8->Instance->CCR2 = 0;
htim8->Instance->CCR3 = 0;
htim8->Instance->CR1 &= ~( TIM_CR1_CEN );
htim8->Instance->CNT = 0;
htim8->Instance->CCER = 0UL | TIM_CCER_CC1E | TIM_CCER_CC2E | TIM_CCER_CC3E;
__HAL_TIM_ENABLE_IT(htim8, TIM_IT_TRIGGER);
}
/**
* @brief 6-step algorithm start
* @param htim4: timer handle pointer
* @param htim8: timer handle pointer
* @retval None
*/
void motor6Step_Start(TIM_HandleTypeDef* htim4, TIM_HandleTypeDef* htim8){
HAL_TIM_Base_Start(htim8);
HAL_TIM_OC_Start_IT(htim4, TIM_CHANNEL_1);
}
My final results:
I hope this will be able to help someone with similar issues in the future.