sl-firmware e21526327b feat: Auto-detect magnetometer + barometer (#24)
Shared I2C1 bus (i2c1.c/h, PB8=SCL PB9=SDA 100kHz):
- i2c1_init() called once in main() before sensor probes.
- hi2c1 exported globally; baro and mag drivers use it directly.

Barometer (bmp280.c):
- Probes I2C1 at 0x76 then 0x77 (covers both SDO options).
- bmp280_init() returns chip_id (0x58/0x60) on success, neg if absent.
- Added bmp280_pressure_to_alt_cm() — ISA barometric formula.
- Added bmp280.h (was missing).

Magnetometer (mag.c / mag.h):
- Auto-detects QMC5883L (0x0D, id=0xFF), HMC5883L (0x1E, id='H43'),
  IST8310 (0x0E, id=0x10) in that order.
- mag_read_heading() returns degrees×10 (0–3599) or -1 if not ready.
- HMC5883L: correct XZY byte order applied.
- IST8310: single-measurement trigger mode.

main.c:
- i2c1_init() + bmp280_init() + mag_init() after all other inits.
- Both skip gracefully (baro_ok=0, mag_type=MAG_NONE) if not present.
- Telemetry JSON: incremental builder appends ",\"hd\":<n>" when mag
  found and ",\"alt\":<n>" when baro found. No extra bytes when absent.

UI (index.html):
- HEADING and ALT rows hidden until first packet with that field.
- Heading shown in degrees, alt in metres (firmware sends cm).

Closes #24.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:48:53 -05:00

156 lines
5.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* mag.c — Auto-detect magnetometer on I2C1
*
* Probes for QMC5883L (0x0D), HMC5883L (0x1E), IST8310 (0x0E) in that order.
* Returns detected type; mag_read_heading() returns heading in degrees×10.
*
* Requires i2c1_init() called before mag_init().
*/
#include "mag.h"
#include "i2c1.h"
#include <math.h>
/* ------------------------------------------------------------------ */
/* I2C helpers (7-bit addr left-shifted for HAL) */
/* ------------------------------------------------------------------ */
static uint16_t s_addr;
static mag_type_t s_type = MAG_NONE;
static uint8_t mag_rreg(uint8_t reg) {
uint8_t v = 0;
HAL_I2C_Mem_Read(&hi2c1, s_addr, reg, 1, &v, 1, 50);
return v;
}
static void mag_wreg(uint8_t reg, uint8_t val) {
HAL_I2C_Mem_Write(&hi2c1, s_addr, reg, 1, &val, 1, 50);
}
static void mag_rregs(uint8_t reg, uint8_t *buf, uint8_t len) {
HAL_I2C_Mem_Read(&hi2c1, s_addr, reg, 1, buf, len, 50);
}
/* ------------------------------------------------------------------ */
/* QMC5883L — QUST QMC5883L (0x0D) */
/* chip_id reg 0x0D = 0xFF */
/* Data regs 0x00-0x05: XL XH YL YH ZL ZH */
/* ------------------------------------------------------------------ */
static int qmc_init(void) {
s_addr = 0x0D << 1;
uint8_t id = mag_rreg(0x0D);
if (id != 0xFF) return 0;
/* Reset */
mag_wreg(0x0B, 0x01); /* SET/RESET period = 0x01 */
HAL_Delay(10);
/* Control 1: continuous mode (0x01), 200 Hz ODR (0x0C), 512 OSR (0x00), 2G (0x00) */
mag_wreg(0x09, 0x0D); /* MODE=cont, ODR=200Hz, RNG=2G, OSR=512 */
HAL_Delay(10);
return 1;
}
static int16_t qmc_heading(void) {
/* Status reg 0x06, bit0=DRDY */
if (!(mag_rreg(0x06) & 0x01)) return -1;
uint8_t buf[6];
mag_rregs(0x00, buf, 6);
int16_t x = (int16_t)((buf[1] << 8) | buf[0]);
int16_t y = (int16_t)((buf[3] << 8) | buf[2]);
float heading_deg = atan2f((float)y, (float)x) * (180.0f / 3.14159265f);
if (heading_deg < 0.0f) heading_deg += 360.0f;
return (int16_t)(heading_deg * 10.0f);
}
/* ------------------------------------------------------------------ */
/* HMC5883L — Honeywell HMC5883L (0x1E) */
/* ID regs 0x0A='H', 0x0B='4', 0x0C='3' */
/* Data regs 0x03-0x08: XH XL ZH ZL YH YL (note XZY order!) */
/* ------------------------------------------------------------------ */
static int hmc_init(void) {
s_addr = 0x1E << 1;
uint8_t a = mag_rreg(0x0A);
uint8_t b = mag_rreg(0x0B);
uint8_t c = mag_rreg(0x0C);
if (a != 'H' || b != '4' || c != '3') return 0;
mag_wreg(0x00, 0x70); /* Config A: 8 samples/output, 15 Hz, normal */
mag_wreg(0x01, 0x20); /* Config B: gain=1090 LSB/Ga */
mag_wreg(0x02, 0x00); /* Mode: continuous measurement */
HAL_Delay(10);
return 1;
}
static int16_t hmc_heading(void) {
/* Status reg 0x09, bit0=RDY */
if (!(mag_rreg(0x09) & 0x01)) return -1;
uint8_t buf[6];
mag_rregs(0x03, buf, 6);
int16_t x = (int16_t)((buf[0] << 8) | buf[1]);
int16_t y = (int16_t)((buf[4] << 8) | buf[5]); /* HMC order: XZY */
float heading_deg = atan2f((float)y, (float)x) * (180.0f / 3.14159265f);
if (heading_deg < 0.0f) heading_deg += 360.0f;
return (int16_t)(heading_deg * 10.0f);
}
/* ------------------------------------------------------------------ */
/* IST8310 — iSentek IST8310 (0x0E) */
/* WHO_AM_I reg 0x00 = 0x10 */
/* Data regs 0x03-0x08: XL XH YL YH ZL ZH */
/* ------------------------------------------------------------------ */
static int ist_init(void) {
s_addr = 0x0E << 1;
uint8_t id = mag_rreg(0x00);
if (id != 0x10) return 0;
/* Soft reset via CNTL2 reg 0x0B bit[0] */
mag_wreg(0x0B, 0x01);
HAL_Delay(20);
/* CNTL1 reg 0x0A: single measurement mode, trigger on write */
/* Use continuous mode bit[2]=1 if supported — trigger manually instead */
mag_wreg(0x0A, 0x01); /* single measurement */
HAL_Delay(10);
return 1;
}
static int16_t ist_heading(void) {
/* Trigger a measurement */
mag_wreg(0x0A, 0x01);
HAL_Delay(10); /* IST8310 needs ~6.6ms for single measurement */
/* STAT1 reg 0x02 bit0=DRDY */
if (!(mag_rreg(0x02) & 0x01)) return -1;
uint8_t buf[6];
mag_rregs(0x03, buf, 6);
int16_t x = (int16_t)((buf[1] << 8) | buf[0]);
int16_t y = (int16_t)((buf[3] << 8) | buf[2]);
float heading_deg = atan2f((float)y, (float)x) * (180.0f / 3.14159265f);
if (heading_deg < 0.0f) heading_deg += 360.0f;
return (int16_t)(heading_deg * 10.0f);
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
mag_type_t mag_init(void) {
if (qmc_init()) { s_type = MAG_QMC5883L; return s_type; }
if (hmc_init()) { s_type = MAG_HMC5883L; return s_type; }
if (ist_init()) { s_type = MAG_IST8310; return s_type; }
s_type = MAG_NONE;
return MAG_NONE;
}
int16_t mag_read_heading(void) {
switch (s_type) {
case MAG_QMC5883L: return qmc_heading();
case MAG_HMC5883L: return hmc_heading();
case MAG_IST8310: return ist_heading();
default: return -1;
}
}