Laelaps  2.3.5
RoadNarrows Robotics Small Outdoor Mobile Robot Project
laeBatt.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: Laelasp
4 //
5 // Library: liblaelaps
6 //
7 // File: laeBatt.cxx
8 //
9 /*! \file
10  *
11  * $LastChangedDate: 2016-03-02 11:18:30 -0700 (Wed, 02 Mar 2016) $
12  * $Rev: 4337 $
13  *
14  * \brief Laelaps battery management and energy monitoring class implementation.
15  *
16  * A class instance runs under the control of the WatchDog thread.
17  *
18  * \author Robin Knight (robin.knight@roadnarrows.com)
19  *
20  * \par Copyright
21  * \h_copy 2015-2017. RoadNarrows LLC.\n
22  * http://www.roadnarrows.com\n
23  * All Rights Reserved
24  */
25 /*
26  * @EulaBegin@
27  *
28  * Unless otherwise stated explicitly, all materials contained are copyrighted
29  * and may not be used without RoadNarrows LLC's written consent,
30  * except as provided in these terms and conditions or in the copyright
31  * notice (documents and software) or other proprietary notice provided with
32  * the relevant materials.
33  *
34  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY
35  * MEMBERS/EMPLOYEES/CONTRACTORS OF ROADNARROWS OR DISTRIBUTORS OF THIS SOFTWARE
36  * BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
37  * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
38  * DOCUMENTATION, EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN
39  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * THE AUTHORS AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
42  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
43  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
44  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
45  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
46  *
47  * @EulaEnd@
48  */
49 ////////////////////////////////////////////////////////////////////////////////
50 
51 #include <sys/types.h>
52 #include <unistd.h>
53 
54 #include <string>
55 
56 #include "rnr/rnrconfig.h"
57 #include "rnr/log.h"
58 
59 #include "Laelaps/laelaps.h"
60 #include "Laelaps/laeUtils.h"
61 #include "Laelaps/laeTune.h"
62 #include "Laelaps/laeMotor.h"
63 #include "Laelaps/laeDb.h"
64 #include "Laelaps/laeBatt.h"
65 
66 
67 using namespace std;
68 using namespace laelaps;
69 
70 // -----------------------------------------------------------------------------
71 // Private
72 // -----------------------------------------------------------------------------
73 
74 //
75 // Logic Power Constraints
76 //
77 // TODO: All non-odroid components need to be measured and verified.
78 //
79 static const double OdroidXU3MinAmps = 1.0; // from website
80 static const double OdroidXU3Volts = 1.8; // from website
81 static const double OdroidXU4MinAmps = 1.0; // from xu3 website
82 static const double OdroidXU4Volts = 1.8; // from website
83 static const double RoboClaw2x30AAmps = 0.03; // from manual
84 static const double RoboClaw2x30AVolts = 5.0; // could be 3.3V
85 static const double Naze32Amps = 0.50; // usb limit ?
86 static const double Naxe32Volts = 5.0; // could be 3.3V
87 static const double BattChargerAmps = 0.0; // ?
88 static const double BattChargerVolts = 0.0; // ?
89 static const double ArduinoAmps = 0.01; // ?
90 static const double ArduinoVolts = 5.0; // ?
91 static const double OtherAmps = 0.01; // ?
92 static const double OtherVolts = 5.0; // ?
93 
94 
95 /*!
96  * \brief Lithium-Ion State of Charge - Cell Voltage table.
97  *
98  * Tables associating the State of Charge for a single cell and sensed voltage
99  * V for the given specific discharge current C.
100  *
101  * C is the normalized value: BatterCapacity(Ah)/discharge(A). For example,
102  * for a 10Ah battery:
103  * 0.5C = 5A
104  * 1C = 10A
105  * 2C = 20A
106  *
107  * Sources:
108  * \li http://web.mit.edu/evt/summary_battery_specifications.pdf
109  * \li http://www.electricrcaircraftguy.com/2013_01_01_archive.html
110  * \li http://epg.eng.ox.ac.uk/content/electric-vehicles-using-physics-based-battery-models-improved-estimation-state-charge
111  */
112 
113 /*!
114  * \brief State of Charge - Battery Voltage entry.
115  */
117 {
118  double m_fSoC; ///< state of charge (%)
119  double m_fV; ///< sensed battery voltage (V)
120 };
121 
122 /*!
123  * \brief Specific Current - State of Charge/Battery Voltage table entry.
124  */
126 {
127  double m_fC; ///< specific normalized current
128  LiIonCellSoCVEntry *m_pTbl; ///< SoC - V table
129  size_t m_sizeTbl; ///< number of entries
130 };
131 
132 
133 /*!
134  * \brief SoC-V table for an 0C unloaded single cell.
135  */
137 {
138  { 0.0, 3.00}, { 10.0, 3.62}, { 20.0, 3.70}, { 30.0, 3.74}, { 40.0, 3.78},
139  { 50.0, 3.84}, { 60.0, 3.88}, { 70.0, 3.93}, { 80.0, 3.97}, { 90.0, 4.05},
140  {100.0, 4.20}
141 };
142 
143 /*!
144  * \brief SoC-V table for an 1C single cell.
145  */
147 {
148  { 0.0, 2.00}, { 10.0, 3.50}, { 20.0, 3.55}, { 30.0, 3.60}, { 40.0, 3.65},
149  { 50.0, 3.70}, { 60.0, 3.75}, { 70.0, 3.78}, { 80.0, 3.80}, { 90.0, 3.90},
150  {100.0, 4.05}
151 };
152 
153 /*!
154  * \brief SoC-V table for an 2C single cell.
155  */
157 {
158  { 0.0, 1.00}, { 10.0, 2.90}, { 20.0, 3.45}, { 30.0, 3.55}, { 40.0, 3.60},
159  { 50.0, 3.65}, { 60.0, 3.70}, { 70.0, 3.75}, { 80.0, 3.76}, { 90.0, 3.80},
160  {100.0, 4.00}
161 };
162 
163 /*!
164  * \brief SoC-V table for an 3C single cell.
165  */
167 {
168  { 0.0, 0.10}, { 10.0, 0.50}, { 20.0, 3.30}, { 30.0, 3.45}, { 40.0, 3.51},
169  { 50.0, 3.60}, { 60.0, 3.65}, { 70.0, 3.70}, { 80.0, 3.74}, { 90.0, 3.78},
170  {100.0, 4.00}
171 };
172 
173 /*!
174  * \brief C - SoC-V table of tables.
175  */
177 {
178  {0.0, LiIonCellSoCVTbl_0C, arraysize(LiIonCellSoCVTbl_0C)},
179  {1.0, LiIonCellSoCVTbl_1C, arraysize(LiIonCellSoCVTbl_1C)},
180  {2.0, LiIonCellSoCVTbl_2C, arraysize(LiIonCellSoCVTbl_2C)},
181  {3.0, LiIonCellSoCVTbl_3C, arraysize(LiIonCellSoCVTbl_3C)}
182 };
183 
184 /*!
185  * \brief Linear interpolate.
186  *
187  * \param x X input in [x0, x1].
188  * \param x0 Lower domain value.
189  * \param x1 Upper domain value.
190  * \param y0 Lower range value.
191  * \param y1 Upper range value.
192  *
193  * \return Interpolated Y output in [y0, y1].
194  */
195 static double linearInterp(double x, double x0, double x1, double y0, double y1)
196 {
197  return y0 + (x - x0)/(x1 - x0) * (y1 - y0);
198 }
199 
200 /*!
201  * \brief Lookup State of Charge, given the battery voltage.
202  *
203  * Linear interpolation is performed as necessary.
204  *
205  * \param fV Input battery voltage.
206  * \param tbl SoC - V table.
207  * \param n Number of table entries.
208  *
209  * \return Interpolated SoC.
210  */
211 static double lookupSoC(double fV, LiIonCellSoCVEntry tbl[], size_t n)
212 {
213  size_t i;
214 
215  // table list voltage for single cell
216  fV /= (double)LaeTuneBattCells;
217 
218  // search accending table for nearest entries
219  for(i = 0; i < n; ++i)
220  {
221  if( tbl[i].m_fV >= fV )
222  {
223  break;
224  }
225  }
226 
227  // lower boundary case
228  if( i == 0 )
229  {
230  return tbl[i].m_fSoC;
231  }
232  // upper boundary case
233  else if( i == n )
234  {
235  return tbl[i-1].m_fSoC;
236  }
237  // interpolate
238  else
239  {
240  return linearInterp(fV, tbl[i-1].m_fV, tbl[i].m_fV,
241  tbl[i-1].m_fSoC, tbl[i].m_fSoC);
242  }
243 }
244 
245 
246 // -----------------------------------------------------------------------------
247 // LaeBattery Class
248 // -----------------------------------------------------------------------------
249 
250 LaeBattery::LaeBattery()
251 {
252  m_bIsCharging = false;
253  m_fBatteryVoltage = 0.0;
254  m_fBatterySoC = 0.0;
255  m_fMotorAmps = 0.0;
256  m_fMotorWatts = 0.0;
257  m_fLogicWatts = 0.0;
258  m_fTotalAmps = 0.0;
259  m_fTotalWatts = 0.0;
260 }
261 
262 LaeBattery::~LaeBattery()
263 {
264 }
265 
266 void LaeBattery::calcMotorEnergyState()
267 {
268  int nCtlr, nMotor;
269  double fVolts, fAmps, fWatts;
270 
271  m_fBatteryVoltage = 0.0;
272  m_fMotorAmps = 0.0;
273  m_fMotorWatts = 0.0;
274 
275  //
276  // Motor controllers have the best voltage sense accuracy. Use if possible.
277  //
278  if( RtDb.m_enable.m_bMotorCtlr )
279  {
280  //
281  // Batteries and motors
282  //
283  for(nCtlr=0; nCtlr<LaeNumMotorCtlrs; ++nCtlr)
284  {
285  fVolts = RtDb.m_motorctlr[nCtlr].m_fBatteryVoltage;
286  for(nMotor=0; nMotor<LaeNumMotorsPerCtlr; ++nMotor)
287  {
288  fAmps = RtDb.m_motorctlr[nCtlr].m_fMotorCurrent[nMotor];
289  fWatts = fAmps * fVolts;
290 
291  m_fMotorAmps += fAmps;
292  m_fMotorWatts += fWatts;
293  }
294  m_fBatteryVoltage += fVolts;
295  }
296 
297  // average
298  m_fBatteryVoltage /= (double)LaeNumMotorCtlrs;
299  }
300 
301  //
302  // For original 2.0 hardware, the only sources to sense battery voltages were
303  // from the motor controllers. New in v2.1+, there is a direct voltage sense
304  // to the WatchDog processor. This change was needed because the motor
305  // controllers' power enable line can be disabled independent of battery
306  // state.
307  //
308  // However, voltage sense is less accurate. Use only if motor controllers'
309  // power is disablbed.
310  //
311  else if( RtDb.m_product.m_uProdHwVer >= LAE_VERSION(2, 1, 0) )
312  {
313  m_fBatteryVoltage = RtDb.m_energy.m_fBatteryVoltage;
314  }
315 }
316 
317 void LaeBattery::calcLogicEnergyState()
318 {
319  //
320  // TODO See https://github.com/hardkernel/EnergyMonitor
321  //
322  m_fLogicAmps = 0.5;
323  m_fLogicWatts = m_fLogicAmps * 1.8;
324 }
325 
326 double LaeBattery::estimateBatteryStateOfCharge()
327 {
328  double fC;
329  size_t n, i;
330  double fSoC0, fSoC1;
331 
332  // normalize specific current
333  fC = m_fTotalAmps / LaeTuneBattCapAh;
334 
335  n = arraysize(LiIonCellSoCTbls);
336 
337  // look for C's nearest entries
338  for(i = 0; i < n; ++i)
339  {
340  if( LiIonCellSoCTbls[i].m_fC >= fC )
341  {
342  break;
343  }
344  }
345 
346  // lower boundary case - single interpolation
347  if( i == 0 )
348  {
349  m_fBatterySoC = lookupSoC(m_fBatteryVoltage, LiIonCellSoCTbls[i].m_pTbl,
350  LiIonCellSoCTbls[i].m_sizeTbl);
351  }
352  // upper boundary case - single interpolation
353  else if( i == n )
354  {
355  m_fBatterySoC = lookupSoC(m_fBatteryVoltage, LiIonCellSoCTbls[i-1].m_pTbl,
356  LiIonCellSoCTbls[i-1].m_sizeTbl);
357  }
358  // double interpolation
359  else
360  {
361  fSoC0 = lookupSoC(m_fBatteryVoltage, LiIonCellSoCTbls[i-1].m_pTbl,
362  LiIonCellSoCTbls[i-1].m_sizeTbl);
363  fSoC1 = lookupSoC(m_fBatteryVoltage, LiIonCellSoCTbls[i].m_pTbl,
364  LiIonCellSoCTbls[i].m_sizeTbl);
365  m_fBatterySoC = linearInterp(fC,
366  LiIonCellSoCTbls[i-1].m_fC, LiIonCellSoCTbls[i].m_fC,
367  fSoC0, fSoC1);
368  }
369 
370  return m_fBatterySoC;
371 }
372 
373 void LaeBattery::update()
374 {
375  m_bIsCharging = RtDb.m_energy.m_bBatteryIsCharging;
376 
377  // calculate the amps, watts, etc. used by all motors
378  calcMotorEnergyState();
379 
380  // calculate the amps, watts, etc. used by all logic circuitry
381  calcLogicEnergyState();
382 
383  //
384  // Totals
385  //
386  m_fTotalAmps = m_fMotorAmps + m_fLogicAmps;
387  m_fTotalWatts = m_fMotorWatts + m_fLogicWatts;
388 
389  //
390  // Estimate battery state of charge.
391  //
392  estimateBatteryStateOfCharge();
393 
394  //
395  // Export key data to database
396  //
397  if( RtDb.m_product.m_uProdHwVer < LAE_VERSION(2, 1, 0) )
398  {
399  // producer
400  RtDb.m_energy.m_fBatteryVoltage = m_fBatteryVoltage;
401  }
402  RtDb.m_energy.m_fBatterySoC = m_fBatterySoC;
403  RtDb.m_energy.m_fTotalCurrent = m_fTotalAmps;
404  RtDb.m_energy.m_fTotalPower = m_fTotalWatts;
405 
406  //fprintf(stderr, "DBG: battV=%lf, battSoC=%lf totCurr=%lf, totPwr=%lf\n",
407  // RtDb.m_energy.m_fBatteryVoltage, RtDb.m_energy.m_fBatterySoC,
408  // RtDb.m_energy.m_fTotalCurrent, RtDb.m_energy.m_fTotalPower);
409 }
static double linearInterp(double x, double x0, double x1, double y0, double y1)
Linear interpolate.
Definition: laeBatt.cxx:195
static LiIonCellSoCVEntry LiIonCellSoCVTbl_0C[]
SoC-V table for an 0C unloaded single cell.
Definition: laeBatt.cxx:136
Laelaps battery management and energy monitoring class interface.
double m_fC
specific normalized current
Definition: laeBatt.cxx:127
static double lookupSoC(double fV, LiIonCellSoCVEntry tbl[], size_t n)
Lookup State of Charge, given the battery voltage.
Definition: laeBatt.cxx:211
static LiIonCellSoCVEntry LiIonCellSoCVTbl_1C[]
SoC-V table for an 1C single cell.
Definition: laeBatt.cxx:146
static LiIonCellSoCVEntry LiIonCellSoCVTbl_3C[]
SoC-V table for an 3C single cell.
Definition: laeBatt.cxx:166
Lithium-Ion State of Charge - Cell Voltage table.
Definition: laeBatt.cxx:116
The <b><i>Laelaps</i></b> namespace encapsulates all <b><i>Laelaps</i></b> related constructs...
Definition: laeAlarms.h:64
size_t m_sizeTbl
number of entries
Definition: laeBatt.cxx:129
Laelaps common utilities.
Laelaps tuning.
static LiIonCellSoCVEntry LiIonCellSoCVTbl_2C[]
SoC-V table for an 2C single cell.
Definition: laeBatt.cxx:156
LiIonCellSoCVEntry * m_pTbl
SoC - V table.
Definition: laeBatt.cxx:128
Laelaps motors, encoder, and controllers hardware abstraction interfaces.
#define LAE_VERSION(major, minor, revision)
Convert version triplet to integer equivalent.
Definition: laelaps.h:158
Specific Current - State of Charge/Battery Voltage table entry.
Definition: laeBatt.cxx:125
static LiIonCellCSoCEntry LiIonCellSoCTbls[]
C - SoC-V table of tables.
Definition: laeBatt.cxx:176
double m_fV
sensed battery voltage (V)
Definition: laeBatt.cxx:119
double m_fSoC
state of charge (%)
Definition: laeBatt.cxx:118
Laelaps real-time "database".
Top-level package include file.