STM32F407读取AD7616(CM2249)
1.AD7616介绍
写在前面
对本次使用AD7616做笔记记录,详细特征不做记录 ,由于网上的资料大多收费或混乱 ,故重新梳理遇到的问题 ,希望能够帮助各位。
特征
CM2249 是一款16位、16通道同步采样模拟数据采集系统。各 通道均内置模拟输入钳位保护、二阶抗混叠滤波器、跟踪保持放 大器、16位 SAR ADC,内置了灵活的数字滤波器、2.5V 基准电 压源、基准电压缓冲以及高速串行和并行接口。 CM2249 采用 5V 单电源供电,并且可以处理 ±10V、±5V 和 ±2.5V 真双极性输入信号,同时所有通道均能以高达 1MSPS 的 采样率采样。输入钳位保护电路可以承受高达 ±19.5V 的电压, 1MΩ的高输入阻抗以及片内滤波器,可以极大简化外围电路设 计。灵活的并行/串行接口SPI/QSPI/MICROWIRE/DSP 兼容 可选循环冗余校验 (CRC) 错误检查 8×2 通道同步采样输入
本次实验使用国产CM2249芯片代替AD7616,使用SPI串行模式,软件模式配置寄存器,双线MISO模式进行测试。在这个模式下,CM2249可以替代AD7616
2.硬件电路连接
1.芯片需要5v和3.3V供电,基本电源部分数据手册有指出。测试的输入通道有V0A~V7A和V0B~V7B共16个通道,支持差分输入和单端输入,如果使用单端输入,需要把所有被测通道的AGND连接在一起,之后通过隔离方式和DGND链接。如果使用差分输入,就要像上图一样,按组和外部连接。这个图就是差分输入的。AGND端口出来之后接1K电阻,再连接到总的AGND。
2.其他功能管脚配置。如果你需要使用基本的串行软件双线功能,可以和我的一致。
引脚名称 描述
35 REFSEL 高电平选择内部参考
37 SEQEN 低电平
38,39 HW_RNGSEL1,HW_RNGSEL0 全部低电平
40 SER/PAR 高电平
45 DB4/SER1W 高电平选择双线输出数据
55 DB10/SDI MOSI
56 DB11/SDOB MISO
57 DB12/SDOA MISO
58,59,60, DB13/OS0 、 DB14/OS1 、 DB15/OS2 全部低电平
61 WR/BURST 低电平
62 SCLK/RD SCLK
63 CS CS
64,65,66, CHSEL0、 CHSEL1、 CHSEL2 全部低电平
67 BUSY GPIO_IN
68 CONVST GPIO_OUT
管脚配置差不多就是这个样子,详细的可以去看看数据手册,下面讲讲软件协议。
3.软件协议和配置
1.时序分析-写寄存器的时序
首先看看MOSI,写寄存器的时序,我使用的是软件模拟SPI。
1.注意首先是RESET ,上电后首先拉低再拉高。在芯片上电完成后,需使用长于 50ns 的高电平脉冲对芯片进行一次复位操 作。 RESET 保持低电平时,器件将被置于关断模式。
2.CONCVST , 拉高再拉底,我选择1ms时间
3.CS拉低
4.SCK, 注意时钟在空闲时应该保持高电平
5.SDI/MOSI ,这里是在时钟在上升沿时将数据放在总线上,注意采样时机
2.时序分析-读数据的时序
1.在2线模式下,SDOA/SDOB一样的在SPI函数中一次读2个管脚就可以了, 而读数据时序就不一样了 ,需要在时钟的下降沿读取,就是SCLK拉低后马上读取总线上的数据。如果使用单根线读取数据,就需要读取32个时钟,双线只需要读取16个。
3.寄存器配置
寄存器配置这里我们主要配置这几个。
1.0X02 /CONFIG配置寄存器可以默认,直接配置0x0000,所有寄存器都只有低8位有效。
2.0X03 /CHANNEL通道寄存器是在转换时配置的,我们要不停往总线上轮询16个通道的数据,所以该寄存器要放在循环中轮询,每次配置只能使能读取1个通道,之后会在代码中体现。
3.0X04~0X07 /RANGE量程寄存器需要在初始化时配置,主要配置内容是测量的电压范围,1个寄存器写4个通道范围共16个,我这里直接全部配置成+-10V范围
寄存器的相关配置就是这些,如果你需要更多功能,可以看看手册。
特别注意一点,写寄存器时,你发送的数据最高位要置为1,之后的6位是寄存器地址,之后的9位才是你需要配置的数据。读寄存器首位则为0。
在之后的代码中,每次调用: 切换通道 → 启动转换 → 等待完成 → 读取结果 → 全部转电压
4.源码
AD7616.C
#include "header_file.h" /** * @brief AD7616 初始化函数 * @note 配置 SPI 引脚默认电平,复位芯片,并设置各通道量程范围 * @retval 无 */ void ad7616_init(void) { /* ---------- 1. 引脚默认电平设置 ---------- */ AD7616_SCLK_H; /* SCLK 置高,SPI 时钟空闲状态为高电平(CPOL=1) */ AD7616_CS_H; /* CS 置高,片选失能,SPI 总线默认空闲状态 */ AD7616_CONVST_L; /* CONVST 置低,停止转换,确保 ADC 处于空闲状态 */ /* ---------- 2. 硬件复位 ---------- */ HAL_Delay(1); /* 等待 1ms,确保引脚电平稳定 */ ad7616_reset(); /* 硬件复位 AD7616,恢复寄存器默认值 */ HAL_Delay(10); /* 等待 10ms,确保复位完成后芯片进入正常工作状态 */ /* ---------- 3. 寄存器配置 (SPI 16-bit 写入) ---------- */ /* AD7616 寄存器写入格式:高字节 [7]=1(写), [6:1]=寄存器地址, [0]=0 */ /* 寄存器地址映射:0x02=配置寄存器, 0x03=通道选择, 0x04~0x07=Ch0~Ch3 量程 */ AD7616_Write_REG_and_Data(AD7616_REG_CONFIG,0x0000); /* 写配置寄存器(0x02):使用默认配置 */ AD7616_Write_REG_and_Data(AD7616_REG_CHAN_SEL,0x0000); /* 写通道寄存器(0x03):使能对应通道 */ AD7616_Write_REG_and_Data(AD7616_REG_RANGE_CH0,Range_10V); /* 写 Ch0 量程寄存器(0x04):设为 ±10V 量程 */ AD7616_Write_REG_and_Data(AD7616_REG_RANGE_CH1,Range_10V); /* 写 Ch1 量程寄存器(0x05):设为 ±10V 量程 */ AD7616_Write_REG_and_Data(AD7616_REG_RANGE_CH2,Range_10V); /* 写 Ch2 量程寄存器(0x06):设为 ±10V 量程 */ AD7616_Write_REG_and_Data(AD7616_REG_RANGE_CH3,Range_10V); /* 写 Ch3 量程寄存器(0x07):设为 ±10V 量程 */ } void ad7616_reset(void) { AD7616_RESET_H; HAL_Delay (1); AD7616_RESET_L; HAL_Delay(1); AD7616_RESET_H; } void ad7616_start_convst(void) { AD7616_CONVST_H; AD7616_CONVST_H; HAL_Delay (1); AD7616_CONVST_L; AD7616_CONVST_L; HAL_Delay (1); } /** * @brief AD7616 通过 SPI 读取两路 A 组和 B 组通道转换结果 * @note AD7616 为双路同时输出 ADC,每个时钟脉冲同时读取 A 和 B 通道一位 * @param cha_data 指针,用于存储 A 通道转换结果(16位有符号) * @param chb_data 指针,用于存储 B 通道转换结果(16位有符号) * @retval 无 */ void ad7616_read_data(int16_t * cha_data, int16_t * chb_data) { uint8_t i; // 循环计数器,逐位读取16位数据 uint16_t rxdata_a = 0; // A通道接收数据缓冲区 uint16_t rxdata_b = 0; // B通道接收数据缓冲区 AD7616_CS_L; // 拉低片选信号,开始 SPI 传输 for(i = 0; i < 16; i++) // 循环读取16位,从最高位到最低位逐位移入 { rxdata_a = rxdata_a << 1; // A通道数据左移一位,准备接收新的比特位 rxdata_b = rxdata_b << 1; // B通道数据左移一位,准备接收新的比特位 AD7616_SCLK_L; // SCLK 拉低,准备读取当前位数据 // 读取 SDOA 引脚电平,为高则当前比特位为 1,数据缓冲区加 1 if (AD7616_D12_SDOA == GPIO_PIN_SET) rxdata_a++; // 读取 SDOB 引脚电平,为高则当前比特位为 1,数据缓冲区加 1 if (AD7616_D11_SDOB == GPIO_PIN_SET) rxdata_b++; AD7616_SCLK_H; // SCLK 拉高,结束当前位传输 } AD7616_CS_H; // 拉高片选信号,结束 SPI 传输 *cha_data = rxdata_a; // 将读取结果存入传入的指针 *chb_data = rxdata_b; // 将读取结果存入传入的指针 } /** * @brief AD7616 通过 SPI 写入16位数据(通常用于寄存器配置) * @note 从最高位(bit15)开始逐位发送,遵循 SPI 协议时序 * @param txdata 要写入的16位数据,高8位为地址和读写标志位,低8位为数据 * @retval 无 */ void ad7616_write_data(uint16_t txdata) { uint8_t i; // 循环计数器,逐位发送16位数据 AD7616_CS_L; // 拉低片选信号,开始 SPI 传输 for(i = 0; i < 16; i++) // 循环发送16位,从最高位到最低位 { // 判断当前最高位(bit15)并设置 SDI 引脚电平 if(txdata & 0x8000) { AD7616_D10_SDI_H; // 当前位为1,SDI 置高 } else { AD7616_D10_SDI_L; // 当前位为0,SDI 置低 } AD7616_SCLK_L; // SCLK 拉低,准备发送数据 txdata <<= 1; // 左移一位,准备发送下一个比特位 AD7616_SCLK_H; // SCLK 拉高,锁存当前位数据到 AD7616 //这里数据才发出去 数据在上升沿采样 } AD7616_CS_H; // 拉高片选信号,结束 SPI 传输 AD7616_D10_SDI_L; // SDI MOSI 置低,恢复默认电平 } /** * @brief 为了高辨识度 将write函数单独拆分成写寄存器地址和数据2个变量 * @param REG : 寄存器地址 * @param Data : 要写入的寄存器数据 */ void AD7616_Write_REG_and_Data(uint16_t REG,uint16_t Data) { uint16_t Together_Data; //总数据 Together_Data=(AD7616_WRITE_CMD | (REG<<9)); //写入 写寄存器功能 + 寄存器地址 Together_Data=Together_Data | Data; //写入 寄存器数据 uint8_t i; // 循环计数器,逐位发送16位数据 AD7616_CS_L; // 拉低片选信号,开始 SPI 传输 for(i = 0; i < 16; i++) // 循环发送16位,从最高位到最低位 { // 判断当前最高位(bit15)并设置 SDI 引脚电平 if(Together_Data & 0x8000) { AD7616_D10_SDI_H; // 当前位为1,SDI 置高 } else { AD7616_D10_SDI_L; // 当前位为0,SDI 置低 } AD7616_SCLK_L; // SCLK 拉低,准备发送数据 Data <<= 1; // 左移一位,准备发送下一个比特位 AD7616_SCLK_H; // SCLK 拉高,锁存当前位数据到 AD7616 //这里数据才发出去 数据在上升沿采样 } AD7616_CS_H; // 拉高片选信号,结束 SPI 传输 AD7616_D10_SDI_L; // SDI MOSI 置低,恢复默认电平 } /** * @brief 将 ADC 原始补码数据转换为实际电压值(±10V) * @param rawCodes : 原始 ADC 数据数组 * @param voltages : 存放转换后电压的数组(长度 >= count) * @param count : 要转换的通道数量 */ void AD7616_Voltage(int16_t *rawCodes, double *voltages, uint8_t count) { for(uint8_t i = 0; i < count; i++) { int16_t signedCode = (int16_t)rawCodes[i]; /* 强制转换为有符号数(补码) */ /* 满量程 ±10V,码值 32768 对应 10V */ voltages[i] = signedCode * 10.0 / 32768.0; /* 线性换算 */ } } /** * @brief AD7616 单次运行函数,每次调用完成一个通道的采样 * @note 每次调用: 切换通道 → 启动转换 → 等待完成 → 读取结果 → 全部转电压 * 采用"读取滞后一拍"机制,读取的是上一轮转换的结果 * @retval 无 */ void AD7616_RunData(void) { static uint8_t Channel = 0; /* 当前触发转换的通道号 (0~7 循环) */ static uint8_t i = 0; /* 数据存储索引(指向上一轮的通道) */ static int16_t CHA_Data[8] = {0}; /* A 组 8 通道原始码值(有符号16位) */ static int16_t CHB_Data[8] = {0}; /* B 组 8 通道原始码值(有符号16位) */ static double Achannel_voltage[8]; /* A 组 8 通道转换后的电压值 */ static double Bchannel_voltage[8]; /* B 组 8 通道转换后的电压值 */ /* ===== 1. 通道轮询切换 (0 → 1 → ... → 7 → 0 循环) ===== */ if(Channel < 7) { Channel++; /* 未到末尾,递增 */ } else { Channel = 0; /* 已到 7,回到 0 */ } /* ===== 2. SPI 写通道选择寄存器 (0x03) ===== */ /* 格式: 0x8600 | (Channel<<4) | Channel */ /* bit6~4: 偶数通道号, bit2~0: 奇数通道号 */ ad7616_write_data(0x8600 | ((Channel & 0x07) << 4) | (Channel & 0x07)); /* ===== 3. 启动转换 ===== */ ad7616_start_convst(); /* CONVST 上升沿触发采样转换 */ /* ===== 4. 等待转换完成 ===== */ while((AD7616_BUSY == GPIO_PIN_SET)) /* BUSY=H → 正在转换,需等待 */ { HAL_Delay(1); /* 每 1ms 轮询一次 */ } /* BUSY=L → 转换完成,退出 */ /* ===== 5. 计算存储索引(读取一定滞后一拍) ===== */ /* 此时读取的是上一轮转换完成后锁存到输出寄存器的结果 */ if(Channel == 0) { i = 7; /* Channel=0 → 上轮是 7 */ } else { i = Channel - 1; /* 上轮 = 当前 Channel - 1 */ } /* ===== 6. SPI 读取 A/B 两路 16 位转换结果 ===== */ ad7616_read_data(&CHA_Data[i], &CHB_Data[i]); /* ===== 7. 将 8 通道原始码值全部转为实际电压 ===== */ AD7616_Voltage(CHA_Data, Achannel_voltage, 8); AD7616_Voltage(CHB_Data, Bchannel_voltage, 8); }AD7616.H
#ifndef _AD7616_H_ #define _AD7616_H_ #include "header_file.h" /* ========== AD7616 寄存器地址定义 ========== */ #define AD7616_REG_CONFIG 0x02 /* 配置CONFIG寄存器 */ #define AD7616_REG_CHAN_SEL 0x03 /* 配置CHANNEL通道选择寄存器 */ #define AD7616_REG_RANGE_CH0 0x04 /* 配置RANGE A1/ V0A-V3A 输入范围寄存器 */ #define AD7616_REG_RANGE_CH1 0x05 /* 配置RANGE A2/ V4A-V7A 输入范围寄存器 */ #define AD7616_REG_RANGE_CH2 0x06 /* 配置RANGE A3/ V0B-V3B 输入范围寄存器 */ #define AD7616_REG_RANGE_CH3 0x07 /* 配置RANGE A4/ V4B-V7B 输入范围寄存器 */ /* ========== SPI 读写命令标志位 ========== */ #define AD7616_WRITE_CMD 0x8000 /* 写命令标志位 (bit15 = 1) */ #define AD7616_READ_CMD 0x0000 /* 读命令标志位 (bit15 = 0) */ /* ========== 地址拼接宏 ========== */ #define AD7616_WRITE(reg) (AD7616_WRITE_CMD | ((reg) << 9)) #define AD7616_READ(reg) (AD7616_READ_CMD | ((reg) << 9)) #define ad7616_range 10000.0 #define Range_10V 0x0000 //所有通道范围 ±10V #define Range_5V 0x00AA //偶通道 ±10V, 奇通道 ±5V #define Range_2V5 0x0055 //偶通道 ±5V, 奇通道 ±2.5V #define AD7616_RESET_L HAL_GPIO_WritePin(GPIOF, GPIO_PIN_1, GPIO_PIN_RESET) #define AD7616_RESET_H HAL_GPIO_WritePin(GPIOF, GPIO_PIN_1, GPIO_PIN_SET) #define AD7616_D4_SER1W_L HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_RESET) #define AD7616_D4_SER1W_H HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_SET) #define AD7616_D10_SDI_L HAL_GPIO_WritePin(GPIOC, GPIO_PIN_3, GPIO_PIN_RESET) #define AD7616_D10_SDI_H HAL_GPIO_WritePin(GPIOC, GPIO_PIN_3, GPIO_PIN_SET) #define AD7616_D11_SDOB HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_4) #define AD7616_D12_SDOA HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2) #define AD7616_SCLK_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET) #define AD7616_SCLK_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET) #define AD7616_CS_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) #define AD7616_CS_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) #define AD7616_BUSY HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_3) #define AD7616_CONVST_L HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_RESET) #define AD7616_CONVST_H HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_SET) extern void ad7616_init(void); extern void ad7616_reset(void); extern void ad7616_start_convst(void); extern void ad7616_read_data(int16_t * cha_data, int16_t * chb_data); extern void ad7616_write_data(uint16_t txdata); void AD7616_Voltage(int16_t *rawCodes, double *voltages, uint8_t count); void AD7616_Write_REG_and_Data(uint16_t REG,uint16_t Data); void AD7616_RunData(); #endifMAIN.C
#include "header_file.h" void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // MX_LWIP_Init(); // MX_SPI2_Init(); //初始化ADC1 // TCP_Client_Init(); //初始化客户端 // TCP_Echo_Init(); //初始化服务器 ad7616_init(); while (1) { AD7616_RunData(); } }以上是所有内容,本人也是小白,可能会有错误的地方,感谢指正!
