Duda con logical shift y magnetómetros.

Buenas tardes, Estoy trabando con el magnetómetro HMC5883L.
encontré unos códigos en Internet que funcionan a la perfección, lo que no entiendo es el porque de los Shift.
En las paginas donde saque el código no dice nada al respecto, y en otros sitios donde estuve buscando tampoco hacen mención al tema.
Alguien tiene idea de por que se utilizan?

#include <Wire.h> //I2C Arduino Library

#define addr 0x1E //I2C Address for The HMC5883

void setup() {

  Serial.begin(9600);
  Wire.begin();


  Wire.beginTransmission(addr); //start talking
  Wire.write(0x02); // Set the Register
  Wire.write(0x00); // Tell the HMC5883 to Continuously Measure
  Wire.endTransmission();
}


void loop() {

  int x, y, z; //triple axis data
  float angulo;
  //Tell the HMC what regist to begin writing data into
  Wire.beginTransmission(addr);
  Wire.write(0x03); //start with register 3.
  Wire.endTransmission();


  //Read the data.. 2 bytes for each axis.. 6 total bytes
  Wire.requestFrom(addr, 6);
  if (6 <= Wire.available()) {
    x = Wire.read() << 8; //MSB  x
    x |= Wire.read(); //LSB  x
    z = Wire.read() << 8; //MSB  z
    z |= Wire.read(); //LSB z
    y = Wire.read() << 8; //MSB y
    y |= Wire.read(); //LSB y

    angulo = atan2(y, x);
    angulo = angulo * (180 / M_PI); //convertimos de Radianes a grados

  }

  // Show Values
  Serial.print("X Value: ");
  Serial.println(x);
  Serial.print("Y Value: ");
  Serial.println(y);
  Serial.print("Z Value: ");
  Serial.println(z);
  Serial.print("Angle Value: ");
  Serial.println(angulo);
  Serial.println(" ");


  delay(500);
}

Desde ya muchas gracias

Cabe recordar que un sistema digital se basa en dos valores: alto/bajo, 0/1, etc.; debido a esto último, todo dato “digitalizado” es representado mediante uno o una secuencia de estos valores. Cuando esta secuencia alcanza una longitud de 8 cifras (bits), se forma lo que conocemos como el byte, siendo esta la “unidad de información” más básica más utilizada. Un conjunto de estos bytes, permite poder almacenar más información distinguible que con uno solo. Ejemplo de esto último: con dos bytes, se puede almacenar un rango más amplio de números, que con uno solo.

En AVR (arquitectura de los microcontroladores de los Arduinos más populares), el tipo de dato int es un espacio en memoria de 16 bits (2 bytes); mientras que Wire lo que nos ofrece es un flujo (“stream”) de bytes, dicho en otras palabras, un flujo de datos que se manipula de 8 en 8 bits, y no de 16 en 16 bits (recuerda que antes había mencionado que 8 bits o un byte es la unidad de información más básica en un sistema digital, de ahí que trabaje de esa forma).

Debido a que dicho flujo funciona con un byte a la vez, las operaciones de bits se utilizan para rellenar espacios de más de 8 bits (o en casos raros donde más bien es de menos):

x = Wire.read() << 8; //MSB  x

x es una variable de tipo int, o mejor dicho, de 16 bits.
La primera operación es de “desplazamiento a la izquierda”; al desplazar 8 bits a la izquierda… adivinaste: rellenamos los 8 bits más significativos (MSB) de la variable.

x |= Wire.read(); //LSB  x

Esta operación se le conoce como “o inclusivo” (“OR”), la cual dicta que el resultado es 1 si al menos un operando es 1 también.
¿Y esto por qué? Primero que nada, si se realiza la operación de asignación (’=’), el valor se nos sobrescribe totalmente, perdiendo así el resultado de la operación anterior; y pues obviamente esto es algo que no desearías hacer.

Luego tenemos que uno de los operandos es de 16 bits, y debido a que las operaciones de bits sólo se pueden realizar cuando ambos operandos tienen la misma longitud; el de 8 bits (resultado de Wire.read()) se convierte también a 16 bits, rellenando con ceros el espacio sobrante.
Tomando en cuenta esto último, y recordando la premisa del “o inclusivo”; lo que va ocurrir es que: todo bit que tenga que estar en 1, lo estará; caso contrario, se dejará tal y cómo estaba en al principio.
Para este caso, los bits que introdujimos para el MSB no se tocarán y ahora se introducen los del LSB; para así finalmente recibir un valor de 16 bits en un flujo de 8 bits.

Usualmente hubiera dicho que esta sería una alternativa al caso:

Wire.readBytes((byte*)&x, sizeof(int));

Pero… algo no cuadra aquí…

Ejemplo: si el módulo tenía pensado enviar 1 (valor, no es un bit), con esas operaciones de bits, la variable leería 1; hasta aquí todo bien.
Si hubieras utilizado mi método, la variable leería 256. La pregunta curiosa es: ¿y esto? ¿Por qué el método de una sola línea no sirve y el de los bits sí?

Esto se responde con la presencia de una característica que poseen los números representados por más de un byte: el orden o “endianness”.
AVR (Arduinos populares) siempre los interpreta a manera de “little-endian”; que quiere decir que el LSB va primero.

Espera un momento… si para Arduino el LSB va primero… ¿por qué en el código de primero aparece más bien el MSB?
Por alguna razón, no todos los sistemas computacionales trabajan igual en ese aspecto. Seguramente tu módulo es de “big-endian”, que obviamente es lo contrario a “little-endian”; y donde, efectivamente, el MSB va primero.

En resumen: las operaciones de bits son “más o menos” un proceso de conversión, mientras que mi línea de código solo se limita a copiar bytes, sin importar el orden.

Genial, buenísima la información. Muchas gracias