简介:本资源是一套面向嵌入式医疗电子开发者的STM32F047与ADS1299硬件协同测试方案,专为心电(ECG)、脑电(EEG)等生物电信号采集系统原型验证设计,适用于具备C#基础及Keil uVision开发经验的中级以上工程师。压缩包共230个文件,含59个.h头文件(定义寄存器映射与接口协议)、54个.c源码(涵盖STM32外设驱动、ADS1299初始化、SPI通信、数据读取与预处理逻辑),以及.o目标文件、.uvprojx工程配置和.hex烧录镜像等,完整支撑从编译到调试的全流程,6.2MB体积轻量易部署。已有111人学习下载,资源提供可直接运行的Keil工程框架、ADS1299关键参数(增益/采样率/通道)配置示例、生物信号采集时序控制代码及初步数据解析逻辑,显著降低高精度AFE芯片集成门槛,助力快速构建便携式生理监测设备原型。
1. 这不是普通ADC测试:ADS1299在STM32F047上跑通心电采集链路,意味着你能真正验证生物电信号前端的完整性
ADS1299不是一块普通ADC芯片——它是TI专为医疗级生物电采集设计的8通道、24位ΔΣ模数转换器,内置PGA、参考源、右腿驱动(RLD)和可编程滤波器,常用于ECG、EEG、EMG设备。而STM32F047是Cortex-M0内核的入门级32位单片机,主频48MHz,资源紧凑(32KB Flash/4KB RAM),却具备SPI主控、DMA、低功耗定时器等关键外设。把ADS1299“跑起来”远不止“读出几个数字”,它要求你完整打通:SPI时序严格匹配(ADS1299要求CPOL=1, CPHA=1,且SCLK空闲高)、DRDY中断同步采样、寄存器配置链(从复位→时钟→通道使能→数据格式→启动转换)、以及对24位数据流的字节序解析与校验。这个.7z压缩包里的测试程序,本质是一套可验证硬件连接正确性、SPI通信鲁棒性、寄存器初始化逻辑完备性的最小闭环系统。适合刚完成PCB焊接的工程师快速定位是芯片没焊好、SPI线接反、还是寄存器写错;也适合教学场景中让学生理解高精度ADC与MCU协同工作的底层约束。它不依赖RTOS或复杂GUI,纯裸机C实现,所有关键路径都暴露在Keil MDK工程里——包括那个被高频搜索的keilkilll.bat,它只是清理编译残留的辅助脚本,真正核心在ads1299.c和stm32f0xx_spi.c中。
2. 从硬件连接到SPI初始化:为什么ADS1299必须用STM32F047的SPI1且不能用软件模拟
2.1 ADS1299与STM32F047的物理层约束必须逐条对齐
ADS1299工作在3.3V供电,其SPI接口为标准四线制(SCLK、DIN、DOUT、CS),但存在三个易被忽略的电气与时序硬约束:
- SCLK极性与相位:ADS1299数据手册明确要求CPOL=1(空闲时SCLK为高电平)、CPHA=1(数据在SCLK第二个边沿采样)。STM32F047的SPI外设支持该模式,但若误配为CPOL=0/CPHA=0,DIN写入会失败,DOUT读取全为0xFF。
- CS(/CS)有效电平与时序:ADS1299的片选信号为低有效,且要求CS下降沿后至少等待50ns才能发送第一个SCLK脉冲;CS上升沿前,SCLK必须已稳定在空闲状态(高电平)。STM32F047的GPIO直接控制CS时,需用
__NOP()插入延时,或启用SPI硬件NSS(但F0系列SPI1仅支持软件NSS)。 - DRDY中断响应窗口:ADS1299每完成一次转换(默认125SPS),拉低DRDY引脚。该信号必须接入STM32F047的外部中断线(如PA0),且中断服务程序(ISR)必须在2μs内响应——否则可能丢失下一个DRDY脉冲。F047的EXTI0中断优先级需设为最高(
NVIC_SetPriority(EXTI0_IRQn, 0)),并关闭中断嵌套。
提示:常见错误是将ADS1299的VREF引脚悬空或接错。它必须由内部2.4V基准(通过寄存器配置)或外部精密2.4V源驱动,若接3.3V会导致满量程计算错误,实测ADC输出值始终卡在0x7FFFFF附近。
2.2 STM32F047 SPI1初始化代码必须显式禁用CRC并配置DMA缓冲区
ADS1299不支持SPI CRC校验,且其DOUT数据流为连续24位(3字节),无帧间隔。因此SPI初始化必须关闭CRC,并预分配足够大的DMA接收缓冲区以避免溢出:
// ads1299_spi_init.c void ADS1299_SPI_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // 使能SPI1时钟 RCC->AHBENR |= RCC_AHBENR_GPIOAEN; // PA4-CS, PA5-SCK, PA6-MISO, PA7-MOSI // 配置GPIO:推挽复用输出(SCK/MOSI),浮空输入(MISO),开漏输出(CS) GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER4 | GPIO_MODER_MODER5 | GPIO_MODER_MODER6 | GPIO_MODER_MODER7)) | (GPIO_MODER_MODER4_0 | GPIO_MODER_MODER5_0 | GPIO_MODER_MODER6_0 | GPIO_MODER_MODER7_0); GPIOA->OTYPER = GPIO_OTYPER_OT_4; // PA4为开漏输出(CS) GPIOA->OSPEEDR = GPIO_OSPEEDR_OSPEEDR4 | GPIO_OSPEEDR_OSPEEDR5 | GPIO_OSPEEDR_OSPEEDR6 | GPIO_OSPEEDR_OSPEEDR7; GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xF<<16 | 0xF<<20 | 0xF<<24 | 0xF<<28)) | (0x0<<16 | 0x0<<20 | 0x0<<24 | 0x0<<28); // AF0 for SPI1 // SPI1配置:CPOL=1, CPHA=1, BR=2(24MHz SCLK/2=12MHz,满足ADS1299最大16MHz) SPI1->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_CPOL | SPI_CR1_CPHA; // 关键:CPOL和CPHA置位 SPI1->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2; // 8-bit data size(注意:ADS1299寄存器操作用8位,数据读取用24位需分三次) // 启用SPI1 SPI1->CR1 |= SPI_CR1_SPE; }2.2.1 为什么必须用SPI1而非SPI2?
STM32F047只有两个SPI:SPI1挂载在APB2总线(最高48MHz),SPI2挂载在APB1(最高36MHz)。ADS1299在125SPS下,单次转换需传输3字节数据,理论带宽需求为125×3×8=3kHz,看似SPI2足够。但实际调试中发现:SPI2在CPOL=1/CPHA=1模式下存在时序抖动,导致DOUT采样错误率超5%。而SPI1在相同配置下误码率为0——这是F047硅片级差异,官方勘误表未提及,但大量用户实测证实。因此,工程实践中强制绑定SPI1。
2.2.2 DMA缓冲区大小如何确定?
ADS1299默认输出速率125SPS,每通道24位,8通道全开时原始数据流为125×8×3=3000字节/秒。但测试程序通常只启用1~2通道,且采用DRDY中断触发单次读取。因此DMA接收缓冲区设为uint8_t rx_buffer[32](容纳10次24位读取)即可。若启用循环DMA模式,需确保SPI1->CR2 |= SPI_CR2_RXDMAEN且DMA1_Channel2->CCR |= DMA_CCR_EN,但F047的DMA通道2仅支持SPI1_RX,不可混用。
3. 寄存器配置链与24位数据解析:ADS1299上电后必须执行的7个关键步骤
3.1 ADS1299寄存器初始化顺序不可颠倒,否则芯片进入未知状态
ADS1299的寄存器映射为16个8位地址(0x00~0x0F),但部分寄存器(如CONFIG1,CONFIG2)写入后需等待内部稳态。标准初始化流程如下(对应ads1299_init.c中ADS1299_Init()函数):
| 步骤 | 寄存器地址 | 写入值 | 作用 | 等待条件 |
|---|---|---|---|---|
| 1 | 0x00 (REG_CNFG) | 0x00 | 软件复位 | 写入后延时10ms |
| 2 | 0x01 (CONFIG1) | 0x96 | 设置CLK=1.024MHz, DR=125SPS, WCT=1 | 无 |
| 3 | 0x02 (CONFIG2) | 0xC0 | 启用内部基准,PGA增益=1 | 无 |
| 4 | 0x03 (LOFF) | 0x00 | 关闭导联脱落检测 | 无 |
| 5 | 0x04 (CH1SET) | 0x01 | 通道1使能,输入短接到REFIN | 无 |
| 6 | 0x0E (DATA_RATE) | 0x01 | 设置数据格式为24位,MSB first | 无 |
| 7 | 0x0F (STATUS) | 0x00 | 清除状态寄存器 | 无 |
注意:步骤2中
CONFIG1=0x96的二进制为10010110,其中bit7=1表示使用内部振荡器(无需外部晶振),bit6:4=001表示数据速率为125SPS,bit3:0=0110表示WCT(Wilson Central Terminal)模式。若误写为0x86(bit7=0),芯片将等待外部CLK输入,DRDY永不拉低。
3.2 24位数据读取必须分三次SPI传输并手动拼接
ADS1299的DOUT线在DRDY下降沿后,连续输出3个字节(MSB→LSB)。STM32F047的SPI硬件只能处理8位/16位传输,因此需执行三次独立SPI读取,并按字节序重组:
// ads1299_read.c uint32_t ADS1299_ReadData(void) { uint8_t tx_buf[3] = {0xFF, 0xFF, 0xFF}; // 发送dummy byte触发DOUT uint8_t rx_buf[3]; GPIOA->BSRR = GPIO_BSRR_BR_4; // CS低电平选中 for(int i = 0; i < 3; i++) { while(!(SPI1->SR & SPI_SR_TXE)); // 等待TXE标志 SPI1->DR = tx_buf[i]; // 发送dummy while(!(SPI1->SR & SPI_SR_RXNE)); // 等待RXNE rx_buf[i] = SPI1->DR; // 读取DOUT } GPIOA->BSRR = GPIO_BSRR_BS_4; // CS高电平释放 // 拼接24位:rx_buf[0]=MSB, rx_buf[2]=LSB,需符号扩展为32位 uint32_t data = ((uint32_t)rx_buf[0] << 16) | ((uint32_t)rx_buf[1] << 8) | rx_buf[2]; return (data & 0x00800000) ? (data | 0xFF000000) : data; // 补码扩展 }3.2.1 为什么tx_buf必须全为0xFF?
ADS1299在DOUT输出期间,DIN线处于高阻态,但SPI主机会持续输出时钟。若tx_buf为0x00,则SCLK期间DIN为低电平,可能被误判为寄存器写入指令。全0xFF确保DIN保持高电平,避免干扰。
3.2.2 符号扩展的必要性
ADS1299输出为24位二进制补码,范围-8,388,608 ~ +8,388,607。当MSB(bit23)为1时,表示负数,需将高8位填充为1。data | 0xFF000000即实现此扩展,否则printf("%d", data)会输出巨大正数。
4. Keil MDK工程结构与keilkilll.bat的真实作用:如何快速定位编译失败的根源
4.1 工程目录中不可删除的5个核心文件及其依赖关系
该.7z包解压后典型的Keil工程结构如下:
ADS1299_Test/ ├── CMSIS/ # 标准外设库头文件(stm32f0xx.h等) ├── FWLIB/ # STM32F0标准外设驱动(spi.c, gpio.c等) ├── USER/ # 用户代码 │ ├── main.c # 主循环:初始化→DRDY中断使能→死循环 │ ├── ads1299.c # ADS1299专用函数:Init(), ReadData(), WriteReg() │ └── stm32f0xx_it.c # 中断服务:EXTI0_IRQHandler()处理DRDY ├── OUTPUT/ # 编译输出目录(可删) ├── Listings/ # 汇编列表文件(可删) └── keilkilll.bat # 批处理脚本其中ads1299.c依赖FWLIB/src/stm32f0xx_spi.c中的SPI_I2S_SendData()和SPI_I2S_ReceiveData(),而stm32f0xx_it.c必须在main.c中调用NVIC_EnableIRQ(EXTI0_IRQn)。若编译报错undefined reference to 'SPI_I2S_SendData',说明FWLIB/src/下的.c文件未加入Keil工程的“Source Group 1”。
4.2 keilkilll.bat不是“破解工具”,而是清理编译缓存的标准化脚本
网络搜索中大量用户误将keilkilll.bat当作Keil授权绕过工具,实则它仅执行三行命令:
@echo off del /q .\Objects\*.axf del /q .\Objects\*.hex del /q .\Objects\*.htm pause其作用是强制删除Keil生成的.axf(可执行镜像)、.hex(Intel Hex格式)和.htm(编译报告),解决以下两类问题:
- 增量编译失效:修改
ads1299.c后,Keil未重新编译spi.c,导致旧版SPI驱动加载,DRDY中断无响应; - Flash下载失败:
.axf文件被占用时,ST-Link下载器报错Cannot access Memory,删掉后重编译即可。
提示:Keil 5用户需将
keilkilll.bat放在工程根目录,右键选择“以管理员身份运行”。若双击无反应,检查Windows是否禁用.bat脚本执行(组策略中“运行脚本”需启用)。
5. 验证程序是否真正跑通的3个硬性指标与1个终极技巧
5.1 必须观测的三个硬件信号波形
仅靠串口打印"OK"无法证明ADS1299正常工作。需用示波器捕获以下信号:
| 信号 | 测试点 | 正常特征 | 异常表现 |
|---|---|---|---|
| DRDY | ADS1299 pin 22 | 方波,周期8ms(125SPS),高电平时间≈3μs | 无脉冲(CS未拉低/CLK未启振)或周期≠8ms(CONFIG1写错) |
| SCLK | ADS1299 pin 21 | 12MHz方波,占空比50%,空闲时为高电平 | 频率错误(BR位设错)、空闲为低(CPOL=0) |
| DOUT | ADS1299 pin 20 | DRDY下降沿后,连续3个字节脉冲,每个字节8位,MSB先传 | 数据恒为0xFF(DIN未上拉/CS时序错)、字节间有间隙(SPI未连续读) |
5.2 终极技巧:用ADC输出值反推PGA增益与基准电压偏差
ADS1299通道1配置为CH1SET=0x01(输入短接到REFIN),此时理想输出应为0x000000(0V差分输入)。但实测值常为0x001234或0xFFEDCB,这反映两个关键参数:
- 零点偏移(Offset):
0x001234表示正向偏移,可通过写入OFFSET1寄存器(0x05)补偿; - 增益误差(Gain Error):若REFIN实际为2.39V(非标称2.4V),则满量程
0x7FFFFF对应电压为2.39 × (1 + PGA增益) / 2^23。用万用表测REFIN电压,代入公式反算PGA实际增益,验证CONFIG2中增益位是否生效。
例如:测得REFIN=2.395V,实测满幅值0x7F8000,则计算增益 =(0x7F8000 / 0x7FFFFF) × (2.4 / 2.395) × 1 = 0.998,说明PGA增益偏差-0.2%,在医疗设备允许范围内(±0.5%)。
本文还有配套的精品资源,点击获取