7 years, 9 months ago.

How can I make mRotaryEncoder work on Nucleo F401RE?

Hi,

I am trying to use a rotary encoder on the Nucleo F401RE, with no luck so far. I am trying to use the mRotaryEncoder library, but it does not seem to work on this plattform.

Here is my program:

Nucleo F401RE program

#include "mbed.h"
#include "mRotaryEncoder.h"
 
DigitalOut myled(LED1);
Serial pc(USBTX, USBRX);
mRotaryEncoder RE(PA_0, PA_1, PA_4, PullUp, 500);
 
int main()
{
    pc.baud(115200);
    pc.format(8,Serial::None,1);
    pc.printf("\nmBed ready.");
 
    while(1)
    {
        myled = 1;
        wait_ms(80);
        pc.printf("\n%d", RE.Get());
        myled = 0;
        wait_ms(200);
        
    }
}

I was also trying an almost identical code with one of my older mBed plattforms, the LPC1768, and it worked first shot!

Here is the code for that:

LPC1768 program

#include "mbed.h"
#include "mRotaryEncoder.h"
 
DigitalOut myled(LED1);
Serial pc(USBTX, USBRX);
mRotaryEncoder RE(p29, p30, p28, PullUp, 500);
 
int main()
{
    pc.baud(115200);
    pc.format(8,Serial::None,1);
    pc.printf("\nmBed ready.");
 
    while(1)
    {
        myled = 1;
        wait_ms(80);
        pc.printf("\n%d", RE.Get());
        myled = 0;
        wait_ms(200);
        
    }
}

I ran a search on the mBed homepage to investigate why this is not working on my Nucleo board, but did not find a solution yet.

I have the feeling that this has something to do with the interrupt handling of the Nucleo board or the PinDetect function of the mBed+Nucleo "combo".

I also tried phisical (I mean external) pull-up resistors of the relevant lines, but in the case of the Nucleo board, it did not help.

I have also read it in the discussions somewhere that the Nucleo boards can have interrupts on virtually any pin with the restriction that if for example one has an interrupt on PA_0, one can not have an interrupt on PB_0 or PC0, etc. The same is valid for the other numbered pins.

Could someone point me into the right direction on solving this?

Thank you and Best Regards:

Balazs

Just to make sure, you are using a recent version of the mbed lib?

posted by Erik - 04 Jul 2016

Hi Erik, Thanks for your question. Yes, it is the latest available mbed lib. The project was created 2 days ago.

posted by Balazs Bornyasz 04 Jul 2016

Which behavior do you see? It does still print messages? Does the encoder do anything or nothing at all?

posted by Erik - 04 Jul 2016

In both cases, the program prints the encoder current value. In case of the LPC1768 this value changes according to how I rotate the ALPS rotary encoder dial, where in case of the STM Nucleo, this value stays at 0 all the time.

In short: in case of the Nucleo, the encoder seems to do nothing at all, no matter if there is a physical pull-up resistor on not.

posted by Balazs Bornyasz 04 Jul 2016

Not sure if this is an answer, but I think the encoder lib depends on attching events for both rising and falling edges of a signal and doesn't Nucleo only support either rising OR falling but not both?

posted by Oliver Broad 04 Jul 2016

I need to check that Oliver, but I am not sure if that has an impact on the situation I am experiencing. I have tried different other rotary encoder libs, some of them were using InterruptIn, none of those seemed to work either.

posted by Balazs Bornyasz 04 Jul 2016

Quick check in the source code shows it _should_ support interrupts on both edges at the same time. But you never know if they implemented it properly.

I will probably have a bit more of a look at it one of coming days. However first another question: Did you try other pins?

posted by Erik - 04 Jul 2016

Yes, I did, and none of the ones I have tried, worked. These pins included:

InterruptIn soft_1(PC_14);

InterruptIn soft_2(PC_15);

InterruptIn soft_3(PH_0);

InterruptIn soft_4(PH_1);

InterruptIn bup(PC_6);

InterruptIn bdown(PB_2);

InterruptIn bleft(PB_3);

InterruptIn bright(PC_8);

Thank you very much for you taking a look into it.

posted by Balazs Bornyasz 04 Jul 2016

I'm having a similar problem with the F446RE when I'm trying to read USER_BUTTON via Interrupt.

main.cpp

#include "mbed.h"
#include "rtos.h"
#include <mRotaryEncoder.h>
 
Serial pc(USBTX, USBRX); // tx, rx
DigitalOut led1(D7);
DigitalOut led2(D2);

int led1_sleep = 1000;
int led2_sleep = 1000;
int pulse = 0;

int active_led = 0;
void toggle_led_editor() {
    switch(active_led) {
    case 1 : active_led = 0;
             break;       
    case 0 : active_led = 1;
             break;
    }
    pc.printf("Active LED has been set to: %i\r\n", active_led);
}

void led1_thread(void const *args) {
    while (true) {
        led1 = !led1;
        Thread::wait(led1_sleep);
    }
}
 
void led2_thread(void const *args) {
    while (true) {
        led2 = !led2;
        Thread::wait(led2_sleep);
    }
}

void readEncoder_thread(void const *args) {
    mRotaryEncoder RE(PA_0, PA_1, USER_BUTTON, PullUp, 500);
    
    // causes a crash when when I press the button
    RE.attachSW(&toggle_led_editor);
    
    while (true) {
        int np = RE.Get();
        if (np != pulse) {
            pulse = np;
            pc.printf("Pulses is now: %i\r\n", np);
            osDelay(5);
        }
    }
}
     
int main() {
    //InterruptIn mybutton(USER_BUTTON);
    //mybutton.fall(&toggle_led_editor);
    led1 = 0;
    led1 = 1;
    Thread thread1(led1_thread);
    Thread thread2(led2_thread);
    Thread thread3(readEncoder_thread);
    
    while (true) {
        Thread::wait(1000);
    }
}
posted by R T 23 Jul 2016

I found my problem- I needed to move my pc.printf() into a mailbox/thread because printing to serial during an interrupt is not allowed in RTOS.

posted by R T 24 Jul 2016

1 Answer

7 years, 9 months ago.

So debug time.

First check: Is the PinDetect interrupt being called? With a printf in its interrupt code it seems not. Weird, so lets try different InterruptIns. I'll quickly skip over this embarassing moment since it turned out PinDetect doesn't use pin interrupts but only a ticker interrupt (and I can assure you InterruptIn does work :P).

So why isn't it called? Regular Ticker works fine. Why does that stupid one not work. So you start trying different stuff: Using a different interrupt function, using not a Ticker point but Ticker object. All shouldn't matter, and indeed doesn't matter. Using a local Ticker object made just before it is being attached. Since at the end of the function where it is attached it would be destroyed again, I added a while(1) statement. And behold, it works properly!

Again: WTF? After removing the local Ticker and going back to the original one it still works properly, as long as I block the program from continuing further. So next step: See where the issue is. Is it the second PinDetect which is made for that third pin no one cares about? Nop, also not the issue. Turns out: you make the RotaryEncoder as global object (perfectly fine btw), and as soon as the code starts the main code, it is broken. At this point I got a workaround for you, but lets first figure out why this is happening.

Why is it happening? I don't know. I might have a look at it later if I can figure it out, but that means modifying the mbed source code until the cause is found. My best guess? This: https://github.com/mbedmicro/mbed/blob/master/hal/targets/hal/TARGET_STM/TARGET_STM32F4/mbed_overrides.c. It says that function is called before main, but the question is if it is called after the global variables are initialized. SystemCoreClockUpdate should be harmless (although no idea why it is called here instead of being set in the clock initialization code like all others do), but I have a feeling that HAL_init is not harmless. It calls among others: https://github.com/mbedmicro/mbed/blob/master/hal/targets/cmsis/TARGET_STM/TARGET_STM32F4/TARGET_NUCLEO_F401RE/hal_tick.c Which nicely screws up your Ticker by resetting the Timers, since they kinda forgot to check if it was already initialized.

Now to get to the end, how do you make it work? Copy line 6 into the main loop. The Encoder then is initialized only after those functions are called, and it seems to work properly in my limitted test (since I don't have a rotary encoder).

Accepted Answer

Hi Erik,

Wow that's what I call thorough analysis!

I really appreciate that you took the time to go to the depths of this.

The bottom line is that moving the contents of line 6 into main also worked for my setup with a rotary encoder.

I am still unsure about my other external InterruptIn declarations, I am currently investigating them.

What should happen now to make a permanent fix for this problem? (I wonder how it could be that I am the first one noticing this issue at all?). Should the mbed source be modified? I am unexperienced with these kind of changes and how to request them to be realized - or investigated.

What dou you think about this?

Big thanks for your effort, Best regards: Balazs

posted by Balazs Bornyasz 06 Jul 2016

Hey,

Nice it works for you now. For a permanent fix the mbed source code needs to be modified yes. The issue is that for STM the mbed code is built on top of the STM code, which requires certain timer functions which IIRC broke RTOS, so they changed them to use mbed timers, but well turns out their solution results in this problem.

Why you are the first one to find it: Well all Tickers which are only set in main will work fine. Even PinDetect will work fine since you normally only activate it in the main function. On the other hand you shouldn't be the first one to try to use something simple like a Rotary Encoder, and those are the things which should just work.

How to solve it: The good part is STM actually maintains their code, as one of the few. The bad news is that it still doesn't guarantee bugs are solved, considering the now 201 open issues and bugs on the mbed source code. Anyway I made a bug report for it: https://github.com/mbedmicro/mbed/issues/2115

posted by Erik - 06 Jul 2016

Hey, do you know if this happens on any toolchain (gcc / arm / µarm / iar) ?

posted by Laurent Meunier 08 Jul 2016

Assuming my guess of the root cause (that function in mbed_override), it should depend on when it is called. I haven't tested it on anything besides the online one (which is essentially the Keil compiler, not sure if you have set ARM or uARM as default for the F401). I suppose it is somewhere in one of compiler specific startup files defined, but to answer your question: No I am not aware if this affects every toolchain.

posted by Erik - 08 Jul 2016

Hi Erik,

Since I've moved line "mRotaryEncoder RE(xxxx)" inside the main() function, I made it local to main() if I'm right. In one of my actual programs where I wish to use the rotary encoder, I use multiple source files and I would like to access the rotary encoder's current value in files other than the main.cpp. Some callbacks like "attachROT" I would also like to use, and inside of the callback function I would use RE.Get(). But this does not work since RE is local to main(). Is there any way to overcome this problem?

posted by Balazs Bornyasz 09 Jul 2016

Yep that is possible, no problem :).

Add as global variable:

mRotaryEncoder *REpointer;

And after you create it in your main you do:

REpointer = &RE;

Finally in your callback do take into account it is now a pointer, so:

value = REpointer->Get();

Obviously do take into account the REpointer needs to be set before you use it, so don't enable interrupts which use it before you have set it.

posted by Erik - 09 Jul 2016

Thanks again Erik, it worked! You have responded extremely fast, I appreciate it!

posted by Balazs Bornyasz 09 Jul 2016