ADS1246是TI公司大致在2009年中期推出的24位ADC,最高采样速率可达2Ksps,其为单通道器件,与之相对应的还有ADS1247和ADS1248三通道器件,但特性并非完全一致。据TI资料介绍,ADS1246在ADS1247/ADS1248功能上做出简化,保留了其部分特性。本次设计,需要用到24位单通道转换器件,于是考虑用到ADS1246,主控制器用STM32L系列。以下为ADS1246的引脚图:
上图显示ADS1246引脚图,其CS/SCLK/DIN/DUT为SPI通讯接口,RESET/START/DRDY为控制与状态脚,AVDD/AVSS以及DVDD/DGND分别为模拟/数字电源供电端,REFP/REFN为基准源输入脚,AINP/AINN为模拟信号输入端。其中,DRDY忙信号指示功能可以附加到DOUT引脚上,这样DRDY脚可以留空。在实际使用中发现,START脚做为ADC的启动脚,还必须得接出来,因我还未找到有通过软件能启动ADS1246转换的方法,但其DS中有提到START信号和SLEEP/WAKEUP相类似的功能,暂未深研究。顺便 提一下,TI关于ADS1246的文档是改自于另一颗ADC器件的文档,所以极其烂……
ADS1246的SPI时序,这个是需要提一下的,一般来说,SPI协议在上升沿锁存数据,下降沿更新数据,这是一般SPI协议的作法。但ADS1246需要在下降沿锁存数据,上升沿更新数据,在设置SPI寄存器的时候需要注意一下,当我采用一般性设置的时候,发现通讯不正常。以下是STM32L的SPI设置,用的是SPI2。
-
//SPI2配置
-
RCC->APB1ENR|=RCC_APB1ENR_SPI2EN;
-
SPI2->CR1=SPI_CR1_MSTR|SPI_CR1_BR|SPI_CR1_SSM|SPI_CR1_SSI|SPI_CR1_CPHA;
//8位模式
-
SPI2->CR1|=SPI_CR1_SPE;
SPI2的驱动:
-
//SPI2写数据
-
void SPI2_WriteBytes(uint8 *TxBuffer,uint16 TxLenth)
-
{
-
uint8 i;
-
while(TxLenth--){
-
while( (SPI2->SR & SPI_SR_TXE) ==
0 );
-
SPI2->DR=*TxBuffer++;
-
while((SPI2->SR&SPI_SR_RXNE)==
0);
-
i=SPI2->DR;
-
}
-
i++;
-
}
上程序中i++的引入在于避免keil-MDK产生编译警告。
-
//SPI2读数据
-
void SPI2_ReadBytes(uint8 *RxBuffer,uint16 RxLenth)
-
{
-
while(RxLenth--){
-
while((SPI2->SR&SPI_SR_TXE)==
0);
-
SPI2->DR=*RxBuffer;
-
while((SPI2->SR&SPI_SR_RXNE)==
0);
-
*RxBuffer++=SPI2->DR;
-
}
-
}
以上驱动代码,能保证SPI在最后一个字节完全发送完成之后退出,如果没有等待SPI_SR_RXNE,则仅仅只是把数据转移到SPI移位寄存器,并未完全送出,不详述。
以下介绍我的驱动过程,在驱动ADS1246的时候,主要参考那个网方的58页的极烂文档,上面没有明确提到整个上电过程以及初始化过程,至于那个相当重要的自校准过程及操作方法也没有提到,所以本人摸索了一整天时间,在此整理。
ADS1246采用SPI通讯 ,其所有通讯引脚(SCK/DIN/DOUT)都在CS脚为低电平的时候有效,在CS为高时均为三态,当DRDY绑定到DOUT脚时,只有在CS为低时才能正确的指示忙状态,若DRDY采用单独的引脚,则不受CS控制。ADS1246的所有通讯过程被分为若干个命令组,有的需要带参数,有的不需要带参数,其实我也不明白它为什么要搞那么麻烦,感觉本来可以很简单的处理,结果弄的很乱。以下为其命令分组:
大致分为命令类(不带参数),读寄存器,写寄存器三类,以下分别分其实现:
-
//ADS1246命令码列表
-
#define ADC_CMD_WAKEUP 0x00 //退出睡眠模式
-
#define ADC_CMD_SLEEP 0x02 //进入睡眠模式
-
#define ADC_CMD_SYNC 0x04 //同步ADC转换
-
#define ADC_CMD_RESET 0x06 //芯片复位
-
#define ADC_CMD_NOP 0xFF //空操作
-
#define ADC_CMD_RDATA 0x12 //单次读取数据
-
#define ADC_CMD_RDATAC 0x14 //连续读取数据
-
#define ADC_CMD_SDATAC 0x16 //停止连续读取
-
#define ADC_CMD_RREG 0x20 //读寄存器
-
#define ADC_CMD_WREG 0x40 //写寄存器
-
#define ADC_CMD_SYSOCAL 0x60 //系统偏移校准
-
#define ADC_CMD_SYSGCAL 0x61 //系统增益校准
-
#define ADC_CMD_SELFOCAL 0x62 //系统自校准
-
#define ADC_CMD_RESTRICTED 0xF1 //
-
/*---------------------------------------------------------
-
写命令
-
---------------------------------------------------------*/
-
void ADS1246_WriteCmd(uint8 Cmd)
-
{
-
ADC_SPI_CS_CLR
-
ADC_WriteBytes
(&Cmd,1);
-
ADC_SPI_CS_SET
-
}
-
-
-
/*---------------------------------------------------------
-
读寄存器
-
---------------------------------------------------------*/
-
void ADS1246_ReadReg(uint8 RegAddr,uint8 *Buffer,uint8 Length)
-
{
-
uint8 Cmd[
2];
-
ADC_SPI_CS_CLR
-
Cmd[
0]=ADC_CMD_RREG|RegAddr;
-
Cmd[
1]=Length
-1;
-
ADC_WriteBytes(Cmd,
2);
-
ADC_ReadBytes(Buffer,Length);
-
Cmd[
0]=ADC_CMD_NOP;
-
ADC_WriteBytes(Cmd,
1);
-
ADC_SPI_CS_SET
-
}
-
-
-
/*---------------------------------------------------------
-
写寄存器
-
---------------------------------------------------------*/
-
void ADS1246_WriteReg(uint8 RegAddr,uint8 *Buffer,uint8 Length)
-
{
-
uint8 Cmd[
2];
-
ADC_SPI_CS_CLR
-
Cmd[
0]=ADC_CMD_WREG|RegAddr;
-
Cmd[
1]=Length
-1;
-
ADC_WriteBytes(Cmd,
2);
-
ADC_WriteBytes(Buffer,Length);
-
ADC_SPI_CS_SET
-
}
在写读存器时,一定要注意,根据其DS文档第32页说明,其后发一个NOP命令可以强制DOUT引脚输出高电平,这样可以随后判断DOUT是否为低进而知道是否处于忙状态,否则会得到一个脉冲。其实在任何的读操作完成后,发一个字节的NOP命令即可将DOUT强制输出高电平。当DRDY绑定到DOUT的时候,这个是非常重要的。
在弄清楚以上命令读写方法之后,需要实现其忙状态判别,这个在很多芯片驱动时都会遇到,它直接提示了其内部的工作状态,只有在不忙时才能继续执行下一条指令。
-
/*---------------------------------------------------------
-
忙状态判断,最长等待时间,200 X 10 ms=2S
-
---------------------------------------------------------*/
-
uint8 ADS1246_WaitBusy()
-
{
-
uint16 i;
-
ADC_SPI_CS_CLR
-
i=
0;
-
while(ADC_RDY_DAT>
0){
-
Wrtos_TaskDelay(
10);
-
i++;
if(i>
200)
return
1;
-
}
-
ADC_SPI_CS_SET
-
return
0;
-
}
ADS1246的校准。在ADS1246内部,其前端是一个独立的多路复用器,其每路输入跟据功能的不同分别接到了相应的电平上,之后是一个能配置为1/2/4/8/16/32/64/128倍的可变增益放大器,之后是ADC转换核心,最后是数字滤波器和数据接口部分。对于本器件,它通过MUX复用器的配置能进行PGA增益和系统偏移的校正。
>. 对于系统偏移校正,指当AIN0-AIN1=0时,在指定的PGA倍率下,ADC的量化输出,此时理论上应该输出为0,但往往达不到0。
>.对于PGA增益校正,指当AIN0-AIN1=VREFP-VREFN时,在PGA=1时,ADC的量化输出,此时理论上应该输出为满量程,但有些设计也达不到。
以上两点,是ADS1246的定义,在DS的35页下方有一个表格,类似以上表述,但这可能和一般性描述有所出入,特别是PGA校正,可能在这里面它自己还进行了一些未表述出来的工作。以下是其校正实现:
-
/*---------------------------------------------------------
-
执行校准---系统校准->偏移校准->增益校准
-
---------------------------------------------------------*/
-
uint8 ADS1246_Calibrate(uint8 Gain)
-
{
-
uint8 R=
0,Cmd;
-
ADS1246_WriteReg(ADC_REG_SYS0,&Gain,
1);
//设置校准增益
-
-
Cmd=
0x00;
-
ADS1246_WriteReg(ADC_REG_MUX,&Cmd,
1);
//恢复正常模式
-
ADS1246_WriteCmd(ADC_CMD_SELFOCAL);
//系统偏移自校准
-
R|=ADS1246_WaitBusy();
//等待校准完成
-
-
Cmd=
0x01;
-
ADS1246_WriteReg(ADC_REG_MUX,&Cmd,
1);
//设置AINP+AINN=(AVDD+AVSS)/2
-
ADS1246_WriteCmd(ADC_CMD_SYSOCAL);
//系统偏移自校准
-
R|=ADS1246_WaitBusy();
//等待校准完成
-
-
Cmd=
0x02;
-
ADS1246_WriteReg(ADC_REG_MUX,&Cmd,
1);
//设置AINP=VREF+,AINN=VREF-; for gain calibration
-
ADS1246_WriteCmd(ADC_CMD_SYSGCAL);
//系统增益校准
-
R|=ADS1246_WaitBusy();
-
-
return R;
-
}
在校准之前,需要先设置MUX复用器到相应的模式,DS文档中对于此有相应的描述,之后进行相应校准,注意,每一步校准都需要一定的时间去完成,所以一定要等待DRDY信号。以上校准过程,我在反复读过其DS文档后写出,不能保证其完全正确,文档中并未有校准过程的详细过程。
ADS1246的上电过程 :
1. RESET低电平复位,拉低,至少保证50us,拉高,等待至少600us,ADS1246硬件复位完成。
2. SPI软件复位,我知道不一定要做这一步,但是建议做这一步。
3. 配置寄存器值,可以一性写完所有寄存器,也可以写自己感兴趣的。
4. 读寄存器值,判断有没有执行成功。这一步建议实现,可有于判断有没有器件存在或是器件是否损坏。
5. 进行芯片自动校准过程,包括偏移校准和增益校准。
6. 初始化完成。
从以上可以看出,其初始化过程还是比较复杂的,那个校准过程还比较耗时,在5sps速率设置下,需要用到至少3S时间。以下为软件实现:
-
/*---------------------------------------------------------
-
初始化
-
---------------------------------------------------------*/
-
uint8 ADS1246_Init(uint8 Gain)
-
{
-
uint8 i,Cmd;
-
ADC_RESET_CLR
-
for
(i=0;i<200;i++);
-
ADC_RESET_SET
-
for
(i=0;i<200;i++);
-
ADS1246_WriteCmd(ADC_CMD_RESET);
-
Wrtos_TaskDelay(
1);
-
ADS1246_WriteReg(ADC_REG_SYS0,&Gain,
1);
//初始化所有寄存器
-
Cmd=
0x08;
-
ADS1246_WriteReg(ADC_REG_ID,&Cmd,
1);
//DOUT兼容DRDY引脚
-
if((Cmd&
0x08)>
0)
return
1;
-
Cmd=ADC_CMD_NOP;
-
ADS1246_ReadReg(ADC_REG_ID,&Cmd,
1);
//读取器件ID
-
Cmd=ADS1246_Calibrate(Gain);
//通道校准
-
return Cmd;
-
}
在初始化过程中,基本按以上几步完成,以上代码中,向ADC_REG_ID第3位写1,用于将DRDY绑定到DOUT引脚上,在读完ADC_REG_ID之后,判断其值是否第3位为高,这样可以大致判断出芯片是否存在是否通讯正常以及寄存器读写是否正常。在确定无误之后,随后发送了ADC_CMD_NOP命令, 这样可将DOUT强制到高电平,为之后判断DRDY为低电平做准备。这一步很重要,否则忙判断函数可能工作不正常。
ADS1246的转换控制过程。ADS1246通过START引脚上的高电平进行启动控制,当START脚变成高电平之后,芯片开始转换,这个高电平的持续时间至少应为5us(其实更短,DS上有明确说明)。在转换完成后,若START仍为高电平,则进行再一次转换,若START为低电平,则进入低功耗模式。所以这个START脚成为单次转换或连续转换方式的控制信号。
-
/*---------------------------------------------------------
-
配置转换参数
-
---------------------------------------------------------*/
-
void ADS1246_Config(uint8 CovGain,uint8 CovRate)
-
{
-
uint8 Cmd;
-
Cmd=CovGain|CovRate;
-
ADS1246_WriteReg(ADC_REG_SYS0,&Cmd,
1);
//设置采样增益和速率
-
}
-
-
/*---------------------------------------------------------
-
启动转换
-
---------------------------------------------------------*/
-
void ADS1246_Start(uint8 CovMode)
-
{
-
ADC_START_SET
//启动ADC采样
-
if(CovMode==ADC_MODE_SINGLECOV)ADC_START_CLR
//产生启动脉冲
-
}
-
-
/*---------------------------------------------------------
-
停止转换,进入低功耗模式
-
---------------------------------------------------------*/
-
void ADS1246_Stop()
-
{
-
ADC_START_CLR
//停止转换
-
}
以上在ADS1246_Start方法中,通过在START线上产生高电平脉冲或是维持高电平的方法来实现单次或是连续模式转换。
以下是数据的读取方法,根所DS文档的描述,首先判断其是否已转换完成,确定处于DRDY闲状态,然后发送读数据指令,随后发送24个CLK,即读取3个字节24位数据,关键的是,在最后再发送1字节的ADC_CMD_NOP指令,用于恢复DOUT为高电平。
-
/*---------------------------------------------------------
-
数据读取
-
---------------------------------------------------------*/
-
int32 ADS1246_Read()
-
{
-
uint8 Cmd[
5];
-
int32 D;
-
Cmd[
0]=ADC_CMD_RDATA;
-
Cmd[
1]=ADC_CMD_NOP;
-
Cmd[
2]=ADC_CMD_NOP;
-
Cmd[
3]=ADC_CMD_NOP;
-
Cmd[
4]=ADC_CMD_NOP;
-
ADC_SPI_CS_CLR
-
ADC_ReadBytes
(Cmd,5);
-
ADC_SPI_CS_SET
-
Cmd[
0]= (Cmd[
1]&
0x80)!=
0 ?
0xFF:
0x00;
-
D=TTS_D32FromArray(Cmd,
1);
-
return D;
-
}
以上方法中,采用了数据单次读取指令ADC_CMD_RDATA,在3字节数据后,空读了一个字节,同时发送了ADC_CMD_NOP。注意24位有符号数扩展成为32位有符号数的方法,最简单的方法即把24位中最高位复制到32位的高8位上。
在实现ADS1246的所有驱动方法之后,引入一组定义如下:
-
//ADS1246寄存器列表
-
#define ADC_REG_BCS 0x00
-
#define ADC_REG_VBIAS 0x01
-
#define ADC_REG_MUX 0x02
-
#define ADC_REG_SYS0 0x03
-
#define ADC_REG_OFC0 0x04
-
#define ADC_REG_OFC1 0x05
-
#define ADC_REG_OFC2 0x06
-
#define ADC_REG_FSC0 0x07
-
#define ADC_REG_FSC1 0x08
-
#define ADC_REG_FSC2 0x09
-
#define ADC_REG_ID 0x0A
-
-
//ADS1246支持的增益列表
-
#define ADC_GAIN_1 0x00
-
#define ADC_GAIN_2 0x10
-
#define ADC_GAIN_4 0x20
-
#define ADC_GAIN_8 0x30
-
#define ADC_GAIN_16 0x40
-
#define ADC_GAIN_32 0x50
-
#define ADC_GAIN_64 0x60
-
#define ADC_GAIN_128 0x70
-
-
//ADS1246支持的转换速率列表
-
#define ADC_SPS_5 0x00
-
#define ADC_SPS_10 0x01
-
#define ADC_SPS_20 0x02
-
#define ADC_SPS_40 0x03
-
#define ADC_SPS_80 0x04
-
#define ADC_SPS_160 0x05
-
#define ADC_SPS_320 0x06
-
#define ADC_SPS_640 0x07
-
#define ADC_SPS_1000 0x08
-
#define ADC_SPS_2000 0x09
-
-
//ADS1246转换模式设置
-
#define ADC_MODE_SINGLECOV 0x00 //单次转换模式
-
#define ADC_MODE_CONTINUOUS 0x01 //连续转换模式
以下是具体调用以及转换实现:
-
//ADS1246应用示例
-
void Task0(void *Tags)
-
{
-
uint8 R,Rxlength,Buffer[
30];
-
int32 D,V,V0=
0;
-
USART1_Init();
-
USART1_SetParams(
9600,
8,
'N',
1);
-
ADS1246_Init(ADC_GAIN_1);
//初始化
-
ADS1246_Config(ADC_GAIN_1,ADC_SPS_5);
//增益1,5sps速率
-
ADS1246_Start(ADC_MODE_CONTINUOUS);
//启动连续转换
-
while(
1){
-
D=ADS1246_Read();
//读转换数据
-
D>>=
6;
//数据转换到18位,丢去末8位
-
-
V=ADS1246_GetVoltage(D,ADC_GAIN_1);
//计算前端输入电压
-
-
Rxlength=TTS_StringFromNumber(Buffer,D,
'D',
0);
//打印18位转换值
-
Rxlength=TTS_StringAppend(Buffer,
" ");
-
USART1_WriteDatas(Buffer,Rxlength,
0);
-
-
Rxlength=TTS_StringFromNumber(Buffer,(f32)V/
1000,
'D',
3);
//打印电压
-
Rxlength=TTS_StringAppend(Buffer,
"mV ");
-
USART1_WriteDatas(Buffer,Rxlength,
0);
-
-
V0=V-V0;
-
Rxlength=TTS_StringFromNumber(Buffer,(f32)V0,
'D',
0);
//打印本次跳动
-
Rxlength=TTS_StringAppend(Buffer,
"uV\r\n");
-
USART1_WriteDatas(Buffer,Rxlength,
0);
-
-
V0=V;
-
}
-
}
以上使用中,将24位值的末8位去掉,只使用其18位,以下是从串口助手看到的数据:
从实际结果看,在只采用其前18位值的情况下仍有4LSB的跳动,测试结果显然说明其无噪声位只能达到16位,这与AD7799之类的芯片相比还是有一定差距。不知道是否我买到山寨货的原因还是芯片本身的原因,其与手册上指出的20位精度还是有较大出入。排除电路设计上的干扰,则芯片本身误差太大,另外不知道什么原因,其PGA增益误差大的吓人,基本用不了,在PGA=1时最多能获得16位有效位,在PGA>0时极度不准确。
24位ADC,看来TI也并非做到极致,还是主要考虑模拟器件公司比较靠谱。
注意事项:
1、最后一条写寄存器操作,在CS拉高之前,必须要等数据被发完(不是送到发送缓冲就完事了),可以加延时,也可以加发一条NOP指令(会等待发送缓冲空,相当于上条有效命令发完),保证上条指令写入数据完成再拉高CS。
/*
* 函数名:SPI1_ReadWriteByte
* 描述 :SPI1 读写一个字节
* 输入 :TxData:要写入的字节
* 输出 :读取到的字节
*/
u8 SPI1_ReadWriteByte(u8 TxData)
{
vu16 retry = 0;
u16 dat;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查发送缓存空标志位,等待发送区空
{
retry++;
if (retry > 200) return 0;
}
SPI_I2S_SendData(SPI1, TxData); //通过外设SPI1发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) //检查接收缓存非空标志位,等待接收完一个 字节
{
retry++;
if (retry > 200) return 0;
}
dat = SPI_I2S_ReceiveData(SPI1);
return (u8)dat; //返回通过SPI1最近接收的数据
}
/*
* 函数名:ADS1248_WriteReg
* 描述 :写ADS1248的寄存器
* 输入 : RegAddr 寄存器起始地址
* *Buffer 存放待写入数据的指针
* length 数据字节数
* 输出 :无
* 返回 :无
*/
void ADS1248_WriteReg(u8 RegAddr,u8 *Buffer,u8 Length)
{
u8 Cmd[2];
ADS1248_CS_0;
Cmd[0] = ADC_CMD_WREG|RegAddr;
Cmd[1] = Length - 1;
spi_write_bytes(Cmd,2);
spi_write_bytes(Buffer,Length);
Cmd[0] = ADC_CMD_NOP; //发一条空操作,保证上条指令被从机读走
spi_write_bytes(Cmd,1);
//Delay_us(20); //或者用这条延时,不用发NOP指令
ADS1248_CS_1;
}
很多用库函数的写指令有时不成功,而用软件模拟的就可以,估计就是这个问题吧。因为库函数操作只是写入缓冲,就退出了,没有等待数据送走,就把CS拉高了。
2、手册提到,START脚为低,芯片处于sleep模式,此时只有RDATA、RDATAC、SDATAC、WAKEUP、NOP命令是有效的。网上很多例程写命令前没有让START为高。自己试过,好像读写寄存器的操作还是有效的。但是有些情况下又不正常,例如改变转换速率。试验还发现,校验指令前必须置位START,否则校验不成功,而且寄存器被全部清零,重新写入也无效。所以,为了保险起见,操作寄存器我一直让START为高。