在工业测量、医疗设备和精密仪器等领域,24位ADC的应用越来越广泛。ADS1256作为TI推出的高精度模数转换器,以其优异的性能和合理的价格成为许多工程师的首选。本文将手把手带你完成一个完整的项目实践,从CubeMX配置到最终电压测量,解决实际开发中可能遇到的各种问题。
要完成这个项目,你需要准备以下硬件组件:
ADS1256与STM32的连接主要涉及SPI接口和几个控制引脚:
| ADS1256引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| SCLK | SPI_SCK | SPI时钟 |
| DIN | SPI_MOSI | 主机输出 |
| DOUT | SPI_MISO | 主机输入 |
| CS | GPIO | 片选信号 |
| DRDY | GPIO | 数据就绪 |
| RESET | GPIO | 复位信号 |
提示:DRDY引脚建议配置为外部中断输入,这样可以及时响应ADC数据就绪事件,避免轮询带来的延迟。
除了SPI接口,还需要配置几个关键GPIO:
c复制// 示例GPIO初始化代码(CubeMX会自动生成)
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET); // CS
/*Configure GPIO pin : PA3 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA2 */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
ADS1256有多个可配置寄存器,常用的包括:
下面是一个典型的初始化序列:
c复制void ADS1256_Init(void)
{
// 硬件复位
HAL_GPIO_WritePin(ADS1256_RESET_GPIO_Port, ADS1256_RESET_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(ADS1256_RESET_GPIO_Port, ADS1256_RESET_Pin, GPIO_PIN_SET);
HAL_Delay(10);
// 等待DRDY变低表示芯片就绪
while(HAL_GPIO_ReadPin(ADS1256_DRDY_GPIO_Port, ADS1256_DRDY_Pin) == GPIO_PIN_SET);
// 发送同步命令
ADS1256_WriteCmd(CMD_SYNC);
HAL_Delay(1);
// 发送自校准命令
ADS1256_WriteCmd(CMD_SELFCAL);
HAL_Delay(100); // 等待校准完成
// 配置寄存器
ADS1256_WriteReg(REG_MUX, 0x01); // AIN0和AIN1差分输入
ADS1256_WriteReg(REG_ADCON, 0x20); // PGA增益=1
ADS1256_WriteReg(REG_DRATE, 0xF0); // 数据速率=30SPS
}
ADS1256的SPI通信需要特别注意时序要求。以下是核心通信函数的实现:
c复制// 写入单个命令
void ADS1256_WriteCmd(uint8_t cmd)
{
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi3, &cmd, 1, 100);
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_SET);
}
// 写入寄存器
void ADS1256_WriteReg(uint8_t reg, uint8_t val)
{
uint8_t buf[3] = {CMD_WREG | reg, 0x00, val};
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi3, buf, 3, 100);
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_SET);
// 等待DRDY变低表示写入完成
while(HAL_GPIO_ReadPin(ADS1256_DRDY_GPIO_Port, ADS1256_DRDY_Pin) == GPIO_PIN_SET);
}
// 读取ADC数据
int32_t ADS1256_ReadData(void)
{
uint8_t buf[3] = {0};
int32_t val = 0;
// 发送读取数据命令
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi3, &CMD_RDATA, 1, 100);
// 读取24位数据
HAL_SPI_Receive(&hspi3, buf, 3, 100);
HAL_GPIO_WritePin(ADS1256_CS_GPIO_Port, ADS1256_CS_Pin, GPIO_PIN_SET);
// 组合24位数据
val = (buf[0] << 16) | (buf[1] << 8) | buf[2];
// 处理符号位(24位数据是有符号的)
if(val & 0x00800000) {
val |= 0xFF000000;
}
return val;
}
ADS1256输出的是24位有符号补码数据,需要转换为实际电压值。转换公式如下:
code复制电压 = (ADC值 × Vref) / (2^23 × PGA增益)
其中:
实现代码示例:
c复制float ADS1256_ConvertToVoltage(int32_t adc_value, float vref, uint8_t pga_gain)
{
// 计算实际增益
float gain = 1.0f;
switch(pga_gain) {
case 0x00: gain = 1.0f; break;
case 0x01: gain = 2.0f; break;
case 0x02: gain = 4.0f; break;
case 0x03: gain = 8.0f; break;
case 0x04: gain = 16.0f; break;
case 0x05: gain = 32.0f; break;
case 0x06: gain = 64.0f; break;
}
// 计算电压
return (adc_value * vref) / (8388608.0f * gain); // 8388608 = 2^23
}
ADS1256有8个单端输入通道或4个差分输入通道。切换通道时需要注意:
示例代码:
c复制void ADS1256_ChangeChannel(uint8_t channel)
{
// 写入新的通道配置
ADS1256_WriteReg(REG_MUX, channel);
// 发送同步命令
ADS1256_WriteCmd(CMD_SYNC);
HAL_Delay(1);
// 发送自校准命令
ADS1256_WriteCmd(CMD_SELFCAL);
// 等待校准完成
while(HAL_GPIO_ReadPin(ADS1256_DRDY_GPIO_Port, ADS1256_DRDY_Pin) == GPIO_PIN_SET);
}
在实际应用中,可能会遇到ADC读数不稳定的问题。以下是几种可能的解决方案:
电源滤波:
输入信号处理:
基准电压稳定:
c复制// 执行系统校准的示例
void ADS1256_SystemCalibration(void)
{
ADS1256_WriteCmd(CMD_SYSGCAL);
HAL_Delay(100); // 等待校准完成
while(HAL_GPIO_ReadPin(ADS1256_DRDY_GPIO_Port, ADS1256_DRDY_Pin) == GPIO_PIN_SET);
}
一个典型的数据采集主程序可能包含以下流程:
为了方便调试,可以通过串口输出测量结果:
c复制int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI3_Init();
MX_USART1_UART_Init();
ADS1256_Init();
printf("ADS1256 High Precision ADC Demo\r\n");
while (1)
{
// 等待数据就绪
while(HAL_GPIO_ReadPin(ADS1256_DRDY_GPIO_Port, ADS1256_DRDY_Pin) == GPIO_PIN_SET);
// 读取ADC值
int32_t adc_value = ADS1256_ReadData();
// 转换为电压
float voltage = ADS1256_ConvertToVoltage(adc_value, 2.5f, 1);
// 输出结果
printf("ADC Value: %ld, Voltage: %.6f V\r\n", adc_value, voltage);
HAL_Delay(100);
}
}
为了代码的可维护性和可移植性,建议采用如下工程结构:
code复制/Drivers
/ADS1256
ads1256.c # ADS1256驱动实现
ads1256.h # 驱动头文件
/Core
/Src
main.c # 主程序
spi.c # SPI接口配置
gpio.c # GPIO配置
/Inc
main.h
spi.h
gpio.h
在项目开发中,我遇到过ADS1256读数偶尔跳变的问题,后来发现是SPI时钟线过长引起的干扰。将SPI时钟频率从1.8MHz降到1MHz并缩短走线后,问题得到解决。这也提醒我们,在高精度测量中,硬件设计细节往往比软件算法更重要。