Affordable and flexible platform to ease prototyping using a STM32F401RET6 microcontroller.

Nucleo F401RE and Quadrature encoders

05 Jun 2014

Hi, Does somebody know how to manage with quadrature encoders on a Nucleo F401RE board ? According to the datasheet of its processor, it has the specific hardware for supporting 4 quadrature encoders. I would like to know if somebody has built a library for this or can do the job. My problem is I want to use a Nucleo F401RE board on a robot with two DC motors and two quadrature encoders for feed-back control. Thanks for the answer.

05 Jun 2014

Hello, this is a quite advanced feature of the device and is not handled by the mbed library.

But there is a lower level hardware abstraction layer provided by ST (the HAL driver) that supports it, and because mbed is build on top of HAL, its API is already available from your mbed application.

This is the HAL driver documentation:

UM1725: Description of STM32F4xx HAL drivers (more documents on the CubeF4 web page)

information on how to use the timer API is described chapter 46.2 , general usage information are in chapter 46.2.2 How to use this driver page 728 and the quadrature encoder API in chapter 46.2.9 Time Encoder functions page 731

You can also see the HAL header file in mbed if you import mbed libray as a program. HAL header files are in your target subdirectory (.../mbed/TARGET_NUCLEO_F401RE).

05 Jun 2014

Thanks Maxime for that piece of information. I took a look at the functions for encoders in the document you mentioned but it doen't seem to be an easy thing to do on the software side and it doesn't give information on how to connect the encoders. I don't think I could succeed with this information only. If you could give me more details on how to use these functions with an example of program it would be very helpfull for me and I think for other Nucleo users who want to build robots taking advantage of the powerfull processor of the very cheap Nucleo F401RE board. Do you build robots yourself ? Best regards.

05 Jun 2014

Fabrice, I am not sure exactly which encoders you are using in your application, but I have been able to interface the WW12 Encoders by Nubotics.

http://www.nubotics.com/products/ww12/index.html

The WW12 can be configured to output the raw Channel A & Channel B signals, which then have to be decoded for rotation direction and rate. I am using the WW12s configured for clock & direction signals, so it is fairly easy to interface with the F401RE. I had to interface the clock signals to PH0 & PH1, so that I could have two distinct interrupts. I've copied some of my pertinent code below.

Regards, Abe

Interfacing encoder clock signals

InterruptIn leftEncoderClk(PH_1);
InterruptIn rightEncoderClk(PH_0);

extern "C" void leftEncoderTick()
{
    leftEncoderDir = leftEncoderDirPin.read();
    if (!leftEncoderDir) leftEncoderCount++;
    else leftEncoderCount--;
}

extern "C" void rightEncoderTick()
{
    rightEncoderDir = rightEncoderDirPin.read();
    if (!rightEncoderDir) rightEncoderCount--;
    else rightEncoderCount++;
}

int main() {
  leftEncoderClk.mode(PullDown);
  rightEncoderClk.mode(PullUp);  
  leftEncoderClk.rise(&leftEncoderTick);
  rightEncoderClk.fall(&rightEncoderTick);
}

/media/uploads/abotics/apeiros_sn754410e_motor_pcb_img1.jpg

06 Jun 2014

Sorry, may be I should have started from the basics... The encoder mode is a dedicated timer configuration available on TIM2, TIM3, TIM4 and TIM5. It uses the TIMx_CHy timer alternate function pins of the device. The TIMx_CHy naming in ST documentation is equivalent to PWMx/y in mbed doc.

A detailed hardware description can be found in the STM32F401 Reference Manual specifically fig87p305 and section 13.3.12 Encoder interface mode p328-330

The library mentioned in my previous post provides a software interface to the encoder mode of the timers.

Unlike the code graciously provided by Abraham, the encoder is handled by hardware, reducing the jitter introduced by interrupt latency (which is not completely deterministic) and reducing the CPU load... but the working principle is exactly the same.

I am sorry I don't have an example at the moment, let me look for one.

06 Jun 2014

Thanks a lot to Maxime and Abraham,

Abraham's solution is based on interrupts and is similar in the way it works to the QEI library for MBED. It is simple to implement but as Maxime said, it will increase the CPU load. In the robot I built for the Eurobot competition 2015, the processor will have many other things to do, especially calculate a double PID feed-back control with a very precise sampling period and this could be affected by interrupts occurring in the middle of the calculation. The solution based on the internal hardware of the processor is more efficient. If I don't succeed in implementing the hardware solution, I will take Abraham's solution, but may be I will have to share the CPU load between two different processors establishing a dialog between them. The encoders I use give 1024 ppr and deliver to signals : channel A and B. Thanks again to both of you. Regards

06 Jun 2014

Hi Abraham, Nice robot, is it supposed to do something special apart from moving ? Regards

06 Jun 2014

Fabrice, thanks. My robot is also designed to monitor a total of (5) IR sensors that are located around the perimeter of the injection molded body. Also, you can see a small part of the gripper mechanism peeking out from the bottom of the body. With a gripper this robot can manipulate its environment, which is very useful. I have been working with a local injection mold supplier and things look good for moving forward with production. I am trying to keep the price below $100 (US) for the gripper version which will not include the WW12 wheel encoders.

I've included an image of the gripper mechanics below. I intend to complete the development of a wireless add-on shield that will also integrate light sensors that will support various light-related behaviors.

Regards, Abe

/media/uploads/abotics/apeiros__bottom.jpg

06 Jun 2014

Hi Abraham, I've just been watching your robot on the videos on your website. Good job ! Regards

06 Jun 2014

Fabrice, thank you for the positive feedback and for taking the time to watch my videos! I am working on several new videos that have my robot fetching a bottle of beer and others where it deliver cat & dog treats using its gripper. Very cool and tons of fun. I will likely put up the money for the injection mold tooling and then pursue a Kickstater campaign as a way to generate sales. I learned a lot from my last Kickstarter and plan to do things in a different and hopefully more intelligent manner.

Regards, Abe

13 Nov 2014

Hi,

Thank you Maxime for these very interesting informations. Fabrice, have you find a way to use the harware timer encoder ? After reading the user manual, I think we need to use HAL_TIM_Encoder, but the doc is not very explicit, and I didn't found examples on the net...

14 Nov 2014

Hi Theo, No, I didn't succeed in using hardware timers for encoders but I'm still interested if someone can show a detailed example of program for two or three quadrature encoders on a Nucleo board. Regards

17 Nov 2014

Hi Fabrice & everyone else

Having just seen this post it has inspired me to publish my Quadrature ABZ code that uses hardware rather than software. This is the first code I've published on MBED so let me know if it works.

http://developer.mbed.org/users/Nigel945426/code/HardwareQuadratureEncoderABZ/

Cheers

Nigel

18 Nov 2014

Nigel, thanks for the example. I'll have to give a try when I get some time.

Regards, Abe

Nigel Webb wrote:

Hi Fabrice & everyone else

Having just seen this post it has inspired me to publish my Quadrature ABZ code that uses hardware rather than software. This is the first code I've published on MBED so let me know if it works.

http://developer.mbed.org/users/Nigel945426/code/HardwareQuadratureEncoderABZ/

Cheers

Nigel

18 Nov 2014

Hi Nigel, Thanks a lot for this example, I'm very busy at the moment but I'll try it as soon as possible. Regards, Fabrice

04 Dec 2014

Hi, thanks for the reply, it works well for me on a F401RE Nucleo board ! Théo

07 Dec 2014

Hello Folks,

It is great to have a solution for one encoder using a hardware timer on a Nucleo board. But generally robot have two quadrature encoders (or more for holonomic robots). Has somebody tried to adapt Nigel's solution with two timers for two encoders ? Thanks for the answer. Fabrice

10 Dec 2014

Hi Nigel , Your program works fine on a Nucleo F411RE too. Thanks a lot. When I read the datasheet of the processor, there is so little information about how to program the timers for working with encoders, that I don't understand how you could manage to write your program. Did you find a complete document on the subject ? As I said in another post, I need to use two quadrature encoders for a robot. I saw on the datasheet that TIMER2 and TIMER5 are equivalent and 32 bit timers but they share PA0 and PA1 on the processor pins and even with the other possibilities of modes on other pins for CH1 and CH2 of these two timers, it seem impossible to use both at the same time. I think I should use the two 16 bit timers TIM3 and TIM4 for the two encoders instead of TIMER2 and TIMER5 ? Can your program be adapted for using TIM3 and TIM4 and how ? Thanks a lot for you help.

Fabrice

11 Dec 2014

Hi Fabrice, In answer to your question about how I managed it - It was not easy!!!! I found a few examples of other people's code for different processors on the net, but none of them worked with my Nucleo F401RE. I did however spend a lot of time working out why they didn't work and eventually found a solution that worked for me. - Although it did take about two weeks of evening programming to finally get it to work. The problem now is that I have forgotten a lot of what I had discovered, ie once I got a single encoder to work, that was it for me.

I have just taken a look at the code and I think it should work for 2 encoders, one using Timer2 and one using Timer5. As you have correctly pointed out the Alternative functions for PA0 and PA1 are both Timer2 & Timer5, and Timer5 does not appear anywhere else. However Timer2 can be programmed on PA5 & PB3. Therefore PA0 & PA1 would be free for Timer5. Can I suggest you try and convert my code to use Timer2 and PA5 & PB3 first. (remember to enable the clock for GPIOB too) Once you get that working then try Timer5 and PA0 & PA1. Then combine the two codes and you should have your solution. Good luck - based on my experience you will need a lot of it. Nigel

11 Dec 2014

Hi Nigel, Thank you for your answer. I'll have a few days free for Christmas and I'll spend time trying what you've said. I hope it will work. Or may be someone could give me a solution before that. Regards, Fabrice

06 Jan 2015

Nice work Nigel.

This is working on 16bit timer TIM1. I tried to implement the same for TIM3 & TIM4, so far not-working.

I would have looked at TIM5 (also 32bit), but I read the mbed uses that for timer_us functions.

void EncoderInitialiseTIM1(void) {
    // configure GPIO PA8 & PA9 aka D7 & D8 as inputs for Encoder
    // Enable clock for GPIOA
    __GPIOA_CLK_ENABLE(); //equivalent from _hal_rcc.h
 
    //stm32f4xx.h 
    GPIOA->MODER   |= GPIO_MODER_MODER8_1 | GPIO_MODER_MODER9_1 ;           //PA8 & PA9 as Alternate Function   /*!< GPIO port mode register,               Address offset: 0x00      */
    GPIOA->OTYPER  |= GPIO_OTYPER_OT_8 | GPIO_OTYPER_OT_9 ;                 //PA8 & PA9 as Inputs               /*!< GPIO port output type register,        Address offset: 0x04      */
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8 | GPIO_OSPEEDER_OSPEEDR9 ;     // Low speed                        /*!< GPIO port output speed register,       Address offset: 0x08      */
    GPIOA->PUPDR   |= GPIO_PUPDR_PUPDR8_1 | GPIO_PUPDR_PUPDR9_1 ;           // Pull Down                        /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
    GPIOA->AFR[0]  |= 0x00000000 ;                                          //  AF01 for PA8 & PA9              /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
    GPIOA->AFR[1]  |= 0x00000011 ;                                          //  bits here refer to gpio8..15    /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
   
    // configure TIM2 as Encoder input
    //RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //0x00000001;  // Enable clock for TIM2
    __TIM1_CLK_ENABLE();
 
    TIM1->CR1   = 0x0001;     // CEN(Counter ENable)='1'     < TIM control register 1
    TIM1->SMCR  = 0x0003;     // SMS='011' (Encoder mode 3)  < TIM slave mode control register
    TIM1->CCMR1 = 0xF1F1;     // CC1S='01' CC2S='01'         < TIM capture/compare mode register 1
    TIM1->CCMR2 = 0x0000;     //                             < TIM capture/compare mode register 2
    TIM1->CCER  = 0x0011;     // CC1P CC2P                   < TIM capture/compare enable register
    TIM1->PSC   = 0x0000;     // Prescaler = (0+1)           < TIM prescaler
    TIM1->ARR   = 0xffff; // reload at 0xfffffff         < TIM auto-reload register
  
    TIM1->CNT = 0x0000;  //reset the counter before we use it  
}
07 Jan 2015

Hello Nigel, David and & everyone else,

I didn't succeed in using TIM5 for a second encoder, TIM5 doen't stop incrementing at a very high speed even if the encoder isn't rotating, the reason could probably be the one given by David.: " TIM5 (also 32bit), but I read the mbed uses that for timer_us functions". Cheers, Fabrice

07 Jan 2015

Timer usage conflicts can also occur with PWM usage, so it's worth checking your target's PeripheralPins.c & PeripheralNames.h if you're using PWM & the encoder is not working.

10 Jan 2015

Hello Nigel, David and & everyone else, Thanks a lot, now it works for my robot with two quadrature encoders at the same time. I have combined Nigel's solution with the one modified by David for TIM1 and now it works fine on the Nucleo 411RE and I think on the Nucleo 401RE too. Of course TIM1 is only a 16 bit timer so I have limited TIM2 to 16 bits count (int16_t EncoderPosition_TIM2). For my robot counting up from 0 to +32767 in forward movement or counting down from 0 to -32768 when it runs backwards (signed integer in 16 bits) corresponds to +/-3,5 meters and its enough for any elementary movement of my robot.

Here is the program : /media/uploads/fablagrenouille/main.cpp Regards, Fabrice

10 Jan 2015

If anyone needs them, here's the equivalent for the final two 16bit encoder inputs using TIM3 & TIM4.

Weirdly, TIM4 works on the Nucleo F401, but not on the F411. I get the same behaviour when using the STM32Cube HAL to set up the encoders (I'll publish that later).

void EncoderInitialiseTIM3(void) {
    // configure GPIO PA0 & PA1 aka A0 & A1 as inputs for Encoder
    // Enable clock for GPIOA
    __GPIOA_CLK_ENABLE(); //equivalent from hal_rcc.h
 
    //stm32f4xx.h 
    GPIOA->MODER   |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1 ;           //PA6 & PA7 as Alternate Function   /*!< GPIO port mode register,               Address offset: 0x00      */
    GPIOA->OTYPER  |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7 ;                 //PA6 & PA7 as Inputs               /*!< GPIO port output type register,        Address offset: 0x04      */
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7 ;     //Low speed                         /*!< GPIO port output speed register,       Address offset: 0x08      */
    GPIOA->PUPDR   |= GPIO_PUPDR_PUPDR6_1 | GPIO_PUPDR_PUPDR7_1 ;           //Pull Down                         /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
    GPIOA->AFR[0]  |= 0x22000000 ;                                          //AF02 for PA6 & PA7                /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
    GPIOA->AFR[1]  |= 0x00000000 ;                                          //nibbles here refer to gpio8..15   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
   
    // configure TIM3 as Encoder input
    // Enable clock for TIM3
    __TIM3_CLK_ENABLE();
 
    TIM3->CR1   = 0x0001;     // CEN(Counter ENable)='1'     < TIM control register 1
    TIM3->SMCR  = TIM_ENCODERMODE_TI12;     // SMS='011' (Encoder mode 3)  < TIM slave mode control register
    TIM3->CCMR1 = 0xF1F1;     // CC1S='01' CC2S='01'         < TIM capture/compare mode register 1
    TIM3->CCMR2 = 0x0000;     //                             < TIM capture/compare mode register 2
    TIM3->CCER  = 0x0011;     // CC1P CC2P                   < TIM capture/compare enable register
    TIM3->PSC   = 0x0000;     // Prescaler = (0+1)           < TIM prescaler
    TIM3->ARR   = 0xffffffff; // reload at 0xfffffff         < TIM auto-reload register
  
    TIM3->CNT = 0x0000;  //reset the counter before we use it  
}

void EncoderInitialiseTIM4(void) {
    //PB6 PB7 aka D10 MORPHO_PB7
    // Enable clock for GPIOA
    __GPIOB_CLK_ENABLE(); //equivalent from hal_rcc.h
 
    //stm32f4xx.h 
    GPIOB->MODER   |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1 ;           //PB6 & PB7 as Alternate Function   /*!< GPIO port mode register,               Address offset: 0x00      */
    GPIOB->OTYPER  |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7 ;                 //PB6 & PB7 as Inputs               /*!< GPIO port output type register,        Address offset: 0x04      */
    GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7 ;     //Low speed                         /*!< GPIO port output speed register,       Address offset: 0x08      */
    GPIOB->PUPDR   |= GPIO_PUPDR_PUPDR6_1 | GPIO_PUPDR_PUPDR7_1 ;           //Pull Down                         /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
    GPIOB->AFR[0]  |= 0x22000000 ;                                          //AF02 for PB6 & PB7                /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
    GPIOB->AFR[1]  |= 0x00000000 ;                                          //nibbles here refer to gpio8..15   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
   
    // configure TIM4 as Encoder input
    // Enable clock for TIM4
    __TIM4_CLK_ENABLE();
 
    TIM4->CR1   = 0x0001;     // CEN(Counter ENable)='1'     < TIM control register 1
    TIM4->SMCR  = TIM_ENCODERMODE_TI12;     //               < TIM slave mode control register
    //TIM_ENCODERMODE_TI1 input 1 edges trigger count
    //TIM_ENCODERMODE_TI2 input 2 edges trigger count
    //TIM_ENCODERMODE_TI12 all edges trigger count
    TIM4->CCMR1 = 0xF1F1;     // CC1S='01' CC2S='01'         < TIM capture/compare mode register 1
    //0xF nibble sets up filter
    TIM4->CCMR2 = 0x0000;     //                             < TIM capture/compare mode register 2
    TIM4->CCER  = TIM_CCER_CC1E | TIM_CCER_CC2E;     //     < TIM capture/compare enable register
    TIM4->PSC   = 0x0000;     // Prescaler = (0+1)           < TIM prescaler
    TIM4->ARR   = 0xffff; // reload at 0xfffffff         < TIM auto-reload register
  
    TIM4->CNT = 0x0000;  //reset the counter before we use it  
}
10 Jan 2015

I've published code showing how to set up the encoders using the STM32Cube HAL at http://developer.mbed.org/users/gregeric/code/Nucleo_Hello_Encoder/

Tested on Nucleo's F030, F401, F411. L0xx included but untested, others can be added.

Perhaps someone could test it on their F411, as TIM4 does not work for me, whereas same code is fine on the F401. TIM4 input pins are configured as PB6 PB7. Have I zapped my inputs?

11 Jan 2015

Hi David, Thanks a lot for all your efforts, now we have the choice of the timers we want to use for encoders and leave the others free for controlling motor with PWM for example. I'll try to find time for trying TIM4 on the Nucleo F411. Regards, Fabrice

12 Jan 2015

Hi David, It works fine for me with TIM4 on the Nucleo F411 conected on PB6 and PB7. PB7 is only available on the Morpho CN7 header not on the Arduino compatible headers. Thanks again for the program. Regards, Fabrice

12 Jan 2015

Hi Fabrice. Thanks for testing & reporting back Ah well, looks like I zapped my F411's PB7 (the one adjacent to the lower of the two grounds on CN7).

Please log in to post a reply.