第二章:血压计硬件接口详解

好,咱们直接进入正题。上一章我们聊了整体架构,这一章要落地了——硬件接口怎么连、怎么驱动。我做了这么多年嵌入式,发现很多开发者软件写得溜,但一到硬件接口就翻车。说白了,接口通信是嵌入式系统的命脉,血压计这种医疗设备更是马虎不得。

2.1 I2C总线驱动气压传感器

气压传感器是血压计的核心感知元件。我常用的方案是MPS20N0040D或者NXP的MPXV5050GP,它们大多通过I2C接口输出数据。为什么选I2C?因为引脚少、布线简单,对于血压计这种小尺寸设备非常友好。

关键参数:血压计气压传感器通常需要测量0~300mmHg范围,精度要求±1mmHg。I2C时钟频率建议设为100kHz(标准模式),别贪快,医疗设备稳定第一。

驱动代码其实不复杂,但有个坑——传感器上电后需要一段稳定时间。我记得第一次调试时,上电立刻读数据,结果全是0xFF。后来查手册才发现,传感器内部有个自检过程,至少等50ms才能正常通信。

// I2C读取气压传感器数据示例
#define SENSOR_ADDR 0x28
#define PRESSURE_REG 0x00

uint8_t i2c_read_pressure(uint16_t *pressure) {
    uint8_t buf[2];
    
    // 发送寄存器地址
    if (HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, &PRESSURE_REG, 1, 100) != HAL_OK) {
        return 1; // 通信失败
    }
    
    // 等待传感器准备数据
    HAL_Delay(10);  // 我个人习惯加10ms延时,确保数据稳定
    
    // 读取2字节压力值
    if (HAL_I2C_Master_Receive(&hi2c1, SENSOR_ADDR, buf, 2, 100) != HAL_OK) {
        return 2; // 读取失败
    }
    
    // 组合数据,注意大端序
    *pressure = ((uint16_t)buf[0] << 8) | buf[1];
    return 0;
}

经验之谈:I2C总线上拉电阻别省。我见过有人用内部上拉,结果通信时好时坏。建议外部上拉4.7kΩ,走线尽量短,远离电磁阀驱动电路。

2.2 SPI接口驱动OLED屏幕

OLED屏幕用来显示测量结果。0.96寸128x64的SSD1306是经典选择,SPI接口。为什么用SPI不用I2C?因为刷新速度快,显示动态数据不卡顿。你想想看,测量过程中要实时显示压力曲线,I2C那速度真不够用。

SPI驱动OLED,说白了就是往显存里写数据。SSD1306内部有个1KB的GRAM,你写什么它就显示什么。我建议用DMA方式传输,别用轮询——CPU宝贵,还要跑算法呢。

// SPI发送一帧数据到OLED
void oled_send_frame(uint8_t *buffer) {
    // 选中OLED(CS拉低)
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET);
    
    // 发送命令:设置页地址
    oled_write_cmd(0xB0);
    
    // 用DMA发送数据,不阻塞CPU
    HAL_SPI_Transmit_DMA(&hspi1, buffer, 128 * 8);
    
    // 取消选中
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET);
}

注意:SPI的时钟极性(CPOL)和时钟相位(CPHA)必须和OLED手册一致。SSD1306通常用模式0(CPOL=0, CPHA=0)或模式3(CPOL=1, CPHA=1)。我曾经因为搞反了这两个参数,屏幕死活不亮,查了两天才发现是SPI模式配置错了。

2.3 GPIO控制气泵与电磁阀

气泵负责充气,电磁阀负责放气。这两个器件是血压计的“手脚”,控制好了测量才准。GPIO控制其实很简单,但要注意驱动能力——MCU的GPIO直接驱动气泵?别闹,电流不够,还会烧引脚。

正确的做法是加MOS管或继电器。我习惯用N沟道MOS管(比如AO3400)做低端驱动,电路简单成本低。电磁阀同理,但要注意续流二极管,否则关断瞬间的反向电动势会击穿MOS管。

器件 控制方式 驱动电路 注意事项
气泵 GPIO高电平开启 N-MOS + 10kΩ下拉 充气电流约300mA,MOS管选型留余量
电磁阀 GPIO高电平开启 N-MOS + 续流二极管 关断时会产生高压,二极管必须加
蜂鸣器 PWM控制 三极管驱动 测量结束提示音,频率2kHz左右
// 气泵控制代码示例
void pump_control(uint8_t state) {
    if (state) {
        HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_SET);
        // 开启气泵,开始充气
    } else {
        HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_RESET);
        // 关闭气泵,停止充气
    }
}

// 电磁阀控制(快速放气)
void valve_open(void) {
    HAL_GPIO_WritePin(VALVE_GPIO_Port, VALVE_Pin, GPIO_PIN_SET);
    // 电磁阀开启,袖带快速放气
}

void valve_close(void) {
    HAL_GPIO_WritePin(VALVE_GPIO_Port, VALVE_Pin, GPIO_PIN_RESET);
}

避坑指南:我曾经遇到过气泵开启瞬间导致MCU复位的问题。原因是气泵启动电流太大,拉低了电源电压。解决方案是在气泵电源端加一个470μF的电解电容,同时软件上做软启动——先PWM低占空比开启,再逐渐加大。

2.4 ADC采集压力信号

除了I2C气压传感器,有些方案还用模拟气压传感器(比如MPX5050GP),输出模拟电压,需要ADC采集。MCU内部ADC通常够用,12位分辨率,参考电压3.3V,可以分辨约0.8mV,对应约0.15mmHg,精度足够了。

ADC采集有个关键点——滤波。压力信号有噪声,尤其是气泵工作时,振动会引入干扰。我一般做两步滤波:硬件上在ADC引脚加一个100nF电容到地,软件上做滑动平均滤波。

// 滑动平均滤波,取最近8次采样平均值
#define FILTER_SIZE 8

uint16_t adc_filter(uint16_t new_value) {
    static uint16_t buffer[FILTER_SIZE] = {0};
    static uint8_t index = 0;
    static uint32_t sum = 0;
    
    // 减去旧值,加上新值
    sum -= buffer[index];
    sum += new_value;
    buffer[index] = new_value;
    
    index = (index + 1) % FILTER_SIZE;
    
    return (uint16_t)(sum / FILTER_SIZE);
}

// ADC转换结果转换为压力值(mmHg)
float adc_to_pressure(uint16_t adc_value) {
    // 假设传感器0V对应0mmHg,3.3V对应300mmHg
    float voltage = (float)adc_value * 3.3f / 4096.0f;
    float pressure = voltage * 300.0f / 3.3f;
    return pressure;
}

核心要点:ADC采样率不用太高,血压信号频率很低,100Hz采样足够了。采样率太高反而引入更多噪声,还浪费CPU资源。我一般设200Hz,然后做10ms窗口的均值滤波。

嗯,到这里硬件接口部分就讲完了。总结一下:I2C负责气压传感器通信,SPI驱动OLED显示,GPIO控制气泵和电磁阀,ADC采集模拟压力信号。这四个接口配合好了,血压计的硬件基础就稳了。下一章我们开始讲蓝牙协议栈的移植和配置,那才是真正考验功力的地方。