七七八八的,畢業設計弄的差不多了。以前遺留的問題也解決的差不多了(雖然有些粗糙)。現在,有點時間來總結畢業設計中的一些內容。
先說點感悟:對於畢業設計做的自動頂空系統來說,我感覺最惱人的要數這個ADS1248的驅動了。對於這個驅動,我他媽差不多整整弄了兩個多月(請原諒我爆句粗口)。(當然,按照導師的說法,我是跨了兩年)。在那差不多兩個月裏,我有很多次找到了以前做OJ題,調試8次改不出來的感覺(氣的牙根癢癢,媽的就是出不來)。當然,也有不少次因爲找到一點點眉目,就高興的能飛的感覺。 最後導師可能怕弄出來個精神病出來,最後給的源碼參考。
好了,言歸正傳。對於ADS1248來說,驅動其工作基本和利用通信協議驅動EEPROM、FLASH差不多,都是發送命令,然後接受模塊的返回信息。只不過對於EEPROM和FLASH’等存儲模塊來說,ADS1248要相對複雜些。總結來說,可以分爲以下步驟:
芯片復位。將ADS1248的nRESET引腳置低即可進行芯片復位。這裏要注意一點的是芯片在復位之後0.6ms內不能進行SPI通信。如下圖所示。
芯片內部初始化設置。在這裏主要是向芯片內的寄存器寫入相關的值,來對芯片進行相應的設置。一般的設置包括:
(1)、寫入MUX0寄存器,設置正極和負極的輸入端口。如下圖所示。
(2)、寫入MUX1寄存器,設置內部晶振時鐘源,是否啓動內部參考電壓,選擇參考電平。如下圖所示。
(3)、寫入SYS0寄存器,設置ADS的輸出速率和增益。如下圖所示。
(4)、寫入IDAC0寄存器,設置DOUT/DRDY引腳是否採用複用形式,以及恆流源的電流大小。如下圖所示。
(5)、寫入IDAC1寄存器,設置恆流源的輸出引腳(差分輸入和單端輸入就是通過這個寄存器進行設置的)。如下圖所示。
3. 芯片校準。包括自偏移校準->偏移校準->增益校準 本質上也就是向MUX1寄存器寫入相關的值。然後等待校準完成。
4 . 開始轉換,並讀取轉換後的值。這裏需要注意兩點。
(1)、轉換是否完成,需要通過nDRDY引腳是否置低(或者是否產生一個脈衝,這個要根據具體的設置)來判斷。即在讀取數據之前要判斷nDRDY引腳是否爲低。
(2)、ADS1248是24位進度的模數轉換器。所以讀出的數據是24位數據,我們如果用int(4個字節)類型來存儲的話,需要進行數據的拼接(接收到的數據是一般是3個單字節的),而且需要符號(正負號)轉換。具體的解釋,見下文。
ADS1248驅動源碼如下:
//寫命令
static void ADS1248_WriteCmd(uint8_t Cmd)
{
AD_nCS_LOW; //拉低片選線,使能SPI通信
HAL_SPI_Transmit(&hspi1, &Cmd, 1,HAL_MAX_DELAY);
AD_nCS_HIGH; //通信結束,拉高片選
}
////讀寄存器
void ADS1248_ReadReg(uint8_t RegAddr,uint8_t *Buffer,uint8_t Length)
{
uint8_t Cmd[2];
AD_nCS_LOW;
AD_START_HIGH; //在寫寄存器時嗎,需要將START拉高(不讓其進入睡眠模式)
Cmd[0]=ADC_CMD_RREG|RegAddr;
Cmd[1]=Length-1;
HAL_SPI_Transmit(&hspi1,Cmd,2,HAL_MAX_DELAY); //發送命令
HAL_SPI_Receive(&hspi1, Buffer, Length, HAL_MAX_DELAY); //接收寄存器數據
Cmd[0]=ADC_CMD_NOP;
HAL_SPI_Transmit(&hspi1, Cmd,1,HAL_MAX_DELAY); //最後在發送一個NOP,強制拉高DOUT
AD_nCS_HIGH;
}
//寫寄存器
static void ADS1248_WriteReg(uint8_t RegAddr,uint8_t *Buffer,uint8_t Length)
{
uint8_t Cmd[2];
AD_nCS_LOW;
AD_START_HIGH; //在寫寄存器時嗎,需要將START拉高(不讓其進入睡眠模式)
HAL_Delay(20); //硬件延遲
Cmd[0]=ADC_CMD_WREG|RegAddr;
Cmd[1]=Length-1;
HAL_SPI_Transmit(&hspi1, Cmd, 2,HAL_MAX_DELAY); //指定向指定寄存器寫入指定字節數據
HAL_SPI_Transmit(&hspi1, Buffer, Length,HAL_MAX_DELAY); //發送數據字節
HAL_Delay(20); //硬件延遲
AD_nCS_HIGH;
AD_START_LOW;
}
//判斷忙狀態
uint8_t ADS1248_WaitBusy(uint32_t Timeout)
{
uint32_t i = 0;
AD_nCS_LOW;
while(nAD_DRDY_STATE > 0)
{
HAL_Delay(1);
i++;
if(i>Timeout)
return 1;
}
AD_nCS_HIGH;
return 0;
}
//ADS1248系統校準 校準順序爲:自偏移校準->偏移校準->增益校準 .
static uint8_t ADS1248_Calibrate(uint8_t Gain)
{
uint8_t R=0;
uint8_t Cmd;
ADS1248_WriteReg(ADC_REG_SYS0,&Gain,1); // 設置增益值、ADC輸出數據率
Cmd=0x20; //0010 0000
ADS1248_WriteReg(ADC_REG_MUX1,&Cmd,1); // 設置系統監測爲自偏移測量
ADS1248_WriteCmd(ADC_CMD_SELFOCAL); // 自偏移校準
R |= ADS1248_WaitBusy(500); // 等待校準完成
Cmd=0x21; //0010 0001
ADS1248_WriteReg(ADC_REG_MUX1,&Cmd,1); // 設置系統監測爲偏移測量
ADS1248_WriteCmd(ADC_CMD_SYSOCAL); // 系統偏移校準
R |= ADS1248_WaitBusy(500); // 等待校準完成
Cmd=0x22;
ADS1248_WriteReg(ADC_REG_MUX1,&Cmd,1); // 設置系統監測爲增益測量
ADS1248_WriteCmd(ADC_CMD_SYSGCAL); // 系統增益校準
R |= ADS1248_WaitBusy(500); // 等待校準完成
return R;
}
//復位ADS1248
void ADS1248_Reset()
{
AD_nCS_HIGH;
AD_START_HIGH; //START要保持高電平,爲了接下來寫入寄存器
nADRST_LOW; //置低nADRST,復位ADS1248
HAL_Delay(20);
nADRST_HIGH;
HAL_Delay(20);
}
//ADS1248初始化
void ADS1248_Init(void)
{
uint8_t Cmd;
uint8_t Gain;
ADS1248_Reset(); //系統復位
HAL_Delay(100);
Gain = ADC_GAIN_16|ADC_SPS_20;
//初始化MUX0多路複用控制寄存器
Cmd = 0x17 ; //00 010 111,Bit7-6:傳感器電流源檢測不使用,Bit5-3:正輸入爲AIN2,Bit2-0:負輸入爲AIN7
ADS1248_WriteReg(ADC_REG_MUX0,&Cmd,1);
Cmd=0x20 ;//0 01 00 000
ADS1248_WriteReg(ADC_REG_MUX1,&Cmd,1); // 將MUX1置,(內部晶振時鐘源,啓動內部參考電壓,選擇REF0作爲參考電平,普通操作)
// 校準時MUX1將被重新賦值,因此這裏可以不用對其進行賦值,校準之後再配置內部參考電壓
ADS1248_WriteReg(ADC_REG_SYS0,&Gain,1); // 設置增益值、ADC輸出數據率
Cmd=0x07 ;//0000 0111 // 設置極大恆流源電流值1500uA(1.5mA)
ADS1248_WriteReg(ADC_REG_IDAC0,&Cmd,1);
Cmd=0x17 ;//0010 0111 // 選擇第一個恆流源輸出引腳 (AIN2) 選擇第二個電流源輸出引腳(AIN7)
ADS1248_WriteReg(ADC_REG_IDAC1,&Cmd,1);
Cmd=ADS1248_Calibrate(Gain); // 通道校準.配置轉換參數
//重新配置MUX1
Cmd=0x20; //0011 0000
ADS1248_WriteReg(ADC_REG_MUX1,&Cmd,1); // 啓用內部參考電壓總是開啓
AD_START_LOW;
}
//啓動轉換
void ADS1248_Start(uint8_t CovMode)
{
AD_START_HIGH ; //啓動ADC轉換
if(CovMode==ADC_MODE_SINGLECOV)
AD_START_LOW; //產生啓動脈衝
}
//停止轉換
void ADS1248_Stop()
{
AD_START_LOW; //停止轉換
}
//讀取ADS1248中的轉換數據
int32_t ADS1248_Read()
{
uint8_t Cmd[5]={ADC_CMD_RDATA,ADC_CMD_NOP,ADC_CMD_NOP,ADC_CMD_NOP,ADC_CMD_NOP}; //最後一個字節是爲了強制拉高nDRDY
uint8_t Buf[5];
int32_t Data = 0;
AD_nCS_LOW;
HAL_SPI_TransmitReceive(&hspi1,Cmd,Buf,5,HAL_MAX_DELAY); //1個命令,3個空操作接收數據,最後一個拉高nDRDY
AD_nCS_HIGH;
Data=Buf[1];
Data=Data*256+Buf[2];
Data=Data*256+Buf[3];
Data = Data*256; //先乘再除是爲了保留正負號
return (Data/256);
}
頭文件既全部源碼見:這兒
相關疑問及解答:
1. 該如何選擇ADS124中的SPI的時鐘極性和時鐘相位。即對於從設備來說,數據在時鐘下降沿移入,在時鐘的上升沿溢出?
答:如下圖所示:
對於SPI協議來說,它的時鐘極性和時鐘相位的設定是爲了兼容不同從設備的要求。其基本的概念就不說了,強調說一下時鐘相位:它指的是在奇數邊沿還是偶數邊沿被採樣。注意是“採樣”。
對於信號傳輸來說,一個是採樣,一個是切換。如上圖所示,信號在奇數邊沿被採樣,那麼它就會在被在偶數邊沿切換。當信號在採樣時,其應該保持穩定狀態,當其處在切換狀態時,其可以發生狀態切換。
如果有這麼個從設備,有這樣的通信規則:它的數據在時鐘下降沿移入,在時鐘的上升沿移出。那麼對於主設備來說,其就需要設置時鐘的極性和相位:使得時鐘下降沿時爲採樣時刻,數據保持在穩定的狀態,這樣從設備就可以再時鐘的下降沿進行採樣,滿足了第一部分(它的數據在時鐘下降沿移入);對於第二部分的規則來說(數據在時鐘的上升沿移出),是處在切換的狀態,此時時鐘處在上升沿過程中,即時鐘上升沿爲切換時刻(也正好對應了時鐘下降沿爲採樣時刻)。
有兩種方式滿足情況:SPOL = 0,CPHA = 1; 或者SPOL = 1,CPHA = 0。
綜上所述,可以有簡單一點的判斷方式,即直接看從設備的的移入時刻。從設備需要下降沿移入,那麼SPI就需要設置成下降沿採樣的狀態。從設備上升沿移入,SPI就需要設置成上升沿採樣的狀態。
2.對於ADS1248來說,爲什麼SPI的時鐘分頻設置成256就可以,設置成4就不行???
答:對於SPI通信來說,兩個設備之間的通信速率受限於速率較低的那個設備。一般來說,主機的通信速率較高,外部設備的通信速率較低。在這裏就是ADS1248的通信速率較低。而對於ADS1248,其最高的通信速率大概在2MHz左右(數據手冊上Tsclk最小爲500ns);而對於STM32主機來說,其通信速率取決於Pclk/分頻率。在這裏採用的是SPI1,它是掛在APB2上的,其設置Pclk的速率爲72MHz,所以最小分頻設置爲64。設置成4的話,一定是不行的了。
2.ADS1248(24位精度)在讀取數據的時候爲什麼要先左移8位,然後在右移8位?
答:這涉及到整數存儲在內存中的問題。在內存中int型整數是32位,其是按照補碼的形式存儲的。即最高位代表符號位,0爲正,1爲負。但是ADS1248是24位精度,這就是說我們只能從ADS1248中讀取24位有效數據。而且這24位有效數據也是按照補碼的形式進行存儲的。如果我們把這24位有效數據放在32位int型的後24位,則會發現其最高位就一直是0,也就是說這樣讀出的數據將一直是正數。(數據應該也是錯誤的,因爲24位的有效數據最高位被當成了普通位)。
鑑於此,我們需要把24位有效數據的最高位移到32位int型的最高位。先左移8位(邏輯移位,低位補零),將最高位放到32位的最高位;再右移8位(算數移位,最高位保留符號位),恢復原值(除去了符號位)。