3
\$\begingroup\$

I need to measure the speed of a rotating motor fitted to an encoder disc with 20 slots. The LM393 based sensor is kept at the right place, so that encoder disc rotates in between the sensing part.

I think, I would need three timers provided by STM32 board: one to provide PWM(TIM4), second(TIM1) to measure the time of (1000msec~1sec) and third timer(TIM2) to measure the number of pulses received from encoder disc in 1 sec.

TIM1's counter period(Auto-reload register) is fixed to 1000 and frequency to 1000Hz. TIM2'S frequency is same as PWM frequency-10kHz. Sensor output is connected to PC15 and PWM to PD12.

The code below uses HAL library. I have doubt about how to use these two timers/counters inside the while loop, which HAL library functions to use here and how to use. Also should the code for PWM control and speed calculation be put in same loop.

int main(void)
{
        volatile GPIO_PinState level; // stores input state from PC15, sensor output is connected to PC15
        uint16_t dutycycle=70;
        int speed=0;                  //speed variable
        int constant=(2*3*1)/20;      //roughly circumference/20 slots

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();       //for measuring 1 second
  MX_TIM4_Init();       //for PWM
  MX_TIM2_Init();      //for counting output pulses from PC15 pin

  HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);    //Start PWM signal
  level=HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_15); //HAL function is called to read pin status

  while (1)
  {
              counter1=__HAL_TIM_GetCounter(&tim2);
              speed=(counter1)*(constant);          //speed variable, counter 1 related to TIM2  

            htim4.Instance->CCR1=dutycycle;   //setting the duty cycle of PWM signal
            HAL_Delay(1000);dutycycle+=10;    //1 sec delay and then increases dutycyle by 10%
            if(dutycycle==90)dutycycle=50;    //resets to 50% duty cycle
  }

Development board is STM32F407, CubeMX is the source code generator and IDE is Eclipse. OS is Linux(Ubuntu 15.04). Kindly suggest.

\$\endgroup\$
2
  • \$\begingroup\$ What kind of STM32 do you have? Most of them if not all have built-in quadrature encoder support. Only one timer with 2 channels are needed. \$\endgroup\$ Apr 1, 2017 at 8:21
  • \$\begingroup\$ Hello Bence, thank you for taking your valuable time. Development board is stm32F407, CubeMX is used to generate the source code, HAL library is also used and IDE is Eclipse. I have also edited this in question. \$\endgroup\$
    – abinjacob
    Apr 1, 2017 at 14:11

4 Answers 4

4
\$\begingroup\$

Best way is to set up a timer in encoder mode to count the pulses of your sensor. If you have both A and B pulses then choose encoder mode TIM_ENCODERMODE_TI12 if only pulse A then TIM_ENCODERMODE_TI1. (This code works fine on an STM32F4 Discovery board.)

void MX_TIM3_Init(void)
{
    TIM_Encoder_InitTypeDef encoderConfig;

    __TIM3_CLK_ENABLE();

    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 0;
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 0xFFFF;
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    encoderConfig.EncoderMode = TIM_ENCODERMODE_TI12;

    encoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
    encoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    encoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
    encoderConfig.IC1Filter = 0x00;

    encoderConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
    encoderConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    encoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
    encoderConfig.IC2Filter = 0x00;

    if(HAL_TIM_Encoder_Init(&htim3, &encoderConfig) != HAL_OK)
    {

    }
}

/**TIM3 GPIO Configuration - Encoder
PB4     ------> TIM3_CH1
PB5     ------> TIM3_CH2
*/
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

Actually you did not tell us the exact type of your STM32 so you should check the timer, pin and alternate function values for yours. After setting up the timer the following simple functions can be used.

Then set up another timer with periodic interrupt and call the Encoder_Read function in the ISR to query the current increment value.

uint32_t Encoder_Read(void)
{
    return TIM3->CNT;
}

void Encoder_Start(void)
{
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

void Encoder_Stop(void)
{
    HAL_TIM_Encoder_Stop(&htim3, TIM_CHANNEL_ALL);
}

In every ISR you can calculate the difference between the previous and actual counter state. That will be the number of pulses over the period from which you can calulate the speed (given you know that how much distance 1 increment means). Be aware of possible counter overflows.

update:
Combined Channel setting: Encoder Mode. Here is a pic. enter image description here

\$\endgroup\$
8
  • \$\begingroup\$ Hello Bence, thanks again for insight into HAL library functions. I found from manual that encoder mode is supported by TIM1 and TIM8 of STM32F407. Now, I am proceeding to follow the encoder method. I need to know few things: 1.Encoder mode can be used just like counter as used in normal mode right? 2. the first code segment, i.e. the timer initialization function; can be used to measure the time and as stated changes can be changed n statements "htim3.Init.Prescaler = 16000;"//for 1 millisec" and "htim3.Init.Period = 1000;"// to count upto 1000 millisec. i.e 1 sec \$\endgroup\$
    – abinjacob
    Apr 1, 2017 at 15:27
  • \$\begingroup\$ @abinjacob The above code was used on an STM32F407VG. 1. Well basically but if you use encoder mode then the timer counter register will be incremented/decremented by the input pulses and not by a regular clock frequency. 2. If you want to measure 1 ms then that is already ready for you, the Systick Interrupt is generated in every ms, you can use that. In the stm32f4xx_it.c there should be a SysTick_Handler which is called with 1 ms period. \$\endgroup\$ Apr 1, 2017 at 17:47
  • \$\begingroup\$ Picture is clearer now, I have few more questions, also correct me if i am wrong. 1. Idea is, encoder will be used to count number of pulses from sensor, right? then, how to connect PC15 with this context, because input signal is coming from PC15. 2. How to use "void SysTick_Handler(void)" function, so that interrupt generated after 1msec can be used to restart the timer counter register used in encoder mode. \$\endgroup\$
    – abinjacob
    Apr 2, 2017 at 2:06
  • \$\begingroup\$ @abinjacob 1. Yes. But if you cannot change that the input signal is connected to PC15 then I am afraid that you cannot use a timer in encoder mode. For that you have to connect the input signal to a pin that is connected to an appropriate timer. 2. In the SysTick_Handler you can call your restart function or simply clear the timer counter register. \$\endgroup\$ Apr 2, 2017 at 12:11
  • \$\begingroup\$ Thank you Bence, was the encoder function generated using CubeMX? I did not find setting in CubeMX, which directly says indicates about encoder mode, how do I generate the code segment as shown by you in Encoder mode, using Cube MX for TIM3. Also , I read in reference manual for stm32f407 that encoder mode can be selected by setting bits of Slave Mode Selection to 001, 010 or 011 for in REgister TIMx slave mode control register (TIMx_SMCR). But that's in register level, here I was using CubeMx so how can it be used? \$\endgroup\$
    – abinjacob
    Apr 2, 2017 at 12:53
1
\$\begingroup\$

You can use a single timer in input capture mode to measure the speed of a motor from the tach pulses generated by the sensor. The sensor signal must be input to one of the timer's input capture channels (i.e., a pin with an alternate function for timer input capture).

Configure the counter to count at a constant rate that is appropriate for the range of speeds that you need to measure. The counter rate should be slow enough to not roll over in the period between tach pulses for the slowest speed that you intend to measure. And the counter rate should be fast enough to provide sufficient time resolution between tach pulses for the fastest speed that you intend to measure.

In input capture mode the timer will count up through the full range of the counter value. And the timer will capture the counter value and provide an interrupt when ever a tach pulse is received. In the interrupt handler you should compare the newly captured timer value with the previously captured value to determine the number of ticks that have occurred between tach pulses. Then with the number of ticks between tach pulses known you can calculate the motor's rotational speed.

Here's some example code. You'll need to choose the TACH_TMR_INSTANCE, TACH_TMR_TICK_RATE, TACH_TMR_IC_CHANNEL and polarity that are appropriate for you. This example is for a 16-bit counter so if you're using a 32-bit counter you'll need to adjust the Period value and the type of tach_timer_values, this_tach_timer_value, and prev_tach_timer_value. Note that this example doesn't show the HAL_TIM_IC_MspInit() function which is where the timer and GPIO clocks get enabled and the GPIO pin is configured for the appropriate alternate function.

TIM_HandleTypeDef tach_timer_handle;
TIM_IC_InitTypeDef tach_timer_config;

HAL_StatusTypeDef TachInit()
{
    HAL_StatusTypeDef hal_status;

    tach_timer_handle.Instance = TACH_TMR_INSTANCE;

    tach_timer_handle.Init.Prescaler = (SystemCoreClock / TACH_TMR_TICK_RATE) - 1;
    tach_timer_handle.Init.Period = 0xFFFF;
    tach_timer_handle.Init.ClockDivision = 0;
    tach_timer_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    tach_timer_handle.Init.RepetitionCounter = 0;

    hal_status = HAL_TIM_IC_Init(&tach_timer_handle);
    if (hal_status != HAL_OK)
    {
        return hal_status;
    }

    tach_timer_config.ICPolarity = TIM_ICPOLARITY_RISING;
    tach_timer_config.ICSelection = TIM_ICSELECTION_DIRECTTI;
    tach_timer_config.ICPrescaler = TIM_ICPSC_DIV1;
    tach_timer_config.ICFilter = 0;

    hal_status = HAL_TIM_IC_ConfigChannel(&tach_timer_handle, &tach_timer_config, TACH_TMR_IC_CHANNEL);
    if (hal_status != HAL_OK)
    {
        return hal_status;
    }

    HAL_TIM_IC_Start_IT(&tach_timer_handle, TACH_TMR_IC_CHANNEL);
    if (hal_status != HAL_OK)
    {
        return hal_status;
    }

    return HAL_OK;
}


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *tim_handle)
{
    // An array for storing the two most recent captured counter values.
    static uint16_t tach_timer_values[2] = {0, 0};
    // An array index to identify the most recently captured counter value.
    static int tach_timer_index = 0;

    if (tim_handle == &tach_timer_handle)
    {
        uint16_t this_tach_timer_value = HAL_TIM_ReadCapturedValue(tim_handle, TACH_TMR_IC_CHANNEL);
        tach_timer_values[tach_timer_index++] = this_tach_timer_value;
        tach_timer_index &= 0x01;
        uint16_t prev_tach_timer_value = tach_timer_values[tach_timer_index];

        unsigned int tach_period_ticks = this_tach_timer_value - prev_tach_timer_value;
    }
}

You don't need to be concerned about roll over of the counter value when calculating tach_period_ticks like this because it's unsigned math and the roll over washes out.

The motor speed can be calculated from tach_period_ticks like this.

speed_rpm = (60 * TACH_TMR_TICK_RATE) / (tach_period_ticks * PULSES_PER_REVOLUTION);
\$\endgroup\$
0
\$\begingroup\$

TIM1 doesn't seem to be doing anything. Your while loop executes at a rate determined by HAL_Delay, which (I think?) is using some other timer not explicitly defined by you (either that or it busy waits).

If your intention is to execute at a rate determined by TIM1, you should set up an interrupt based on this timer's match condition (or overflow, or whatever ST calls it).

In general, I can believe the idea of counting pulses given a fixed time period would be an indicator of motor speed, but I don't know enough about your particular hardware to say more.

\$\endgroup\$
1
  • \$\begingroup\$ Hello @Mitchell, TIM1 will be used to measure 1000 millisec of duration, in code it is not shown because i am not sure how to use this functionality from HAL library, hardware is STM32F407(discovery board, a development board) and is based on ARM cortex m4 microprocessor \$\endgroup\$
    – abinjacob
    Apr 1, 2017 at 13:35
0
\$\begingroup\$

How to use timers in STM32 board and HAL library in order to measure speed of motor?

for those types of things, first think about how you would measure it, and then how you would measure it with a particular approach / library.

generally speaking, you will need a counter and a time base: so you have the number of pulses in a known period of time.

depending on the speed of the motor, you can use the time base to gate the counter, or visa versa. for example, you can preload the counter with an offset, start the time base, and then interrupt on the counter overflow. or the other way around.

So at max, two timers are needed. in some cases, one timer is sufficient.

Once you figure out the concept, how to implement it in your software environment is simple.

\$\endgroup\$
1
  • \$\begingroup\$ thank you for answering, I undertand your idea, but how to emcorporated it in codes, i am finding difficulty in; I am using CUbeMX source code generator and Eclipse IDE, board is STM32f407. \$\endgroup\$
    – abinjacob
    Apr 1, 2017 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.