最近项目调试使用到了AD7327芯片,为了防止后续忘记又得各种查资料,将自己这次调试的心得记录于此,如果错误请指正。谢谢。
开发环境:STM32F407VET6+AD7327 keil mdk 5.23
这款芯片有官方例程,代码开发起来省事儿好多。
1.芯片简介
-12位精度
- 支持软件选择量程:±10V、±5V、±2.5V,0-10V
-500ksps传输速率
-8个模拟量输入通道,既可配置为单个输入,也可配置为双路输入(传感器信号包含V+、V-两根信号线情况下)
-内置2.5V参考电压
注意!!!设计硬件时请先参考芯片手册Table 6,注意根据你要使用的量程选择相应的电压。如果采集电压含有正负,则电源也需要接正负电源;如果只有正电压,则可以只接电源正和地。
此处可能导致后期程序配置好了寄存器,确定了量程,结果发现输入电压没到最大量程还差好远,结果已经到了fff最大值了,莫名其妙的一脸懵...
2.引脚简介
硬件接线按照上图所示即可,注意以下几点:
-1. 1脚CS 片选。接mcu一个IO口即可;初始化芯片及数据转换读取时需要对该引脚输出高低电平
-2. 2脚DIN SPI通讯数据引脚,我使用STM32做主机控制AD7327芯片,因此,DIN脚接mcu的SPI_MOSI
-3. 18脚.DOUT SPI通讯数据引脚,DOUT脚接mcu的SPI_MISO
-4. 20脚SCLK SPI时钟,接SPI_SCK
-5. 17脚VDRIVE 接mcu的IO口,初始化后拉高即可。注意该引脚必须和VCC电压相同,最大也不能超过VCC+0.3V。我是用的 stm32f4芯片,因此VCC就是用的3.3V,所以该引脚直接接到stm32的一个IO口即可。
PS.注意硬件电路上的电容方向,我的板子上有个电容方向画反了,结果上电一个劲儿的发热。建议画板时一定要仔细,参考官方模板电路,焊好后通电没有发现什么问题也要摸一摸芯片和各个元器件看是不是有短路或者接错导致的发热现象出现
3.硬件电路
注意vin0-vin7,引脚号并不是按照顺序来的,我之前引脚号弄错了,还花了一番功夫,结果发现原理图上引脚号跟芯片文档对不上,还好电源引脚没有错误,不至于烧掉元器件。
未连接的输入端可以不接或者通过一个电阻下拉到地,不要直接接地!!!请特别注意!!!!否则会导致输入采样电压莫名被拉低...
4.寄存器简介
AD7327包含4个寄存器,通过SPI发送一个16位的值来读写AD7237.
16位命令中,最高位第15位为写入命令,1有效
第14位与第13位用来选择对相应寄存器进行写入操作
4.1 控制寄存器
控制寄存器注意以下几点:
1)coding 即为AD值的类型,0则输出补码,1则输出二进制原码
2)ref 电压参考。0则开启外部参考,不使用内部参考;1则使用内部参考作为AD输出值的参考
3)2-10位,ADD2-ADD0地址选择,如下图所示。我需要8个独立输入通道,因此我配置ADD2-ADD0:111 mode1 = 0,mode0 = 0
4)Table11中电源模式选择中两个参数所示,00即为正常模式
5)sequence选择
全为0或全为1,不使用sequence,需要每次进行读取前进行寄存器配置。
seq1=0,seq2=1,例如我使用VIN0,VIN2两个通道,且使用自动获取模式,则按此设置后,AD7327会自动只获取VIN0 VIN2通道的数值。大多数情况使用此设置。
seq1=1,seq2=0,如下图,会从VIN0开始到我们设定的通道地址。即设置通道地址为VIN5,按此设置后,AD7327会获取VIN0~VIN5的数值
4.2 序列寄存器
选择相应输入采集通道值,输出到总线。我使用了8个通道,因此全选即可,VIN0-VN7全为1即可。
4.3 量程寄存器
通过设置VinxA与VinxB,来配置相应通道的输入量程,我使用的8个通道均为±10V,因此配置为00即可。
5.数据输出
通过向SPI总线写入0x00来读取AD值,返回的16位数据中,第15-13位为当前12位采集值的地址值,第12位为正负标志,若采用补码方式,采集到负电压,则该位为1.
具体ADD2-ADD0对应哪个通道,可以在上述4.1控制寄存器中table10中找到
6.代码
官方例程中已经配置好各个寄存器地址,在ad7327.h文件中,此处我就不放那些代码了,自己去官网下就好。
H文件增加内容如下:
/*edit by hyarcher25 用于读取数据地址判断*/
#define CH0Add 0x00
#define CH1Add 0x01
#define CH2Add 0x02
#define CH3Add 0x03
#define CH4Add 0x04
#define CH5Add 0x05
#define CH6Add 0x06
#define CH7Add 0x07
6.1 sequence模式,自动读取指定通道
初始化
#define AD_AUTO 1 //默认使用sequence模式,若想单独读取,改成0即可
uint8_t AD7327Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI1_Init();//SPI1初始化
SPI1_SetSpeed(SPI_BaudRatePrescaler_2);//spi速率选择
//PA4 Vdrive/PC4 CS 引脚初始化
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
AD_Vdrive_1;//Vdrive拉高
AD_CS_HIGH;//片选拉高,禁止AD读取,带配置完寄存器后拉低
delayms(20);
//量程选择
WriteToRangeRegister1 (IN10vCH0, IN10vCH1, IN10vCH2, IN10vCH3);
delayms(20);
WriteToRangeRegister2 (IN10vCH4, IN10vCH5, IN10vCH6, IN10vCH7);
delayms(20);
#if AD_AUTO //使用自动sequence模式,则初始化以下代码,否则无需执行
//我使用了8个通道,因此VIN0-VIN7全部要初始化
WriteToSequenceRegister (VIN0|VIN1|VIN2|VIN3|VIN4|VIN5|VIN6|VIN7);
delayms(20);
WriteToControlRegister(CH0|CH1|CH2|CH3|CH4|CH5|CH6|CH7, Mode0, Normal,
StraightBinary, Ref_En, SequencerPrev);
delayms(20);
#endif
return 0;
}
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_PinAFConfig(GPIOA , GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA , GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA , GPIO_PinSource7, GPIO_AF_SPI1);
//GPIOA 5 6 7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);
//SPI_I2S_DeInit(SPI1);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //空闲状态高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //时钟第一个跳边沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
使用sequence模式,配置完成后,系统则会按照VIN0-VIN7的顺序采集转换并送出
假如只需要读取某几个通道的数据,则初始化后选择相应通道即可。
SPI读写
uint16_t SPI1_ReadWriteByte(uint16_t TxData)
{
uint16_t returnData;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}
AD_CS_LOW;//拉低cs
SPI_I2S_SendData(SPI1, TxData); //发送命令
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){}
returnData = SPI_I2S_ReceiveData(SPI1);//读取SPI数据
AD_CS_HIGH;//拉高cs
return returnData;//返回SPI读取到的值
}
读取AD值函数
uint16_t ReadSequence (void)
{
uint16_t DataToWrite = 0x00;
uint16_t ReturnData = 0x00;
DataToWrite = 0x0000;
ReturnData = SPI1_ReadWriteByte (DataToWrite);
return ReturnData;
}
写入00,芯片便会按照之前的设置返回采集值
注意返回值第15-13位为通道值,第12位为正负标志
其他功能函数
求绝对值,便于数据处理
//求绝对值
static uint16_t Absolute(uint16_t val)
{
uint16_t temp;
//判断第13位正负标志位
switch((val >> 12) & 0x0001)
{
case 0://负值
temp = (0x0fff - (val&0x0fff));//先去掉高4位,再求其绝对值
break;
case 1://正值
temp = (val&0x0fff);//去掉高4位留下数据位后直接返回
break;
default:
temp = val;
break;
}
return temp;
}
自动读取,个人项目使用,三个通道的。请手动修改其中内容
void AutoReadAD(void)
{
uint16_t value_temp;
uint8_t i, j;
for(i = 0; i < 10; i ++)//采10组
{
for(j = 0; j < 3; j ++)//3个通道,保证每个通道一个初值
{
value_temp = ReadSequence();
delayus(1);//没有此延时可能会导致接收的数据莫名其妙异常,有时又没问
//题,至今没找到原因
//右移13位,判断地址
switch(value_temp >> 13)
{
case CH2Add://CH2
adOriginal[0][i] = Absolute(value_temp);
break;
case CH3Add://CH3
adOriginal[1][i] = Absolute(value_temp);
break;
case CH7Add://CH7接的参考2.5V
adOriginal[2][i] = Absolute(value_temp);
break;
default:
break;
}//end of switch
}//end of for
}//end of for
//求均值
ValueAverageCal();
}
6.2 不使用sequence,每次需要配置后再读取指定通道
static uint16_t ReadCHx(uint16_t VINx, uint16_t CHx)
{
uint16_t value;
uint8_t i;
double sum = 0;
WriteToSequenceRegister (VINx);//读取VINx通道的值
delayms(10);
WriteToControlRegister(CHx, Mode0, Normal, StraightBinary, Ref_En, Sequencer);
delayms(10);
for(i = 0; i < N; i ++)//采集N次
{
ADValue_Original[i] = (ReadSequence() & 0X0FFF);//读取值后12位放入数组
sum += ADValue_Original[i];//N次值相加求和
}
value = (uint16_t) (sum / N);//取平均值
return value;
}
VINx与CHx为相同通道值。就是你想读通道1,就把x写1,读通道2,就把x改成2
这个貌似还有一点bug,我基本没使用过这个模式,都是使用sequence模式自动读取,无需每次配置,更为方便,sequence模式也可以读取指定通道值。
PS.近来发现一些问题,SPI速率与AD速率匹配不太对,例如我开通全部通道,要求每个通道在10ms内采集100个 共800个数据,按照AD7327 5000ksps的速率应该是小菜一碟,但实际采集到的数据就不是很正确,把SPI各个速率设定都试过了,效果不明显。
但是如果单个通道好像又效果好一些。把数据采集速度降低比如采集20个数据,数据又正常了。 头疼,真是奇怪。。。望大神解惑!