我們經常會碰到多通道AD采集的需求,有時候甚至需要高精度的ADC器件。本篇我們將來設計并實現ADS1256模數轉換器的驅動。并簡單討論該驅動使用方式。
1、功能概述
??ADS1256是TI公司推出的一款低噪聲高分辨率的24位Sigma-Delta(E-v)模數轉換器(ADC)。E-vADC與傳統的逐次逼近型和積分型ADC相比有轉換誤差小而價格低廉的優點,但由于受帶寬和有效采樣率的限制,E-vADC不適用于高頻數據采集的場合。該款ADS1256可適合于采集最高頻率只有幾千赫茲的模擬數據的系統中,數據輸出速率最高可為30K采樣點/秒,4路差分或8路偽差分輸入,有完善的自校正和系統校正系統,SPI串行數據傳輸接口。其結構圖如下所示:
??從結構圖可以看出來,ADS1256是模擬區域與數字區域完全獨立的ADC,即AVDD給模擬區域供電,DVDD給數字區域供電,在原理圖設計方面按照官方指導文檔,需要對兩個區域做獨立的布線與隔離處理,才能讓信噪比最佳。
??ADS1256采用SSOP的封裝形式,具有8個模擬輸入通道,共28個引腳,與其類似的2通道產品ADS1255共有20引腳,其實兩者操作相同,所以我們設計驅動也會考慮兼容性。其中ADS1256引腳排布和定義如下圖所示:
??ADS1255和ADS1256的操作是通過一組寄存器來控制的。這些寄存器包含了配置部件所需的所有信息,如數據速率、多路復用器設置、PGA設置、校準等。這些寄存器的地址及結構如下表所述:
??我們知道了這些寄存器的定義,那么就可以操作ADS1256了。可是我們怎么來實現對這些寄存器的訪問呢?這就涉及到操作命令的問題了。ADS1256有多個操作命令,具體如下表所示:
??在以上這些命令中,除了讀寫寄存器操作需要有第二個字節命令和數據外,其它命令都是獨立使用的。
2、驅動設計與實現
??我們已經了解了ADS1256的相關結構、寄存器及操作命令。接下來我們就來考慮如何設計ADS1256的驅動程序。
2.1、對象定義
??與以往一樣,我們依然是基于對象來實現ADS1256的驅動程序,所以我們需要抽象出ADS1256的對象類型。
2.1.1、抽象數據類型
??我們先來考慮一下ADS1256對象類型的定義問題。一個對象一般來說主要包括屬性和操作兩個方面的內容,我們也從這兩個方面來分析ADS1256對象。
??首先我們來考慮ADS1256模數轉換器對象的屬性。這些屬性必須能夠標識ADS1256模數轉換器對象的特征,或者是存儲ADS1256模數轉換器對象的某種狀態。對于ADS1256模數轉換器對象我們希望可以記錄寄存器的狀態,所以我們將各個寄存器定義為該對象的屬性。
??接下來我們再來考慮一下ADS1256模數轉換器對象的操作。一個對象有各種各樣的操作,或者說他能實現很多的操作,但不是所有的操作都是我們要提取的。我們需要考慮的是那些對象所獨有并且同類對象都必不可少的操作,以及那些不能由對象獨自完成,依賴于具體平臺但又決定對象的行為的必要操作。對于ADS1256莫數轉換器,我們需要讀寫數據,操作片選信號,讀取就緒信號等。但這些操作都依賴于具體的軟硬件平臺,我們將這些操作定義對象的操作,通過函數指針的方式將具體的操作函數傳遞給對象變量,以便于適用于不同的軟硬件平臺。此外,由于時序控制的需要我們需要在驅動中使用延時操作,而延時操作的實現依賴于具體的軟硬件平臺,所以我們也將其抽象為對象的操作。根據上述我們的分析,可以定義ADS1256模數轉換器對象的類型如下:
/*定義ADS1256對象類型*/
typedef struct ADS1256Object {
uint8_t Register[11];
void (*ReadWrite)(uint8_t *wData,uint8_t *rData,uint16_t size); //實現讀寫操作
void (*ChipSelect)(ADS1256CSType cs); //實現片選
uint16_t (*GetReadyInput)(void); //實現Ready狀態監視
void (*Delay)(volatile uint32_t nTime); //實現ms延時操作
}ADS1256ObjectType;
2.1.2、對象初始化函數
??我們抽象了ADS1256模數轉換器的對象類型,使用這一對象類型我們可以獲得具體的對象變量,但這一對象變量必須要進行必要的屬性和操作設定才能進行正確的操作。為了完成對象變量屬性和操作的配置,我們需要一個對象初始化函數。
/*ADS1256初始化配置函數*/
void ADS1256Initialization(ADS1256ObjectType *ads, //待初始化的ADS1256對象
ADS1256OrderType order, //數據順序
ADS1256ACALType acal, //自動校準使能
ADS1256BufenType bufEn, //模擬量緩存使能
ADS1256ClkoutType clkOut, //時鐘輸出類型
ADS1256SDCSType sdcs, //傳感器檢測電流
ADS1256GainType gain, //增益
ADS1256DRateType dataRate, //數據輸出速率
ADS1256DIOType *dio, //輸入輸出配置
ADS1256ReadWriteType readWrite, //讀寫函數指針
ADS1256ChipSelectType cs, //片選函數指針
ADS1256GetReadyInputType ready, //就緒函數指針
ADS1256DelaymsType delayms //毫秒延時函數指針
)
{
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);
}
??在這個初始化函數中,我們完成兩個方面的內容:一是對屬性的賦值和對操作函數指針進行初始化;二是對對象變量所代表的對象進行初始化配置。
2.2、對象操作
??我們獲得了ADS1256模數轉換器的對象類型,也編寫了初始化對象變量的函數,接下來我們考慮一下ADS1256模數轉換器的一些主要的操作過程。
2.2.1、讀數據
??作為模數轉換器,我們首要的目的就是從其獲得我們想要的數據。ADS1256讀數據分為連續讀取和非連續讀取,在這里我們考慮單次讀取數據的操作。當模數轉換完成后,就緒信號下拉到“0”,這個時候可以讀取數據,數據讀取后就緒信號將上拉到“1”。
??根據上述描述和時序圖我們可以編寫讀取數據的操作如下:
/*ADS1256讀取數據*/
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、讀寄存器
??我們已經了解ADS1256有11個寄存器,這些寄存器都可讀取。讀取寄存器的命令由2個字節組成。第一個字節是讀寄存器命令0x10與寄存器起始地址合并而成。第二個字節是所要讀取的寄存器數量減1。具體的操作時序如下圖所示:
??根據上述對讀寄存器的描述和時序圖我們可以編寫讀寄存器的操作如下:
/*讀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個自己組成。第一個字節是讀寄存器命令0x50與寄存器起始地址合并而成。第二個字節是所要寫的寄存器數量減1。具體的操作時序如下圖所示:
??根據上述對寫寄存器的描述和時序圖我們可以編寫寫寄存器的操作如下:
/*寫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、驅動的使用
??我們已經設計并實現了ADS1256模數轉換器的驅動程序。在這一節中我們將設計一個簡單的例子,通過這個例子我們將簡單的驗證驅動程序的正確性,并簡要說明驅動的使用方法。
3.1、聲明并初始化對象
??我們為ADS1256模數轉換器設計的驅動是基于對象開發的,所以我們在使用驅動之前需要聲明一個ADS1256模數轉換器對象變量。使用我們前面定義的ADS1256模數轉換器對象類型聲明這一變量如下:
??ADS1256ObjectType ads1256;
??聲明了這個對象變量后,我們還需要使用對象初始化函數來將這個對象變量變量進行初始化。我們設計的初始換函數有多個參數:
ADS1256ObjectType *ads, //待初始化的ADS1256對象
ADS1256OrderType order, //數據順序
ADS1256ACALType acal, //自動校準使能
ADS1256BufenType bufEn, //模擬量緩存使能
ADS1256ClkoutType clkOut, //時鐘輸出類型
ADS1256SDCSType sdcs, //傳感器檢測電流
ADS1256GainType gain, //增益
ADS1256DRateType dataRate, //數據輸出速率
ADS1256DIOType *dio, //輸入輸出配置
ADS1256ReadWriteType readWrite, //讀寫函數指針
ADS1256ChipSelectType cs, //片選函數指針
ADS1256GetReadyInputType ready, //就緒函數指針
ADS1256DelaymsType delayms //毫秒延時函數指針
??這些參數中,ADS1256對象我們已經聲明。數據順序、自動校準使能、模擬量緩存使能、時鐘輸出類型、傳感器檢測電流、增益、數據輸出速率等幾個參數均為枚舉量,我們們根據需要選擇就可,在這里我們均按默認值選擇。輸入輸出配置這個參數,因有4個IO需獨立配置,所以我們定義一個數組,并將其傳入。剩下的幾個函數指針其原型定義如下:
/*定義讀寫操作函數指針類型*/
typedef void (*ADS1256ReadWriteType)(uint8_t *wData,uint8_t *rData,uint16_t size);
/*實現片選*/
typedef void (*ADS1256ChipSelectType)(ADS1256CSType cs);
/*實現Ready狀態監視*/
typedef uint16_t (*ADS1256GetReadyInputType)(void);
/*實現ms延時操作*/
typedef void (*ADS1256DelaymsType)(volatile uint32_t nTime);
??我們需要根據函數的原型聲明來結合具體的軟硬件平臺設計這幾個函數,并將函數指針以參數的形式傳遞給初始函數。我們是在STM32平臺來實現這個示例,所以延時函數我們直接采用HAL_Delay即可,其他幾個函數實現如下:
/*定義片選信號函數*/
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);
}
}
/* 定義就緒信號讀取函數 */
uint16_t ADS1256CheckReady(void)
{
return HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0);
}
/*定義發送數據函數*/
void ADS1256WriteReadData(uint8_t *wData,uint8_t *rData,uint16_t size)
{
HAL_SPI_TransmitReceive(&ads1256hspi,wData,rxData,size,1000);
}
??根據上述這些定義后,我們可以調用初始化函數來實現對ADS1256對象變量進行初始化。具體如下:
ADS1256DIOType dio[4]={ADS1256_DIO_INPUT,ADS1256_DIO_INPUT,ADS1256_DIO_INPUT,ADS1256_DIO_OUTPUT};
/*ADS1256初始化配置函數*/
ADS1256Initialization(&ads1256, //待初始化的ADS1256對象
ADS1256_ORDER_MOST, //數據順序
ADS1256_ACAL_DISABLE, //自動校準使能
ADS1256_BUFEN_DISABLE, //模擬量緩存使能
ADS1256_CLKOUT_FCLKIN, //時鐘輸出類型
ADS1256_SDCS_OFF, //傳感器檢測電流
ADS1256_GAIN1, //增益
ADS1256_DRATE_30000SPS, //數據輸出速率
dio, //輸入輸出配置
ADS1256WriteReadData, //讀寫函數指針
ADS1256CS, //片選函數指針
ADS1256CheckReady, //就緒函數指針
HAL_Delay //毫秒延時函數指針
);
3.2、基于對象進行操作
??在完成對象變量的初始化后,我們就可以通過操作這個對象變量獲取采集的數據。這里我們采集8路單端輸入的數據。需要注意的是每次讀出來的數據并非我們當前設定的通道的數據,而是我們上次設定的通道的數據。據此我們設計簡單的數據采集函數如下:
/*獲取通道數據*/
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、應用總結
??我們設計了ADS1256模數轉換器的驅動程序,并利用一個簡單的例子對齊進行了驗證,讀取數據沒有問題。
??在使用驅動程序時需要注意,片選信號并非必須實現。因為有些時候我們可能需要在硬件上直接將其選中,此時添加片選操作函數是沒有什么意義的,我們可以在初始化時傳入NULL來完成。
??在使用驅動程序時需要注意,ADS1256模數轉換器在設置通道選擇然后就可以在轉換過程中讀取上一個轉換周期的數據。所以在驅動程序中,設置通道選擇后,沒有等待轉化完成,而是直接讀取了數據,這個數據實際上是上一次轉換的數據,這樣可以充分利用轉換周期。所以在使用驅動程序需要注意讀取的數據所對應的通道。
評論
查看更多