AD7606:从硬件连接到 STM32 串行驱动软件分层实现
前言
AD7606 是工业采集、电源监控、运动控制和多通道数据采集系统中常见的一颗同步采样 ADC。它支持 8/6/4 通道同步采样,分辨率为 16 位,输入范围支持 ±10V 和 ±5V,内部集成输入钳位、1MΩ 模拟输入阻抗、二阶抗混叠滤波器、2.5V 基准、过采样数字滤波器,并支持高速串行和并行接口。官方资料也明确说明,AD7606 支持 SPI/QSPI/MICROWIRE/DSP 兼容的串行接口,最高可实现所有通道 200kSPS 的采样吞吐率。(模拟器件)
在 STM32 项目中,AD7606 可以使用并行接口读取,也可以使用串行接口读取。并行接口速度快,但占用 IO 多;串行接口占用 IO 少,连接更简单,更适合多数中低速工业控制场景。本文以 STM32 通过 GPIO 或 SPI 思路串行读取 AD7606 为主线,从硬件连接、关键时序、数据格式、APP/BSP 软件分层几个方面完整说明。
一、AD7606 串行驱动的硬件连接
STM32 串行驱动 AD7606 时,核心信号可以分为五类:
| 信号类型 | 典型引脚 | 作用 |
|---|---|---|
| 转换控制 | CONVST_A、CONVST_B | 启动 ADC 同步转换 |
| 转换状态 | BUSY | 判断转换是否正在进行 |
| 串行读取 | CS、RD/SCLK、DOUTA、DOUTB | 读取转换结果 |
| 模式配置 | RANGE、OS0、OS1、OS2、PAR/SER/BYTE SEL | 配置量程、过采样、接口模式 |
| 系统控制 | RESET、STBY、REF_SELECT | 复位、待机、基准选择 |
硬件连接示意如下:

AD7606 采用单 5V 模拟供电,同时提供 VDRIVE 引脚用于匹配数字接口电平。若 STM32 使用 3.3V IO,通常应使 VDRIVE 也工作在 3.3V 逻辑域。AD7606 产品资料中给出的 VDRIVE 范围为 2.3V 至 5V,因此可以兼容常见 MCU 电平。(模拟器件)
二、硬件设计重点
1. 串行模式选择
AD7606 支持灵活的并行/串行接口。若采用串行读取方式,PAR/SER/BYTE SEL 必须配置为串行模式。否则,STM32 即使产生了 CS 和 SCLK,也无法从 DOUTA 得到正确的串行数据。
串行模式下,一般使用以下信号完成读数:
CS :片选信号
RD/SCLK :串行时钟
DOUTA :串行数据输出 A
DOUTB :串行数据输出 B,可选
如果只使用 DOUTA,所有通道数据会依次从 DOUTA 移出;如果同时使用 DOUTA 和 DOUTB,可以提高读取效率。
2. 量程选择 RANGE
AD7606 支持两种真双极性输入范围:
RANGE = 1:±10V
RANGE = 0:±5V
具体电平逻辑应以所用芯片手册和原理图为准。工程调试时,应先明确系统前端输入范围。例如电源模块、模拟量采集模块中常见的是 ±10V 或 0~10V。若硬件配置为 ±10V,而上位机只关心 0~10V,则软件协议层还需要做额外映射。
3. 过采样 OS0~OS2
AD7606 提供硬件引脚配置过采样倍率。过采样可以降低噪声、提高稳定性,但会降低有效带宽和采样响应速度。官方资料中也说明,AD7606 内部带有可由引脚驱动的数字滤波功能,可提升 SNR 并降低 3dB 带宽。(模拟器件)
常见调试建议:
初始调试:关闭过采样
噪声较大:再逐步开启 2 倍、4 倍或更高过采样
响应要求高:不要盲目开启高倍率过采样
4. CONVST_A 和 CONVST_B
AD7606 是同步采样 ADC,转换由 CONVST_A 和 CONVST_B 触发。对于多通道同步采样,工程上常见做法是让 CONVST_A 和 CONVST_B 同时动作,或者在硬件上将两者短接。
推荐原则:
需要全部通道同步采样:CONVST_A 和 CONVST_B 同步触发
只采部分通道:根据具体芯片型号和通道分组处理
不确定硬件连接:优先通过示波器确认 CONVST_A/B 实际波形
5. 空载输入不等于 0V
AD7606 的模拟输入阻抗为 1MΩ,属于高阻输入。输入端悬空时,通道电压不会自然等于 0V,而是容易受到漏电流、PCB 杂散、电磁干扰、前级电路状态等影响。官方资料明确列出 AD7606 具有 1MΩ 模拟输入阻抗。(模拟器件)
因此,空载时不同通道读数不同,并不能直接判定 ADC 异常。正确验证方式是:
1. 将输入端短接到对应模拟地
2. 观察原始码是否接近 0
3. 再接入标准电压源验证线性关系
若产品要求未接线时显示稳定 0,可在模拟前端增加合适阻值的下拉电阻,例如 100kΩ、470kΩ 或 1MΩ,具体取值应结合输入阻抗、采样精度和前级电路决定。
三、AD7606 串行读取时序
一次完整采样通常包含以下步骤:
1. 产生 CONVST 上升沿,启动转换
2. BUSY 拉高,表示 AD7606 正在转换
3. 等待 BUSY 拉低,表示转换完成
4. 拉低 CS
5. 输出 SCLK
6. 从 DOUTA 或 DOUTB 读取数据
7. 读完后拉高 CS
推荐采样流程如下:

在调试阶段,推荐采用“先启动转换、等待 BUSY 完成、再读取数据”的方式。该方式逻辑最直观,便于示波器和逻辑分析仪对照波形。
稳定后,也可以采用流水线方式:
第 N 次周期:读取第 N-1 次转换结果,同时启动第 N 次转换
第 N+1 次周期:读取第 N 次转换结果,同时启动第 N+1 次转换
流水线方式效率更高,但要求初始化阶段已经先完成一次转换触发,否则第一次读取可能是无效数据。
四、STM32 软件结构设计
推荐的软件架构如下:
main.c
└── 系统主循环
app.c
├── 周期任务调度
├── 1ms / 10ms / 采样周期任务
└── 系统状态管理
app_ad7606.c
├── AD7606 采样任务
├── 电压/电流模式切换
├── 数据格式转换
├── 滤波与标定
└── 对外协议数据更新
bsp_ad7606.c
├── AD7606 初始化
├── AD7606 复位
├── AD7606 启动转换
├── AD7606 等待 BUSY
└── AD7606 串行读数据
这种结构的核心思想是:BSP 只负责芯片时序,APP 负责业务逻辑。
五、为什么要区分 APP 层和 BSP 层?
1. BSP 层保持干净
BSP 层只需要关心 AD7606 本身:
复位时序
CONVST 触发
BUSY 等待
CS/SCLK/DOUT 读取
基础 GPIO 控制
BSP 层不应该关心客户协议、EtherCAT 对象字典、0~10V 映射、电流/电压模式切换等业务逻辑。
这样做的好处是:后续即使把 GPIO 模拟时序改成硬件 SPI,APP 层也不需要大改。
2. APP 层负责业务逻辑
APP 层更适合处理:
采样周期管理
通道模式管理
电压/电流输入切换
原始码转换
滤波处理
异常判断
协议上传
客户数据格式适配
例如客户要求输出 uint16_t,这不应该影响底层 AD7606 驱动。底层仍然读取 AD7606 原始 16 位数据;APP 层再根据协议要求转换成客户需要的数据格式。
3. 更容易定位问题
分层后,调试问题可以快速归类:
读数完全不变化:优先查 BSP 时序和硬件连接
BUSY 没有变化:优先查 CONVST、RESET、供电和模式脚
接标准电压源正常,空载乱跳:优先查模拟输入悬空
本地读数正确,上位机显示异常:优先查 APP 层数据编码
电压值有固定偏差:优先查标定和量程换算
六、AD7606 数据格式:补码与无符号输出
AD7606 是双极性 ADC,输出编码为二进制补码。官方数据资料中列出其输出编码为 two’s complement。(模拟器件)
这意味着,原始 16 位数据不能简单按普通无符号数理解。
以 ±10V 量程为例:
| AD7606 原始码 | 按无符号数看 | 按补码理解 | 电压含义 |
|---|---|---|---|
| 0x8000 | 32768 | -32768 | 接近 -10V |
| 0xFFFF | 65535 | -1 | 接近 0V,略小于 0 |
| 0x0000 | 0 | 0 | 约 0V |
| 0x0001 | 1 | +1 | 接近 0V,略大于 0 |
| 0x7FFF | 32767 | +32767 | 接近 +10V |
因此,当原始数据出现 0xFFFF 时,它并不代表正满量程,而是补码意义下的 -1,也就是非常接近 0V。
可以。建议博客里补上**“示范代码”**,但不要写成项目源码复盘,而是写成“新手可快速移植的最小驱动模板”。下面这一段可以直接插入到博客的软件章节中。
配图仍可使用:
图1:STM32 串行驱动 AD7606 硬件连接框图
图2:AD7606 推荐软件采样流程图
七、STM32 串行驱动 AD7606 示范代码
下面给出一份基于 STM32 HAL GPIO 模拟串行时序 的 AD7606 最小驱动示范代码。该代码重点用于快速调试 CONVST、BUSY、CS、SCLK、DOUTA 这几根关键信号。
AD7606 支持 8/6/4 通道同步采样,输入范围支持 ±10V 和 ±5V,数字接口支持灵活的并行/串行方式,并兼容 SPI/QSPI/MICROWIRE/DSP 串行接口;其模拟输入阻抗为 1MΩ,VDRIVE 支持 2.3V 到 5.25V 逻辑电平范围。(Analog Devices)
7.1 文件结构建议
Core
├── Inc
│ ├── bsp_ad7606.h
│ └── app_ad7606.h
│
└── Src
├── bsp_ad7606.c
└── app_ad7606.c
推荐分层方式如下:
bsp_ad7606.c:只处理 AD7606 底层时序
app_ad7606.c:处理采样流程、数据转换、协议输出
main.c/app.c :周期调用 APP 层采样函数
这种结构的好处是:底层驱动保持干净,后续即使从 GPIO 模拟时序改成硬件 SPI,APP 层业务逻辑也不需要大改。
1. BSP 层:AD7606 底层驱动
1.1 bsp_ad7606.h
下面的宏名采用 CubeMX 常见命名方式。实际移植时,只需要在 CubeMX 中配置对应 GPIO,然后让宏名与工程中的引脚名称保持一致。
#ifndef __BSP_AD7606_H__
#define __BSP_AD7606_H__
#include "main.h"
#include <stdint.h>
#define AD7606_CHANNEL_COUNT 4U
/*
* 根据硬件原理图修改以下 GPIO 名称。
* 推荐在 CubeMX 中把引脚命名为:
* AD7606_CS
* AD7606_SCLK
* AD7606_DOUTA
* AD7606_BUSY
* AD7606_CONVSTA
* AD7606_CONVSTB
* AD7606_RESET
* AD7606_STBY
* AD7606_RANGE
* AD7606_OS0
* AD7606_OS1
* AD7606_OS2
* AD7606_SER
* AD7606_REF_SELECT
*/
#define AD7606_CS_LOW() HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_RESET)
#define AD7606_CS_HIGH() HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_SET)
#define AD7606_SCLK_LOW() HAL_GPIO_WritePin(AD7606_SCLK_GPIO_Port, AD7606_SCLK_Pin, GPIO_PIN_RESET)
#define AD7606_SCLK_HIGH() HAL_GPIO_WritePin(AD7606_SCLK_GPIO_Port, AD7606_SCLK_Pin, GPIO_PIN_SET)
#define AD7606_CONVSTA_LOW() HAL_GPIO_WritePin(AD7606_CONVSTA_GPIO_Port, AD7606_CONVSTA_Pin, GPIO_PIN_RESET)
#define AD7606_CONVSTA_HIGH() HAL_GPIO_WritePin(AD7606_CONVSTA_GPIO_Port, AD7606_CONVSTA_Pin, GPIO_PIN_SET)
#define AD7606_CONVSTB_LOW() HAL_GPIO_WritePin(AD7606_CONVSTB_GPIO_Port, AD7606_CONVSTB_Pin, GPIO_PIN_RESET)
#define AD7606_CONVSTB_HIGH() HAL_GPIO_WritePin(AD7606_CONVSTB_GPIO_Port, AD7606_CONVSTB_Pin, GPIO_PIN_SET)
#define AD7606_RESET_LOW() HAL_GPIO_WritePin(AD7606_RESET_GPIO_Port, AD7606_RESET_Pin, GPIO_PIN_RESET)
#define AD7606_RESET_HIGH() HAL_GPIO_WritePin(AD7606_RESET_GPIO_Port, AD7606_RESET_Pin, GPIO_PIN_SET)
#define AD7606_STBY_HIGH() HAL_GPIO_WritePin(AD7606_STBY_GPIO_Port, AD7606_STBY_Pin, GPIO_PIN_SET)
#define AD7606_RANGE_10V() HAL_GPIO_WritePin(AD7606_RANGE_GPIO_Port, AD7606_RANGE_Pin, GPIO_PIN_SET)
#define AD7606_RANGE_5V() HAL_GPIO_WritePin(AD7606_RANGE_GPIO_Port, AD7606_RANGE_Pin, GPIO_PIN_RESET)
#define AD7606_SER_MODE() HAL_GPIO_WritePin(AD7606_SER_GPIO_Port, AD7606_SER_Pin, GPIO_PIN_SET)
#define AD7606_REF_EXTERNAL() HAL_GPIO_WritePin(AD7606_REF_SELECT_GPIO_Port, AD7606_REF_SELECT_Pin, GPIO_PIN_RESET)
#define AD7606_REF_INTERNAL() HAL_GPIO_WritePin(AD7606_REF_SELECT_GPIO_Port, AD7606_REF_SELECT_Pin, GPIO_PIN_SET)
#define AD7606_OS0_LOW() HAL_GPIO_WritePin(AD7606_OS0_GPIO_Port, AD7606_OS0_Pin, GPIO_PIN_RESET)
#define AD7606_OS1_LOW() HAL_GPIO_WritePin(AD7606_OS1_GPIO_Port, AD7606_OS1_Pin, GPIO_PIN_RESET)
#define AD7606_OS2_LOW() HAL_GPIO_WritePin(AD7606_OS2_GPIO_Port, AD7606_OS2_Pin, GPIO_PIN_RESET)
#define AD7606_BUSY_READ() HAL_GPIO_ReadPin(AD7606_BUSY_GPIO_Port, AD7606_BUSY_Pin)
#define AD7606_DOUTA_READ() HAL_GPIO_ReadPin(AD7606_DOUTA_GPIO_Port, AD7606_DOUTA_Pin)
typedef enum
{
AD7606_OK = 0,
AD7606_ERROR_BUSY_START_TIMEOUT,
AD7606_ERROR_BUSY_DONE_TIMEOUT,
AD7606_ERROR_NULL_POINTER
} ad7606_status_t;
void AD7606_Init(void);
void AD7606_Reset(void);
void AD7606_StartConvst(void);
ad7606_status_t AD7606_WaitBusyFinish(uint32_t start_timeout_us, uint32_t done_timeout_us);
ad7606_status_t AD7606_ReadRaw(uint16_t *raw_buf, uint8_t channel_count);
#endif
1.2 bsp_ad7606.c
下面是 GPIO 模拟串行方式的底层示范代码。为了让新手更容易调通,时序延时写得比较保守。正式产品中,可以根据示波器实测结果进一步优化。
#include "bsp_ad7606.h"
static void AD7606_DelayShort(volatile uint32_t n)
{
while (n--)
{
__NOP();
}
}
static void AD7606_DelayUs(uint32_t us)
{
/*
* 简单阻塞延时,适合调试。
* 不同主频下需要根据示波器微调。
* 正式项目建议改成 DWT 或硬件定时器延时。
*/
while (us--)
{
AD7606_DelayShort(80);
}
}
void AD7606_Reset(void)
{
AD7606_RESET_LOW();
AD7606_DelayUs(1);
AD7606_RESET_HIGH();
AD7606_DelayUs(2);
AD7606_RESET_LOW();
AD7606_DelayUs(1);
}
void AD7606_StartConvst(void)
{
/*
* 如果硬件已经将 CONVST_A 和 CONVST_B 短接,
* 只驱动其中一路也可以。
* 为了通用性,示范代码同时驱动 A/B。
*/
AD7606_CONVSTA_LOW();
AD7606_CONVSTB_LOW();
AD7606_DelayShort(100);
AD7606_CONVSTA_HIGH();
AD7606_CONVSTB_HIGH();
}
void AD7606_Init(void)
{
/*
* 1. 选择参考源
* 外部基准:AD7606_REF_EXTERNAL()
* 内部基准:AD7606_REF_INTERNAL()
*/
AD7606_REF_EXTERNAL();
/*
* 2. 选择输入量程
* RANGE = 1:±10V
* RANGE = 0:±5V
*/
AD7606_RANGE_10V();
/*
* 3. 选择串行模式
*/
AD7606_SER_MODE();
/*
* 4. 关闭过采样:OS2 OS1 OS0 = 000
*/
AD7606_OS0_LOW();
AD7606_OS1_LOW();
AD7606_OS2_LOW();
/*
* 5. 默认状态
*/
AD7606_CS_HIGH();
AD7606_SCLK_HIGH();
AD7606_CONVSTA_HIGH();
AD7606_CONVSTB_HIGH();
AD7606_STBY_HIGH();
/*
* 6. 复位芯片
*/
AD7606_Reset();
/*
* 7. 先启动一次转换。
* 如果后续采用“读上一次,启动下一次”的流水线模式,
* 初始化阶段需要先触发一次转换。
*/
AD7606_StartConvst();
}
ad7606_status_t AD7606_WaitBusyFinish(uint32_t start_timeout_us, uint32_t done_timeout_us)
{
/*
* 等待 BUSY 拉高,说明转换开始。
* 如果采用的是“读上一次数据”的流水线方式,
* 也可以只等待 BUSY 为低后读取。
*/
while ((AD7606_BUSY_READ() == GPIO_PIN_RESET) && (start_timeout_us > 0U))
{
start_timeout_us--;
AD7606_DelayUs(1);
}
if (start_timeout_us == 0U)
{
return AD7606_ERROR_BUSY_START_TIMEOUT;
}
/*
* 等待 BUSY 拉低,说明转换完成。
*/
while ((AD7606_BUSY_READ() == GPIO_PIN_SET) && (done_timeout_us > 0U))
{
done_timeout_us--;
AD7606_DelayUs(1);
}
if (done_timeout_us == 0U)
{
return AD7606_ERROR_BUSY_DONE_TIMEOUT;
}
return AD7606_OK;
}
ad7606_status_t AD7606_ReadRaw(uint16_t *raw_buf, uint8_t channel_count)
{
uint8_t ch;
uint8_t bit;
if (raw_buf == 0)
{
return AD7606_ERROR_NULL_POINTER;
}
/*
* 调试阶段推荐 CS 拉低后连续读取所有通道。
* 这样更容易确认通道顺序和串行时序。
*/
AD7606_CS_LOW();
AD7606_DelayShort(20);
for (ch = 0; ch < channel_count; ch++)
{
uint16_t data = 0;
for (bit = 0; bit < 16; bit++)
{
AD7606_SCLK_LOW();
AD7606_DelayShort(30);
data <<= 1;
if (AD7606_DOUTA_READ() == GPIO_PIN_SET)
{
data |= 1U;
}
AD7606_SCLK_HIGH();
AD7606_DelayShort(40);
}
raw_buf[ch] = data;
}
AD7606_CS_HIGH();
return AD7606_OK;
}
2. APP 层:采样流程与数据转换
2.1 app_ad7606.h
#ifndef __APP_AD7606_H__
#define __APP_AD7606_H__
#include "main.h"
#include <stdint.h>
#define APP_AD7606_CHANNEL_COUNT 4U
typedef struct
{
uint16_t raw_code[APP_AD7606_CHANNEL_COUNT];
int16_t signed_code[APP_AD7606_CHANNEL_COUNT];
uint16_t offset_code[APP_AD7606_CHANNEL_COUNT];
float voltage[APP_AD7606_CHANNEL_COUNT];
} app_ad7606_data_t;
void APP_AD7606_Init(void);
void APP_AD7606_Task(void);
const app_ad7606_data_t *APP_AD7606_GetData(void);
#endif
2.2 app_ad7606.c
APP 层不直接关心底层 GPIO 如何翻转,只关心“什么时候采样、如何解释数据、如何给上层使用”。
AD7606 的输出编码是 two’s complement(二进制补码),因此 0xFFFF 不是正满量程,而是补码意义下的 -1,代表接近 0V 的负方向 1LSB。官方数据手册中也列出 AD7606 输出编码为 Twos complement。(Analog Devices)
#include "app_ad7606.h"
#include "bsp_ad7606.h"
#define AD7606_BUSY_START_TIMEOUT_US 20U
#define AD7606_BUSY_DONE_TIMEOUT_US 100U
/*
* ±10V 量程:
* FSR = 20V
* LSB = 20 / 65536 V
*/
#define AD7606_BIPOLAR_10V_LSB (20.0f / 65536.0f)
static app_ad7606_data_t s_ad7606_data;
static int16_t AD7606_RawToSigned(uint16_t raw)
{
/*
* AD7606 原始输出为 16bit 补码。
* 直接强转 int16_t 即可得到有符号码值。
*/
return (int16_t)raw;
}
static uint16_t AD7606_RawToOffsetBinary(uint16_t raw)
{
/*
* 将 AD7606 补码转换为偏移二进制。
*
* 原始补码:
* 0x8000 ≈ -FS
* 0x0000 ≈ 0V
* 0x7FFF ≈ +FS
*
* 偏移二进制:
* 0x0000 ≈ -FS
* 0x8000 ≈ 0V
* 0xFFFF ≈ +FS
*/
return (uint16_t)(raw ^ 0x8000U);
}
static float AD7606_SignedToVoltage_10V(int16_t signed_code)
{
return ((float)signed_code) * AD7606_BIPOLAR_10V_LSB;
}
void APP_AD7606_Init(void)
{
for (uint8_t i = 0; i < APP_AD7606_CHANNEL_COUNT; i++)
{
s_ad7606_data.raw_code[i] = 0;
s_ad7606_data.signed_code[i] = 0;
s_ad7606_data.offset_code[i] = 0;
s_ad7606_data.voltage[i] = 0.0f;
}
AD7606_Init();
}
void APP_AD7606_Task(void)
{
ad7606_status_t status;
/*
* 调试阶段推荐:
* 1. 启动转换
* 2. 等待 BUSY 完成
* 3. 读取数据
*/
AD7606_StartConvst();
status = AD7606_WaitBusyFinish(AD7606_BUSY_START_TIMEOUT_US,
AD7606_BUSY_DONE_TIMEOUT_US);
if (status != AD7606_OK)
{
/*
* 可在此处添加错误计数或日志输出。
* 例如:
* AD7606_ERROR_BUSY_START_TIMEOUT:CONVST 可能没有触发成功
* AD7606_ERROR_BUSY_DONE_TIMEOUT :BUSY 一直未结束
*/
return;
}
status = AD7606_ReadRaw(s_ad7606_data.raw_code, APP_AD7606_CHANNEL_COUNT);
if (status != AD7606_OK)
{
return;
}
/*
* 将原始码转换成不同形式,方便调试和协议输出。
*/
for (uint8_t ch = 0; ch < APP_AD7606_CHANNEL_COUNT; ch++)
{
s_ad7606_data.signed_code[ch] = AD7606_RawToSigned(s_ad7606_data.raw_code[ch]);
s_ad7606_data.offset_code[ch] = AD7606_RawToOffsetBinary(s_ad7606_data.raw_code[ch]);
s_ad7606_data.voltage[ch] = AD7606_SignedToVoltage_10V(s_ad7606_data.signed_code[ch]);
}
}
const app_ad7606_data_t *APP_AD7606_GetData(void)
{
return &s_ad7606_data;
}
3. main.c 中的最小调用示范
对于刚开始调试的项目,可以先不用复杂定时任务,直接在主循环中每隔一段时间采样一次。
#include "main.h"
#include "app_ad7606.h"
#include <stdio.h>
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
APP_AD7606_Init();
while (1)
{
const app_ad7606_data_t *adc;
APP_AD7606_Task();
adc = APP_AD7606_GetData();
printf("CH1 raw=0x%04X, signed=%d, offset=%u, voltage=%.4f V\r\n",
adc->raw_code[0],
adc->signed_code[0],
adc->offset_code[0],
adc->voltage[0]);
printf("CH2 raw=0x%04X, signed=%d, offset=%u, voltage=%.4f V\r\n",
adc->raw_code[1],
adc->signed_code[1],
adc->offset_code[1],
adc->voltage[1]);
printf("CH3 raw=0x%04X, signed=%d, offset=%u, voltage=%.4f V\r\n",
adc->raw_code[2],
adc->signed_code[2],
adc->offset_code[2],
adc->voltage[2]);
printf("CH4 raw=0x%04X, signed=%d, offset=%u, voltage=%.4f V\r\n\r\n",
adc->raw_code[3],
adc->signed_code[3],
adc->offset_code[3],
adc->voltage[3]);
HAL_Delay(500);
}
}
调试时建议同时打印四种值:
raw_code :AD7606 原始 16bit 数据
signed_code :按 int16_t 解释后的补码值
offset_code :转换后的 uint16_t 偏移二进制值
voltage :换算后的电压值
这样可以避免把 0xFFFF 误判成正满量程。对于 AD7606 原始补码来说,0xFFFF 实际上是 -1,非常接近 0V。
4. 如果只采集 0~10V
有些工业产品虽然使用 AD7606 的 ±10V 量程,但业务上只采集 0~10V。这种情况下,也可以把 0~10V 映射成 0~65535:
static uint16_t AD7606_ToUint16_0To10V(uint16_t raw)
{
int32_t signed_code;
signed_code = (int16_t)raw;
if (signed_code <= 0)
{
return 0;
}
if (signed_code >= 32767)
{
return 65535;
}
return (uint16_t)((signed_code * 65535L) / 32767L);
}
这种方式的协议含义是:
0 ≈ 0V
65535 ≈ +10V
负电压钳位为 0
如果系统未来需要支持负电压,不推荐这种方式;如果产品明确只测 0~10V,这种方式对客户最直观。
八、推荐调试步骤
1. 先确认硬件模式
重点检查:
5V 模拟供电是否正常
VDRIVE 是否匹配 MCU IO 电平
REF_SELECT 与实际基准电路是否一致
RANGE 是否符合目标量程
PAR/SER/BYTE SEL 是否为串行模式
OS0~OS2 是否处于预期过采样配置
RESET 是否有正确释放
CONVST_A/B 是否有触发脉冲
BUSY 是否有高低变化
2. 再确认数字时序
建议用示波器或逻辑分析仪同时观察:
CONVST_A
CONVST_B
BUSY
CS
SCLK
DOUTA
FRSTDATA
正常情况下,CONVST 触发后,BUSY 应先拉高再拉低;BUSY 结束后,CS 拉低,SCLK 输出固定数量脉冲,DOUTA 依次输出通道数据。
3. 最后确认模拟输入
不要用悬空输入判断 ADC 是否准确。
推荐测试顺序:
输入短接 AGND,验证 0V 附近读数
输入 1V,验证正向线性
输入 5V,验证中间点
输入 10V,验证满量程附近
如果支持负电压,再输入 -1V、-5V、-10V 验证补码关系
九、常见问题总结
问题 1:所有通道读数一样
优先检查:
串行/并行模式脚是否配置正确
DOUTA 是否接错
CS/SCLK 时序是否正确
CONVST_A/B 是否都被正确触发
通道读取数量是否与芯片型号一致
问题 2:空载时通道读数不同
这是高阻模拟输入悬空后的常见现象。AD7606 输入阻抗为 1MΩ,未接信号源时输入点没有确定电位。应通过短接 AGND 或加下拉电阻验证。(模拟器件)
问题 3:0V 附近出现 65535
这是二进制补码显示导致的误解。65535 = 0xFFFF,在 int16_t 补码含义下是 -1,代表接近 0V 的负方向 1LSB。
问题 4:客户要 uint16_t,但 AD7606 是补码怎么办?
推荐在协议层明确编码方式:
如果保留原始数据:UINT16 + two’s complement
如果希望客户直观看数:UINT16 + offset binary
如果只测 0~10V:UINT16 + 0~10V 线性映射
十、结语
STM32 串行驱动 AD7606 的核心不是代码量,而是对硬件模式、转换时序和数据编码的理解。
硬件上要确认供电、参考、量程、串行模式、CONVST/BUSY 时序;软件上要把 BSP 和 APP 分开,BSP 只处理 AD7606 底层时序,APP 层负责采样调度、模式切换、滤波标定和客户协议输出。
对于工业项目,推荐采用如下结构:
BSP 层:负责芯片驱动
APP 层:负责业务处理
协议层:负责数据定义和上传
这样做的好处是清晰、稳定、可维护。后续无论是从 GPIO 模拟串行改为硬件 SPI,还是增加 DMA、EtherCAT 上传、滤波标定、电压/电流模式切换,都不会破坏整体软件结构。
十一、看到结尾的奖励

更多推荐
所有评论(0)