Lectura de Datos Crudos del Sensor MPU9250 Mediante Interfaz I²C en el Chip nRF52832

Definición de la Interfaz Física y Asignación de Señales

La interacción entre el procesador nRF52832 y el módulo combinado MPU9250 se gestiona exclusivamente mediante el bus serie I²C. Esta topología permite la transferencia síncrona de registros de configuración y lecturas continuas de los tres sensores internos. La alimentación nominal del sistema opera a 3.3 V, requiriendo resistencias de tracción externas para garantizar la integridad de las líneas SDA y SCL bajo cargas capacitivas moderadas.

Puerto MCU nRF52832 Pin MPU9250 Rol Funcional Notas Técnicas
P0.24 SDA Datos Bidireccionales Requiere pull-up de 4.7 kΩ
P0.25 SCL Reloj Maestro Requiere pull-up de 4.7 kΩ
VBAT/VCC VCC Alimentación Lógica Regulación estable a 3.3 V
GND GND Referencia Común Tierra compartida obligatoria
P0.13 INT Señal de Intercupción Indicador de disponibilidad (opcional)

Código Base y Gestión del Controlador TWI

#include "nrf.h"
#include "nrf_drv_twi.h"
#include "app_error.h"
#include "boards.h"
#include "nrf_delay.h"
#include "nrf_log.h"

// Definiciones del protocolo
#define DIRECCION_MPU9250     0x68
#define DIRECCION_MAGNETOM    0x0C
#define ID_INSTANCIA_TWIC     0

// Mapa de direcciones críticas
#define REG_ID_CHIP           0x75
#define REG_GESTION_ALIMENT   0x6B
#define REG_CTRL_USUARIO      0x6A
#define REG_MASTRE_I2C        0x24
#define REG_DATOS_EXT_00      0x49
#define REG_CNTL_MAG          0x0A

// Estado global del bus
static const nrf_drv_twi_t i2c_ctrl = NRF_DRV_TWI_INSTANCE(ID_INSTANCIA_TWIC);
static volatile bool operacion_transmitir_finalizada = false;
static volatile bool operacion_recibir_finalizada = false;
static uint8_t buffer_temporal[12];

// Prototipos expuestos
void on_transfer_complete(nrf_drv_twi_evt_t const *evt, void *ctx);
ret_code_t configurar_bus_i2c(void);
ret_code_t verificar_identificador_chip(uint8_t *id_dev);
ret_code_t escribir_registro_mpu(uint8_t dir, uint8_t val);
ret_code_t leer_registros_mpu(uint8_t dir, uint8_t *buf, uint8_t longitud);
ret_code_t inicializar_submodulo_mag(void);
ret_code_t capturar_ejes_inerciales(int16_t *eje_x, int16_t *eje_y, int16_t *eje_z, bool es_giroscopio);
ret_code_t obtener_fuerza_magnetica(int16_t *campo_x, int16_t *campo_y, int16_t *campo_z);
void desplegar_valores_capturados(int16_t *acel, int16_t *giro, int16_t *mag);

Configuración Inicial del Periférico

void on_transfer_complete(nrf_drv_twi_evt_t const *evt, void *ctx) {
    if (evt->type == NRF_DRV_TWI_EVT_DONE) {
        switch (evt->xfer_desc.type) {
            case NRF_DRV_TWI_XFER_TX:
                operacion_transmitir_finalizada = true;
                break;
            case NRF_DRV_TWI_XFER_RX:
            case NRF_DRV_TWI_XFER_TXRX:
                operacion_recibir_finalizada = true;
                break;
            default:
                break;
        }
    } else if (evt->type == NRF_DRV_TWI_EVT_ADDRESS_NACK || evt->type == NRF_DRV_TWI_EVT_DATA_NACK) {
        NRF_LOG_WARNING("Fallo de acuse en dirección o datos");
    }
}

ret_code_t configurar_bus_i2c(void) {
    ret_code_t codigo_err;
    
    nrf_drv_twi_config_t configuracion_mpu = {
        .scl                = ARDUINO_SCL_PIN,
        .sda                = ARDUINO_SDA_PIN,
        .frequency          = NRF_DRV_TWI_FREQ_100K,
        .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
        .clear_bus_init     = false
    };

    codigo_err = nrf_drv_twi_init(&i2c_ctrl, &configuracion_mpu, on_transfer_complete, NULL);
    APP_ERROR_CHECK(codigo_err);
    
    return nrf_drv_twi_enable(&i2c_ctrl);
}

Secuencia de Arranque y Verificación de Identidad

ret_code_t verificar_identificador_chip(uint8_t *id_dev) {
    return leer_registros_mpu(REG_ID_CHIP, id_dev, sizeof(*id_dev));
}

ret_code_t configurar_registros_internos(void) {
    ret_code_t err;
    uint8_t chip_id;

    err = verificar_identificador_chip(&chip_id);
    if (err != NRF_SUCCESS || chip_id != 0x71) {
        NRF_LOG_ERROR("Dispositivo no detectado. ID recibido: 0x%02X", chip_id);
        return NRF_ERROR_NOT_FOUND;
    }

    // Salir de modo standby y fijar reloj interno
    err = escribir_registro_mpu(REG_GESTION_ALIMENT, 0x00);
    if (err != NRF_SUCCESS) return err;

    // Frecuencia de muestreo: 1 kHz / (1+7) ≈ 125 Hz
    err = escribir_registro_mpu(0x19, 0x07);
    if (err != NRF_SUCCESS) return err;

    // Filtro paso bajo digital (DLPF) habilitado
    err = escribir_registro_mpu(0x1A, 0x06);
    if (err != NRF_SUCCESS) return err;

    // Rango giroscopio: ±2000°/s
    err = escribir_registro_mpu(0x1B, 0x18);
    if (err != NRF_SUCCESS) return err;

    // Rango acelerómetro: ±16g
    err = escribir_registro_mpu(0x1C, 0x18);
    if (err != NRF_SUCCESS) return err;

    // Preparar ruta para mag vía esclavo I²C
    err = escribir_registro_mpu(REG_CTRL_USUARIO, 0x00);
    if (err != NRF_SUCCESS) return err;

    nrf_delay_ms(10);
    
    // Activar clock maestro y velocidad relativa
    err = escribir_registro_mpu(REG_MASTRE_I2C, 0x0D);
    return err;
}

Rutinas de Adquisición Asíncrona

ret_code_t leer_registros_mpu(uint8_t direccion_registro, uint8_t *buffer_datos, uint8_t cantidad_bytes) {
    ret_code_t res;
    
    operacion_transmitir_finalizada = false;
    nrf_drv_twi_xfer_desc_t envio = NRF_DRV_TWI_XFER_DESC_TX(DIRECCION_MPU9250, &direccion_registro, 1);
    res = nrf_drv_twi_xfer(&i2c_ctrl, &envio, NRF_DRV_TWI_FLAG_TX_POSTINC);
    if (res != NRF_SUCCESS) return res;
    
    while (!operacion_transmitir_finalizada);

    operacion_recibir_finalizada = false;
    nrf_drv_twi_xfer_desc_t接收 = NRF_DRV_TWI_XFER_DESC_RX(DIRECCION_MPU9250, buffer_datos, cantidad_bytes);
    res = nrf_drv_twi_xfer(&i2c_ctrl, &接收, 0);
    if (res != NRF_SUCCESS) return res;
    
    while (!operacion_recibir_finalizada);
    
    return NRF_SUCCESS;
}

ret_code_t escribir_registro_mpu(uint8_t registro, uint8_t valor) {
    ret_code_t respuesta;
    
    operacion_transmitir_finalizada = false;
    uint8_t payload[2] = {registro, valor};
    nrf_drv_twi_xfer_desc_t transmision = NRF_DRV_TWI_XFER_DESC_TX(DIRECCION_MPU9250, payload, 2);
    respuesta = nrf_drv_twi_xfer(&i2c_ctrl, &transmision, NRF_DRV_TWI_FLAG_TX_POSTINC);
    
    while (!operacion_transmitir_finalizada);
    return respuesta;
}

Extracción de Magnitudes Físicas y Conversión Numérica

ret_code_t capturar_ejes_inerciales(int16_t *resultado_eje_x, int16_t *resultado_eje_y, int16_t *resultado_eje_z, bool tipo_dato) {
    ret_code_t estado;
    uint8_t puntero_reg = tipo_dato ? 0x43 : 0x3B; // 0x43=Gyro, 0x3B=Accel
    
    estado = leer_registros_mpu(puntero_reg, buffer_temporal, 6);
    if (estado != NRF_SUCCESS) return estado;

    *resultado_eje_x = (int16_t)((buffer_temporal[0] << 8) | buffer_temporal[1]);
    *resultado_eje_y = (int16_t)((buffer_temporal[2] << 8) | buffer_temporal[3]);
    *resultado_eje_z = (int16_t)((buffer_temporal[4] << 8) | buffer_temporal[5]);
    
    return NRF_SUCCESS;
}

ret_code_t obtener_fuerza_magnetica(int16_t *valor_x, int16_t *valor_y, int16_t *valor_z) {
    ret_code_t retorno;
    uint8_t flag_estado;
    
    retorno = leer_registros_mpu(0x02, &flag_estado, 1);
    if (retorno != NRF_SUCCESS) return retorno;

    if (!(flag_estado & 0x01)) return NRF_SUCCESS;

    retorno = leer_registros_mpu(REG_DATOS_EXT_00, buffer_temporal, 7);
    if (retorno != NRF_SUCCESS) return retorno;

    if (buffer_temporal[6] & 0x08) {
        NRF_LOG_WARNING("Desbordamiento del magnetómetro AK8963");
        return NRF_ERROR_DATA_SIZE;
    }

    *valor_x = (int16_t)((buffer_temporal[1] << 8) | buffer_temporal[0]);
    *valor_y = (int16_t)((buffer_temporal[3] << 8) | buffer_temporal[2]);
    *valor_z = (int16_t)((buffer_temporal[5] << 8) | buffer_temporal[4]);
    
    return NRF_SUCCESS;
}

Despliegue de Registro y Transformación a Unidades Estándar

void desplazar_valores_en_usart(int16_t *datos_acel, int16_t *datos_giro, int16_t *datos_mag) {
    float escala_acel = 2048.0f;
    float escala_giro = 16.4f;
    float escala_mag  = 0.15f;

    NRF_LOG_RAW_INFO("Ejes Acum: X=%6d Y=%6d Z=%6d  ", datos_acel[0], datos_acel[1], datos_acel[2]);
    NRF_LOG_RAW_INFO("Giros:    X=%6d Y=%6d Z=%6d  ", datos_giro[0], datos_giro[1], datos_giro[2]);
    NRF_LOG_RAW_INFO("Campo Mag:X=%6d Y=%6d Z=%6d\r\n", datos_mag[0], datos_mag[1], datos_mag[2]);

    float g_real[] = { datos_acel[0]/escala_acel, datos_acel[1]/escala_acel, datos_acel[2]/escala_acel };
    float dps_real[] = { datos_giro[0]/escala_giro, datos_giro[1]/escala_giro, datos_giro[2]/escala_giro };
    float ut_real[] = { datos_mag[0]*escala_mag, datos_mag[1]*escala_mag, datos_mag[2]*escala_mag };

    NRF_LOG_RAW_INFO("Acumuladores: %.2fg %.2fg %.2fg  ", g_real[0], g_real[1], g_real[2]);
    NRF_LOG_RAW_INFO("Velocidad Ang.: %.2f°/s %.2f°/s %.2f°/s  ", dps_real[0], dps_real[1], dps_real[2]);
    NRF_LOG_RAW_INFO("Intensidad Campo: %.2fuT %.2fuT %.2fuT\r\n", ut_real[0], ut_real[1], ut_real[2]);
}

Optimización de Throughput e Integrcaión de Algoritmos

Para aplicaciones que requieren alta frecuencia de muestreo (>100Hz), se recomienda habilitar el módulo DMA del SOC. Esto libera la CPU de bucles de espera activos, permitiendo procesamiento concurrente de fusión inercial. El flujo de lectura puede consolidarse en una única transacción de escritura-leitura masiva recuperando 14 bytes consecutivos (Acelerómetro 6B + Temperatura 2B + Giroscopio 6B), reduciendo la sobrecarga de handshaking del bus.

La estimación de orientación espacial suele implementarse mediante filtros complementarios o algoritmos Mahony. La combinación ponderada de la integral del giroscopio (propagación rápida pero propensa a deriva) con la proyección gravitacional calculada mediante atan2 del acelerómetro proporcinoa una trayectoria estable. La corrección de cabeceo (yaw) requiere integración del magnetómetro compensada por inclinación.

La transmisión inalámbrica hacia terminales móviles se realiza típicamente mediante el servicio NUS (Nordic UART Service) del stack BLE. Los valores crudos enteros de 16 bits se empaquetan en estructuras contiguas y se envían a través de características escritas en modo Notificación, minimizando latencia frente al protocolo GATT tradicional.

Matriz de Diagnóstico de Errores Frecuentes

  • NACK en Dirección I²C: Verificar polaridad de líneas SDA/SCL, asegurar tracciones adecuadas y confirmar que AD0 está conectado a GND o VCC según corresponda.
  • HOFL Permanente en AK8963: Revisar secuencia de arranque del esclavo magnético. Incrementar tiempo de retardo tras escritura en CNTL. Eliminar fuentes electromagnéticas cercanas.
  • Deriva Térmica en Giroscopio: Implementar compensación basada en el sensor de temperatura interno del MPU9250. Ejecutar rutina de calibración en reposo antes de cada sesión operativa.
  • Corrupción de Tramas Masivas: Reducir baudrate del bus a 50kHz temporalmente para validar integridad física antes de escalar a 400kHz.

Especificaciones Técnicas y Referencias Normativas

Velocidad Operativa Bus 100 kHz / 400 kHz
Resolución ADC Interno 16 Bits Signed
Rango Dinámico Acelerómetro ±2g, ±4g, ±8g, ±16g
Rango Dinámico Giroscopio ±250, ±500, ±1000, ±2000 °/s
Capacidad Magnetómetro ±4800 µT (Modo 16-bit)
Frecuencia Máxima Lectura Continua 1 kHz (depende DLPF)
  • MPU-9250 Product Specification v1.4 (InvenSense)
  • AK8963 Datasheet (Asahi Kasei Microdevices)
  • nRF52832 - nRF52 Series Product Specification v1.6
  • nRF5 SDK Documentation: Drivers & Peripherals: I²C (TWI/NVIC)
  • Application Note AN-160: Orientation Sensing With MEMS Accelerometers (Bosch)

Etiquetas: nRF52832 MPU9250 Protocolo I²C Firmwares Embarcados Calibración IMU

Publicado el 8-13 20:07