第二章:血压计硬件接口详解
好,咱们直接进入正题。上一章我们聊了整体架构,这一章要落地了——硬件接口怎么连、怎么驱动。我做了这么多年嵌入式,发现很多开发者软件写得溜,但一到硬件接口就翻车。说白了,接口通信是嵌入式系统的命脉,血压计这种医疗设备更是马虎不得。
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采集模拟压力信号。这四个接口配合好了,血压计的硬件基础就稳了。下一章我们开始讲蓝牙协议栈的移植和配置,那才是真正考验功力的地方。