-
-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
setMaxCurrentShunt normalization optimized #29
setMaxCurrentShunt normalization optimized #29
Conversation
setMaxCurrentShunt normalize redefined
Thank you for this PR. |
Sorry for the inconvenience, |
Found some time to have a look. Note: the unit in the debug print is incorrect. uA / bit should be A / bit setMaxCurrentShunt(8, 0.01, true ) setMaxCurrentShunt(8, 0.01, true ) So the normalized Max Current is below the requested max current. So I consider the problem confirmed. |
I have this (temporart) variation int INA226::setMaxCurrentShunt(float maxCurrent, float shunt, bool normalize)
{
#define printdebug true
// fix #16 - datasheet 6.5 Electrical Characteristics
// rounded value to 80 mV
float shuntVoltage = abs(maxCurrent * shunt);
if (shuntVoltage > 0.080) return INA226_ERR_SHUNTVOLTAGE_HIGH;
if (maxCurrent < 0.001) return INA226_ERR_MAXCURRENT_LOW;
if (shunt < INA226_MINIMAL_SHUNT) return INA226_ERR_SHUNT_LOW;
_current_LSB = maxCurrent * 3.0517578125e-5; // maxCurrent / 32768;
#ifdef printdebug
Serial.println();
Serial.print("normalize:\t");
Serial.println(normalize ? " true":" false");
Serial.print("initial current_LSB:\t");
Serial.print(_current_LSB, 8);
Serial.println(" A / bit");
#endif
uint32_t calib = 0;
uint32_t factor = 1;
// normalize the LSB to a round number
// LSB will increase
if (normalize)
{
calib = round(0.00512 / (_current_LSB * shunt));
_current_LSB = 0.00512 / (calib * shunt);
#ifdef printdebug
Serial.print("Prescale current_LSB:\t");
Serial.print(_current_LSB, 8);
Serial.println(" A / bit");
#endif
// new maximum current must be able to handle the requested maxCurrent.
float newMaxCurrent = 32768; // # steps of unit 1 A (* 10^-n)
while (newMaxCurrent > maxCurrent)
{
newMaxCurrent *= 0.1;
factor *= 10;
}
if (newMaxCurrent < maxCurrent)
{
// we must correct the last iteration
factor /= 10;
}
// else if (newMaxCurrent == maxCurrent) // no correction needed.
// scale current_LSB
_current_LSB = 1.0 / factor;
// factor = 1;
// while (_current_LSB < 1)
// {
// _current_LSB *= 10;
// // Serial.println(_current_LSB, 8);
// factor *= 10;
// }
// _current_LSB = 1.0 / factor;
}
// auto scale calibration
calib = round(0.00512 / (_current_LSB * shunt));
while (calib > 65535)
{
#ifdef printdebug
Serial.print("Adjust calib:\t");
Serial.println(calib);
#endif
_current_LSB *= 10;
calib /= 10;
}
_writeRegister(INA226_CALIBRATION, calib);
_maxCurrent = _current_LSB * 32768;
_shunt = shunt;
#ifdef printdebug
Serial.print("factor:\t");
Serial.println(factor);
Serial.print("Final current_LSB:\t");
Serial.print(_current_LSB, 8);
Serial.println(" A / bit");
Serial.print("Calibration:\t");
Serial.println(calib);
Serial.print("Max current:\t");
Serial.print(_maxCurrent);
Serial.println(" A");
Serial.print("Shunt:\t");
Serial.print(_shunt, 8);
Serial.println(" ohm");
Serial.print("ShuntV:\t");
Serial.print(shuntVoltage, 4);
Serial.println(" Volt");
#endif
return INA226_ERR_NONE;
} |
This code gives similar results as yours, but it explains the how a bit more. Got this output INA.setMaxCurrentShunt(8, 0.01, true); normalize: true The requested maxCurrent is a factor 4 above the requested maxCurrent. I'm going to think how I can round the _current_LSB in normalized form to 1, 2.5 or 5 times some power of 10. In this case It would provide a higher resolution. |
Unsurprisingly I don’t think my code is hard to understand ;-) If you agree to use 2.5uA * factor (1 ,10 or 100) I could make the code simpler: uint32_t calib = 0;
uint8_t factor = 1;
float currentLSB = 0;
// normalize the LSB to a round number
// LSB will increase
if (normalize)
{
if (_current_LSB <= 10e-6 ) {
// _current_LSB is <= 10uA
// start normalization with 2.5uA and a step of 2.5uA
currentLSB = 2.5e-6
factor = 1;
}else if (_current_LSB <= 100e-6 ) {
// _current_LSB is > 10uA and <= 100uA
// start normalization with 25uA and a step of 25uA
currentLSB = 25e-6
factor = 10;
}else {
// _current_LSB is > 100uA
// start normalization with 250uA and a step of 250uA
currentLSB = 250e-6
factor = 100;
}
while( currentLSB < _current_LSB ) {
currentLSB = currentLSB + 2.5e-6 * factor;
}
_current_LSB = currentLSB;
} |
It can be very well understood however think that it was my code part (determining the factor) that could be improved upon. Calculating the factor in a loop allows a larger range of usage then only the three levels you propose.
I strongly prefer the three step 1, 2, 5 scheme.
|
Ok, so in which range are we working on?
How should the current_LSB steps should look like?
|
Note: 1, 2, 4, 6, 8, 10, 20, 40, 60, 80, ... would be easier to implement |
The second one was my thoughts, Currently it is 1,10,100, 1000 which is "screaming" for improvement of accuracy. There is one scenario in which I encountered a "problem". Compare these calls. INA.setMaxCurrentShunt(0.5, 0.01, true); // auto scale calibration
calib = round(0.00512 / (_current_LSB * shunt));
while (calib > 65535)
{
_current_LSB *= 10; <<<<<<<<<<
calib /= 10;
Serial.println(calib);
} |
Please give it a try, I can have a look tomorrow (other appointments) |
Ok, I see.
To solve this, the current_lsb calculation has to be shunt resistor centric: Assumptions / limits:
uint32_t calib = 0;
// uint8_t factor = 1;
if (normalize)
{
// adc lsb is 2.5uV; the lowest current we can messure based on given shunt resistor is ...
_current_LSB = 2.5e-6 / shunt;
// start to normalize with a multiple of 10 (next lower)
if (_current_LSB > 1000e-6 ) {
_current_LSB = 1000e-6; // 1mA
}else if (_current_LSB > 100e-6 ) {
_current_LSB = 100e-6; //100uA
}else if (_current_LSB > 10e-6 ) {
_current_LSB = 10e-6; // 10uA
}else {
_current_LSB = 1e-6; // 1uA
}
// check against max current and normalize current_lsb to * 1, 2, 5 or 10
if (_current_LSB * 32768 >= maxCurrent ) {
_current_LSB *=1;
}else if (_current_LSB * 2 * 32768 >= maxCurrent ) {
_current_LSB *=2;
}else if (_current_LSB * 5 * 32768 >= maxCurrent ) {
_current_LSB *=5;
}else {
_current_LSB *=10;
}
} Note: the code is NOT tested; it is just for discusson; if you like it, I will test it of corse! 2.) auto scale // auto scale calibration
calib = round(0.00512 / (_current_LSB * shunt));
while (calib > 65535)
{
_current_LSB *= 2;
calib /= 2;
} Note: This part should not be necessary für normalized _current_LSB; but it is more safe to double check it anyway |
sorry for closing this PR by accident 😟; I don't know how to undo, so I reopend it. |
Is a GUI "bug", the close with comment button should be much further away from the comment one, e.g. on the left, |
The proof is in the pudding test, so yes please test it if you have time. |
Will be working on - #31 today |
FYI released 0.5.0 |
Hello Rob, I've rewritten the code. I think its working now very well. Unfortunaley I'm not very familiar with github. Do you see the new code? Is this the right procedure or should I do a new pull request? Thx. |
I'm quite busy and will try to look into it this evening. I've started a build |
btw: I have a version with integer rather than float also. But it needs lower and upper limits to work. eg. shunt resistor from 0.001 to 1 Ohm. I don't know if this would be accaptable for you? |
Think it would not please all the users of the library - which I don't know, so that is an assumption. What would be the added value for users? Another thought (thinking out loud). The disadvantage is that for calculating the power I need 2 calls to the device unless the user uses the last two values. //////////////// UNCHANGED
float INA226::getShuntVoltage()
{
int16_t val = _readRegister(INA226_SHUNT_VOLTAGE);
return val * 2.5e-6; // fixed 2.50 uV
}
float INA226::getBusVoltage()
{
uint16_t val = _readRegister(INA226_BUS_VOLTAGE);
return val * 1.25e-3; // fixed 1.25 mV
}
//////////////// CHANGED
float INA226::getCurrent()
{
return getShuntVoltage() / _shunt; // can be optimized by storing/using 1.0 / shunt;
}
float INA226::getPower()
{
return getBusVoltage() * getCurrent();
}
Could be the basis for a tiny_INA226 or mini_ina226 library? |
Hi, sorry, one more thing. The new code uses about 78 Bytes more Flash (0 RAM) than your temporary fix. I have an alternative version, which is less elegant* but uses 116 Bytes less Flash and 24 Bytes more (!) RAM. What do you think? *) a simple array of values and a loop to check: const uint16_t norm_val[] = { 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000 }; // 1uA ... 5mA
uint16_t currentLSB_uA = float(_current_LSB * 1e+6);
currentLSB_uA++; // ceil would be more precise, but uses 176 bytes of flash
byte i;
for(i=0; i < sizeof(norm_val)/sizeof(uint16_t); i++){
if(norm_val[i] >= currentLSB_uA){
_current_LSB = norm_val[i] * 1e-6;
i=0xFF;
break;
}
}
if(i!=0xFF) {
return INA226_ERR_NORMALIZE_FAILED;
} Thx, Armin |
Would be nice. On the other hand for my projects the footprint of this version of the lib is totally fine. Maybe a "setMaxCurrentShuntRaw" would be an easy improvement to save some bytes of FLASH and RAM. |
Hi, just had a small test run and it works pretty well
Think that is a good idea, uint16_t currentLSB_uA = float(_current_LSB * 1e+6); // float casting is not needed as 1e6 is float. byte i; ==> I prefer uint8_t as byte can be signed on some platforms. so it would become uint8_t norm_val[] = { 1, 2, 5 };
uint16_t factor = 1;
uint16_t currentLSB_uA = float(_current_LSB * 1e+6);
currentLSB_uA++; // ceil would be more precise, but uses 176 bytes of flash
bool failed = true;
for ( uint16_t factor = 1; factor < 10000; factor *= 10)
{
for(uint8_t i = 0; i < sizeof(norm_val)/sizeof(uint16_t); i++)
{
uint16_t norm = norm_val[i] * factor;
if (norm >= currentLSB_uA)
{
_current_LSB = norm * 1e-6;
failed = false;;
break;
}
}
}
if (failed) {
return INA226_ERR_NORMALIZE_FAILED;
} |
Hi, your are right! I played around with your suggestion to see whats the impact on Flash/RAM. It is strange. If you don't catch the break in the outer loop, the code is working, but it is 86 byte longer. Thus I suggest a do-while loop. New size is +4 bytes on RAM and 0 Bytes on Flash compared to your temporary fix. // normalize _current_LSB
const uint8_t norm_val[] = { 1, 2, 5 };
uint16_t factor = 1;
uint16_t currentLSB_uA = float(_current_LSB * 1e+6);
currentLSB_uA++; // ceil would be more precise, but uses 176 bytes of flash
bool failed = true;
do{
for(uint8_t i=0; i < sizeof(norm_val)/sizeof(uint8_t); i++){
if( norm_val[i] * factor >= currentLSB_uA){
_current_LSB = norm_val[i] * factor * 1e-6;
failed = false;
break;
}
}
factor *= 10;
}while(factor < 100000 and failed);
if(failed) {
_current_LSB = 0;
return INA226_ERR_NORMALIZE_FAILED;
} But thinking in this direction, there would be an easier solution: // normalize _current_LSB
uint16_t factor = 1;
uint16_t currentLSB_uA = float(_current_LSB * 1e+6);
currentLSB_uA++; // ceil would be more precise, but uses 176 bytes of flash
bool failed = true;
do{
if( 1*factor >= currentLSB_uA){
_current_LSB = 1*factor * 1e-6;
failed = false;
}else if( 2*factor >= currentLSB_uA){
_current_LSB = 2*factor * 1e-6;
failed = false;
}else if( 5*factor >= currentLSB_uA){
_current_LSB = 5*factor * 1e-6;
failed = false;
}else {
factor *= 10;
}
}while(factor < 100000 and failed);
if(failed) {
_current_LSB = 0;
return INA226_ERR_NORMALIZE_FAILED;
} Which is 90 bytes of Flash and 4 bytes of RAM less. |
I made a new version based on my last input. I think this could be the final version. That do you think?! |
Only very minor comments, style things. |
looks good and I will merge it later this afternoon |
Created a PR to release this code as 0.5.1 Thanks again for finding the bug and develop an improved algorithm |
You are wellcome! Thank you for your work and your patience with me! |
Now we have to wait if it triggers new issues or not. Think the code is quite robust so I wont expect them soon. |
Hello Rob, thank you very much for your INA226 LIB! It is essential for my little DIY project and a great help! Thank you for sharing it!
I think there is a bug in setMaxCurrentShunt at the normalization code. After normalization _current_LSB is less than before.
setMaxCurrentShunt(8, 0.01, true )
normalize: true
initial current_LSB: 0.00024414 uA / bit
Prescale current_LSB: 0.00024416 uA / bit
factor: 10000
Final current_LSB: 0.00010000 uA / bit
Calibration: 5120
Max current: 3.28 A
Shunt: 0.01000000 ohm
ShuntV: 0.0800 Volt
setMaxCurrentShunt(20, 0.002, true )
normalize: true
initial current_LSB: 0.00061035 uA / bit
Prescale current_LSB: 0.00061040 uA / bit
factor: 10000
Final current_LSB: 0.00010000 uA / bit
Calibration: 25600
Max current: 3.28 A
Shunt: 0.00200000 ohm
ShuntV: 0.0400 Volt
There would be an easy fix by changing '_current_LSB = 1.0 / factor;" to '_current_LSB = 10.0 / factor;" at line 235.
Never the less I thought, the normalization could be less greedy and more flexible. I hope you like it!
Best regards,
Armin