2018-02-19 08:43:39 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2018 Eistec AB
|
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:04:09 +02:00
|
|
|
* 2018 Otto-von-Guericke-Universität Magdeburg
|
2018-02-19 08:43:39 +01:00
|
|
|
*
|
|
|
|
* 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>
|
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:04:09 +02:00
|
|
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
2018-02-19 08:43:39 +01:00
|
|
|
*
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
2020-10-21 15:58:33 +02:00
|
|
|
#include <assert.h>
|
2018-02-19 08:43:39 +01:00
|
|
|
#include <stdint.h>
|
|
|
|
#include "phydat.h"
|
|
|
|
|
|
|
|
#define ENABLE_DEBUG 0
|
|
|
|
#include "debug.h"
|
|
|
|
|
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:04:09 +02:00
|
|
|
#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,
|
|
|
|
};
|
|
|
|
|
2019-07-18 15:16:43 +02:00
|
|
|
#define LOOKUP_LEN ARRAY_SIZE(lookup_table_positive)
|
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:04:09 +02:00
|
|
|
|
|
|
|
void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim)
|
2018-02-19 08:43:39 +01:00
|
|
|
{
|
2019-07-18 15:16:43 +02:00
|
|
|
assert(dim <= ARRAY_SIZE(dat->val));
|
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:04:09 +02:00
|
|
|
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
|
|
|
|
}
|
2018-02-19 08:43:39 +01:00
|
|
|
}
|
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:04:09 +02:00
|
|
|
|
|
|
|
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);
|
2018-02-19 08:43:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|