1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/phydat/phydat.c
Marian Buschsieweke fe46cae00d
sys/phydat: New phydat_fit API
The current phydat_fit implementation the following limitations:
- The API is way more complicated to use than needed
- It doesn't perform any rounding
- It uses `long` in a place where actual width (or better range) of the type
  is pretty important.

This commit addresses these limitations and uses lookup-tables to reduce the
number of divisions required.

Before this commit code using it looked like this:
``` C
long values[] = { 100000, 2000000, 30000000 };
phydat_t dat = { .scale = 42, .unit = UNIT_V };
phydat_fit(&dat, values[0], 0, phydat_fit(&dat, values[1], 1, phydat_fit(&dat, values[2], 2, 0)));
```

Now it can be used like this:
``` C
int32_t values[] = { 100000, 2000000, 30000000 };
phydat_t dat = { .unit = UNIT_V, .scale = 42 };
phydat_fit(&dat, values, 3);
```
2018-10-24 13:05:51 +02:00

121 lines
3.1 KiB
C

/*
* Copyright (C) 2018 Eistec AB
* 2018 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_phydat
* @{
*
* @file
* @brief Generic sensor/actuator data handling
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <stdint.h>
#include "phydat.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#ifndef PHYDAT_FIT_TRADE_PRECISION_FOR_ROM
#define PHYDAT_FIT_TRADE_PRECISION_FOR_ROM 1
#endif
static const uint32_t lookup_table_positive[] = {
327674999,
32767499,
3276749,
327674,
32767,
};
#if !(PHYDAT_FIT_TRADE_PRECISION_FOR_ROM)
static const uint32_t lookup_table_negative[] = {
327684999,
32768499,
3276849,
327684,
32768,
};
#endif
static const uint32_t divisors[] = {
100000,
10000,
1000,
100,
10,
};
#define LOOKUP_LEN (sizeof(lookup_table_positive) / sizeof(int32_t))
void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim)
{
assert(dim <= (sizeof(dat->val) / sizeof(dat->val[0])));
uint32_t divisor = 0;
uint32_t max = 0;
const uint32_t *lookup = lookup_table_positive;
/* Get the value with the highest magnitude and the correct lookup table.
* If PHYDAT_FIT_TRADE_PRECISION_FOR_ROM is true, the same lookup table will
* be used for both positive and negative values. As result, -32768 will be
* considered as out of range and scaled down. So statistically in 0.00153%
* of the cases an unneeded scaling is performed, when
* PHYDAT_FIT_TRADE_PRECISION_FOR_ROM is true.
*/
for (unsigned int i = 0; i < dim; i++) {
if (values[i] > (int32_t)max) {
max = values[i];
#if !(PHYDAT_FIT_TRADE_PRECISION_FOR_ROM)
lookup = lookup_table_positive;
#endif
}
else if (-values[i] > (int32_t)max) {
max = -values[i];
#if !(PHYDAT_FIT_TRADE_PRECISION_FOR_ROM)
lookup = lookup_table_negative;
#endif
}
}
for (unsigned int i = 0; i < LOOKUP_LEN; i++) {
if (max > lookup[i]) {
divisor = divisors[i];
dat->scale += 5 - i;
break;
}
}
if (!divisor) {
/* No rescaling required */
for (unsigned int i = 0; i < dim; i++) {
dat->val[i] = values[i];
}
return;
}
/* Applying scale and add half of the divisor for correct rounding */
uint32_t divisor_half = divisor >> 1;
for (unsigned int i = 0; i < dim; i++) {
if (values[i] >= 0) {
dat->val[i] = (uint32_t)(values[i] + divisor_half) / divisor;
}
else {
/* For negative integers the C standards seems to lack information
* on whether to round down or towards zero. So using positive
* integer division as last resort here.
*/
dat->val[i] = -((uint32_t)((-values[i]) + divisor_half) / divisor);
}
}
}