我們經(jīng)常會碰到多通道AD采集的需求,有時候甚至需要高精度的ADC器件。本篇我們將來設(shè)計并實現(xiàn)ADS1256模數(shù)轉(zhuǎn)換器的驅(qū)動。并簡單討論該驅(qū)動使用方式。
1、功能概述
??ADS1256是TI公司推出的一款低噪聲高分辨率的24位Sigma-Delta(E-v)模數(shù)轉(zhuǎn)換器(ADC)。E-vADC與傳統(tǒng)的逐次逼近型和積分型ADC相比有轉(zhuǎn)換誤差小而價格低廉的優(yōu)點,但由于受帶寬和有效采樣率的限制,E-vADC不適用于高頻數(shù)據(jù)采集的場合。該款A(yù)DS1256可適合于采集最高頻率只有幾千赫茲的模擬數(shù)據(jù)的系統(tǒng)中,數(shù)據(jù)輸出速率最高可為30K采樣點/秒,4路差分或8路偽差分輸入,有完善的自校正和系統(tǒng)校正系統(tǒng),SPI串行數(shù)據(jù)傳輸接口。其結(jié)構(gòu)圖如下所示:
??從結(jié)構(gòu)圖可以看出來,ADS1256是模擬區(qū)域與數(shù)字區(qū)域完全獨立的ADC,即AVDD給模擬區(qū)域供電,DVDD給數(shù)字區(qū)域供電,在原理圖設(shè)計方面按照官方指導(dǎo)文檔,需要對兩個區(qū)域做獨立的布線與隔離處理,才能讓信噪比最佳。
??ADS1256采用SSOP的封裝形式,具有8個模擬輸入通道,共28個引腳,與其類似的2通道產(chǎn)品ADS1255共有20引腳,其實兩者操作相同,所以我們設(shè)計驅(qū)動也會考慮兼容性。其中ADS1256引腳排布和定義如下圖所示:
??ADS1255和ADS1256的操作是通過一組寄存器來控制的。這些寄存器包含了配置部件所需的所有信息,如數(shù)據(jù)速率、多路復(fù)用器設(shè)置、PGA設(shè)置、校準(zhǔn)等。這些寄存器的地址及結(jié)構(gòu)如下表所述:
??我們知道了這些寄存器的定義,那么就可以操作ADS1256了。可是我們怎么來實現(xiàn)對這些寄存器的訪問呢?這就涉及到操作命令的問題了。ADS1256有多個操作命令,具體如下表所示:
??在以上這些命令中,除了讀寫寄存器操作需要有第二個字節(jié)命令和數(shù)據(jù)外,其它命令都是獨立使用的。
2、驅(qū)動設(shè)計與實現(xiàn)
??我們已經(jīng)了解了ADS1256的相關(guān)結(jié)構(gòu)、寄存器及操作命令。接下來我們就來考慮如何設(shè)計ADS1256的驅(qū)動程序。
2.1、對象定義
??與以往一樣,我們依然是基于對象來實現(xiàn)ADS1256的驅(qū)動程序,所以我們需要抽象出ADS1256的對象類型。
2.1.1、抽象數(shù)據(jù)類型
??我們先來考慮一下ADS1256對象類型的定義問題。一個對象一般來說主要包括屬性和操作兩個方面的內(nèi)容,我們也從這兩個方面來分析ADS1256對象。
??首先我們來考慮ADS1256模數(shù)轉(zhuǎn)換器對象的屬性。這些屬性必須能夠標(biāo)識ADS1256模數(shù)轉(zhuǎn)換器對象的特征,或者是存儲ADS1256模數(shù)轉(zhuǎn)換器對象的某種狀態(tài)。對于ADS1256模數(shù)轉(zhuǎn)換器對象我們希望可以記錄寄存器的狀態(tài),所以我們將各個寄存器定義為該對象的屬性。
??接下來我們再來考慮一下ADS1256模數(shù)轉(zhuǎn)換器對象的操作。一個對象有各種各樣的操作,或者說他能實現(xiàn)很多的操作,但不是所有的操作都是我們要提取的。我們需要考慮的是那些對象所獨有并且同類對象都必不可少的操作,以及那些不能由對象獨自完成,依賴于具體平臺但又決定對象的行為的必要操作。對于ADS1256莫數(shù)轉(zhuǎn)換器,我們需要讀寫數(shù)據(jù),操作片選信號,讀取就緒信號等。但這些操作都依賴于具體的軟硬件平臺,我們將這些操作定義對象的操作,通過函數(shù)指針的方式將具體的操作函數(shù)傳遞給對象變量,以便于適用于不同的軟硬件平臺。此外,由于時序控制的需要我們需要在驅(qū)動中使用延時操作,而延時操作的實現(xiàn)依賴于具體的軟硬件平臺,所以我們也將其抽象為對象的操作。根據(jù)上述我們的分析,可以定義ADS1256模數(shù)轉(zhuǎn)換器對象的類型如下:
/*定義ADS1256對象類型*/
typedef struct ADS1256Object {
uint8_t Register[11];
void (*ReadWrite)(uint8_t *wData,uint8_t *rData,uint16_t size); //實現(xiàn)讀寫操作
void (*ChipSelect)(ADS1256CSType cs); //實現(xiàn)片選
uint16_t (*GetReadyInput)(void); //實現(xiàn)Ready狀態(tài)監(jiān)視
void (*Delay)(volatile uint32_t nTime); //實現(xiàn)ms延時操作
}ADS1256ObjectType;
2.1.2、對象初始化函數(shù)
??我們抽象了ADS1256模數(shù)轉(zhuǎn)換器的對象類型,使用這一對象類型我們可以獲得具體的對象變量,但這一對象變量必須要進行必要的屬性和操作設(shè)定才能進行正確的操作。為了完成對象變量屬性和操作的配置,我們需要一個對象初始化函數(shù)。
/*ADS1256初始化配置函數(shù)*/
void ADS1256Initialization(ADS1256ObjectType *ads, //待初始化的ADS1256對象
ADS1256OrderType order, //數(shù)據(jù)順序
ADS1256ACALType acal, //自動校準(zhǔn)使能
ADS1256BufenType bufEn, //模擬量緩存使能
ADS1256ClkoutType clkOut, //時鐘輸出類型
ADS1256SDCSType sdcs, //傳感器檢測電流
ADS1256GainType gain, //增益
ADS1256DRateType dataRate, //數(shù)據(jù)輸出速率
ADS1256DIOType *dio, //輸入輸出配置
ADS1256ReadWriteType readWrite, //讀寫函數(shù)指針
ADS1256ChipSelectType cs, //片選函數(shù)指針
ADS1256GetReadyInputType ready, //就緒函數(shù)指針
ADS1256DelaymsType delayms //毫秒延時函數(shù)指針
)
{
uint8_t Order[]={STATUS_ORDER_MOST,STATUS_ORDER_LEAST};
uint8_t ACAL[]={STATUS_ACAL_DISABLE,STATUS_ACAL_ENABLE};
uint8_t BUFEN[]={STATUS_BUFEN_DISABLE,STATUS_BUFEN_ENABLE};
uint8_t clkck[]={ADCON_CLOCK_OFF,ADCON_CLOCK_FCLKIN,ADCON_CLOCK_HALF,ADCON_CLOCK_QUARTER};
uint8_t sDCS[]={ADCON_SDCS_OFF,ADCON_SDCS_05uS,ADCON_SDCS_2uS,ADCON_SDCS_10uS};
uint8_t gains[]={ADCON_PGA_GAIN1,ADCON_PGA_GAIN2,ADCON_PGA_GAIN4,ADCON_PGA_GAIN8,
ADCON_PGA_GAIN16,ADCON_PGA_GAIN32,ADCON_PGA_GAIN64};
uint8_t dRate[]={DRATE_30000SPS,DRATE_15000SPS,DRATE_7500SPS,DRATE_3750SPS,
DRATE_2000SPS,DRATE_1000SPS,DRATE_500SPS,DRATE_100SPS,
DRATE_60SPS,DRATE_50SPS,DRATE_30SPS,DRATE_25SPS,
DRATE_15SPS,DRATE_10SPS,DRATE_5SPS,DRATE_2_5SPS};
uint8_t dir[4][2]={{GPIO_DIR0_OUTPUT,GPIO_DIR0_INPUT},
{GPIO_DIR1_OUTPUT,GPIO_DIR1_INPUT},
{GPIO_DIR2_OUTPUT,GPIO_DIR2_INPUT},
{GPIO_DIR3_OUTPUT,GPIO_DIR3_INPUT}};
if((ads==NULL)||(readWrite==NULL)||(ready==NULL)||(delayms==NULL))
{
return;
}
ads->ReadWrite=readWrite;
ads->GetReadyInput=ready;
ads->Delay=delayms;
if(cs==NULL)
{
ads->ChipSelect=ADS1256ChipSelect;
}
else
{
ads->ChipSelect=cs;
}
for(int i=0; i<11;i++)
{
ads->Register[i]=0x00;
}
ads->Register[REG_STATUS]=Order[order]||ACAL[acal]||BUFEN[bufEn];
ads->Register[REG_MUX]=0x00;
ads->Register[REG_ADCON]=clkck[clkOut]||sDCS[sdcs]||gains[gain];
ads->Register[REG_DRATE]=dRate[dataRate];
ads->Register[REG_IO]=dir[0][dio[0]]||dir[1][dio[1]]||dir[2][dio[2]]||dir[3][dio[3]];
WriteADS1256Register(ads,REG_STATUS,1);
WriteADS1256Register(ads,REG_MUX,1);
WriteADS1256Register(ads,REG_ADCON,1);
WriteADS1256Register(ads,REG_DRATE,1);
WriteADS1256Register(ads,REG_IO,1);
ADS1256Calibration(ads,SELFCAL);
ReadADS1256Register(ads,REG_STATUS,11);
}
??在這個初始化函數(shù)中,我們完成兩個方面的內(nèi)容:一是對屬性的賦值和對操作函數(shù)指針進行初始化;二是對對象變量所代表的對象進行初始化配置。
2.2、對象操作
??我們獲得了ADS1256模數(shù)轉(zhuǎn)換器的對象類型,也編寫了初始化對象變量的函數(shù),接下來我們考慮一下ADS1256模數(shù)轉(zhuǎn)換器的一些主要的操作過程。
2.2.1、讀數(shù)據(jù)
??作為模數(shù)轉(zhuǎn)換器,我們首要的目的就是從其獲得我們想要的數(shù)據(jù)。ADS1256讀數(shù)據(jù)分為連續(xù)讀取和非連續(xù)讀取,在這里我們考慮單次讀取數(shù)據(jù)的操作。當(dāng)模數(shù)轉(zhuǎn)換完成后,就緒信號下拉到“0”,這個時候可以讀取數(shù)據(jù),數(shù)據(jù)讀取后就緒信號將上拉到“1”。
??根據(jù)上述描述和時序圖我們可以編寫讀取數(shù)據(jù)的操作如下:
/*ADS1256讀取數(shù)據(jù)*/
static uint32_t ADS1256ReadData(ADS1256ObjectType *ads)
{
uint8_t cmd[1]={RDATA};
uint8_t rData[3];
uint32_t result=0;
while(ads->GetReadyInput()==1);
ads->ChipSelect(ADS1256CS_Enable);
ads->ReadWrite(cmd,rData,3);
ads->ChipSelect(ADS1256CS_Disable);
result=rData[0];
result=(result<<8)+rData[1];
result=(result<<8)+rData[2];
return result;
}
2.2.2、讀寄存器
??我們已經(jīng)了解ADS1256有11個寄存器,這些寄存器都可讀取。讀取寄存器的命令由2個字節(jié)組成。第一個字節(jié)是讀寄存器命令0x10與寄存器起始地址合并而成。第二個字節(jié)是所要讀取的寄存器數(shù)量減1。具體的操作時序如下圖所示:
??根據(jù)上述對讀寄存器的描述和時序圖我們可以編寫讀寄存器的操作如下:
/*讀ADS1256寄存器*/
static void ReadADS1256Register(ADS1256ObjectType *ads,uint8_t regAddr,uint8_t regNum)
{
uint8_t cmd[2];
uint8_t rData[11];
cmd[0]=RREG|regAddr;
cmd[1]=regNum-1;
ads->ChipSelect(ADS1256CS_Enable);
ads->ReadWrite(cmd,rData,2);
cmd[0]=0;
cmd[1]=0;
ads->ReadWrite(cmd,rData,regNum);
ads->ChipSelect(ADS1256CS_Disable);
for(int i=0;iRegister[regAddr+i]=rData[i];
}
}
2.2.3、寫寄存器
??在ADS1256的11個寄存器中有一些寄存器用于配置ADS1256的工作特性,可以寫這些寄存器。寫寄存器的命令也是由2個自己組成。第一個字節(jié)是讀寄存器命令0x50與寄存器起始地址合并而成。第二個字節(jié)是所要寫的寄存器數(shù)量減1。具體的操作時序如下圖所示:
??根據(jù)上述對寫寄存器的描述和時序圖我們可以編寫寫寄存器的操作如下:
/*寫ADS1256寄存器*/
static void WriteADS1256Register(ADS1256ObjectType *ads,uint8_t regAddr,uint8_t regNum)
{
uint8_t wData[7];
uint16_t index=0;
uint8_t rData[2];
wData[index++]=WREG|regAddr;
wData[index++]=regNum-1;
for(int i=0;iRegister[regAddr+i];
}
ads->ChipSelect(ADS1256CS_Enable);
ads->ReadWrite(wData,rData,index);
ads->ChipSelect(ADS1256CS_Disable);
}
3、驅(qū)動的使用
??我們已經(jīng)設(shè)計并實現(xiàn)了ADS1256模數(shù)轉(zhuǎn)換器的驅(qū)動程序。在這一節(jié)中我們將設(shè)計一個簡單的例子,通過這個例子我們將簡單的驗證驅(qū)動程序的正確性,并簡要說明驅(qū)動的使用方法。
3.1、聲明并初始化對象
??我們?yōu)锳DS1256模數(shù)轉(zhuǎn)換器設(shè)計的驅(qū)動是基于對象開發(fā)的,所以我們在使用驅(qū)動之前需要聲明一個ADS1256模數(shù)轉(zhuǎn)換器對象變量。使用我們前面定義的ADS1256模數(shù)轉(zhuǎn)換器對象類型聲明這一變量如下:
??ADS1256ObjectType ads1256;
??聲明了這個對象變量后,我們還需要使用對象初始化函數(shù)來將這個對象變量變量進行初始化。我們設(shè)計的初始換函數(shù)有多個參數(shù):
ADS1256ObjectType *ads, //待初始化的ADS1256對象
ADS1256OrderType order, //數(shù)據(jù)順序
ADS1256ACALType acal, //自動校準(zhǔn)使能
ADS1256BufenType bufEn, //模擬量緩存使能
ADS1256ClkoutType clkOut, //時鐘輸出類型
ADS1256SDCSType sdcs, //傳感器檢測電流
ADS1256GainType gain, //增益
ADS1256DRateType dataRate, //數(shù)據(jù)輸出速率
ADS1256DIOType *dio, //輸入輸出配置
ADS1256ReadWriteType readWrite, //讀寫函數(shù)指針
ADS1256ChipSelectType cs, //片選函數(shù)指針
ADS1256GetReadyInputType ready, //就緒函數(shù)指針
ADS1256DelaymsType delayms //毫秒延時函數(shù)指針
??這些參數(shù)中,ADS1256對象我們已經(jīng)聲明。數(shù)據(jù)順序、自動校準(zhǔn)使能、模擬量緩存使能、時鐘輸出類型、傳感器檢測電流、增益、數(shù)據(jù)輸出速率等幾個參數(shù)均為枚舉量,我們們根據(jù)需要選擇就可,在這里我們均按默認值選擇。輸入輸出配置這個參數(shù),因有4個IO需獨立配置,所以我們定義一個數(shù)組,并將其傳入。剩下的幾個函數(shù)指針其原型定義如下:
/*定義讀寫操作函數(shù)指針類型*/
typedef void (*ADS1256ReadWriteType)(uint8_t *wData,uint8_t *rData,uint16_t size);
/*實現(xiàn)片選*/
typedef void (*ADS1256ChipSelectType)(ADS1256CSType cs);
/*實現(xiàn)Ready狀態(tài)監(jiān)視*/
typedef uint16_t (*ADS1256GetReadyInputType)(void);
/*實現(xiàn)ms延時操作*/
typedef void (*ADS1256DelaymsType)(volatile uint32_t nTime);
??我們需要根據(jù)函數(shù)的原型聲明來結(jié)合具體的軟硬件平臺設(shè)計這幾個函數(shù),并將函數(shù)指針以參數(shù)的形式傳遞給初始函數(shù)。我們是在STM32平臺來實現(xiàn)這個示例,所以延時函數(shù)我們直接采用HAL_Delay即可,其他幾個函數(shù)實現(xiàn)如下:
/*定義片選信號函數(shù)*/
void ADS1256CS(ADS1256CSType en)
{
if(ADS1256CS_Enable==en)
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET);
}
}
/* 定義就緒信號讀取函數(shù) */
uint16_t ADS1256CheckReady(void)
{
return HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0);
}
/*定義發(fā)送數(shù)據(jù)函數(shù)*/
void ADS1256WriteReadData(uint8_t *wData,uint8_t *rData,uint16_t size)
{
HAL_SPI_TransmitReceive(&ads1256hspi,wData,rxData,size,1000);
}
??根據(jù)上述這些定義后,我們可以調(diào)用初始化函數(shù)來實現(xiàn)對ADS1256對象變量進行初始化。具體如下:
ADS1256DIOType dio[4]={ADS1256_DIO_INPUT,ADS1256_DIO_INPUT,ADS1256_DIO_INPUT,ADS1256_DIO_OUTPUT};
/*ADS1256初始化配置函數(shù)*/
ADS1256Initialization(&ads1256, //待初始化的ADS1256對象
ADS1256_ORDER_MOST, //數(shù)據(jù)順序
ADS1256_ACAL_DISABLE, //自動校準(zhǔn)使能
ADS1256_BUFEN_DISABLE, //模擬量緩存使能
ADS1256_CLKOUT_FCLKIN, //時鐘輸出類型
ADS1256_SDCS_OFF, //傳感器檢測電流
ADS1256_GAIN1, //增益
ADS1256_DRATE_30000SPS, //數(shù)據(jù)輸出速率
dio, //輸入輸出配置
ADS1256WriteReadData, //讀寫函數(shù)指針
ADS1256CS, //片選函數(shù)指針
ADS1256CheckReady, //就緒函數(shù)指針
HAL_Delay //毫秒延時函數(shù)指針
);
3.2、基于對象進行操作
??在完成對象變量的初始化后,我們就可以通過操作這個對象變量獲取采集的數(shù)據(jù)。這里我們采集8路單端輸入的數(shù)據(jù)。需要注意的是每次讀出來的數(shù)據(jù)并非我們當(dāng)前設(shè)定的通道的數(shù)據(jù),而是我們上次設(shè)定的通道的數(shù)據(jù)。據(jù)此我們設(shè)計簡單的數(shù)據(jù)采集函數(shù)如下:
/*獲取通道數(shù)據(jù)*/
void GetADS1256ChannelValue(void)
{
int32_t dataCode[8];
for(ADS1256ChannelType ainP=ADS1256_AIN0;ainPif(ainP==ADS1256_AIN0)
{
dataCode[7]=ADS1256SingleReadData(&ads1256,ainP,ADS1256_AINCOM);
}
else
{
dataCode[ainP]=ADS1256SingleReadData(&ads1256,ainP,ADS1256_AINCOM);
}
}
}
4、應(yīng)用總結(jié)
??我們設(shè)計了ADS1256模數(shù)轉(zhuǎn)換器的驅(qū)動程序,并利用一個簡單的例子對齊進行了驗證,讀取數(shù)據(jù)沒有問題。
??在使用驅(qū)動程序時需要注意,片選信號并非必須實現(xiàn)。因為有些時候我們可能需要在硬件上直接將其選中,此時添加片選操作函數(shù)是沒有什么意義的,我們可以在初始化時傳入NULL來完成。
??在使用驅(qū)動程序時需要注意,ADS1256模數(shù)轉(zhuǎn)換器在設(shè)置通道選擇然后就可以在轉(zhuǎn)換過程中讀取上一個轉(zhuǎn)換周期的數(shù)據(jù)。所以在驅(qū)動程序中,設(shè)置通道選擇后,沒有等待轉(zhuǎn)化完成,而是直接讀取了數(shù)據(jù),這個數(shù)據(jù)實際上是上一次轉(zhuǎn)換的數(shù)據(jù),這樣可以充分利用轉(zhuǎn)換周期。所以在使用驅(qū)動程序需要注意讀取的數(shù)據(jù)所對應(yīng)的通道。
評論