@@ -92,6 +92,227 @@ void FOCMotor::useMonitoring(Print &print){
9292 #endif
9393}
9494
95+ // Measure resistance and inductance of a motor
96+ int FOCMotor::characteriseMotor (float voltage, float correction_factor=1 .0f ){
97+ if (!this ->current_sense || !this ->current_sense ->initialized )
98+ {
99+ SIMPLEFOC_DEBUG (" ERR: MOT: Cannot characterise motor: CS unconfigured or not initialized" );
100+ return 1 ;
101+ }
102+
103+ if (voltage <= 0 .0f ){
104+ SIMPLEFOC_DEBUG (" ERR: MOT: Cannot characterise motor: Voltage is negative or less than zero" );
105+ return 2 ;
106+ }
107+ voltage = _constrain (voltage, 0 .0f , voltage_limit);
108+
109+ SIMPLEFOC_DEBUG (" MOT: Measuring phase to phase resistance, keep motor still..." );
110+
111+ float current_electric_angle = electricalAngle ();
112+
113+ // Apply zero volts and measure a zero reference
114+ setPhaseVoltage (0 , 0 , current_electric_angle);
115+ _delay (500 );
116+
117+ PhaseCurrent_s zerocurrent_raw = current_sense->readAverageCurrents ();
118+ DQCurrent_s zerocurrent = current_sense->getDQCurrents (current_sense->getABCurrents (zerocurrent_raw), current_electric_angle);
119+
120+
121+ // Ramp and hold the voltage to measure resistance
122+ // 300 ms of ramping
123+ current_electric_angle = electricalAngle ();
124+ for (int i=0 ; i < 100 ; i++){
125+ setPhaseVoltage (0 , voltage/100.0 *((float )i), current_electric_angle);
126+ _delay (3 );
127+ }
128+ _delay (10 );
129+ PhaseCurrent_s r_currents_raw = current_sense->readAverageCurrents ();
130+ DQCurrent_s r_currents = current_sense->getDQCurrents (current_sense->getABCurrents (r_currents_raw), current_electric_angle);
131+
132+ // Zero again
133+ setPhaseVoltage (0 , 0 , current_electric_angle);
134+
135+
136+ if (fabsf (r_currents.d - zerocurrent.d ) < 0 .2f )
137+ {
138+ SIMPLEFOC_DEBUG (" ERR: MOT: Motor characterisation failed: measured current too low" );
139+ return 3 ;
140+ }
141+
142+ float resistance = voltage / (correction_factor * (r_currents.d - zerocurrent.d ));
143+ if (resistance <= 0 .0f )
144+ {
145+ SIMPLEFOC_DEBUG (" ERR: MOT: Motor characterisation failed: Calculated resistance <= 0" );
146+ return 4 ;
147+ }
148+
149+ SIMPLEFOC_DEBUG (" MOT: Estimated phase to phase resistance: " , 2 .0f * resistance);
150+ _delay (100 );
151+
152+
153+ // Start inductance measurement
154+ SIMPLEFOC_DEBUG (" MOT: Measuring inductance, keep motor still..." );
155+
156+ unsigned long t0 = 0 ;
157+ unsigned long t1 = 0 ;
158+ float Ltemp = 0 ;
159+ float Ld = 0 ;
160+ float Lq = 0 ;
161+ float d_electrical_angle = 0 ;
162+
163+ uint iterations = 40 ; // how often the algorithm gets repeated.
164+ uint cycles = 3 ; // averaged measurements for each iteration
165+ uint risetime_us = 200 ; // initially short for worst case scenario with low inductance
166+ uint settle_us = 100000 ; // initially long for worst case scenario with high inductance
167+
168+ // Pre-rotate the angle to the q-axis (only useful with sensor, else no harm in doing it)
169+ current_electric_angle += 0 .5f * _PI;
170+ current_electric_angle = _normalizeAngle (current_electric_angle);
171+
172+ for (size_t axis = 0 ; axis < 2 ; axis++)
173+ {
174+ for (size_t i = 0 ; i < iterations; i++)
175+ {
176+ // current_electric_angle = i * _2PI / iterations; // <-- Do a sweep of the inductance. Use eg. for graphing
177+ float inductanced = 0 .0f ;
178+
179+ float qcurrent = 0 .0f ;
180+ float dcurrent = 0 .0f ;
181+ for (size_t j = 0 ; j < cycles; j++)
182+ {
183+ // read zero current
184+ zerocurrent_raw = current_sense->readAverageCurrents (20 );
185+ zerocurrent = current_sense->getDQCurrents (current_sense->getABCurrents (zerocurrent_raw), current_electric_angle);
186+
187+ // step the voltage
188+ setPhaseVoltage (0 , voltage, current_electric_angle);
189+ t0 = micros ();
190+ delayMicroseconds (risetime_us); // wait a little bit
191+
192+ PhaseCurrent_s l_currents_raw = current_sense->getPhaseCurrents ();
193+ t1 = micros ();
194+ setPhaseVoltage (0 , 0 , current_electric_angle);
195+
196+ DQCurrent_s l_currents = current_sense->getDQCurrents (current_sense->getABCurrents (l_currents_raw), current_electric_angle);
197+ delayMicroseconds (settle_us); // wait a bit for the currents to go to 0 again
198+
199+ if (t0 > t1) continue ; // safety first
200+
201+ // calculate the inductance
202+ float dt = (t1 - t0)/1000000 .0f ;
203+ if (l_currents.d - zerocurrent.d <= 0 || (voltage - resistance * (l_currents.d - zerocurrent.d )) <= 0 )
204+ {
205+ continue ;
206+ }
207+
208+ inductanced += fabsf (- (resistance * dt) / log ((voltage - resistance * (l_currents.d - zerocurrent.d )) / voltage))/correction_factor;
209+
210+ qcurrent+= l_currents.q - zerocurrent.q ; // average the measured currents
211+ dcurrent+= l_currents.d - zerocurrent.d ;
212+ }
213+ qcurrent /= cycles;
214+ dcurrent /= cycles;
215+
216+ float delta = qcurrent / (fabsf (dcurrent) + fabsf (qcurrent));
217+
218+
219+ inductanced /= cycles;
220+ Ltemp = i < 2 ? inductanced : Ltemp * 0.6 + inductanced * 0.4 ;
221+
222+ float timeconstant = fabsf (Ltemp / resistance); // Timeconstant of an RL circuit (L/R)
223+ // SIMPLEFOC_DEBUG("MOT: Estimated time constant in us: ", 1000000.0f * timeconstant);
224+
225+ // Wait as long as possible (due to limited timing accuracy & sample rate), but as short as needed (while the current still changes)
226+ risetime_us = _constrain (risetime_us * 0 .6f + 0 .4f * 1000000 * 0 .6f * timeconstant, 100 , 10000 );
227+ settle_us = 10 * risetime_us;
228+
229+
230+ // Serial.printf(">inductance:%f:%f|xy\n", current_electric_angle, Ltemp * 1000.0f); // <-- Plot an angle sweep
231+
232+
233+ /* *
234+ * How this code works:
235+ * If we apply a current spike in the d´-axis, there will be cross coupling to the q´-axis current, if we didn´t use the actual d-axis (ie. d´ != d).
236+ * This has to do with saliency (Ld != Lq).
237+ * The amount of cross coupled current is somewhat proportional to the angle error, which means that if we iteratively change the angle to min/maximise this current, we get the correct d-axis (and q-axis).
238+ */
239+ if (axis)
240+ {
241+ qcurrent *= -1 .0f ; // to d or q axis
242+ }
243+
244+ if (qcurrent < 0 )
245+ {
246+ current_electric_angle+=fabsf (delta);
247+ } else
248+ {
249+ current_electric_angle-=fabsf (delta);
250+ }
251+ current_electric_angle = _normalizeAngle (current_electric_angle);
252+
253+
254+ // Average the d-axis angle further for calculating the electrical zero later
255+ if (axis)
256+ {
257+ d_electrical_angle = i < 2 ? current_electric_angle : d_electrical_angle * 0.9 + current_electric_angle * 0.1 ;
258+ }
259+
260+ }
261+
262+ // We know the minimum is 0.5*PI radians away, so less iterations are needed.
263+ current_electric_angle += 0 .5f * _PI;
264+ current_electric_angle = _normalizeAngle (current_electric_angle);
265+ iterations /= 2 ;
266+
267+ if (axis == 0 )
268+ {
269+ Lq = Ltemp;
270+ }else
271+ {
272+ Ld = Ltemp;
273+ }
274+
275+ }
276+
277+ if (sensor)
278+ {
279+ /* *
280+ * The d_electrical_angle should now be aligned to the d axis or the -d axis. We can therefore calculate two possible electrical zero angles.
281+ * We then report the one closest to the actual value. This could be useful if the zero search method is not reliable enough (eg. high pole count).
282+ */
283+
284+ float estimated_zero_electric_angle_A = _normalizeAngle ( (float )(sensor_direction * pole_pairs) * sensor->getMechanicalAngle () - d_electrical_angle);
285+ float estimated_zero_electric_angle_B = _normalizeAngle ( (float )(sensor_direction * pole_pairs) * sensor->getMechanicalAngle () - d_electrical_angle + _PI);
286+ float estimated_zero_electric_angle = 0 .0f ;
287+ if (fabsf (estimated_zero_electric_angle_A - zero_electric_angle) < fabsf (estimated_zero_electric_angle_B - zero_electric_angle))
288+ {
289+ estimated_zero_electric_angle = estimated_zero_electric_angle_A;
290+ } else
291+ {
292+ estimated_zero_electric_angle = estimated_zero_electric_angle_B;
293+ }
294+
295+ SIMPLEFOC_DEBUG (" MOT: Newly estimated electrical zero: " , estimated_zero_electric_angle);
296+ SIMPLEFOC_DEBUG (" MOT: Current electrical zero: " , zero_electric_angle);
297+ }
298+
299+
300+ SIMPLEFOC_DEBUG (" MOT: Inductance measurement complete!" );
301+ SIMPLEFOC_DEBUG (" MOT: Measured D-inductance in mH: " , Ld * 1000 .0f );
302+ SIMPLEFOC_DEBUG (" MOT: Measured Q-inductance in mH: " , Lq * 1000 .0f );
303+ if (Ld > Lq)
304+ {
305+ SIMPLEFOC_DEBUG (" WARN: MOT: Measured inductance is larger in D than in Q axis. This is normally a sign of a measurement error." );
306+ }
307+ if (Ld * 2 .0f < Lq)
308+ {
309+ SIMPLEFOC_DEBUG (" WARN: MOT: Measured Q inductance is more than twice the D inductance. This is probably wrong. From experience, the lower value is probably close to reality." );
310+ }
311+
312+ return 0 ;
313+
314+ }
315+
95316// utility function intended to be used with serial plotter to monitor motor variables
96317// significantly slowing the execution down!!!!
97318void FOCMotor::monitor () {
0 commit comments