I have simplefoc running and am getting up to 120 rad/s. I have been reading and reading the forums and cannot determine if my limit is because of my sensor or my motor.voltage_limit.
I have set the voltage_limit to 1. The motor starts to overheat past that. I have also tried to set the phase_resistance and a current_limit. Of course, I have read that as5600 with i2c is not even any good past 200 rads/s. I do want more, but since I am maxing out below that anyway, I had assumed my limit is not the sensor. But I do not understand if current performance improves when the loop is faster (e.g. using a as5047u with abi and the STM32HWEncoder).
For my application I do not care about position, but I would like velocity accuracy up to 400 rads/s (accuracy at all speeds, but especially at higher speeds). I also do not need so much torque. I can sacrifice torque for speed (but they seem to be connected).
The project is for a small, direct drive air raid siren.
Thanks in advance! Happy to paste my code if that is helpful
Alternatively you could try running MagneticSensorI2C through SmoothingSensor (which is also in the Arduino-FOC-drivers repository) to compensate for the delay, although it will require a small edit in Sensor.cpp. Line 11 angle_prev_ts = _micros(); in Sensor::update() needs to be moved above line 8 float val = getSensorAngle(); so the timestamp is set before the communication delay happens.
SmoothingSensor is super easy to use. Just create it like SmoothingSensor smooth(sensor, motor); and then in setup() do motor.linkSensor(&smooth); instead of the usual motor.linkSensor(&sensor);
Thanks! I just tried the smoothing sensor and got a bit more speed but not much. I paste my code below. A couple of questions.
Its a bit late tonight for trying the sensorless flux observer, but how accurate is it with speed? That is, can I trust it? I was getting higher speeds with a no-frills sensorless mode but I have not double checked it against anything.
Is there anyway to estimate what speed I would get with AS5047u with ABI using STM32HWEncoder?
Can you kindly explain why a faster loop improves current performance and allows for higher speeds?
I actually had a somewhat tunable system using one hall sensor and a PID controller running on a nano through a cheap Simonk esc. It goes fast, but not low. SimpleFOC goes slow but not fast. Trying to cover both.
Here is the code. I am actually currently testing on a different low resistance high kv motor just in case I burn something out.
/**
* B-G431B-ESC1 position motion control example with encoder
*
*/
#include <SimpleFOC.h>
#include <SimpleFOCDrivers.h>
#include <encoders/smoothing/SmoothingSensor.h>
// Motor instance
BLDCMotor motor = BLDCMotor(7, 0.1, 2000);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
// Gain calculation shown at https://community.simplefoc.com/t/b-g431b-esc1-current-control/521/21
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f/7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
// encoder instance
MagneticSensorI2C sensor = MagneticSensorI2C(AS5600_I2C);
SmoothingSensor smooth = SmoothingSensor(sensor, motor);
// velocity set point variable
float target = 0;
// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&target, cmd); }
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// initialize encoder sensor hardware
sensor.init();
Wire.setClock(400000);
// link the motor to the sensor
motor.linkSensor(&smooth);
// driver config
// power supply voltage [V]
driver.voltage_power_supply = 12;
driver.init();
// link the motor and the driver
motor.linkDriver(&driver);
// link current sense and the driver
currentSense.linkDriver(&driver);
// current sensing
currentSense.init();
// no need for aligning
currentSense.skip_align = true;
motor.linkCurrentSense(¤tSense);
// aligning voltage [V]
motor.voltage_sensor_align = 3;
// index search velocity [rad/s]
motor.velocity_index_search = 3;
// set motion control loop to be used
motor.controller = MotionControlType::velocity;
motor.foc_modulation = FOCModulationType::SpaceVectorPWM;
// contoller configuration
// default parameters in defaults.h
// velocity PI controller parameters
motor.PID_velocity.P = 0;
motor.PID_velocity.I = 0;
// default voltage_power_supply
//motor.voltage_limit = 6;
//motor.voltage_limit = 0.75; // amps
//motor.PID_velocity.limit = motor.voltage_limit;
driver.voltage_limit = 2;
motor.voltage_limit = 1;
//motor.current_limit = 0.01; // amps
motor.PID_velocity.limit = motor.voltage_limit;
// jerk control using voltage voltage ramp
// default value is 300 volts per sec ~ 0.3V per millisecond
motor.PID_velocity.output_ramp = 1000;
// velocity low pass filtering time constant
motor.LPF_velocity.Tf = 0.01;
// comment out if not needed
motor.useMonitoring(Serial);
motor.monitor_downsample = 1000;
motor.motion_downsample = 4;
// initialize motor
motor.init();
// align encoder and start FOC
motor.initFOC();
// add target command T
command.add('T', doTarget, "target velocity");
Serial.println(F("Motor ready."));
Serial.println(F("Set the target velocity using serial terminal:"));
_delay(1000);
}
void loop() {
// main FOC algorithm function
motor.loopFOC();
motor.PID_velocity.P = target;
//Serial.println(target);
// Motion control function
motor.move(200);
// function intended to be used with serial plotter to monitor motor variables
// significantly slowing the execution down!!!!
motor.monitor();
// user communication
command.run();
}
I also thought it might be helpful to show my sensorless code. I am getting past 750 rad/s (supposedly). I am really confused how the phase resistance, kv argument, motor.current_limit, and driver.current_limit. I am noticing that I can up the latter 2 and so long as I have the LPF on the velocity control to ramp up and down there does not seem to be an overheating/overcurrent problem. but I really have no idea how accurate this is.
// Open loop motor control example
#include <SimpleFOC.h>
BLDCMotor motor = BLDCMotor(7, 0.1, 2000);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
// Gain calculation shown at https://community.simplefoc.com/t/b-g431b-esc1-current-control/521/21
// Stepper motor & driver instance
//StepperMotor motor = StepperMotor(50);
//StepperDriver4PWM driver = StepperDriver4PWM(9, 5, 10, 6, 8);
//target variable
float target_velocity = 0;
LowPassFilter targetFilter = LowPassFilter(2.0f);
// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&target_velocity, cmd); }
void doLimit(char* cmd) { command.scalar(&motor.voltage_limit, cmd); }
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// driver config
// power supply voltage [V]
driver.voltage_power_supply = 12;
// limit the maximal dc voltage the driver can set
// as a protection measure for the low-resistance motors
// this value is fixed on startup
driver.voltage_limit = 4;
if(!driver.init()){
Serial.println("Driver init failed!");
return;
}
// link the motor and the driver
motor.linkDriver(&driver);
// limiting motor movements
// limit the voltage to be set to the motor
// start very low for high resistance motors
// current = voltage / resistance, so try to be well under 1Amp
motor.voltage_limit = 2; // [V]
motor.motion_downsample = 4;
motor.monitor_downsample = 100;
// open loop control config
motor.controller = MotionControlType::velocity_openloop;
motor.foc_modulation = FOCModulationType::SpaceVectorPWM;
// init motor hardware
if(!motor.init()){
Serial.println("Motor init failed!");
return;
}
// add target command T
command.add('T', doTarget, "target velocity");
command.add('L', doLimit, "voltage limit");
Serial.println("Motor ready!");
Serial.println("Set target velocity [rad/s]");
_delay(1000);
}
void loop() {
// open loop velocity movement
// using motor.voltage_limit and motor.velocity_limit
// to turn the motor "backwards", just set a negative target_velocity
//LowPassFilter LPF_target(0.1); // the higher the longer new values need to take effect
motor.move(targetFilter(target_velocity));
//motor.move(target_velocity);
// user communication
command.run();
//delayMicroseconds(100);
}
At high speed, the amount of rotor movement between updates is relatively large, so it catches up to the stator field and the magnetic force becomes radial rather than tangential (trying to expand/contract the rotor rather than spinning it). The faster you can update, the closer you can keep the magnetic force to being purely tangential at all times.
With slow sensor communication, you have the additional problem that the measured rotor angle is already outdated by the time you get the PWM outputs updated. That’s where SmoothingSensor can help, by predicting how much the rotor has moved between the time of measuring the angle and when you actually calculate the PWM duty from it.
Open loop’s speed is as accurate as you can get. The waveform is generated from the CPU clock, so barring any variation in that, it will always be spot on. The main problem is that failures are semi-catastrophic. If the rotor fails to keep up with the electromagnetic field, it generally can’t re-sync until you stop and ramp back up to speed again. And efficiency is poor because you need excess current to reduce the chance of desync.
You could potentially use your I2C sensor to monitor whether the measured angle is “close enough” to the open loop angle, and if not then intervene and correct the open loop angle to prevent /recover from a desync.
As with a position sensor, it depends on how well tuned the velocity PID is. You won’t be able to get it as perfect as open loop, but probably good enough.
Many thanks! I really appreciate all the help and am starting to understand this all much better.
I was noticing a complete stop assuming I was reaching the voltage limit I imposed. But it also did not seem to literally burn anything out. So by semi-catastrophic, I assume that while you have to restart you do not necessarily literally explode the system when there is a complete stall? Would this also be the case with the flux observer?
I actually meant here open loop not sensorless. So the question remains whether or not catastrophes can be avoided with the flux observer while still maintaining accuracy.
Any clarification on this would be helpful. In closed loop, any motor.voltage_limit over 1 was clearly getting too hot. I thought limiting the phase resistance throttled me but that might change with a faster sensor. Either way, with open loop, I have now been able to bump motor.voltage_limit to 2. The higher I go, the higher the max velocity seems to be until I reach a limit. No overheating as far as I can tell and no failures so long as keep a ramp on velocity changes. I am curious why this is and how I can go so long as I have the phase resistance and kv specified.
AFAIK simonk firmware is based on BEMF measurement, which is another way to drive commutation. We call that “sensorless” because there is no position sensor, but you do need phase voltage sensing for it.
It starts in open loop mode, and when the motor is at speed it can use BEMF sensing to drive the commutation.
The flux observer is another kind of “sensorless”, but this time based on current sensing.
In terms of catastrophic failure, it depends on your use case, and also the motor. It sounds like your setup doesn’t fail catastrophically even when stalled or when it loses position, and that’s because the motor currents aren’t rising so quickly or high that things burn up before you have a chance to turn them off.
But with a different motor that could be quite different…
You can’t limit the phase resistance, it’s a physical property of the motor. You can set it to different values (also ones diverging from reality) or leave it unset. If you set it, then it affects the control algorithm and the way we calculate things.
The higher the voltage limit, the higher your max speed will be, until you reach the motors KV limit, where the backEMF current equals the drive current and the motor doesn’t accelerate any more.
It sounds to me like you need a better sensor. I2C sensors aren’t suitable for reaching higher speeds…
It is odd that you’re getting better efficiency in open loop than closed loop. Usually it’s the other way around.
The I2C sensor is what’s saving you from catastrophe Even in open loop mode, it’s giving a true velocity measurement which is used in the voltage-based current limiting calculation. So when the motor stalls, the sensor sees that the speed dropped and the applied voltage is reduced accordingly. If you were running without the sensor, it would continue applying high voltage according the open loop speed, and without the back EMF of the rotor actually spinning, it would quickly burn.
But even with a sensor, voltage-based current limiting is not totally reliable. I’ve burnt a couple drivers when motors started oscillating rapidly from bad PID tuning.
B-G431B-ESC1 has hardware current sense, so it would be best to use that, but I don’t think we have any code to use it in open loop mode. Might be something we should add to the library.
Flux observer does use hardware current sense, so it should be reliably non-explosive.
I am now seeing that efficiency is a problem. I rather quickly overdischarged a 3s lipo. When testing open loop, I actually did not have the sensor hooked up at all. I guess I should and will see if I get the same speed. I would like to try the flux observer but do not have a way at the moment to measure inductance. All I have is a multimeter. How efficient is the flux observer?
If you link the sensor in open loop mode, and call loopFOC() to make sure it’s updated then it would use the sensor velocity. But then you’d also be waiting on the I2C read times each iteration which would again kill the performance…
And the voltage is only increased by including the BEMF (subject to the limits), so I’m not sure I see it as a protection function. If you use only the motor resistance and not the KV to get the estimated current control and current limit, but no BEMF voltage correction, the voltage won’t rise as high.
When I take out the kv rating, I cannot get very far. But I dont have current sensing linked. And when I link it, I do not get any readings. So now I am wondering if my performance in the closed loop mode might have been effected by 0 values for currents. I wish I understood this all better. I am getting good performance out of both the 920 and 2300 kv motors. The 2300 kv motor gets faster but I assume with less torque. I have not tried this under load yet.
Are there any safe values I can try the flux observer with or do they have to be very accurate?
Any idea why I am not getting current readings? I have a build_opt.h file with -DHAL_OPAMP_MODULE_ENABLED
Inductance is too low to measure anyway. You have to hunt for it, hence the tediousness of tuning. Resistance and kv should be close to the listed values, although my motor (Racerstar BR4114 400kv) actually needed 420 for the kv, and I had to hunt for resistance since it’s not listed in the specs and was lower than my meters can measure accurately (0.04f). Inductance turned out to be 0.000025f. If I remember correctly, too high will make a horrible squeal noise and too low will do nothing, so you can get in the ballpark pretty quickly. But then it takes a while to narrow it down, especially since you’re not sure the resistance and kv are right either.
And don’t forget what I said before about moving the flux_linkage calculation from the constructor to the update function for tuning, so kv changes actually take effect.
As long as the motor voltage limit is kept low during tuning, there should be no danger of burning anything.
Not sure what to try for the current sense. I just copied the example code and it worked straight away, no build_opt.h needed. I’m using Arduino IDE 1.8.13 and STM32 boards 2.3.0. There is a board definition specifically for B-G431B-ESC1, so make sure you’re not using generic G431.
That is all very helpful thanks. Assuming it requires current sensing, I will try it once I get the current sensing figured out properly. Seems to be running, just 0 values all the way.
In open loop mode, the efficiency is about 2x that of a cheapo simonk esc. But from an external power meter, it looks like I am never drawing more than 0.5 amps of current. I wonder if, at that point, I am safe to use a 12v 10+amp ac dc convertor. That way, efficiency and runtime become less of an issue so long as the motor keeps up.
I am also a bit confused about the phase resistance. For my 920kv motor, my multimeter seems to hover around 3 Ohms but that seems way too high. I have set the phase resistance between 0.1 and 0.2 in the code rather arbitrarily. Anything lower does not provide enough torque; higher values start to make things hot. Also only works if I additionally provide a kv rating. Assuming that might be because of the lack of current sensing.
Some multimeters are better than others, but usually they aren’t great at measuring very low resistances. If you have a lab PSU you can set it to a low current like 0.05A, pass it through the motor using two phases, and see what voltage you get, and use that to calculate the phase to phase resistance…
When running without real current sensing, the current is estimated based on V=IR, so increasing the resistance is the same thing as lowering your current limit. When you increase the resistance without lowering the current limit, the voltage applied to the motor is higher, so it gets hot faster. When you set the resistance too low without increasing the current limit, the resulting voltage is too low and the motor doesn’t move.
In your case I would prefer to set a more realistic resistance value and instead lower the current limit, which would more accurately reflect reality. But in the end it doesn’t matter.
If you use real current sensing in foc-current (closed loop) mode then things will be different, but I’d get a better sensor for that, like at AS5047 you mentioned.
Thanks : ) I will surely be ordering the AS5047. The question is whether or not to try the flux observer. I have the current sensing properly linked now.
Does this still apply? Right now I have not set any limits except for motor and driver.voltage_limits.
Oh… and by more realistic, I assume you mean higher?
Only you can decide that others have had some very nice results with it - I think it depends a bit on your use case, but for your application it sounds like it could work?
MCUViewer is a handy tool you can use with the B-G431 to visualize what’s going on.
Not sure exactly but I think in this case you get a default current limit based on the voltage limit.
Yes, the current limit will be used if you set the resistance.
Yeah, higher, so the value set matches the 3ohm reality…
it’s really not important but the way my brain works I like the values to be “correct”. I think it helps me to understand better and avoid errors if the units and their values match reality and I don’t have to make my brain do an extra level of translation. But as mentioned it’s not really important.
I have gone ahead and ordered some AS5047P sensors. But ultimately, I am aiming for two things:
variable speed up to 500+ rad/s with absolute accuracy
no catastrophes!
position is of little to no importance
I have not tried it. Will do!
I was confused about this because here Velocity Open-Loop | Arduino-FOC it says “For drone motors, the voltage limits should stay very low, under 1 volt. Because they have phase resistance of 0.05 to 0.2 Ohms”. So I was not sure if I was not getting proper readings. In open loop, so long as I have the phase_resistance and the current_limit set, I can go far above 1 volt. The same settings in closed loop seem to act totally different, but then again that still might be a sensor issue. But even at lower speeds, I seem to hit non-sensor related limits.
On another note, I have noticed in open loop that the play between phase_resistance and current_limit seems to determine at which speed the system is maximally efficient.
Agreed! But as mentioned, I was unsure if my measurements are correct!
I have been reading and rereading our thread to make sure I understand everything. Given that in my application (the tuning of a siren to an EXACT frequency), I am wondering if implementing something like @dekutree64 has described here in open loop is the best idea once I have the AS5047P sensor. I guess two questions arise:
If I can get the velocity accuracy at high speeds that I am going for with a closed-loop system (be it with the sensor of the observer), that seems safer.
However, if the efficiency of open loop is not that much better and I can make the open loop a bit more failsafe using the method described by @dekutree64, then that seems more ideal for my specific use case. I guess for these drone motors that I can target a speed for reasonably good efficiency even in open loop.
Yeah, I think it’s worth a shot to do “open loop with recovery”. Especially if you add some code to use the hardware current sense for limiting (I have no idea why your mosfets didn’t combust when the motor stalled at high voltage in open loop without the sensor linked). It should be almost the same as the dc_current case in loopFOC. In fact, I think you could make it work simply by changing the setPhaseVoltage line in velocityOpenloop to electrocal_angle = _electricalAngle(shaft_angle, pole_pairs);, and commenting out the if(controller==velocity_openloop) early return in loopFOC so it continues on to the dc_current code and setPhaseVoltage. Do not link the sensor to the motor, or the open loop angle will be ovewritten in the call to electricalAngle in loopFOC.
But do still try the flux observer. Even if the speed isn’t precise enough to use directly, you could probably use it for the open loop recovery to eliminate the need for a position sensor.
Sounds like an interesting approach. And would be cool to have this without a position sensor. I guess my mosfets did not combust because I had both phase_resistance and current_limit set.
Your proposed solution sounds terrifyingly simple. But if I understand correctly, then setPhaseVoltage will be handled by loopFOC. But would I also need to comment out electrical_angle = electricalAngle(); in line 350 in loopFoc?
Any further clues on how to approach this also welcome ; ) I guess this would be for a catastrophe like the siren impeller jamming against the stator? Like a kill switch?