0

I'm reading data from a sensor, that sends them as two 8bit values and user have to convert them into one signed 16bit value. I save these values into an array of 5400 elements (the sensor is accelerometer+gyro+magnetometer, so 9 axes, so in the end I'll have 300 values of each). So lets say I need reading from accelerometer's X axis. In my array I save these in first two places, so my accX readings are on indexes 0,1 then 18,19 then 36,37 and so on..... Now I need to extract these into new 16bit array with 300 elements that will represent only the X axis from accelerometer. What I came up is following:

uint8_t rawData[5400]={0};

int16_t *AccX = malloc(300*sizeof(int16_t));

for(int i=0;i<300;i++){
    AccX[i]=(rawData[i+1+i*17]<<8 | rawData[i+0+i*17]);
}
//do some calculations
free(AccX);

Yet in my AccX array are values I do not expect to be there. Any idea what might have gone wrong? The sensor is LSM9DS1.

Edit: sorry I didn't give more info about the device. I know for sure this works:

x_accel = (rawData[1]<<8 | rawData[0]);

also I inspected the rawData array and it contains correct data. It goes wrong when I try to fill that data to AccX array

muliku
  • 416
  • 3
  • 17
  • To make the index calculations easier, I suggest *two* loop variables, one for the `AccX` index, one for the `rawData` indexes. Increase the first by one, and the second by `18`. – Some programmer dude Aug 22 '18 at 13:14
  • I would change the type of `AccX` to `uint16_t *`. – mch Aug 22 '18 at 13:18
  • 2
    Endiannes ? btw. why heap ? – Bartek Woźniak Aug 22 '18 at 13:19
  • If you define a data structure consisting of 18 one-byte items in the correct order, then you can access the raw data through a pointer to that struct (and/or an array of 300 structs). It should then be relatively easy to pull out the correct bytes every time. – Tim Randall Aug 22 '18 at 13:19
  • @TimRandall that sounds like it breaks the strict aliasing rule. – mch Aug 22 '18 at 13:20
  • 1
    BTW: not directly related to your problem: it's pointless to allocate dynamic memory for small fixed size buffers. Replace `int16_t *AccX = malloc(300*sizeof(int16_t));` with `int16_t AccX[300];`. This is far more efficient and you don't need to worry about freeing the memory. – Jabberwocky Aug 22 '18 at 13:20
  • To really help you, we'd need to know a bit more about how this data is sent. Is it big-endian or little-endian? Right now you simply store byte1byte0 in memory and call it an int, is that the right order? etc... – Qubit Aug 22 '18 at 13:25
  • @Qubit data should be first LSB X, second MSB X, third LSB Y and so on... – muliku Aug 22 '18 at 13:28
  • @mch if the data is accessed through a `char*` and a pointer to struct, there should be no problem with aliasing. (And if there's any risk of this data being written to while it's being read, there are a lot more problems with this case than we were told in the initial question.) – Tim Randall Aug 22 '18 at 13:29
  • To help debugging your issue, take a look at the hexadecimal values of the data. Both the data in the `rawData` array, and the data in `AccX`. Are the bytes in the correct order in `AccX`? – Some programmer dude Aug 22 '18 at 13:32
  • "Yet in my AccX array are values I do not expect to be there" - data in rawData[] array is correct ? Did You try to convert value for some axis manually ? Use debugger after receiving data package and check your arrays values. – Bartek Woźniak Aug 22 '18 at 13:35
  • You can't do anything before you read the manual to see if the device is little endian or big endian. – Lundin Aug 22 '18 at 14:25
  • @muliku: Are you sure your raw data array has *acceleration* fields first? In the register map, the *gyroscope* registers are before the *acceleration* fields. So, I'd suggest using `[i*18+7]` and `[i*18+6]` instead. – Nominal Animal Aug 22 '18 at 14:43
  • @NominalAnimal yeah I'm sure. I inspected it and it's there. It had (for example) 0xFF4E, which in two's complement is -178 (value I'd expect) but then in the AccX was 0x1E80 – muliku Aug 22 '18 at 14:48
  • @muliku: What C compiler are you using? I don't know of any way how `0x4E 0xFF` would be converted to `0x1E80`. – Nominal Animal Aug 22 '18 at 15:21

1 Answers1

3

Look at the datasheet. Essentially, there are two separate devices in a single package.

You need the accelerometer/gyroscope device registers:

  • 21 and 22 (0x15 and 0x16) for temperature

  • 24 and 25 (0x18 and 0x19) for gyroscope X axis

  • 26 and 27 (0x1A and 0x1B) for gyroscope Y axis

  • 28 and 29 (0x1C and 0x1D) for gyroscope Z axis

  • 40 and 41 (0x28 and 0x29) for accelerometer X axis

  • 42 and 43 (0x2A and 0x2B) for accelerometer X axis

  • 44 and 45 (0x2C and 0x2D) for accelerometer Z axis

For the magnetic field, you need the magnetic sensor registers:

  • 40 and 41 (0x28 and 0x29) for X axis field strength

  • 42 and 43 (0x2A and 0x2B) for Y axis field strength

  • 44 and 45 (0x2C and 0x2D) for Z axis field strength

These are all in two's complement format, with least significant byte first.

Since practically all microcontrollers use two's complement format for signed integers (STM32 definitely does, as do ARMs and AVRs, including all Arduinos), you can write five simple helper functions. In pseudo-C:

static int16_t  temperature;
static int16_t  gyro_x, gyro_y, gyro_z;
static int16_t  accel_x, accel_y, accel_z;
static int16_t  mag_x, mag_y, mag_z;
static uint8_t  buf[6];


static void update_temperature(void)
{
    /* Read 2 bytes from registers 0x15 and 0x16 (21 and 22)
       from the accelerometer/gyroscope device, to buf[] */
    temperature = (int16_t)((((unsigned int)buf[1]) << 8) | (unsigned int)(buf[0]));
}

static void update_gyro(void)
{
    /* Read 6 bytes from registers 0x18..0x1D (24 through 29)
       from the accelerometer/gyroscope device, to buf[] */
    gyro_x = (int16_t)((((unsigned int)buf[1]) << 8) | (unsigned int)(buf[0]));
    gyro_y = (int16_t)((((unsigned int)buf[3]) << 8) | (unsigned int)(buf[2]));
    gyro_z = (int16_t)((((unsigned int)buf[5]) << 8) | (unsigned int)(buf[4]));
}

static void update_accel(void)
{
    /* Read 6 bytes from registers 0x28..0x2D (40 through 45)
       from the accelerometer/gyroscope device, to buf[] */
    accel_x = (int16_t)((((unsigned int)buf[1]) << 8) | (unsigned int)(buf[0]));
    accel_y = (int16_t)((((unsigned int)buf[3]) << 8) | (unsigned int)(buf[2]));
    accel_z = (int16_t)((((unsigned int)buf[5]) << 8) | (unsigned int)(buf[4]));
}

static void update_mag(void)
{
    /* Read 6 bytes from registers 0x28..0x2D (40 through 45)
       from the magnetic sensor device, to buf[] */
    mag_x = (int16_t)((((unsigned int)buf[1]) << 8) | (unsigned int)(buf[0]));
    mag_y = (int16_t)((((unsigned int)buf[3]) << 8) | (unsigned int)(buf[2]));
    mag_z = (int16_t)((((unsigned int)buf[5]) << 8) | (unsigned int)(buf[4]));
}

static void update_all(void)
{
    update_temperature();
    update_gyro();
    update_accel();
    update_mag();
}

The assignments look funky, but the compiler should optimize them to sane machine code.

You'll probably want to write a helper function to do the actual reads (or perhaps three functions; one for reading 2 bytes from the accelerometer/gyroscope device to buf[], one for reading 6 bytes from the accelerometer/gyroscope device to buf[], and one for reading 6 bytes from the magnetic sensor device to buf[], all from consecutive registers given as a parameter to the function).

This approach uses 26 bytes of RAM, and should be suitable for all microcontrollers, both 8-bit (AVRs) and 32-bit (STM32, ARMs). The above code is not sensitive to the byte order (endianness) of the microcontroller.


If your device sends the gyroscope, accelerometer, and magnetic sensor values via USB or serial to your computer, you need to know the byte order (endianness) of the output values is.

The sensor itself provides them in least significant byte first. STM32 is, as far as I know, little-endian too, so it should also provide them least significant byte first.

So, lets assume you receive 18*N bytes from the device, with gyroscope axes first, then accelerometer axes, and finally magnetic sensor axis readings. The following should convert those values to signed integers for easy manipulation:

#include <stdlib.h>
#include <inttypes.h>

static inline void extract16toint(int *const                 to,
                                  const unsigned char *const from,
                                  const size_t               n,
                                  const size_t               stride)
{
    const unsigned char *const end = from + n * stride;
    const unsigned char       *src = from;
    int                       *dst;

    while (src < end) {
        *(dst++) = (int16_t)(  (unsigned int)(src[0])
                            | ((unsigned int)(src[1]) << 8) );
        src += stride;
    }
}

static void extract_gyroX(int *const                  to,
                          const unsigned char *const  data,
                          const size_t                n)
{
    extract16toint(to, data + 0, n, 18);
}

static void extract_gyroY(int *const                  to,
                          const unsigned char *const  data,
                          const size_t                n)
{
    extract16toint(to, data + 2, n, 18);
}

static void extract_gyroZ(int *const                  to,
                          const unsigned char *const  data,
                          const size_t                n)
{
    extract16toint(to, data + 4, n, 18);
}

static void extract_accelX(int *const                  to,
                           const unsigned char *const  data,
                           const size_t                n)
{
    extract16toint(to, data + 6, n, 18);
}

static void extract_accelY(int *const                  to,
                           const unsigned char *const  data,
                           const size_t                n)
{
    extract16toint(to, data + 8, n, 18);
}

static void extract_accelZ(int *const                  to,
                           const unsigned char *const  data,
                           const size_t                n)
{
    extract16toint(to, data + 10, n, 18);
}

static void extract_magX(int *const                  to,
                         const unsigned char *const  data,
                         const size_t                n)
{
    extract16toint(to, data + 12, n, 18);
}

static void extract_magY(int *const                  to,
                         const unsigned char *const  data,
                         const size_t                n)
{
    extract16toint(to, data + 14, n, 18);
}

static void extract_magZ(int *const                  to,
                         const unsigned char *const  data,
                         const size_t                n)
{
    extract16toint(to, data + 16, n, 18);
}

In OP's case, calling something like

int *accelX = malloc(300 * sizeof (int));
extract_accelX(accelX, rawData, 300);

should work.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Yeah, in my previous code I had pretty much what you are saying and it worked fine. But now, when I'm trying to fill an array it shows just some junk. rawData array has values I would expect but then when I try to fill AccX array in a for loop (as in OP) it just fills it with garbage – muliku Aug 22 '18 at 14:16
  • Since the target is STM32, the 32 meaning 32 bits, `(uint16_t)buf[1]` is completely superfluous clutter. `buf[1]` will get promoted to `int` regardless of the cast. To get rid of implicit promotions and make the code robust, you need to write `gyro_x = (int16_t) ( (uint32_t)buf[1] << 8) | (uint32_t)buf[0] );`. More info at [Implicit type promotion rules](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules). – Lundin Aug 22 '18 at 14:30
  • @Lundin: Wouldn't that make `gyro_x = (int16_t)( (((uint_fast16_t)buf[1]) << 8) | ((uint_fast16_t)buf[0]) );` even better? I don't want the example to be specific for STM32. I want it to produce correct (but not silly) machine code on 8-bit microcontrollers, too. – Nominal Animal Aug 22 '18 at 14:40
  • @NominalAnimal No, in case `int` is 16 bits then `uint16_t` or `uint_fast16_t` would block the explicit promotion and work as intended. But it will not work on a 32 bitter with 16 bit data instructions, where the MCU reads 16 bit data as fast as it reads 32 bits. In such a case you would still get the implicit promotion. Notably, implicit promotion has nothing to do with performance, it is just an artificial rule intended to block overflows. (Which in practice causes more bugs than it prevents.) For fast code that is also portable to 8/16 bit MCUs, you should probably cast to `unsigned int`. – Lundin Aug 22 '18 at 14:47
  • @Lundin: You have me convinced re. `unsigned int`; it should work correctly in all cases even on oddball compilers. Some of the proprietary microcontroller C compilers don't follow standards. I know `avr-gcc`, `arm-none-eabi-gcc`, and `gcc` generate the correct code either way (same codebase, but I did check all separately to verify!), and pretty much optimal if `-Os`, `-O1`, or `-O2` is used; and using one of those is a good idea on microcontrollers.) Thank you! – Nominal Animal Aug 22 '18 at 14:56