Skip to content

Commit 0f219e1

Browse files
xyzzy42nashif
authored andcommitted
drivers/sensor: si7006: Fix math for calculating sensor values
The existing code rounded the result to an integer, then multiplied that integer by 1000000 to get micro-degrees or micro-percent, and then divided by 1000000 to get whole degrees/percent and took the modulus to get fractional degrees/percent. Obviously, multiplying and then dividing an integer by the same value has no effect! The result is the humidity and temperature were always rounded down to the nearest integer. Fix this to properly keep the fractional component. This is done in a way that avoids any integer divisions, which are slow on all CPUs, but especially most microcontrollers, e.g. Cortex-M, lack any integer division instruction. Avoiding the base 10 math does not require more code. One just needs to think in binary and use binary fractions instead of base 10 fractions. Signed-off-by: Trent Piepho <[email protected]>
1 parent 88649da commit 0f219e1

File tree

1 file changed

+50
-16
lines changed

1 file changed

+50
-16
lines changed

drivers/sensor/silabs/si7006/si7006.c

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <zephyr/drivers/i2c.h>
1515
#include <stdio.h>
1616
#include <stdlib.h>
17+
#include <zephyr/sys/util.h>
1718
#include "si7006.h"
1819
#include <zephyr/logging/log.h>
1920

@@ -112,25 +113,58 @@ static int si7006_channel_get(const struct device *dev,
112113
struct si7006_data *si_data = dev->data;
113114

114115
if (chan == SENSOR_CHAN_AMBIENT_TEMP) {
115-
116-
int32_t temp_ucelcius = (((17572 * (int32_t)si_data->temperature)
117-
/ 65536) - 4685) * 10000;
118-
119-
val->val1 = temp_ucelcius / 1000000;
120-
val->val2 = temp_ucelcius % 1000000;
121-
122-
LOG_DBG("temperature = val1:%d, val2:%d", val->val1, val->val2);
116+
/* Raw formula: (temp * 175.72) / 65536 - 46.85
117+
* To use integer math, scale the 175.72 factor by 128 and move the offset to
118+
* inside the division. This gives us:
119+
*
120+
* (temp * 175.72 * 128 - 46.86 * 128 * 65536) / (65536 * 128)
121+
* The constants can be calculated now:
122+
* (temp * 22492 - 393006285) / 2^23
123+
*
124+
* There is a very small amount of round-off error in the factor of 22492. To
125+
* compenstate, a constant of 5246 is used to center the error about 0, thus
126+
* reducing the overall MSE.
127+
*/
128+
129+
/* Temperature value times two to the 23rd power, i.e. temp_23 = temp << 23 */
130+
const int32_t temp_23 = si_data->temperature * 22492 - (393006285 - 5246);
131+
/* Integer component of temperature */
132+
int32_t temp_int = temp_23 >> 23;
133+
/* Fractional component of temperature */
134+
int32_t temp_frac = temp_23 & BIT_MASK(23);
135+
136+
/* Deal with the split twos-complement / BCD format oddness with negatives */
137+
if (temp_23 < 0) {
138+
temp_int += 1;
139+
temp_frac -= BIT(23);
140+
}
141+
val->val1 = temp_int;
142+
/* Remove a constant factor of 64 from (temp_frac * 1000000) >> 23 */
143+
val->val2 = (temp_frac * 15625ULL) >> 17;
144+
145+
LOG_DBG("temperature %u = val1:%d, val2:%d", si_data->temperature,
146+
val->val1, val->val2);
123147

124148
return 0;
125149
} else if (chan == SENSOR_CHAN_HUMIDITY) {
126-
127-
int32_t relative_humidity = (((125 * (int32_t)si_data->humidity)
128-
/ 65536) - 6) * 1000000;
129-
130-
val->val1 = relative_humidity / 1000000;
131-
val->val2 = relative_humidity % 1000000;
132-
133-
LOG_DBG("humidity = val1:%d, val2:%d", val->val1, val->val2);
150+
/* Humidity times two to the 16th power. Offset of -6 not applied yet. */
151+
const uint32_t rh_16 = si_data->humidity * 125U;
152+
/* Integer component of humidity */
153+
const int16_t rh_int = rh_16 >> 16;
154+
/* Fraction component of humidity */
155+
const uint16_t rh_frac = rh_16 & BIT_MASK(16);
156+
157+
val->val1 = rh_int - 6; /* Apply offset now */
158+
/* Remove a constant factor of 64 from (rh_frac * 1000000) >> 16 */
159+
val->val2 = (rh_frac * 15625) >> 10;
160+
161+
/* Deal with the split twos-complement / BCD format oddness with negatives */
162+
if (val->val1 < 0) {
163+
val->val1 += 1;
164+
val->val2 -= 1000000;
165+
}
166+
167+
LOG_DBG("humidity %u = val1:%d, val2:%d", si_data->humidity, val->val1, val->val2);
134168

135169
return 0;
136170
} else {

0 commit comments

Comments
 (0)