Troubleshoot modulation / hall effect sensors / STM32 NUCLEO F303RE

That’s not exactly how you would remove the noise from the driver, but this is way off-topic. Yes, I agree, it is a good wire management solution though. When you get frustrated you can always yank on the cables and will keep the board from breaking.

The PMs are the little dimples around the case (in the picture some of the top ones are painted red). You can count them, although a couple are slightly out of the picture.

Sometimes the calibration routine (called from initFOC) disagrees with the number 8, but I’d say 80+% of the time it says the PP number checks.

I can try using 1pp with open velocity and compare with 8.

The signals from the Hall sensors are solid, and I’ve checked the readings. I’ve ran this motor with my own code, a simple 6 step commutation, and with off the shelf controller boards.

I’ve installed the 2 POTs you recommended, and will test on open loop.

Thank you!

Is this a custom or off-the-shelf motor? I know some people do closed/proprietary work, you don’t have to answer that. Could it be that the motor design is “not quite there yet”?

May I recommend that you try with another motor, such as a gimbal motor for which you know for sure it works, as a reference? I’m shooting in the dark here.

That’s good, so there must be something here. Perhaps PID tuning-wise? May be @runger can provide more input.

Could you comment out the commander from the loop and see if anything changes?

This is the line:

// user communication
// command.run();

The commander doesn’t really do anything unless you control the motor via serial.

Let’s see.

Cheers,
Valentine

Posting from another account, because “For the safety of our community” I am not allowed to post again from another 17 hours or so :slight_smile:

No, this is a relatively old motor, has been running on ceiling fans for years. The use of Hall effect sensors is new.

I’ve ordered another motor with Hall effects, also from China, but it won’t get here before the 9th.

I will comment out the commander, and see if that helps, even if it doesn’t I’m not using it anyway.

Yes, I’m using the defaults for all PID and the low pass filter initializations.

Harold will post the results of our open loop tests.

Thank you, Leo

That’s weird. Let me see if we can fix that.

OK please go back posting from @leo. Do not post from Leo2, it’s confusing.

Hey there - if the circles in your diagram represent the permanent magnets, then I count 24 of them, which would mean 12PP…

Dimples, on the other hand, I count 16, for 8PP.

18/16 is actually a good combination, but I’ve never seen an out-runner with fewer rotor magnets than stator teeth - that’s usually a feature of in-runners… still, I must admit I don’t know enough about it to say if its a problem or not.

It wasn’t my choice, I was not allowed to post again today (can’t post more than one image at a time either), I didn’t make the rules :slight_smile: I’ll try posting with the other account, let’s see if that goes through

We tested the open loop with two pots. we did not notice any difference between 1 pole pair vs 8 pole pair in the settings other then i need to command a speed a factor of 8 higher but the power consumption and speed were the same and I can sweep the volts and rpm to get it running smoothly. What we want to know is how we can should go about tuning the PID loop to get this working in closed loop mode

Yes, it’s 18/16, let’s see if I can post this with my original account. Thank you!

I dropped the requirement, let’s see if you can post.

No you did not, I know.

Cheers,
Valentine

Correct, there should not be any difference. That’s the whole test.

That’s good, we are getting closer.

Perhaps @runger can help here?

Cheers,
Valentine

The 24 circles are representing all the valid hall sensor placements (8 for each phase) and the filled ones are the spots were we actually have them placed. On the magnet ring in the previous photo you can see the dimples with alternating blue and red marks marking the north and south polarities of which there are 16 total

Please review this:

And in general, please read everything in the documentation about PID tuning.

Cheers,
Valentine

Here’s my “simple” PID tuning instructions:

  1. first test open loop. This needs no sensor and only uses the PWM driver. If it works well, PWM is working and the other modes have a chance of working :white_check_mark:
  2. next, torque-voltage mode, with appropriately low voltage_limits - this needs a sensor, so if this is not working well while open-loop is working, then there is something wrong with the sensor or the pole count, or some other parameters like KV/Resistance
  3. then, when torque-voltage is working, you can try tuning PID for velocity mode:

Set all parameters P, I, D to 0. Raise P until you get oscillation, then back it off again a little. Then raise I until the velocity tracks the set-point well. D is almost always best left at 0, sometimes tiny values (0.001 or less) can be set, but I generally just get oscillation if I set D to non-zero.

Also of interest is the LPF setting, here the lower the values, the less latency, and you’ll have better control, but of course the velocity should be somewhat smooth so depending on the sensor you have to raise the value a bit sometimes.

Max ramp on the PID can also be lowered if the motion is too jerky or the sudden changes in velocity cause problems with your PSU.

  1. Once velocity mode is working well, angle mode tends to be no problem. You only need to set the P value on the Angle PID, and a value between 2 and 20 generally works well.

  2. After that you can try current control, the current sensing has its own PIDs and LPFs…

1 Like

Hello @runger, @Valentine

I like @runger’s approach of going through tuning steps, so I translated it into the code using #defines.

Step 1 is checked, as @runger noted, we get a nice sinusoidal curve as pictured. Torque/voltage is where we’re failing, and started to ask for help :slight_smile:

Step 2, TUNING_STEP_TORQUE_VOLTAGE, is where we are, once complete we’ll go to TUNING_STEP_VELOCITY_MODE, etc.

@runger: there is something wrong with the sensor or the pole count, or some other parameters like KV/Resistance

We are very sure about pole count, we placed the Hall sensors (according to the pictured diagram), R and KV were calculated based on spinning the motor with a 6 step algorithm and measuring.

Here’s the 3 Hall effect signals, with the weird current shape (before I had uploaded the 3 phases and the current shape):

And, the code:

#include <SimpleFOC.h>

#define POWER_SUPPLY_VOLTS 20
#define HALF_POWER_SUPPLY_VOLTS (POWER_SUPPLY_VOLTS >> 1)
#define IDEAL_DRIVER_VOLTAGE_LIMIT HALF_POWER_SUPPLY_VOLTS
#define OPEN_LOOP_DRIVER_VOLTAGE_LIMIT ((float)POWER_SUPPLY_VOLTS/4.0f)

#define OPEN_LOOP_RPMS 30

#define MOTOR_POLE_PAIRS 8
#define MOTOR_R 1.6
#define MOTOR_KV 20.0
#define MOTOR_VOLTAGE_SENSOR_ALIGN 3

#define TUNING_STEP_OPEN_LOOP 1
#define TUNING_STEP_TORQUE_VOLTAGE 2
#define TUNING_STEP_VELOCITY_MODE 3
#define TUNING_STEP_CURRENT_CONTROL 4

//#define TUNING_STEP TUNING_STEP_OPEN_LOOP
#define TUNING_STEP TUNING_STEP_TORQUE_VOLTAGE
//#define TUNING_STEP TUNING_STEP_VELOCITY_MODE
//#define TUNING_STEP TUNING_STEP_CURRENT_CONTROL

#if TUNING_STEP == TUNING_STEP_OPEN_LOOP

#define DRIVER_VOLTAGE_LIMIT OPEN_LOOP_DRIVER_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity_openloop
//float target_value = OPEN_LOOP_RPMS * _RPM_TO_RADS;
float target_value = -OPEN_LOOP_RPMS * _RPM_TO_RADS;

#elif TUNING_STEP == TUNING_STEP_TORQUE_VOLTAGE

#define DRIVER_VOLTAGE_LIMIT IDEAL_DRIVER_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::torque
float target_value = 10;
//float target_value = -10;

#elif TUNING_STEP == TUNING_STEP_VELOCITY_MODE

#define DRIVER_VOLTAGE_LIMIT IDEAL_DRIVER_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity
float target_value = 5;
//float target_value = -5;

#elif TUNING_STEP == TUNING_STEP_CURRENT_CONTROL

#define DRIVER_VOLTAGE_LIMIT IDEAL_DRIVER_VOLTAGE_LIMIT
//#define TORQUE_CONTROL_TYPE dc_current
#define TORQUE_CONTROL_TYPE foc_current
#define MOTION_CONTROL_TYPE MotionControlType::velocity
float target_value = 1;
//float target_value = -1;

#else

#error Unknown tuning step
#define DRIVER_VOLTAGE_LIMIT IDEAL_DRIVER_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity_openloop
float target_value = 0;

#endif

//#define FOC_MODULATION_TYPE FOCModulationType::Trapezoid_120
//#define FOC_MODULATION_TYPE FOCModulationType::Trapezoid_150
#define FOC_MODULATION_TYPE FOCModulationType::SinePWM
//#define FOC_MODULATION_TYPE FOCModulationType::SpaceVectorPWM

BLDCMotor motor = BLDCMotor(MOTOR_POLE_PAIRS, MOTOR_R, MOTOR_KV);
BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9, PA10, PC10, PC11, PC12);
HallSensor sensor = HallSensor(PA15, PB3, PB10, MOTOR_POLE_PAIRS);

void reportSpeed() {
static int lastMillis;
int newMillis = millis();

if(newMillis > lastMillis + 2000) {
lastMillis = newMillis;
Serial.println(sensor.getVelocity() / _RPM_TO_RADS/** 9.549297*/);
}
}

void doA(){sensor.handleA();} void doB(){sensor.handleB();} void doC(){sensor.handleC();}

void configPIDsLPFs() {
//config below when torque-voltage is working
/*
motor.PID_velocity.P = 0;
motor.PID_velocity.I = 0;
motor.PID_velocity.D = 0;
motor.PID_velocity.output_ramp = 1;
motor.PID_velocity.limit = POWER_SUPPLY_VOLTS;

motor.P_angle.P = 0;
motor.P_angle.I = 0;
motor.P_angle.D = 0;
motor.P_angle.output_ramp = 1;
motor.P_angle.limit = POWER_SUPPLY_VOLTS;

motor.LPF_velocity.Tf = 0;
motor.LPF_angle.Tf = 0;
*/
}

void setup() {
Serial.begin(115200);
motor.useMonitoring(Serial);

sensor.init();
sensor.enableInterrupts(doA, doB, doC);
motor.linkSensor(&sensor);

driver.voltage_power_supply = POWER_SUPPLY_VOLTS;
driver.voltage_limit = DRIVER_VOLTAGE_LIMIT;
driver.init();
motor.linkDriver(&driver);

motor.torque_controller = TORQUE_CONTROL_TYPE;
motor.voltage_sensor_align = MOTOR_VOLTAGE_SENSOR_ALIGN;
motor.foc_modulation = FOC_MODULATION_TYPE;
motor.controller = MOTION_CONTROL_TYPE;

configPIDsLPFs();

motor.init();
motor.initFOC();
motor.target = target_value;

Serial.println(F(“Motor ready.”));
_delay(1000);
}

void loop() {
motor.loopFOC();
motor.move(target_value);
reportSpeed();
};

Hey,

I wasn’t so clear, but its motor.voltage_limit that should be half the driver.voltage_limit to prevent clipping.
This is because the motor.voltage_limit acts on the Q-Axis voltage, which can be positive or negative.
The driver.voltage_limit is a hard limit on the output, and acts on the positive only voltage 0 - PSU voltage.

I don’t think that’s your problem here, but you may want to adjust the code.

Looking at the current waveforms, I really have to ask again about driver OCP - if you look at them, there is a noticeable dip on both the positive and negative flanks of the signal. If you imagine those away, it would look fairly sinusoidal…

Its also telling that when you run it with a 5V limit in open loop the currents are nice, while if you run it at 10V (20V/2) you get the funny waveform.
You say PSU is limited to 3A - but 10V/1.6Ω is 6.25A… so maybe the PSU is dropping out. Can you monitor the PSU voltage on the oscilloscope to see if it stays stable?

Another explanation could be Hall Sensor accuracy - not sure because I never use the Hall Sensors. But at 8PP and 3 Hall sensors placed electrically 120° apart, you would get a CPR of 48 if I’m not wrong… That’s super-low compared to the other sensors normally used with SimpleFOC.
So it may also be a case of loop speed exceeding the Hall Sensor’s measurement speed… can you plot what your position and velocity signals look like, by any chance?

@runger thank you for the suggestions, and yes, it seems to be close to working, if not for the discontinuities, it has too be some setting that we’re missing.

I fixed the voltage assignment to motor.voltage_limit, but, since you mentioned that we got a nice current shape when running with 5V, I tried using constant 5 and 3V values.

Also, in reference to the low CPR, I added counters to increase the interval between calls to motor.loopFOC, and to motor.move. loopFOC is called once every 2000 loop() executions, and .move is called once every 20000.

Amazingly, the motor continues to spin, but the current wave still looks the same.

Harold will plot velocity and position and upload a picture soon.

Updated code below, for reference.

#include <SimpleFOC.h>

#define POWER_SUPPLY_VOLTS 20
#define HALF_POWER_SUPPLY_VOLTS (POWER_SUPPLY_VOLTS >> 1)
//#define IDEAL_MOTOR_VOLTAGE_LIMIT HALF_POWER_SUPPLY_VOLTS
#define IDEAL_MOTOR_VOLTAGE_LIMIT 3
#define OPEN_LOOP_MOTOR_VOLTAGE_LIMIT ((float)POWER_SUPPLY_VOLTS/4.0f)

#define OPEN_LOOP_RPMS 30

#define MOTOR_POLE_PAIRS 8
#define MOTOR_R 1.6
#define MOTOR_KV 20.0
#define MOTOR_VOLTAGE_SENSOR_ALIGN 3

#define TUNING_STEP_OPEN_LOOP 1
#define TUNING_STEP_TORQUE_VOLTAGE 2
#define TUNING_STEP_VELOCITY_MODE 3
#define TUNING_STEP_CURRENT_CONTROL 4

//#define TUNING_STEP TUNING_STEP_OPEN_LOOP
#define TUNING_STEP TUNING_STEP_TORQUE_VOLTAGE
//#define TUNING_STEP TUNING_STEP_VELOCITY_MODE
//#define TUNING_STEP TUNING_STEP_CURRENT_CONTROL

#if TUNING_STEP == TUNING_STEP_OPEN_LOOP

#define MOTOR_VOLTAGE_LIMIT OPEN_LOOP_MOTOR_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity_openloop
//float target_value = OPEN_LOOP_RPMS * _RPM_TO_RADS;
float target_value = -OPEN_LOOP_RPMS * _RPM_TO_RADS;

#elif TUNING_STEP == TUNING_STEP_TORQUE_VOLTAGE

#define MOTOR_VOLTAGE_LIMIT IDEAL_MOTOR_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::torque
float target_value = 10;
//float target_value = -10;

#elif TUNING_STEP == TUNING_STEP_VELOCITY_MODE

#define MOTOR_VOLTAGE_LIMIT IDEAL_MOTOR_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity
float target_value = 5;
//float target_value = -5;

#elif TUNING_STEP == TUNING_STEP_CURRENT_CONTROL

#define MOTOR_VOLTAGE_LIMIT IDEAL_MOTOR_VOLTAGE_LIMIT
//#define TORQUE_CONTROL_TYPE dc_current
#define TORQUE_CONTROL_TYPE foc_current
#define MOTION_CONTROL_TYPE MotionControlType::velocity
float target_value = 1;
//float target_value = -1;

#else

#error Unknown tuning step
#define MOTOR_VOLTAGE_LIMIT IDEAL_MOTOR_VOLTAGE_LIMIT
#define TORQUE_CONTROL_TYPE voltage
#define MOTION_CONTROL_TYPE MotionControlType::velocity_openloop
float target_value = 0;

#endif

//#define FOC_MODULATION_TYPE FOCModulationType::Trapezoid_120
//#define FOC_MODULATION_TYPE FOCModulationType::Trapezoid_150
#define FOC_MODULATION_TYPE FOCModulationType::SinePWM
//#define FOC_MODULATION_TYPE FOCModulationType::SpaceVectorPWM

BLDCMotor motor = BLDCMotor(MOTOR_POLE_PAIRS, MOTOR_R, MOTOR_KV);
BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9, PA10, PC10, PC11, PC12);
HallSensor sensor = HallSensor(PA15, PB3, PB10, MOTOR_POLE_PAIRS);

void reportSpeed() {
static int lastMillis;
int newMillis = millis();

if(newMillis > lastMillis + 2000) {
lastMillis = newMillis;
Serial.println(sensor.getVelocity() / _RPM_TO_RADS/** 9.549297*/);
}
}

void doA(){sensor.handleA();} void doB(){sensor.handleB();} void doC(){sensor.handleC();}

void configPIDsLPFs() {
//config below when torque-voltage is working
/*
motor.PID_velocity.P = 0;
motor.PID_velocity.I = 0;
motor.PID_velocity.D = 0;
motor.PID_velocity.output_ramp = 1;
motor.PID_velocity.limit = POWER_SUPPLY_VOLTS;

motor.P_angle.P = 0;
motor.P_angle.I = 0;
motor.P_angle.D = 0;
motor.P_angle.output_ramp = 1;
motor.P_angle.limit = POWER_SUPPLY_VOLTS;

motor.LPF_velocity.Tf = 0;
motor.LPF_angle.Tf = 0;
*/
}

void setup() {
Serial.begin(115200);
motor.useMonitoring(Serial);

sensor.init();
sensor.enableInterrupts(doA, doB, doC);
motor.voltage_limit = MOTOR_VOLTAGE_LIMIT;
motor.linkSensor(&sensor);

driver.voltage_power_supply = POWER_SUPPLY_VOLTS;
//driver.voltage_limit = MOTOR_VOLTAGE_LIMIT;
driver.init();
motor.linkDriver(&driver);

motor.torque_controller = TORQUE_CONTROL_TYPE;
motor.voltage_sensor_align = MOTOR_VOLTAGE_SENSOR_ALIGN;
motor.foc_modulation = FOC_MODULATION_TYPE;
motor.controller = MOTION_CONTROL_TYPE;

configPIDsLPFs();

motor.init();
motor.initFOC();
motor.target = target_value;

Serial.println(F(“Motor ready.”));
_delay(1000);
}

void loop() {
const int loopMaxCounter = 2000;
static int loopCounter;
if(++loopCounter >= loopMaxCounter) {
loopCounter = 0;
motor.loopFOC();
}
const int moveMaxCounter = loopMaxCounter * 10;
static int moveCounter;
if(++moveCounter == moveMaxCounter) {
moveCounter = 0;
motor.move(target_value);
}
reportSpeed();
};

Here is the plot showing the Current, velocity and shaft position.

I used the following lines to normalize them and output to the scope:

analogWrite(PA4, -(int16_t)((motor.sensor->getAngle() - motor.sensor->getFullRotations()*TWO_PI) * 4095 / TWO_PI));
analogWrite(PA5, -(int16_t)(motor.sensor->getVelocity()*100));

Good morning!

Below is a plot of the PWM parameters dc_a, dc_b, and dc_c calculated by BLDCDriver3PWM::setPwm from Ua, Ub, and Uc, and passed to _writeDutyCycle3PWM. dc_b and dc_c look pretty normal and dc_a displays highlights that somewhat resemble the weird current shape. Would this further indicate that the current shape is not caused by OCP, but by the calculated PWM dc_a?

runger: not sure because I never use the Hall Sensors

Do you know what motor with Hall sensors was used to test HallSensor.cpp/h?

Also, if it helps, we can send you one of our motors, with Hall sensors installed, for you to experiment with.

Thank you!

How are these plots being generated?

Can you trace the PWM output pins directly?

None of them really look right to me, normally the PWM output should be 2-state, varying between VDD and GND voltages, and not 3-state like the b & c traces.
The a phase looks totally wrong to me. They should all have a square waveform, center-aligned, but with different pulse-widths.

Can you disconnect the motor, run just the BLDCDriver class, and call driver.setPwm(0.25, 0.5, 0.75)? And see what the output on the PWM pins looks like?