Skip to content

Commit 1533bf2

Browse files
committed
Merge branch 'sensor_fixes' of git://github.com/runger1101001/Arduino-FOC into runger1101001-sensor_fixes
2 parents e33782a + 9114ecd commit 1533bf2

18 files changed

+308
-286
lines changed

src/BLDCMotor.cpp

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ int BLDCMotor::initFOC( float zero_electric_offset, Direction _sensor_direction
101101
if(sensor){
102102
exit_flag *= alignSensor();
103103
// added the shaft_angle update
104+
sensor->updateSensor();
104105
shaft_angle = sensor->getAngle();
105106
}else if(monitor_port) monitor_port->println(F("MOT: No sensor."));
106107

@@ -164,13 +165,15 @@ int BLDCMotor::alignSensor() {
164165
_delay(2);
165166
}
166167
// take and angle in the middle
168+
sensor->updateSensor();
167169
float mid_angle = sensor->getAngle();
168170
// move one electrical revolution backwards
169171
for (int i = 500; i >=0; i-- ) {
170172
float angle = _3PI_2 + _2PI * i / 500.0f ;
171173
setPhaseVoltage(voltage_sensor_align, 0, angle);
172174
_delay(2);
173175
}
176+
sensor->updateSensor();
174177
float end_angle = sensor->getAngle();
175178
setPhaseVoltage(0, 0, 0);
176179
_delay(200);
@@ -201,6 +204,7 @@ int BLDCMotor::alignSensor() {
201204
// set angle -90(270 = 3PI/2) degrees
202205
setPhaseVoltage(voltage_sensor_align, 0, _3PI_2);
203206
_delay(700);
207+
sensor->updateSensor();
204208
zero_electric_angle = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
205209
_delay(20);
206210
if(monitor_port){
@@ -217,7 +221,8 @@ int BLDCMotor::alignSensor() {
217221
// Encoder alignment the absolute zero angle
218222
// - to the index
219223
int BLDCMotor::absoluteZeroSearch() {
220-
224+
// sensor precision: this is all ok, as the search happens near the 0-angle, where the precision
225+
// of float is sufficient.
221226
if(monitor_port) monitor_port->println(F("MOT: Index search..."));
222227
// search the absolute zero with small velocity
223228
float limit_vel = velocity_limit;
@@ -229,7 +234,7 @@ int BLDCMotor::absoluteZeroSearch() {
229234
angleOpenloop(1.5f*_2PI);
230235
// call important for some sensors not to loose count
231236
// not needed for the search
232-
sensor->getAngle();
237+
sensor->updateSensor();
233238
}
234239
// disable motor
235240
setPhaseVoltage(0, 0, 0);
@@ -247,15 +252,25 @@ int BLDCMotor::absoluteZeroSearch() {
247252
// Iterative function looping FOC algorithm, setting Uq on the Motor
248253
// The faster it can be run the better
249254
void BLDCMotor::loopFOC() {
255+
// update sensor - do this even in open-loop mode, as user may be switching between modes and we could lose track
256+
// of full rotations otherwise.
257+
if (sensor) sensor->updateSensor();
258+
250259
// if open-loop do nothing
251260
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop ) return;
261+
252262
// shaft angle
263+
// TODO sensor precision: the shaft_angle actually stores the complete position, including full rotations, as a float
264+
// For this reason it is NOT precise when the angles become large.
265+
// Additionally, the way LPF works on angle is a precision issue, and the angle-LPF is a problem
266+
// when switching to a 2-component representation.
253267
shaft_angle = shaftAngle(); // read value even if motor is disabled to keep the monitoring updated
254268

255269
// if disabled do nothing
256270
if(!enabled) return;
257271

258272
// electrical angle - need shaftAngle to be called first
273+
// TODO sensor precision: this will have precision issues because the shaft_angle does...
259274
electrical_angle = electricalAngle();
260275

261276
switch (torque_controller) {
@@ -300,14 +315,14 @@ void BLDCMotor::loopFOC() {
300315
// - if target is not set it uses motor.target value
301316
void BLDCMotor::move(float new_target) {
302317

303-
// get angular velocity
318+
// downsampling (optional)
319+
if(motion_cnt++ < motion_downsample) return;
320+
motion_cnt = 0;
321+
// get angular velocity - sensor precision: this value is numerically precise. It is filtered by Velocity LPF, and downsamling (depending on sensor)
304322
shaft_velocity = shaftVelocity(); // read value even if motor is disabled to keep the monitoring updated
305323

306324
// if disabled do nothing
307325
if(!enabled) return;
308-
// downsampling (optional)
309-
if(motion_cnt++ < motion_downsample) return;
310-
motion_cnt = 0;
311326
// set internal target variable
312327
if(_isset(new_target)) target = new_target;
313328

@@ -322,11 +337,14 @@ void BLDCMotor::move(float new_target) {
322337
}
323338
break;
324339
case MotionControlType::angle:
340+
// TODO sensor precision: this calculation is not numerically precise. The target value cannot express precise positions when
341+
// the angles are large. This results in not being able to command small changes at high position values.
342+
// to solve this, the delta-angle has to be calculated in a numerically precise way.
325343
// angle set point
326344
shaft_angle_sp = target;
327345
// calculate velocity set point
328346
shaft_velocity_sp = P_angle( shaft_angle_sp - shaft_angle );
329-
// calculate the torque command
347+
// calculate the torque command - sensor precision: this calculation is ok, but based on bad value from previous calculation
330348
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
331349
// if torque controlled through voltage
332350
if(torque_controller == TorqueControlType::voltage){
@@ -337,7 +355,7 @@ void BLDCMotor::move(float new_target) {
337355
}
338356
break;
339357
case MotionControlType::velocity:
340-
// velocity set point
358+
// velocity set point - sensor precision: this calculation is numerically precise.
341359
shaft_velocity_sp = target;
342360
// calculate the torque command
343361
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if current/foc_current torque control
@@ -350,13 +368,16 @@ void BLDCMotor::move(float new_target) {
350368
}
351369
break;
352370
case MotionControlType::velocity_openloop:
353-
// velocity control in open loop
371+
// velocity control in open loop - sensor precision: this calculation is numerically precise.
354372
shaft_velocity_sp = target;
355373
voltage.q = velocityOpenloop(shaft_velocity_sp); // returns the voltage that is set to the motor
356374
voltage.d = 0;
357375
break;
358376
case MotionControlType::angle_openloop:
359-
// angle control in open loop
377+
// angle control in open loop -
378+
// TODO sensor precision: this calculation NOT numerically precise, and subject
379+
// to the same problems in small set-point changes at high angles
380+
// as the closed loop version.
360381
shaft_angle_sp = target;
361382
voltage.q = angleOpenloop(shaft_angle_sp); // returns the voltage that is set to the motor
362383
voltage.d = 0;
@@ -574,6 +595,8 @@ float BLDCMotor::velocityOpenloop(float target_velocity){
574595
// quick fix for strange cases (micros overflow + timestamp not defined)
575596
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
576597

598+
// sensor precision: this calculation is numerically precise since we re-normalize
599+
// the shaft-angle to the range 0-2PI
577600
// calculate the necessary angle to achieve target velocity
578601
shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts);
579602
// for display purposes
@@ -583,6 +606,8 @@ float BLDCMotor::velocityOpenloop(float target_velocity){
583606
float Uq = voltage_limit;
584607
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
585608

609+
// sensor precision: this calculation is numerically precise, since shaft_angle is
610+
// in the range 0-2PI
586611
// set the maximal allowed voltage (voltage_limit) with the necessary angle
587612
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs));
588613

@@ -605,6 +630,9 @@ float BLDCMotor::angleOpenloop(float target_angle){
605630

606631
// calculate the necessary angle to move from current position towards target angle
607632
// with maximal velocity (velocity_limit)
633+
// TODO sensor precision: this calculation is not numerically precise. The angle can grow to the point
634+
// where small position changes are no longer captured by the precision of floats
635+
// when the total position is large.
608636
if(abs( target_angle - shaft_angle ) > abs(velocity_limit*Ts)){
609637
shaft_angle += _sign(target_angle - shaft_angle) * abs( velocity_limit )*Ts;
610638
shaft_velocity = velocity_limit;
@@ -618,6 +646,10 @@ float BLDCMotor::angleOpenloop(float target_angle){
618646
float Uq = voltage_limit;
619647
if(_isset(phase_resistance)) Uq = current_limit*phase_resistance;
620648
// set the maximal allowed voltage (voltage_limit) with the necessary angle
649+
// TODO sensor precision: this calculation is not numerically precise. The angle is not constrained,
650+
// and can grow large. It first gets multiplied by the number of pole-pairs (in _electrocalAngle()),
651+
// and then gets normalized to 0-2PI (before it gets used in setPhaseVoltage()). At large angles,
652+
// the precision can mean the range 0-2PI is not well represented at the end of that calculation.
621653
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs));
622654

623655
// save timestamp for next call

src/common/base_classes/Sensor.cpp

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,71 @@
22
#include "../foc_utils.h"
33
#include "../time_utils.h"
44

5-
/**
6-
* returns 0 if it does need search for absolute zero
7-
* 0 - magnetic sensor (& encoder with index which is found)
8-
* 1 - ecoder with index (with index not found yet)
9-
*/
10-
int Sensor::needsSearch(){
11-
return 0;
5+
// TODO add an init method to make the startup smoother by initializing internal variables to current values rather than 0
6+
7+
void Sensor::updateSensor() {
8+
float val = getSensorAngle();
9+
angle_prev_ts = _micros();
10+
float d_angle = val - angle_prev;
11+
// if overflow happened track it as full rotation
12+
if(abs(d_angle) > (0.8f*_2PI) ) full_rotations += ( d_angle > 0 ) ? -1 : 1;
13+
angle_prev = val;
1214
}
1315

14-
/** get current angular velocity (rad/s)*/
15-
float Sensor::getVelocity(){
1616

17+
/** get current angular velocity (rad/s)*/
18+
float Sensor::getVelocity() {
1719
// calculate sample time
18-
unsigned long now_us = _micros();
19-
float Ts = (now_us - velocity_calc_timestamp)*1e-6f;
20+
float Ts = (angle_prev_ts - vel_angle_prev_ts)*1e-6;
2021
// quick fix for strange cases (micros overflow)
2122
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
22-
23-
// current angle
24-
float angle_c = getAngle();
2523
// velocity calculation
26-
float vel = (angle_c - angle_prev)/Ts;
27-
24+
float vel = ( (float)(full_rotations - vel_full_rotations)*_2PI + (angle_prev - vel_angle_prev) ) / Ts;
2825
// save variables for future pass
29-
angle_prev = angle_c;
30-
velocity_calc_timestamp = now_us;
26+
vel_angle_prev = angle_prev;
27+
vel_full_rotations = full_rotations;
28+
vel_angle_prev_ts = angle_prev_ts;
3129
return vel;
32-
}
30+
}
31+
32+
void Sensor::init() {
33+
// initialize all the internal variables of Sensor to ensure a "smooth" startup (without a 'jump' from zero)
34+
getSensorAngle(); // call once
35+
delayMicroseconds(1);
36+
vel_angle_prev = getSensorAngle(); // call again
37+
vel_angle_prev_ts = _micros();
38+
delay(1);
39+
getSensorAngle(); // call once
40+
delayMicroseconds(1);
41+
angle_prev = getSensorAngle(); // call again
42+
angle_prev_ts = _micros();
43+
}
44+
45+
46+
float Sensor::getShaftAngle() {
47+
return angle_prev;
48+
}
49+
50+
51+
52+
float Sensor::getAngle(){
53+
return (float)full_rotations * _2PI + angle_prev;
54+
}
55+
56+
57+
58+
double Sensor::getPreciseAngle() {
59+
return (double)full_rotations * (double)_2PI + (double)angle_prev;
60+
}
61+
62+
63+
64+
int32_t Sensor::getFullRotations() {
65+
return full_rotations;
66+
}
67+
68+
69+
70+
int Sensor::needsSearch() {
71+
return 0; // default false
72+
}

0 commit comments

Comments
 (0)