Arduino-FOC
Arduino-FOC copied to clipboard
Use interrupts for time accuracy
I am trying to run a stepper motor. I initially setup a magnetic sensor. But switch to open loop as a first test, and It runs ok with a velocity of 20 to 80. But still, lower speeds are erratic. I did a bit of research and other drivers have similar issues when doing full steps at low speeds. Because there are resonance issues, a common solution is to do micro-stepping in low RPM and full steps at higher RPM. What seemed strange to me is that this library runs a sinusidal shape from which is equivalent to microstepping. I've tested with 1.8°, 36mm Pancake Motor LDO-36STH17-1004AHG and 1701HSM140AE nema 17.
I dug into the code, and I think the issue is the call to loopFOC every one millisecond. That added update frequency has no function with respect to the motor, I assume the time to run that loop varies from run to run. I think that extra noise is causing issues with the stepper.
There are several built in latencies in measurements and processing speed. In a blue pill there are is a 100ms latency just on calling loopFOC. I am not an expert in steppers, but the following should be worthwhile even if I'm missing something in my setup.
I propose to use a timer an interrupt and a short array to cycle through all the PWM signals. And just write directly to the ports to generate them. I also propose to precompute steps with pwm values for a pole cycle. Each step in the cycle would be loaded as the last action of the interrupt. And I also propose to account for latencies explicitly, using past and current acceleration and velocity.
It may sound like a lot of changes, but it actually an initial set of changes would be easy to make. By adding time to several calls, and changing the pwm generation. setPwm would change significantly since it would now write to a table to be used by the interrupt.
All this would also have the very positive side effect of removing the limit of pins available. Since they all could be set from the interrupt.
Modify loopFOC to take in a time parameter Modify sensors to take in a time parameter Modify setPhaseVoltage to take in a time parameter Modify setPwm to take in a time parameter
FOCMotor would account for latency and give estimates of future values based on current velocity and acceleration.
FOCMotor could have a re-estimation threshold. In case substantial changes, values could be recalculated.
I already have some of this code. I would be happy to donate some of my time to implement this. We could follow up on discord, This was just too long. And I was not sure where to post it in discord.
Well, I am not sure this approach would be an improvement…
the SimpleFOC library is mainly for field oriented control, that’s the FOC in the name.
Stepping through the commutation in a fixed way is already supported by the open loop modes, and when driving the motor in open loop, you don’t need to call loopFOC() at all. but that’s not the main focus of the library…
the main focus is the FOC modes, which implement field oriented control based on closed loop feedback from a position sensor (encoder or magnetic sensor). For this the updates must occur very frequently, 1kHz would in fact be fairly slow, and limit the max speed you could achieve in FOC mode.
if your aim is just to spin a motor as quickly as possible, without regards to efficiency or performance at low speeds, the open loop mode or a „traditional“ 6-step commutation scheme might be faster. But if you care about efficiency and torque at low speeds, then FOC has many advantages.
the functions already take time into account, so calling them using interrupts might make them more regular, but shouldn’t make a difference to performance. Usually you want loopFOC to run as often as possible, so using a tight loop rather than interrupts actually gets you better performance here.
have you taken a look at the „Theory corner“ in our documentation? The concepts behind field oriented control are explained there.
On open loop you only need move()
and I think the issue subsists.
What speed range are you targeting? I'd like to use it for a robot, and I am still unsure about the gear ratio I may use. But I am thinking 30:1 to 100:1. And I'd like the robot arm to be fairly responsive. I would like to see a 90 degree move in 2 seconds or 7.5 RPM. If I used a gear ratio of 65:1 that would be close to 500 RPM. But I also would like to be able to move much slower. So I say the range would be between 20 to 500 RPM. With a 12 pole motor you got 12 identical cycles per revolution. For FOC I assume I would need at least 16 steps for every cycle (that is me assuming, and I could be wrong). So 16 steps and 12 cycles, I think that would be around 192 distinct pwm values needed per revolution. At that rate we are talking of calling loopFOC
and move
at around 96kHz.
If the limit for Arduino is 1kHz or 10kHz. Then a tradeoff would need to be made. But MCU like these are used all the time, there has to be a way. Or maybe the limit is lower than what I expect.
One problem with my description is that, at 16Mhz to have a 255 value resolution. The pwm frequency would be at most 62kHz. If you were to have 12 steps every cycle it may work. But there are other processors that could be used.
I just wanted to see what are reasonable limits for different processors. And If I am way out of base. My feeling is that acceleration and velocity does not vary wildly. And if it did, there may be a choice to be made by the final user. Would it be preferable to prevent high speeds so that abrupt changes can fall in regular processing. Or is it preferable to slow down, and keep the option of higher speeds. Or is there a third alternative which could leverage precomputed values possibly degrading available processor time, or motor performance.
Regards
I did err on the calc for the BLDC example I used 500 RPM as if it was 500 RPS. So it's not 96kHz, its 1.6kHz.
I have been reading on the theory, and I made some measurements 1.6 millis for the move() and loopFOC() for a stepper with an AS5600. And 0.4 just for move() in open loop.
A stepper on FOC with at most 5% error, the maximum speed I think is around 37 RPM. Because of the number of poles. I think that is on the low side. And I also think with some changes it could go as high as 500 RPM with simple FOC. I think there is a valid use case for a wide range of speeds, but that is just me, and you have been building a community already. I may be able to make my proposed changes just by using the driver API. Plus some other top level code. That way there are no changes and if it works, then maybe.
Would you consider adding a theory channel to discourse?
So the speed you can achieve in open-loop depends on the MCU you're using... if your motor dynamics permit it, you can achieve very high speeds. Classic Arduino (8bit ATMega) are very slow MCUs by today's standards. Here you may really be limited to loop speeds <2kHz, even in open-loop modes. But more modern MCUs, like STM32 or ESP32 can achieve loop speeds of 20kHz or even more, in closed loop!
So assuming 20kHz, 12 poles and 6 steps (the minimum) per electrical revolution, you could in theory achieve 277RPS or 16666RPM. --> in closed loop. Most of the motors used by SimpleFOC users won't even spin this fast. The "speed record" I've seen from people in the forum is 9000RPM with FOC control. In open loop you should be able to go even much faster! At the same time, in closed loop modes you have excellent torque and smooth motion even at slow speeds.
So how did older controllers do it on an Arduino 8bit MCU? --> Firmwares like SimonK and BLHeli don't do FOC control. They implement 6-step commutation based on hall sensors, and are written in a highly optimised way (using assembler and only integer math) so they can achieve the high speeds on these older MCUs. But they can't do things like position control or torque control, don't work well at low speeds and are far less energy efficient.
Would you consider adding a theory channel to discourse?
I'll mention it to the others, I would not see why not? It's a good suggestion!
Also note that motors are designed to a purpose: so if you want high torque at low speeds, a high pole, low KV "gimbal" type motor will be good for this. But due to its high pole count, it will be hard to drive fast. For high-speed applications, a low pole, high KV type motor (often in-runners with <6 poles) will be much easier to spin quickly, but harder to get good control and torque at low speeds.
Ok!! Thank you for mentioning this. In my case it's a stepper motor. It is working at 150 rpm, but I can feel it hesitating randomly. And then al low RPM it just completely unstable.
A few days ago I was running with a TMC2130 Trinamic driver, and I did get better performance. Changing the number of steps with the speed made a difference. It ran fine at low speeds. It is harder to get information from Trinamic, unless of course I had some product I was selling. The docs are good but, there are just too many variables, and it takes too long to try every single combination. It just feels like a thorn on my side, this stepper should work on open loop. I will try adding the motor constants, even for open loop.
On another note. I am getting a test ready using simavr. So I can iterate more rapidly. This code:
for (float target = 0.1f; target <= 1; target=target + 0.1f){
Serial.println("Move!");
motor.move(target);
}
Gives me this output:
Motor ready!..
Set target position [rad]..
Move!..
0.2401249885..
0.4712706565..
0.0386043357..
Move!..
0.2342249870..
0.4739608287..
0.0418141651..
Move!..
0.2283250093..
0.4765211105..
0.0451538991..
Move!..
0.2224250078..
0.4789731502..
0.0486018466..
Move!..
0.2165499925..
0.4812828063..
0.0521672201..
Move!..
0.2106999874..
0.4834716796..
0.0558283329..
Move!..
0.2048500061..
0.4855306148..
0.0596193695..
Move!..
0.1990499973..
0.4874347209..
0.0635153055..
Move!..
0.1932749843..
0.4892179965..
0.0675069713..
I only changed the time_utils.h. And:
void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, void* params){
Serial.println(dc_a, DEC);
Serial.println(dc_b, DEC);
Serial.println(dc_c, DEC);
}
The Trinamic documentation does suggest different modes of operation at different speeds. For robotic applications that may be very useful. Slow operation with high accuracy. And still have the ability to make faster movements. It may require different modes of operation, and hopefully still using the same motor library.
Although by your notes, sounds like you already got that for bldc, I want to use a stepper because of the torque.
Here are the images I get from simulating an open loop run for BLDC and Stepper. I expected to see something different for the stepper.
It is somewhat unexpected but it does make sense.
Hey @arturohernandez10 , we now have a stepper-motors channel on Discord, and several users are quite active working on Stepper support. Perhaps it's better to continue the discussion there?
We still don't have a theory channel on Discourse, but people post theory questions and ideas there anyways, please feel free to do so also!
If it is ok I will close this issue, as it is not clear what the action point is.