第4章:传感器驱动开发——生物电势传感器、温度传感器、加速度计驱动调试

各位好,欢迎来到第4章。

传感器驱动,说白了就是让芯片跟外部世界对话。医疗贴片里最常见的三类传感器:生物电势传感器(比如ADS129x系列)、温度传感器、加速度计。这三类传感器,我每个都踩过坑,今天把经验掰开揉碎了讲给你听。

4.1 生物电势传感器(ADS129x)驱动调试

ADS129x系列是TI家的王牌产品,专门做ECG、EEG信号采集。我第一次用ADS1298的时候,心想不就是个ADC嘛,SPI读写就完事了。结果一上电,数据全是乱码。嗯,这里要注意,这类芯片的初始化顺序非常讲究。

4.1.1 初始化流程

我个人习惯,先把芯片复位,然后配置寄存器。ADS129x的寄存器配置顺序不能乱,否则内部状态机可能卡住。

// 伪代码示例:ADS129x初始化
void ads129x_init(void) {
    // 1. 硬件复位
    RESET_PIN_LOW();
    delay_ms(10);
    RESET_PIN_HIGH();
    delay_ms(20);

    // 2. 发送SDATAC命令,停止连续读取模式
    spi_write(0x11);  // SDATAC

    // 3. 配置寄存器
    spi_write_register(CONFIG1, 0x96);  // 采样率250SPS
    spi_write_register(CONFIG2, 0xC0);  // 测试信号使能
    spi_write_register(CONFIG3, 0xEC);  // 内部参考使能

    // 4. 配置通道
    for (int i = 0; i < 8; i++) {
        spi_write_register(CHnSET + i, 0x60);  // 增益12,正常输入
    }

    // 5. 发送RDATAC命令,进入连续读取模式
    spi_write(0x12);  // RDATAC
}

我在项目中遇到过一个问题:配置完寄存器后,读取到的数据全是0x800000。折腾了两天,最后发现是CONFIG3寄存器的位7没置1,内部参考电压没打开。你想想看,参考电压都没开,ADC能正常工作才怪。

4.1.2 数据读取与同步

ADS129x的数据输出是24位有符号数,通过SPI读取。但有个坑:DRDY引脚下降沿表示数据准备好,但SPI读取必须在DRDY为低时完成,否则数据可能被覆盖。

警告: 千万不要在中断服务函数里直接做SPI读取!我曾经这么干过,结果中断嵌套导致数据错位。正确的做法是:DRDY中断里只设置一个标志位,在主循环里读取数据。
// 推荐的数据读取方式
volatile bool data_ready = false;

void DRDY_IRQHandler(void) {
    data_ready = true;  // 只设置标志位
}

void main_loop(void) {
    while (1) {
        if (data_ready) {
            data_ready = false;
            // 在主循环中读取数据
            int32_t ecg_data[8];
            ads129x_read_data(ecg_data);
            // 处理数据...
        }
    }
}

4.1.3 测试信号验证

调试时,我建议先用芯片自带的测试信号验证。ADS129x内部可以产生1Hz方波或直流信号,配置CONFIG2寄存器即可。

测试信号类型 CONFIG2配置值 预期输出
1Hz方波 0xC0 交替输出+Vref和-Vref
直流测试 0x80 固定输出Vref/2

如果测试信号读出来不对,先别怀疑芯片坏了。检查SPI时序,特别是时钟极性和相位。ADS129x要求SPI模式1(CPOL=0, CPHA=1),我见过太多人栽在这个细节上。

4.2 温度传感器驱动调试

医疗贴片里的温度传感器,通常用来监测皮肤温度或环境温度。常用的有DS18B20、TMP117这类数字传感器,也有NTC热敏电阻加ADC的方案。

4.2.1 数字温度传感器(以TMP117为例)

TMP117是TI的高精度数字温度传感器,精度±0.1°C,I2C接口。说实话,这类传感器的驱动相对简单,但有个细节:上电后需要等待转换完成。

// TMP117读取温度示例
float tmp117_read_temperature(void) {
    uint8_t buf[2];
    // 读取温度寄存器(地址0x00)
    i2c_read(0x48, 0x00, buf, 2);
    
    // 合并数据,注意是16位有符号数
    int16_t raw = (buf[0] << 8) | buf[1];
    
    // 分辨率0.0078125°C/LSB
    float temp = raw * 0.0078125f;
    return temp;
}

我记得有一次,客户反馈温度读数跳变很大。排查后发现是I2C总线上的上拉电阻太小,导致信号过冲。换了个4.7kΩ的电阻,问题就解决了。嗯,硬件上的小问题,往往在软件上表现为诡异的现象。

4.2.2 NTC热敏电阻方案

如果成本敏感,NTC方案更常见。但NTC的非线性特性,需要查表或公式计算。

技巧: 我建议用Steinhart-Hart方程计算温度,精度比查表法高,而且省Flash空间。公式是:1/T = A + B*ln(R) + C*(ln(R))^3。系数A、B、C可以从NTC手册查到,或者自己标定。
// Steinhart-Hart方程计算温度
float ntc_calc_temperature(float resistance) {
    const float A = 1.129148e-3;
    const float B = 2.34125e-4;
    const float C = 8.76741e-8;
    
    float lnR = log(resistance);
    float invT = A + B * lnR + C * lnR * lnR * lnR;
    float temp_kelvin = 1.0f / invT;
    float temp_celsius = temp_kelvin - 273.15f;
    return temp_celsius;
}

4.3 加速度计驱动调试

加速度计在医疗贴片里用来检测运动状态、跌倒检测、步态分析。常用的有ADXL345、MPU6050等。我主要讲ADXL345,因为它功耗低、接口简单,很适合电池供电的贴片设备。

4.3.1 初始化与配置

ADXL345上电后默认是待机模式,需要先进入测量模式。另外,数据格式、带宽、中断配置都要根据应用场景来设。

// ADXL345初始化
void adxl345_init(void) {
    // 1. 设置数据格式:±2g,13位分辨率,右对齐
    spi_write_register(0x31, 0x0B);  // DATA_FORMAT
    
    // 2. 设置带宽:100Hz
    spi_write_register(0x2C, 0x0A);  // BW_RATE
    
    // 3. 使能所有轴
    spi_write_register(0x2E, 0x00);  // INT_ENABLE
    
    // 4. 进入测量模式
    spi_write_register(0x2D, 0x08);  // POWER_CTL
}

这里有个坑:DATA_FORMAT寄存器的位3控制自检功能。我刚开始调试时,不小心把这个位置1了,结果读出来的加速度值全是满量程。折腾了半天才发现是自检模式打开了。你想想看,自检模式下传感器输出的是固定电压,不是真实加速度。

4.3.2 数据读取与校准

加速度计读出来的原始数据,需要做校准才能用。为什么?因为每个传感器都有零偏误差和灵敏度误差。

我个人习惯的校准方法:

  1. 零偏校准: 把传感器水平放置,采集1000个样本取平均,得到X、Y、Z轴的零偏值。
  2. 灵敏度校准: 把传感器分别朝上、朝下放置,计算灵敏度系数。
// 加速度计数据校准
typedef struct {
    float offset_x, offset_y, offset_z;
    float scale_x, scale_y, scale_z;
} accel_calib_t;

void accel_calibrate(accel_calib_t *calib) {
    // 水平放置时,理论上Z轴为1g,X、Y为0g
    // 实际采集到的值
    calib->offset_x = mean_x;
    calib->offset_y = mean_y;
    calib->offset_z = mean_z - 1.0f;  // 减去1g
    
    // 灵敏度校准(简化版)
    calib->scale_x = 1.0f;
    calib->scale_y = 1.0f;
    calib->scale_z = 1.0f;
}

float accel_get_calibrated_x(accel_calib_t *calib, float raw_x) {
    return (raw_x - calib->offset_x) * calib->scale_x;
}
重点: 医疗设备里的加速度计校准,不能只做一次。温度变化、器件老化都会影响零偏。我建议在设备每次上电时做一次快速校准,或者定期自检。

4.3.3 运动检测算法

加速度计的数据,经过滤波后才能用于运动检测。我常用的方法:

  • 低通滤波: 去除高频噪声,截止频率5-10Hz。
  • 滑动平均: 平滑数据,减少抖动。
  • 阈值检测: 判断是否发生跌倒或剧烈运动。
// 简单的一阶低通滤波
float low_pass_filter(float input, float prev_output, float alpha) {
    // alpha = 0.1 ~ 0.3,越小越平滑
    return alpha * input + (1 - alpha) * prev_output;
}

// 跌倒检测(简化版)
bool fall_detected(float accel_magnitude) {
    // 加速度幅值 = sqrt(x^2 + y^2 + z^2)
    // 正常站立时约1g,跌倒时可能超过3g或低于0.5g
    if (accel_magnitude > 3.0f || accel_magnitude < 0.5f) {
        return true;
    }
    return false;
}

我在项目中遇到过一个问题:跌倒检测的误报率太高。后来发现是用户正常走路时,加速度幅值也会短暂超过3g。解决办法是加一个时间窗口,只有持续超过阈值200ms以上才判定为跌倒。嗯,算法这东西,光看理论不行,得结合实际数据调参。

4.4 多传感器协同调试

医疗贴片里,这三个传感器往往同时工作。调试时要注意时序冲突和电源管理。

传感器 接口 采样率 功耗
ADS129x SPI 250-1000 SPS ~5mW
TMP117 I2C 1-10 SPS ~0.1mW
ADXL345 SPI/I2C 100-3200 SPS ~0.5mW

我建议的调试顺序:先调温度传感器,因为它最简单,可以验证I2C总线是否正常。然后调加速度计,验证SPI通信。最后调ADS129x,因为它的时序要求最严格。一步一步来,出问题了也好定位。

个人经验: 调试多传感器系统时,准备一个逻辑分析仪。我每次调试新板子,都会先抓一下SPI和I2C的波形。看一眼波形,就知道通信有没有问题,比看代码快多了。

好了,这一章的内容就到这里。传感器驱动开发,说白了就是跟硬件打交道。多动手、多抓波形、多读数据手册,慢慢就有感觉了。下一章我们讲电源管理与低功耗设计,那可是医疗贴片续航的关键。