That’s a bit pushing it. You may get better results if you go for SPI AS5047 sensor. AS5600 is a simple digital potentiometer (100RPM max), I’m surprised you got that far on it.
PS The AS5600 does not even list the RPM in the specs, it’s that low.
That’s a bit pushing it. You may get better results if you go for SPI AS5047 sensor. AS5600 is a simple digital potentiometer (100RPM max), I’m surprised you got that far on it.
PS The AS5600 does not even list the RPM in the specs, it’s that low.
Hey @daniel,
Yes, your MCU is fast and you can have loop times even much higher than 2.5kHz if you use a different sensor. I2C based AS5600 is really not meant for high speeds or fast loop times. But even an ideal sensor you will still not be able to come to 500rad/s.
The open-loop control is really really fast. cca 10x-100x faster then the FOC and it makes a lot of difference.
Velocity control algorithm takes longer time to execute and that is probably the reason why you see such a high difference. Try using the motion downsampling to let more time to the FOC algorithm and less for your motion control as your task for the moment is very simple. With some motion downsampling you should be able to reach at least this 150rad/s.
You are using a timer?
There is part of the equation that influences a lot and that is power supply voltage. As when going faster your back-emf rises motor’s torque fill diminish and it will not be able to reach higher speeds. So one simple way you can increase your speed range would be to use a higher power-supply voltage.
We do not do the compensation of the delay in the library in this way. We do have the adaptive time sampling which will help in motion control applications but not in the field vector setting. There is something that you can do if you are motivated though, we do use the motor.zero_electric_angle
which basically determines the calibrated offset in between sensor and the motor 0 angles. For higher velocities (due to this delay you are interested in) we will have a certain offset in between where the motor is and where we this it is which will depend on velocity.
So you could add your estimation of the offset to the motor.zero_electric_angle
:
You could do something like this in your loop
float normal_zero_angle = motor.zero_electric_angle;
float alpha = 0.1; // something small - this is up to you to find depending on your motor and sensor
void loop (){
motor.zero_electric_angle=normal_zero_angle + alpha*motor.shaft_velocty;
...
motor.loopFOC();
motor.move();
}
Thank you for your responses! I was not aware that there was anything like an RPM-limitation on the AS5600. I’ve just received some AS5048A boards, hopefully I’ll manage to print a mount for it this weekend and dome so testing with it. I’ll report back on the results!
I don’t necessarily need to reach 500 rad/sec, I think that for my application (biped robot legs with probably 1:9 reduction) should be okay with 200 rad/sec, maybe even less. I’m just very curious what the limiting factors are (and of course, getting higher speeds is more satisfying )
I had assumed the difference between velocity control and torque control would mostly be caused by poor PID/filter tuning. I will experiment more with downsampling as well. I have been testing with higher supply voltages (adding extra li-ion cells). I mostly got higher torques but I don’t think I got much higher speeds. Also with a simple ESC and two cells these motors reach much higher RPMs, so I didn’t think voltage should be the limiting factor. But perhaps speed on an ESC and with FOC is completely incomparable?
For now I’ve simply limited the loop iteration speed with a simple timing check (and tested that the slowest loop iterations finish well in time of course). I might play around with some interrupt-based options later.
I realize that indeed much higher speeds can be achieved with open-loop control, given it’s simplicity and non-dependence on slow sensors and noisy values etc. But with zero ability to deal with disturbances of course. Is it expected though that I need to manually adjust the current limit corresponding to the speed? IE, at low speeds it doesn’t work with a high current limit so I need to start out with low current limits, then as speed is going up it doesn’t work anymore with the low current limits so I need to crank up the limits.
I was already sniffing around in the library code to find a good spot to experiment with some sort of timing compensation based on current velocity, but your suggestion to adjust the zero_electric_angle is much simpler! I will definitely play around a bit with that
Thanks again for your thoughts!
I tried with the AS5048 and it was a difference like day and night! The motor is running super smooth at both low as well as higher rpm and effortlessly reaches velocities well above 200 rad/s! I sort of feel like I’ve just been wasting a lot of time with the 5600 although I do wonder if the difference is fully caused by the sensor itself, or if perhaps the alligment also plays a role, since the 5600 are not even centered on their boards properly (its the cheap white boards from Ali Express) and the cogging and alignment tests reported error distributions in the range of 30 degrees with the as5600 (after I got it to work, see https://github.com/simplefoc/Arduino-FOC/pull/157).
I did notice something weird with the 5048 though. In the sensor test example sketch it just worked flawlessly, but with I my own code it didn’t work. Somehow it gets a very low clock rate (~2kbps), at which it seems the chip just refuses to respond. After a lot of trial and error I noticed one difference between the senor test example and my code : the sensor test example uses the as5147 default config and my code used the as5048 default config. This turned out to be the key: when I changed my code to use the 5147 default config it immediately worked. I’m super puzzled over this though, because as far as I understand from checking out MagneticSensorSPI.cpp, the as5048 default config is literally a copy of the as5147 default config. They do clearly behave differently though, so there must be something going on.
It’s really mainly the I2C interface vs. SPI. That makes a huge speed difference, which is what is allowing you to reach higher RPMs. But it is certainly also true that the 10 vs 14 bits and the quality of the sensor PCB help get smoother, better results.
Thanks a lot for reporting the problems with the MagneticSensorSPI class. I’ve made an issue and we’ll investigate it.
In the meantime you could also try the MagneticSensorAS5048A class from our drivers repository. This is specific to the AS5048A, and also lets you read out the magnitude and diagnostic registers which you can’t get with the generic MagneticSensorSPI class.
Hello !!
I’ve been able to successfully run full FOC on the G431B-ESC board with an SPI magnetic sensor (AS5047) a 1 MHz thanks to all the great tips I could find on this forum (B-G431B-ESC1: Beginner guide + I2C guide - #91 by Grizzly). Motor is 100W AT2303 1500KV.
While operation at low RPMs runs smoothly after tuning, I can’t achieve speeds beyond 750 rad/s. As I increase velocity, current draw also increases as expected (all below 1A). Going beyond 600-800 rad/s, the current suddenly jumps to the power source limit (3A), the motor becomes very noisy and the reported speed actually decreases (and doesn’t reach the target).
For my application, I would need to go to 1500 rad/s. Is there any tips you would have to reach that? I am open to any changes, going open loop at high speed or even switching to 6 step control etc…
Have you tried voltage mode with lag compensation ?
What is your power supply voltage ?
At what voltage target are you having problems ?
Hey, 1500rad/s is pretty fast for FOC control. You’ll have to push the MCU to its limits
How many pole pairs is this motor? Probably 7?
Could you add some timing to estimate your main loop speed (like, count the number of iterations in one second).
One thing you could try is to activate the CORDIC for the trig calculations, this should give a small speed increase.
Thanks for the super fast reply!
Trying Torquemode:voltage does not let me go beyond ~230rad/s. I will try the lag compensation now.
Power supply: 12V
I’ve been doing all my tests in velocity feedback, as such I don’t have voltage targets. I’ll update soon on that.
Here you can see the jump around 750 rad/s when I control in current (FOC current mode):
@runger, that’s what I figured looking through the comments, but it still seemed possible? I will try your suggestions, thank you so much! Yes, the motor has 7PP. Any chance that switching to 6 step control at higher speed could be a solution?
EDIT: the main loop runs at 7 kHz quite consistently across speeds. In voltage mode I get to 7.8 kHz.
Some quick thoughts:
7PP at 750rad/s is about 836 electrical revolutions per second. You will need several loopFOC iterations per e-rev to correctly commutate the motor. So I think it is reasonable to assume you are hitting a MCU speed limit, and improving the loop time will allow you to go faster.
Some things to speed up the loop:
On the motor driving side:
And maybe use the dev branch
Thanks for the great suggestions! I’ve been busy trying this out, here is the outcome:
Before any changes, I could reach reliably 600 rad/s (on release 2.3.0).
I haven’t tried the CORDIC functions, could you point me to any resources that might show me how to enable that?
Were you using the recent release 2.3.1 already ?
The dev branch only has a SVPWM implementation that should be faster than SVPWM with 2.3.1.
No, I upgraded from 2.3.0 to 2.3.1, since I saw that it was very recent I assumed it would have the faster SVPWM. I’ll give the dev branch a try now
This delay between the info of the sensor coming in and then the electrical angle being set is a significant thing for sure, and not just due to I2C communication times. I used PWM and analog and it was still an issue just due to code execution time, because it affects motor timing. The main point of FOC is to get the motor timing nearly optimal at all times, and that is where most of the value comes from . But when your sensor readings are stale you are never going to get good motor timing, thus you get poor efficiency and torque without realizing what you are missing. I think this is happening a lot to people without being noticed.
This appproach of compensating the zero offset linearly with RPM is what I did and it helped a lot.
It is one legit reason in some cases for these apparent RPM ceilings, but I am sure there are other reasons as well.
It’s worth noting that the M0 core of an STM32 processor operating at 64 MHZ got 7khz loop frequency in open loop i.e. basically just outputting the 3 phase sine wave, and the M4 core, which has floating point hardware, of the b-G431b-esc1 board at 168 Mhz clock speed got 37.5 Mhz. So it’s way way faster.
Ralph, can you be sure to post your code and stuff at some point? I am a fan of this microcontroller board and you seem to be getting pretty good results so I think it would be good to share and encourage adoption of this board.
My only complaint is that the g431 processor that other board uses is clearly superior for motor drivers per se… I still think it’s more advisable to have a single actual flagship board with power stage and everything, because motor drivers are a special case, rolling your own isn’t really advisable and the requirements are not amenable to a general purpose board like the pico being used.
I have discussed this topic with @dekutree64 offline.
FOC would need to extrapolate the position based on a delay/lag.
That’s one of the effects I wanted to highlight in this post.
It’s much more visible on slow hardware and as the speed increases of course.
One workaround in voltage mode could be to use the lag compensation and tweak the phase_inductance to also compensate for the delay.
Hi all,
Small report from my side, I am now managing to get 1000 rad/s. Things that I did to get there:
- Increase SPI frequency to 10 MHz. This was by far what led to the biggest speed gain. My main loop went from 7kHz to 11.1kHz
- Running in SVPWM in Voltage mode with the Dev branch. This was the most reliable and also provided the least current draw at given speed, once the inductance value was properly tuned. Got me to main loop speeds of 13.3 kHz.
- Motion Downsample = 3
All this makes me think that my sensor is what is limiting the max speed. I actually have the AS5048A in SPI (not the AS5047 as indicated earlier). Would it make sense to switch to the faster AS5047P sensor or an ABI sensor instead? I could order one and test.
Here is my code:
#include <Arduino.h>
#include <SimpleFOC.h>
#include <pinmap_custom.h>
// MagneticSensorSPI(int cs, float _cpr, int _angle_register)
MagneticSensorSPI sensor = MagneticSensorSPI(PA15, 14, 0x3FFF);
SPIClass SPI_3(PB5, PB4, PB3); //(mosi, miso, sclk)
// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
// current sensor
LowsideCurrentSense current_sense = LowsideCurrentSense(0.003, -64.0 / 7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
// Commander interface constructor
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&motor.target, cmd); }
void doMotor(char* cmd){ command.motor(&motor,cmd); }
void setup(){
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
delay(100);
sensor.init(&SPI_3);
motor.linkSensor(&sensor);
// driver config
driver.voltage_power_supply = 15;
driver.init();
// link the driver to the current sense
current_sense.linkDriver(&driver);
// link the motor and the driver
motor.linkDriver(&driver);
// limiting motor movements
motor.phase_resistance = 0.251; // [Ohm]
motor.phase_inductance = 0.0001; // [Ohm]
motor.foc_modulation = FOCModulationType::SpaceVectorPWM;
motor.current_limit = 10; // [Amps] - if phase resistance defined
// motor.voltage_limit = 1; // [V] - if phase resistance not defined
motor.velocity_limit = 100; // [rad/s] cca 50rpm
motor.voltage_sensor_align = 1;
// set torque mode to be used
motor.torque_controller = TorqueControlType::voltage; // ( default )
// motor.torque_controller = TorqueControlType::dc_current;
// motor.torque_controller = TorqueControlType::foc_current;
// set motion control loop to be used
// motor.controller = MotionControlType::torque; // - torque control
motor.controller = MotionControlType::velocity; // - velocity motion control
// motor.controller = MotionControlType::angle; // - position/angle motion control
// motor.controller = MotionControlType::velocity_openloop; // - velocity open-loop control
// motor.controller = MotionControlType::angle_openloop; // - position open-loop control
motor.motion_downsample = 3.0; //gives us 10% speed range.
motor.useMonitoring(Serial);
motor.monitor_downsample = 100; // set downsampling can be even more > 100
motor.monitor_variables = _MON_VEL;// _MON_CURR_Q | _MON_CURR_D; // set monitoring of d and q currents _MON_TARGET | _MON_VEL | _MON_ANGLE |
motor.monitor_decimals = 2; //!< monitor outputs decimal places
motor.PID_velocity.P = 0.04;
motor.PID_velocity.I = 1;
motor.PID_velocity.D = 0;
motor.PID_velocity.output_ramp = 1000;
motor.LPF_velocity.Tf = 0.005;
motor.PID_current_q.P = 0.5;
motor.P_angle.P = 60.0;
// init motor hardware
motor.init();
// current sense init hardware
current_sense.init();
// link the current sense to the motor
motor.linkCurrentSense(¤t_sense);
// align sensor and start FOC
motor.initFOC();
// set the initial motor target
motor.target = 0; // unit depends on control mode
// add target command T
command.add('T', doTarget, "target angle");
command.add('M',doMotor,"my motor motion");
_delay(100);
}
void loop(){
static int i = 0;
static int millis_prev = 0;
i ++;
if(millis()-millis_prev>1000){
Serial.print("Main frq: "); Serial.print(i);
i=0;
millis_prev = millis();
}
digitalWrite(LED_BUILTIN, millis()%2000>1000);
motor.loopFOC();
motor.move();
motor.monitor();
command.run();
}
I think that you could get even higher speeds if you change your hardware a little and use some STM32 board with the STM32HWEncoder sensor type, with a magnetic encoder that has ABZ output.
I can’t imagine that at 1000rad/s you are doing angle control, so a quadrature encoder will give you even lower overhead for reading current position (at lack of absolute position). It also does not require interrupts as the encoder is implemented as a ‘silicon/hardware peripheral’ so all you have to do is read a register → very quick sensor update rate. It doesn’t require any managed code to track the position.
Using ABZ with interrupts will be a non-starter at these speeds - your MCU will be totally overwhelmed with that kind of interrupt load.
You can use the PIO stuff in the rp2040 to do interrupt free encoder reading. It’s just as you describe, but programmable. The state machine will count the pulses and update a register, then updates a fifo, you read the fifo and you are good. the state machine can also give an interrupt to the main cpu every whatever many pulses or something. Or you can request a reading and grab the latest position or whatever, very flexible and powerful.
It’s too bad they limited the pio program lenght to 32 instructions, or you could make something that output the pwm precursor to the sine wave automatically, you just tell it the frequency and amplitude. That would be nice and free a lot of cpu resources, oh well. There might be a way to do something like that but it would take some skill.