第三章 硬件接口基础:GPIO配置与中断、UART/I2C/SPI通信协议、ADC采样与DMA传输
各位同学,欢迎来到第三章。这一章我们聊聊硬件接口,说白了就是芯片跟外界打交道的那几根“触角”。做医疗贴片,传感器要读数据,无线模块要发数据,控制信号要拉高拉低——这些全得靠接口。我这些年调试贴片产品,踩过的坑有一半都出在接口配置上。今天我把经验掰开揉碎讲给你听。
3.1 GPIO配置与中断:最基础也最容易翻车
GPIO,通用输入输出口。听起来简单吧?就两个状态:高电平或低电平。但医疗贴片里,GPIO往往连着关键信号——比如电极检测、按键唤醒、LED指示。配置不对,整个系统就废了。
3.1.1 推挽输出 vs 开漏输出
我个人习惯,驱动LED或蜂鸣器这类负载,用推挽输出。它能主动拉高拉低,驱动能力强。但如果是I2C总线,必须用开漏输出,加上拉电阻。为什么?因为开漏允许多个设备共用一根线,谁拉低谁说话。
避坑指南:我曾经在项目里用推挽输出接了I2C的SCL线,结果两个设备同时拉高时直接短路,烧了一个传感器。嗯,从那以后我每次画原理图都会再三确认GPIO模式。
3.1.2 中断配置:别让CPU空转
医疗贴片讲究低功耗。CPU不能一直轮询按键或传感器状态,那太费电了。所以要用中断——让外设在事件发生时主动通知CPU。
配置中断时,有几点要注意:
- 触发方式:上升沿、下降沿、双边沿、低电平、高电平。我建议按键用下降沿触发,配合硬件消抖(RC滤波)。软件消抖?嗯,能不用就不用,它会让CPU醒来多次。
- 中断优先级:医疗贴片里,心率检测中断优先级最高,按键次之,通信最低。别搞反了,否则心跳数据丢了可是要出事的。
- 中断服务函数要短:只做标志位设置,别在里面做复杂计算。你想想看,中断里跑个浮点运算,其他中断全堵死了。
// 我常用的GPIO中断配置示例(STM32平台)
void GPIO_Init_With_Interrupt(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0; // 按键引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP; // 内部上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0); // 优先级设高
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
// 中断服务函数——只做一件事
void EXTI0_IRQHandler(void)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
{
key_pressed_flag = 1; // 设标志位
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
}
}
小技巧:调试中断时,可以在中断里翻转一个测试GPIO,用示波器看波形。这样能直观看到中断响应时间和触发频率。我每次调中断都这么干,比看日志快多了。
3.2 UART/I2C/SPI通信协议:选对协议省一半功夫
医疗贴片里,传感器和主控之间要通信。常见的就三种:UART、I2C、SPI。选哪个?看场景。
3.2.1 UART:简单但别忽略流控
UART最常用在调试日志输出,或者跟蓝牙模块通信。配置就三个参数:波特率、数据位、停止位。我一般用115200波特率,8数据位,1停止位,无校验。
但注意,UART没有时钟线,收发双方必须约定好波特率。如果两边晶振精度不一样,时间长了就会丢字节。医疗贴片里,我建议用带硬件流控(RTS/CTS)的UART,或者干脆用DMA+环形缓冲区,防止数据溢出。
警告:我曾经在量产批次中发现,某批芯片的UART波特率偏差达到3%,导致通信不稳定。排查了两天才发现是晶振负载电容匹配问题。所以,量产前一定要做波特率误差测试。
3.2.2 I2C:两根线搞定多设备
I2C只需要SDA和SCL两根线,可以挂多个设备。医疗贴片里,温度传感器、加速度计、电量计经常用I2C。配置时注意:
- 速率:标准模式100kHz,快速模式400kHz。别盲目用高速,线长了容易出问题。我一般用100kHz,稳定第一。
- 地址:7位地址还是10位地址?大部分传感器用7位。注意地址冲突,两个设备地址一样就完蛋了。
- 上拉电阻:I2C总线必须加上拉电阻,阻值根据总线电容和速率算。我习惯用4.7kΩ,大部分场景都适用。
// I2C读取传感器数据的典型流程
uint8_t I2C_ReadSensor(uint8_t dev_addr, uint8_t reg_addr)
{
uint8_t data = 0;
HAL_I2C_Master_Transmit(&hi2c1, dev_addr, ®_addr, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, dev_addr, &data, 1, 100);
return data;
}
3.2.3 SPI:速度快,但线多
SPI适合高速数据传输,比如刷显示屏、读SD卡。它需要4根线:SCK、MOSI、MISO、CS。配置时注意:
- 时钟极性(CPOL)和相位(CPHA):这个最容易搞错。我每次都要查数据手册,确认是模式0还是模式3。搞反了,读出来的数据全是乱的。
- 片选信号:每个从设备需要一个独立的CS引脚。别偷懒共用CS,除非你确定设备支持菊花链。
经验之谈:SPI调试时,用逻辑分析仪抓SCK和MISO的波形。我遇到过好几次,代码看起来没问题,但波形显示时钟毛刺太多,最后发现是PCB走线太长导致的信号反射。加个小电阻(22Ω)串联在SCK线上就解决了。
3.3 ADC采样与DMA传输:不占CPU的采样方案
医疗贴片里,ADC用来采集生物信号——心电、肌电、血氧。这些信号微弱、变化快,采样率要求高。如果每次采样都让CPU去读,CPU就干不了别的了。所以要用DMA。
3.3.1 ADC配置:分辨率与采样时间
ADC配置有几个关键参数:
| 参数 | 说明 | 我的建议 |
|---|---|---|
| 分辨率 | 12位、10位、8位等 | 医疗信号用12位,精度够用 |
| 采样时间 | ADC采样保持的时间 | 信号源阻抗高时,采样时间要长 |
| 参考电压 | 内部参考或外部参考 | 要求精度时用外部参考 |
| 转换模式 | 单次、连续、扫描 | 多通道用扫描+连续模式 |
嗯,这里要注意:采样时间不是越长越好。太长了,采样率上不去;太短了,采样值不准。我一般根据传感器数据手册推荐的采样时间,再留20%余量。
3.3.2 DMA传输:让数据自己跑
DMA(直接存储器访问)可以在没有CPU干预的情况下,把ADC转换结果自动搬运到内存里。配置DMA时,我习惯用循环模式——这样DMA会不停地填满缓冲区,满了就从头开始覆盖。CPU只需要在空闲时处理缓冲区里的数据。
// ADC + DMA 配置示例
void ADC_DMA_Init(void)
{
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);
// 启动后,ADC自动转换,DMA自动搬运,CPU可以睡觉
}
// 在main循环中处理数据
void Process_ADC_Data(void)
{
// 检查DMA传输完成标志
if(adc_dma_complete_flag)
{
// 处理adc_buffer中的数据
for(int i = 0; i < ADC_BUFFER_SIZE; i++)
{
// 计算实际电压值
float voltage = (float)adc_buffer[i] * 3.3f / 4096.0f;
// 存入环形缓冲区供后续处理
ring_buffer_push(voltage);
}
adc_dma_complete_flag = 0;
}
}
实用技巧:DMA缓冲区大小要跟采样率匹配。比如采样率1kHz,你希望每100ms处理一次数据,那缓冲区大小就是100个样本。太小了,CPU处理不过来;太大了,数据延迟高。我一般设成1024个样本,够用。
3.3.3 避坑:DMA与CPU的数据竞争
DMA在后台写缓冲区,CPU在前台读缓冲区。如果不同步,就会读到半新半旧的数据。解决办法:用双缓冲区或乒乓缓冲。DMA写一个缓冲区时,CPU读另一个。切换时用标志位同步。
我曾经踩过的坑:在一个心电贴片项目中,我用了单缓冲区+DMA循环模式。结果CPU读数据时,DMA正好覆盖了还没处理的部分,导致心电波形出现周期性毛刺。后来改成双缓冲区,问题立刻消失。所以,别省那点内存,双缓冲区是必须的。
小结
这一章我们聊了GPIO配置与中断、三种通信协议、以及ADC+DMA采样。这些都是医疗贴片嵌入式开发的基本功。你想想看,一个贴片要采集生物信号、跟手机通信、还要低功耗运行——哪一步离得开这些接口?
下一章,我们会深入实时操作系统(RTOS)的任务调度与低功耗管理。到时候我会分享更多实战中的血泪教训。嗯,今天就到这里,各位回去可以拿开发板试试配置一个ADC+DMA的例程,看看波形对不对。