1
votes

I'm a bit unsure what's the best approach to the problem given my knowledge of the STM32. I want to measure the speed and position of a motor with an integrated hall encoder of 6400 rising/falling edges per rotation, separated into two channels (one CH gives 3200 rising/falling edges).

What's the best way to do it?

The thing is... I have 4 motors to measure. I considered many options, but I would like one that only generates interrupts when the position data is already known (basically, so I don't increment myself a position variable at each pulse but instead let a timer do it for me).

From what I know, a few timers support a mode called "Encoder mode". I don't know the details about this mode, but I would like (if possible) to be able to calculate my speed at a fixed amount of time (say around 20ms). Is it possible in encoder mode, with one timer, to know both the rising/falling edges count (which I guess would be in the CNT register) and have it trigger an interrupt at each 20 ms, so that I can divide the CNT register by 20ms to get the count/sec speed within the ISR?

The other option I have is to count with Input Capture direct mode with two channels on each timer (one for each motor), and have another timer with a fixed period of 20ms, and calculate all the speeds of the 4 motors there. But it requires 5 timers...

If anything else, is there a way DMA could help to keep it to 4 timers? For example, can we count with DMA?

Thanks!

2
Sounds like you need the RTC to run in parallel with the decoding input capture timer.Lundin

2 Answers

1
votes

The encoder interface mode on the STM32F407 is supported on timers 1 & 8 (Advanced Control timers - 16 bit) and timers 2 to 5 (General purpose timers - 16/32 bit). Timers 9 to 14 (also General purpose) do not support quadrature encode input.

It is important that in this mode the timer is operating as a counter rather than a timer. The quadrature input allows up/down count depending on the direction, so that it will provide relative position.

Note that if your motor will only ever travel in one direction, you do not need the encoder mode, you can simply clock a timer from a single channel, although that will reduce the resolution significantly, so accuracy at low speeds may suffer.

To determine speed, you need to calculate change in relative position over time.

All ARM Cortex-M devices have a SYSTICK timer which will generate a periodic interrupt. You can use this to count time.

You then have two possibilities:

  • read the encoder counter periodically whereby the change in count is directly proportional to speed (because change in time will be a constant),
  • read the encoder aperiodically and calculate change in position over change in time

The reload value for the encoder interface mode is configurable, for this application (speed rather then position), you should set the to the maximum (0xffff or 0xffffffff) since it makes the arithmetic simpler as you won't have to deal with wrap-around (so long as it does not wrap-around twice between reads).

For the aperiodic method and assuming you are using timers 2 to 5 in 32 bit mode, the following pseudo-code will generate speed in RPM for example:

int speedRPM_Aperiodic( int timer_id )
{
    int rpm = 0 ;

    static struct
    {
        uint32_t count ;
        uint32_t time ;
    } previous[] = {{0,0},{0,0},{0,0},{0,0}} ;

    if( timer_id < sizeof(previous) / sizeof(*previous) )
    {
        uint32_t current_count = getEncoderCount( timer_id ) ;
        int delta_count = previous[timer_id].count - current_count ;
        previous[timer_id].count = current_count ;

        uint32_t current_time = getTick() ;
        int delta_time = previous[timer_id].time - current_time ;
        previous[timer_id].time = current_time ;

        rpm = (TICKS_PER_MINUTE * delta_count) / 
              (delta_time * COUNTS_PER_REVOLUTION) ;
    }

    return rpm ;
}

The function needs to be called often enough that the count does not wrap-around more than once, and not so fast that the count is too small for accurate measurement.

This can be adapted for a periodic method where delta_time is fixed and very accurate (such as from the timer interrupt or a timer handler):

int speedRPM_Periodic( int timer_id )
{
    int rpm = 0 ;

    uint32_t previous_count[] = {0,0,0,0} ;

    if( timer_id < sizeof(previous_count) / sizeof(*previous_count) )
    {
        uint32_t current_count = getEncoderCount( timer_id ) ;
        int delta_count = previous[timer_id].count - current_count ;
        previous_count[timer_id] = current_count ;

        rpm = (TICKS_PER_MINUTE * delta_count) / 
              (SPEED_UPDATE_TICKS * COUNTS_PER_REVOLUTION) ;
    }

    return rpm ;
}

This function must then be called exactly every SPEED_UPDATE_TICKS.

The aperiodic method is perhaps simpler to implement, and is good for applications where you want to know the mean speed over the elapsed period. Suitable for example a human readable display that might be updated relatively slowly.

The periodic method is better suited to speed control applications where you are using a feed-back loop to control the speed of the motor. You will get poor control if the feedback timing is not constant.

The aperiodic function could of course be called periodically, but has unnecessary overhead where delta time is deterministic.

0
votes

A timer can count on one type of event

It can count either on some external signal like your sensors, or on a clock signal, but not on both of them at once. If you want to do something in every 20ms, you need something that counts on a stable clock source.

DMA can of course count the transfers it's doing, but to make it do something at every 20 ms, it has to be triggered at fixed time intervals by something.

Therefore, you need a fifth timer.

Fortunately, there are lots of timers to choose from

  • 10 more timers

The F407 has 14 hardware timers. You don't want to use more than 4 of them, I'm assuming 10 of them are used elsewhere in your application. Check the usage of them. Perhaps there is one that counts on a suitable clock frequency, and can generate an interrupt with a frequency that is suitable for sampling your encoders.

  • SysTick timer

Cortex-M cores have an internal timer called SysTick. Many applications use it to generate an interrupt at every 1ms for timekeeping and other periodic tasks. If that's the case, you can read the encoder values in every 20th SysTick interrupt - this has the advantage of not requiring additional interrupt entry/exit overhead. Otherwise you can set it up directly to generate an interrupt at every 20ms. Note that you won't find SysTick in the Reference Manual, it's documented in the STM32F4 Programmers Manual.

  • Real-Time clock

The RTC has a periodic auto-wakeup function that can generate an interrupt every 20 ms.

  • UART

Unless you are using all 6 UARTS, you can set one of them to a really slow baud rate like 1000 baud, and keep transmitting dummy data (you don't have to assign a physical pin to it). Transmitting at 1000 baud, with 8 bit, one start and one stop bit gives an interrupt at every 10 ms. (It won't let you go down to 500 baud unless your APB frequency is lower than the maximum allowed)