I am working with a sensor which is a 3-axis accelerometer. It outputs data in two bytes in standard-format signed 16-bit value. The sensor is uncalibrated and the value needs to be converted to g-units. Full scale is +/-2g and I have, by measurement, a reading, As, for +1g. The current question is: How to scale the readings into g units?

One COULD use standard floating-point division, A / As, where A is the uncalibrated reading corrected for offset. BUT, in my case, the combination of the sample rate and the number of channels to be sampled make floating-point problematic; there are occasions when there is not enough remaining time to write data to a microSD card.

Another option is fixed-point arithmetic. There are at least two ways to do this: (1) a straight fixed-point division, and (2) multiply the readings by a precomputed reciprocal of As (1 / As). This posting describes a comparison of these two methods. For example purposes, we will use A = 1234 = 0x04d2 and As = 17039 = 0x428f. In this example, the calculator answer is 0.0724g.

METHOD 1 - FIXED POINT DIVISION

We start off by noting that a simple integer division of A by As will result in zero, because A is smaller than As. So, if we multiply A by some convenient value, say 0x10000, then the answer from the division will be multiplied by the same factor. This is equivalent to shifting A to the left by 16 bits. In effect, the numerator for the division problem becomes 0x04d20000. We can mentally think of this as 0x04d2.0000 (or, substitute your favorite radix point). Now, we are computing 0x04d20000/0x428f which is 0x0000128a (or, if you like 0x0000.128a). Since this is 0x010000 = 65536 times the ratio, this is the same as 0.0724. Thus, we have an answer that is within the number of significant digits shown, to the expected value.

How can this be implemented? Here is the code that was used (volatiles are used to prevent removal by optimizer):

#include <avr/io.h> //AS is assumed to be the 1g "full scale" value #define AS (int16_t)17039 //Ao is the offset corrected reading. #define Ao (int16_t)1234 // the following union is to convert an int16_t into a fixed-point int32_t // with the value in the two high bytes of the 4-byte "fixed". volatile union axis { int16_t input[2]; int32_t fixed; } axis_x; int main(void) { //put the value into the upper half of axis_x.fixed axis_x.input[1] = Ao; axis_x.input[0] = 0; volatile int32_t scaled1 = axis_x.fixed / AS; //good! ANSWER IS 256*256 * fractional part //632 cycles including union loading and division while(1) {} return(0) }

METHOD 2 - FIXED POINT RECIPROCAL MULTIPLICATION

With this method, it is first necessary to compute 1/As. This computation is not counted in the operation cycle count since it can be done just once before sampling begins. We might start by trying 1 * 65536 which effectively makes the numerator 0x010000. But, this is not sufficient because the result is only 0x000003 which does not have sufficient resolution. So, for testing purposes, uint32_t 0x01000000 (effectively 0x01.000000) will be used. Note that the radix point does NOT have to fall on byte boundaries but it makes post processing a LOT easier! Then 1/As = 0x01000000 / 0x428f = 0x000003d8 and more resolution has been provided. The result is 0x00128730 which is 0.0732; the small error is probably due to the small number of significant bytes in 1/As.

Here is the code that was used to implement this (volatiles used to prevent removal by optimizer):

#include <avr/io.h> int main(void) { volatile int32_t mult = (int32_t)0x01000000 / As; volatile int32_t scaled3 = Ao * mult; //Takes 57 clock cycles! while (1) {} return 0; }

ANALYSIS

While the “normal” fixed point division is a lot faster than a floating point one, the division using multiplication by the reciprocal of the divisor is more than 10 times faster than normal fixed-point. While an improvement was expected, it was not expected to be this great. Results may vary, according to the numeric values used in the computation.

While it might seem that the multiplication method has a penalty of a 32-bit intermediate value, the normal division, as implemented, requires a union to be implemented. Thus, the variable-memory footprint is similar.

What is NOT shown, here, is how to turn these fixed point values into ASCIIfied decimal strings. That is the next challenge. When there is something to report, a tutorial will be added.

Jim