2013-07-10 06:27 AM
I need to output a simulated quadrature signal on two mcu pins. Is there a way to cleanly do this using the advanced timing features of TIM1 & TIM8?
I know that there are other ways that essentially amount to using timers to bit bang the output, but I am looking for a better way. #stm32-timer-compare-quadrature #quadrature-encoder #stm32f4072013-07-10 07:18 AM
I know that there are other ways that essentially amount to using timers to bit bang the output, but I am looking for a better way.
Let us know what you find. I'd probably just phase align two timers in 50/50 PWM
2013-07-10 07:41 AM
I'd use toggle on two channels of a single timer, OC values set half period apart.
JW2013-07-10 03:58 PM
I'd use toggle on two channels of a single timer, OC values set half period apart.
That's certainly workable, and cleverer than my first idea.// STM32F4-Discovery TIM4 Quadrature Output PD.12 and PD.13 - sourcer32@gmail.com
#include ''stm32f4xx.h''
//******************************************************************************
void RCC_Configuration(void)
{
/* enable peripheral clock for TIM4 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/* GPIOD clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
}
//******************************************************************************
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOD Configuration: TIM4 on PD12/PD13 LED */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Connect TIM4 pin */
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4); // PD12 TIM4_CH1
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4); // PD13 TIM4_CH2
}
//******************************************************************************
void TIM4_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
int Prescaler, Period;
Prescaler = ((SystemCoreClock / 2) / 20000); // 20 KHz timebase, assumes APB1 H/4 TIMCLK4 H/2
Period = 20000 / 1; // 1 Hz - 1 second on, 1 second off duty
// The toggle halves the frequency, a phase shift of 90 degrees (1/4) is 180 degrees (1/2)
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = Period - 1;
TIM_TimeBaseStructure.TIM_Prescaler = Prescaler - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 & 2 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = (Period * 1) / 4; // CH1 at 25%
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
TIM_OCInitStructure.TIM_Pulse = (Period * 3) / 4; // CH2 at 75%, ie half cycle later
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
/* TIM4 enable counter */
TIM_Cmd(TIM4, ENABLE);
}
//******************************************************************************
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
TIM4_Configuration();
while(1); /* Infinite loop */
}
2013-09-16 03:09 PM
I ended up forgetting that I posted this question here and then implementing the toggle solution anyways. It does work quite well. My procedure is equivalent to above.
In order to simulate your encoder traveling in the reverse direction, just switch which encoder is delayed. Also, note that the minimum delay is <1>, so the channel that you do not delay should still have a delay of 1.
On the edge case that you care about the start state of the pins, you will need to force their values before enabling the timer.2013-09-17 09:57 AM
One thing that I noticed here is that you only have the option of tying one of your OC channels to the TRGO. The downside to this is that if you are tying this signal into a timer that wants to mimic 4X decoding, it will not work. You will only be able to feed both edges of one phase into the second timer. Not a deal breaker, but something to note.
2016-01-14 05:28 AM
Hi, I made something very similar but on STM32F446 with STM32CubeF4 1.1 :
TIM1 run at 20MHz with ARR to the desired period. TIM1_CH1 and TM1_CH2 are OCMODE_TOGGLE output, here is the init :void TIM_US_Init(void) {
TIM_OC_InitTypeDef sConfigOC;
/* Compute the prescaler value to have TIM1 counter clock equal to 20 MHz -> precision 0.1 us on semi-period */
uint32_t uwPrescalerValue = (uint32_t)((SystemCoreClock / (20000000)) - 1);
TimUsHandle.Instance = TIM_US;
TimUsHandle.Init.Period = 0xFFFF;
TimUsHandle.Init.Prescaler = uwPrescalerValue;
TimUsHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TimUsHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_OC_Init(&TimUsHandle);
sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
sConfigOC.Pulse = 0;
// sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
// sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_OC_ConfigChannel(&TimUsHandle, &sConfigOC, TIM_US_SEQ_OUT_CHANNEL);
HAL_TIM_OC_ConfigChannel(&TimUsHandle, &sConfigOC, TIM_US_SEQ_OUT_B_CHANNEL);
}
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef* htim) {
GPIO_InitTypeDef GPIO_InitStruct;
if (htim->Instance == TIM_US) {
TIM_US_CLK_ENABLE();
TIM_US_SEQ_OUT_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = TIM_US_SEQ_OUT_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = TIM_US_SEQ_OUT_GPIO_AF;
HAL_GPIO_Init(TIM_US_SEQ_OUT_GPIO_PORT, &GPIO_InitStruct);
TIM_US_SEQ_OUT_B_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = TIM_US_SEQ_OUT_B_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = TIM_US_SEQ_OUT_B_GPIO_AF;
HAL_GPIO_Init(TIM_US_SEQ_OUT_B_GPIO_PORT, &GPIO_InitStruct);
HAL_NVIC_SetPriority(TIM_US_IRQn, 1, 0);
}
}
Then on runtime I start the dignal generation like this :
uint16_t desired_period; // contain the desired period for the signal
HalfPeriodCounter = 0;
HalfPeriodMax = (uint32_t)(SEQ_TRAME_TTL / desired_period); // used to stop signal after as elapsed time
__HAL_TIM_SET_AUTORELOAD(&TimUsHandle, 0);
__HAL_TIM_SET_COMPARE(&TimUsHandle, TIM_US_SEQ_OUT_CHANNEL, 1);
__HAL_TIM_SET_COMPARE(&TimUsHandle, TIM_US_SEQ_OUT_B_CHANNEL, (uint32_t) (desired_period / 2) + 1);
__HAL_TIM_SET_COUNTER(&TimUsHandle, 0);
HAL_TIM_OC_Start_IT(&TimUsHandle, TIM_US_SEQ_OUT_CHANNEL);
HAL_TIM_OC_Start(&TimUsHandle, TIM_US_SEQ_OUT_B_CHANNEL);
__HAL_TIM_SET_AUTORELOAD(&TimUsHandle, desired_period - 1);
And the interrupt CallBack :
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim == &TimUsHandle) {
if (htim->Channel == TIM_US_SEQ_OUT_ACTIVE_CHANNEL) {
if (++HalfPeriodCounter == HalfPeriodMax) {
HAL_TIM_OC_Stop_IT(htim, TIM_US_SEQ_OUT_CHANNEL);
HAL_TIM_OC_Stop(htim, TIM_US_SEQ_OUT_B_CHANNEL);
HAL_NVIC_DisableIRQ(TIM_US_IRQn);
}
}
}
}
With logic analyzer I can observe the desired signal form, with correct quadrature, but sometimes there is a logic inversionso signal B appear to be ''before'' the primary signal and not ''after'' (phase delay of 1/4 period).
As I am simulating a rotary encoder, this phase invertion look like a rotation invertion which is bad for the system.
How can I prevent this signal B for behing in advance of signal A ?
Thanks,
Georges
2016-01-14 07:39 AM