Kode Dot

Magnetometer

The compass in a Kode Dot: a QMC5883P on the shared I2C bus, what it measures, why the board itself is the first thing it sees, and the code to read it.

What it is

The magnetometer measures the magnetic field where the Kode Dot is, on three axes, in gauss. Away from magnets and iron, that field is the Earth's: about half a gauss, pointing roughly north and, in Europe, steeply down. The direction of the field across the two horizontal axes is a compass heading.

The part is a QMC5883P from QST: a magneto-resistive bridge on each axis, a 16-bit converter, and temperature compensation on the chip, so what you read out is already corrected. It is a sensor of field, not of north: anything magnetic near it adds to what it reads, and on the Kode Dot several things are.

How it is connected

flowchart LR
p4["ESP32-P4"] -- "I2C, 0x2C" --> mag["QMC5883P"]
One part on the shared I2C bus, at its own address

The QMC5883P sits on the P4's shared I2C bus at 0x2C, brought up by kode_bsp_init and reached through kode_bsp_i2c(). It has no interrupt line and needs none: you configure it, then read six bytes whenever you want the field. Whether its data-ready pin reaches the P4 is TBD; no Kode example uses it.

Specifications

ThingValue
Address0x2C. Register 0x00 reads 0x80, the part's identity
Range±30, ±12, ±8 or ±2 gauss, bits 3:2 of CTRL2 (0x0B): 1000, 2500, 3750 or 15000 counts per gauss
Rate10, 50, 100 or 200 Hz, bits 3:2 of CTRL1 (0x0A). Mode in bits 1:0: suspend, normal, single, continuous. Suspended out of reset
Output0x01–0x06: x y z, int16_t, low byte first, temperature-compensated on the chip. Bit 0 of 0x09 says a sample is ready
SetupThe datasheet's sequence: 0x29 ← 0x06, then CTRL2, then CTRL1
Resolution2 mG on X and Y, 3 mG on Z, at ±30 gauss
Supply2.5–3.6 V, 35 µA typical at 10 Hz. Bit 7 of CTRL2 is a soft reset
Kode driverNone yet. TBD: a QMC5883P driver in kode-sdk
ValidatedTBD: not yet run on a V1 board. Register map from QST's datasheet, rev. E

Gotchas

Example code

Put the compass in normal mode and log the field ten times a second. Turn the Kode Dot a full circle on the table and each axis swings by hundreds of milligauss.

/*
 * compass.c - 2026-09-17 - from Kode with love
 *
 * Reads the Kode Dot's QMC5883P over the shared I2C bus. Puts the part in
 * normal mode at 200 Hz on the ±8 gauss range and logs the field in
 * milligauss ten times a second. Turn the Kode Dot on the table and the
 * three numbers swing by hundreds of milligauss.
 */
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "kode.h"
 
#define MAG_ADDR    0x2C
#define REG_XOUT_L  0x01   /* six bytes: x y z, low byte first */
#define REG_CTRL1   0x0A   /* mode, rate, oversampling */
#define REG_CTRL2   0x0B   /* range, set/reset, soft reset */
#define REG_SIGN    0x29   /* axis sign; the datasheet's setup writes 0x06 */
 
static i2c_master_dev_handle_t s_mag;
 
/* One register write: address byte, then the value. */
static void mag_write(uint8_t reg, uint8_t value)
{
    uint8_t frame[2] = { reg, value };
    ESP_ERROR_CHECK(i2c_master_transmit(s_mag, frame, 2, 100));
}
 
/* Attach the compass to the bus kode_bsp owns, configure it, then log forever. */
void app_main(void)
{
    ESP_ERROR_CHECK(kode_bsp_init());
    i2c_device_config_t cfg = { .device_address = MAG_ADDR, .scl_speed_hz = KODE_I2C_FREQ_HZ };
    ESP_ERROR_CHECK(i2c_master_bus_add_device(kode_bsp_i2c(), &cfg, &s_mag));
 
    mag_write(REG_SIGN, 0x06);    /* the datasheet's setup sequence, in its order */
    mag_write(REG_CTRL2, 0x08);   /* ±8 gauss, set/reset on */
    mag_write(REG_CTRL1, 0xCD);   /* normal mode, 200 Hz, oversampling 8 */
 
    while (1) {
        uint8_t reg = REG_XOUT_L, raw[6];
        ESP_ERROR_CHECK(i2c_master_transmit_receive(s_mag, &reg, 1, raw, 6, 100));
        int16_t x = (int16_t)(raw[0] | raw[1] << 8);
        int16_t y = (int16_t)(raw[2] | raw[3] << 8);
        int16_t z = (int16_t)(raw[4] | raw[5] << 8);
        ESP_LOGI("mag", "field %6ld %6ld %6ld mG", x * 1000L / 3750, y * 1000L / 3750, z * 1000L / 3750);
        vTaskDelay(pdMS_TO_TICKS(100));   /* 3750 counts per gauss on the ±8 G range */
    }
}

Compiled on the date in the header against the kode component with ESP-IDF v6.1. TBD: a run on a V1 board.

In the SDK