I’m controlling a brushless motor with an arduino nano and a custom motor driver board. I’m using the 6PWM driver and a 600ppm encoder. I am running the motor in closed loop position control and setting the target angle to 5 rad, waiting a few seconds, then setting the target angle to -5 rad.
After a few cycles the motor draws far more current when spinning in one direction and eventually slows down and stops.
I hooked up another arduino to the encoder lines and noticed that the position of the target angles slowly drifted. I believe this is caused by the driver missing encoder pulses and the motor phase becoming missaligned with the driver phase.
I am using software interrupts as the 6PWM driver uses one of the hardware interrupt pins.
I have since switched to a raspberry pi pico from your advice. The encoder inputs are now on a hardware interrupt pin.
The problem has gotten better after the switch, however I still notice that the at higher speeds, the motor slows down and draws more current before stopping completely. I am now certain that the issue is the driver missing encoder pulses.
I toggle a pin in one of the encoder pin’s interrupt function and monitiored its switching vs the encoder pin. After a while of running the motor, I noticed that the interrupt function, while usually only delayed ~10us from the encoder pin, can be delayed by over 100us after the encoder pin changes. I have also noticed it missing encoder pulses of 120us completely.
Is there anything that could be blocking the interrupt function from being called? This issue seems strange to me.
Yes, interrupts execute one at a time and when they happen at the same time, one goes first and the other gets delayed. If it gets delayed so much that the next interrupt of the same type happens, then it can be skipped entirely, I believe. I’d have to dig into the details of the Pico Datasheet to say for sure, as MCU types can differ in the way they handle interrupts.
But for sure it is possible to overload any MCU with interrupts and cause them to become unreliable.
On the other hand the Pico is a fairly fast MCU, so I have to say I am surprised you can overload it with encoder interrupts. Perhaps it is due to some other interrupt routine, for example involved in SPI, Serial comms or similar?
I think the problem may be related to the way the Encoder class is implemented: the current implementation receives the interrupt call, but “doesn’t trust it”. Instead, it checks again the state of the pin within the interrupt routine. However, if the interrupt is delayed, the pin state may no longer correspond to the one that generated the interrupt - and the encoder class “miscounts” even though the interrupt was received.
We could try to remove this double-checking in the Encoder class - it is helpful in situations where there is noise on the encoder signals and the speed is not very high, but it does not work when speeds increase…
Out of interest, what speeds are you running at before you get problems?
Another good option might be to instead use our “GenericSensor” class and combine it with an existing Pico implementation for Encoders based on hardware, perhaps the PIO? Something like this:
Another option can be to switch MCU again - for STM32 MCUs we have a STM32HardwareEncoder class that uses these chip’s timer hardware to implement the encoder counting with no interrupts or MCU overhead…
At first glance this looks good. Let me check into the code and see if I can find any problems…
One comment is to add a delay in the main loop, maybe 10ms or so? delay(10)
Otherwise this loop will execute very very fast, and flood the serial port with values.
(When you add code to control a motor, remember to remove the dalay again)
I spent a while verifying my setup yesterday and I am at the same conclusion.
I briefly looked through the source code but plan to take a deeper dive into it. I found the STM32F439xx HAL User Manual had some documentation for the code that is used. Am I looking in the right place? Is there somewhere else I can look to understand the code?
By the way, I just want to thank you for your incredible support. I have never seen a project with such an active and responsive community.
And I thought there was another one, but I can’t find it right now, so I’m probably confusing it with something else. I’m not at home on my main PC so I can’t check my files…
I think I should give the hardware encoder code some more love at some point soon anyway. When it works it works pretty well, and is much better than the interrupt based solution.
But it should include support for the Index pin on MCUs that can support it, and it should take into account the older generation of timers (like on F401 and F103) which don’t have Index support and don’t seem to work with the current code.
If you do figure out the problem, I would be super-grateful if you feel like posting your findings here…
Bringing this topic back up. I’ve been playing around with the STMHardwareEncoder library, trying to use it with an incremental encoder and a Nucelo F446RE. I’ve been unable to get it to work using TIM1 PWM pins, which the datasheet says supports quadrature encoders. Any suggestions on where to start with troubleshooting? Below is the code i’m using to just test the encoder without any of the motor code.
Using PA8 and PA9 with the STMHardwareEncoder class, I now get an upload error (“Error 1”) when flashing using PlatformIO. This seems to temporarily make the Nucleo unable to be reflashed unless I hold reset while flashing, then flash again. I can confirm using the interrupt based method works fine on those pins. Have you seen this before?
Hey! I’m using the Nucleo board with attached USB/stlink daughterboard. I’m uploading through that via USB. Right now I just have the encoder code and nothing else - no motor code. Nothing else is running either, just VScode.
When I tried PA0 and PA1 for the A/B inputs, the upload succeeds, but Serial printing doesnt work…odd. Anyways, this isnt critical and I know the HW encoder stuff isnt well tested at this point, so no need to try and debug unless you have similar hardware on your side and feel like doing it.
It’s strange, because its never given me any problems, but of course there are so many STM32 MCUs and pin combinations one can use…
If there is a conflict with another peripheral like serial output, or SWD, then of course that peripheral can’t work… the serial output to the ST-Link uses USART2 but I’m not sure on which pins. Of course it makes sense to check carefully there aren’t any pin-conflicts, but other than this the encoder code should not really interfere with other operations.
The code is probably not very robust, so if you feed it parameters it doesn’t like I imagine there is a chance it just crashes hard, and this can lead to an unresponsive MCU, requiring a restart.
one can usually find such cases fairly quickly though by adding some printfs to find which call isn’t succeeding.
Hello, i just want to tell about my expérience with the esp32. I rebuilded an cnc with dc motor. When i started the project simple foc had no code for dc motor. I developp the solution with the encodor code from the simple foc library, i build trapezoid wave and all what’s need. The solution run well bu it not so easy to go to a precise position and keep it. I build the solution with the arduino as ide and the version 1.x of the Espressif compiler. With a lot of work the précision of the position is very good and fast. Orders from auomate are given by modbus protocol. After fiew week i decide to do a small update but i changed the espressif compiler version to 2.07. I go to the client and forget I changed the compiler. I install the release and the machine lost the precision an is no more usable. I check all the machine, compile the version before and i was unable to restore performances. The machine lost interrupt ! At 23 o’clock i remeber a changed the compiler and restore the old one. The machine start with all performances and I can use my latest version. What’s append? Esp32 use shared interrupt and for shure somethink changed with the new compiler… but… I found esp32 has hardware counters! These counter can be used for encoder. There is a library… esp32encodor that use these hardware counter inside esp32. I have no chance to test this library until this day because the project is finished… with the old compiler… so interrupt is very good on the paper but… it’s never replace a hardware solution… Regards
@runger, have you tried using the STM32 HW Encoder with a STM32G4 processor?
I’m trying on the G431_ESC, and it doesn’t work. The digitalPinToPinName() and pinmap_function() functions cannot find PB6 alternate mapping, and return 0 (i.e. an invalid peripheral).
Once I fixed those, the timer is not properly initialized (it’s missing a HAL call to properly configure the timer. i fixed that as well, and now the encoder angle seems to work (I’m using a MT6701 in ABZ mode, 1024 cpr). I get the right readings for angle rotations.
But the getVelocity() function seems to return bogus values frequently. It might very well be that my motor still is vibrating a lot and that can throw off the sensor, so I’m not sure
I do appreciate that the code is not very robust, but I was wondering if getVelocity() is supposed to work. The code to calculate speed seems quite different from the standard quadrature encoder. Was the STM32 HW encoder based on a different version? If it works for others, I know it’s me
I’ve used it successfully, but not on the G431_ESC1… is it connected to the hall inputs?
I’ve been working a lot with the STM32 timers since writing that code, so I might give it an overhaul… if you don’t first
The thing with the alternate functions I think was known - it’s a pain to make the code so general that it finds all the timer associations by itself. And in some cases it’s ambiguous when you only specify a pin, in the way Arduino likes… how should the code know which timer you want? So really the API would have to be a bit different, I guess.
getVelocity() should work, but there is no special code for handling low-speed scenarios…
For the first issue (not finding the right timer for the PB6 and PB7 pins), the problem is in how the board variant is defined in ArduinoSTM32. in the file variants/STM32G4xx/G431C(6-8-B)U_G441CBU/PeripheralPins_B_G431B_ESC1 the definitions for PB_6 and PB_7 are commented out (for reasons I don’t understand… most pins are commented out in that file. It could be that the board has no exposed pins like a Nucleo, so the owner doesn’t expose them… even if PB6, PB7 and PB8 are used for the encoder). By removing the // only for PB_6 and PB_7, the code starts working better, but it still fails in multiple ways: the function pinmap_function() doesn’t return the correct value for Alternate (which should be 2), and PB7 returns TIM3, which is not correct either.
It seems that having a custom encoder for the B431_ESC (or at least a portion of #ifdef code) would be simpler than trying to fix the broken variant pinout definition and figuring out how to find the correct alternate configuration.
Just in case, the code below works on the B431_ESC with no changes to the ARDUINO board files (I changed only the init function), using PB6 and PB7 as A and B. No Z (index) support