Search code examples
timerembeddedstm32encoderstm32f4discovery

STM32F407 timers with hall encoders


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!


Solution

  • 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.