1. 程式人生 > >PCF8591 AD/DA轉換模組

PCF8591 AD/DA轉換模組

不積跬步無以至千里,不積小流無以成江海。

程式碼下載到Github<傳送門>。

A/D 重要指標

1、ADC 的位數
一個 n 位的 ADC 表示這個 ADC 共有 2 的 n 次方個刻度。8 位的 ADC,輸出的是從 0~255 一共 256 個數字量,也就是 2 的 8 次方個數據刻度。

2、基準源
基準源,也叫基準電壓,是 ADC 的一個重要指標,要想把輸入 ADC 的訊號測量準確,那麼基準源首先要準,基準源的偏差會直接導致轉換結果的偏差。比如一根米尺,總長度本應該是 1 米,假定這根米尺被火烤了一下,實際變成了 1.2 米,再用這根米尺測物體長度的話自然就有了較大的偏差。假如我們的基準源應該是 5.10V,但是實際上提供的卻是 4.5V,這樣誤把 4.5V 當成了 5.10V 來處理的話,偏差也會比較大。

3、解析度
解析度是數字量變化一個最小刻度時,模擬訊號的變化量,定義為滿刻度量程與 2^n -1 的比值。假定 5.10V 的電壓系統,使用 8 位的 ADC 進行測量,那麼相當於 0~255 一共 256 個刻度把 5.10V 平均分成了 255 份,那麼解析度就是 5.10/255 = 0.02V。

解析度和精度並不是一個概念,詳細看這裡

4、INL(積分非線性度)和 DNL(差分非線性度)

ADC精度關係重大的兩個指標是INL(IntegralNonLiner)和 DNL(Differencial NonLiner)
INL 指的是 ADC 器件在所有的數值上對應的模擬值,和真實值之間誤差最大的那一個點的誤差值,是 ADC 最重要的一個精度指標,單位是 LSB。

LSB(Least Significant Bit)是最低有效位的意思,那麼它實際上對應的就是 ADC的解析度。一個基準為5.10V的8位ADC,它的解析度就是 0.02V,用它去測量一個電壓訊號,得到的結果是 100,就表示它測到的電壓值是 100*0.02V=2V,假定它的 INL 是 1LSB,就表示這個電壓訊號真實的準確值是在1.98V~2.02V 之間的,按理想情況對應得到的數字應該是 99~101,測量誤差是一個最低有效位,即 1LSB。

DNL 表示的是 ADC 相鄰兩個刻度之間最大的差異,單位也是 LSB。一把解析度是 1 毫米的尺子,相鄰的刻度之間並不都剛好是 1 毫米,而總是會存在或大或小的誤差。同理,一個 ADC 的兩個刻度線之間也不總是準確的等於解析度,也是存在誤差,這個誤差就是 DNL。

一個基準為 5.10V 的 8 位 ADC,假定它的 DNL 是 0.5LSB,那麼當它的轉換結果從 100 增加到 101 時,理想情況下實際電壓應該增加 0.02V,但 DNL 為 0.5LSB 的情況下實際電壓的增加值是在 0.01~0.03V 之間。值得一提的是 DNL 並非一定小於 1LSB,很多時候它會等於或大於 1LSB,這就相當於是一定程度上的刻度紊亂,當實際電壓保持不變時,ADC 得出的結果可能會在幾個數值之間跳動,很大程度上就是由於這個原因(但並不完全是,因為還有無時無處不在的干擾的影響)。

5、轉換速率
轉換速率,是指 ADC 每秒能進行取樣轉換的最大次數,單位是 sps (或 s/s、sa/s,即 samplesper second),它與 ADC 完成一次從模擬到數字的轉換所需要的時間互為倒數關係。ADC 的種類比較多,其中積分型的 ADC 轉換時間是毫秒級的,屬於低速 ADC;逐次逼近型 ADC轉換時間是微秒級的,屬於中速 ADC;並行/序列的 ADC 的轉換時間可達到納秒級,屬於高速 ADC。

PCF8591
是一個單電源低功耗的 8 位 CMOS 資料採集器件,具有 4 路模擬輸入,1 路模擬輸出和一個序列 I 2 C 匯流排介面用來與微控制器通訊。與前面講過的 24C02 類似,3 個地址引腳 A0、A1、A2 用於程式設計硬體地址,允許最多 8 個器件連線到 I 2 C 匯流排而不需要額外的片選電路。器件的地址、控制以及資料都是通過 I 2 C 匯流排來傳輸

這裡寫圖片描述

其中引腳 1、2、3、4 是 4 路模擬輸入,引腳 5、6、7 是 I 2 C 匯流排的硬體地址,8 腳是數字地 GND,9 腳和 10 腳是 I 2 C 匯流排的 SDA 和 SCL。12 腳是時鐘選擇引腳,如果接高電平表示用外部時鐘輸入,接低電平則用內部時鐘,我們這套電路用的是內部時鐘,因此 12 腳直接接 GND,同時 11 腳懸空。13 腳是模擬地 AGND,在實際開發中,如果有比較複雜的類比電路,那麼 AGND 部分在佈局佈線上要特別處理,而且和 GND 的連線也有多種方式,這個板子上沒有複雜的模擬部分電路,所以我們把 AGND 和 GND 接到一起。14 腳是基準源,15 腳是 DAC 的模擬輸出,16 腳是供電電源 VCC。

PCF8591 的 ADC 是逐次逼近型的,轉換速率算是中速,但是它的速度瓶頸在 I 2 C 通訊上。由於 I 2 C 通訊速度較慢,所以最終的 PCF8591 的轉換速度,直接取決於 I 2 C 的通訊速率。由於 I 2 C 速度的限制,所以 PCF8591 得算是個低速的 AD 和 DA 的整合,主要應用在一些轉換速度要求不高,希望成本較低的場合,比如電池供電裝置,測量電池的供電電壓,電壓低於某一個值,報警提示更換電池等類似場合。

ref 基準電壓的提供有兩種方法。一是採用簡易的原則,直接接到 VCC 上去,但是由於 VCC 會受到整個線路的用電功耗情況影響,一來不是準確的 5V,實測大多在 4.8V 左右,二來隨著整個系統負載情況的變化會產生波動,所以只能用在簡易的、對精度要求不高的場合。方法二是使用專門的基準電壓器件,比如 TL431,它可以提供一個精度很高的 2.5V 的電壓基準。

這裡寫圖片描述

藍橋的板子是直接接到VCC上的,不過它還進行了並聯電容的處理。

這裡寫圖片描述

對於AD 來說,只要輸入訊號超過 Vref 基準源,它得到的始終都是最大值,即 255,也就是說它實際上無法測量超過其 Vref 的電壓訊號的。需要注意的是,所有輸入訊號的電壓值都不能超過 VCC,即+5V,否則可能會損壞 ADC 晶片。(注意Vref和VCC不一定相等,它取決於你採用哪種方式接線!)

程式設計:
PCF8591 的通訊介面是 I 2 C,那麼程式設計肯定是要符合這個協議的。微控制器對 PCF8591 進行初始化,一共傳送三個位元組即可!

第一個位元組:器件地址位元組

這裡寫圖片描述

其中 7 位代表地址,1 位代表讀寫方向。地址高 4 位固定是 0b1001,低三位是 A2,A1,A0,這三位我們電路上都接了 GND,因此也就是 0b000。

第二個位元組:器件控制位元組
這裡寫圖片描述

控制位元組的第 6 位是 DA 使能位,這一位置 1 表示 DA 輸出引腳使能,會產生模擬電壓輸出功能。

第4位和第5位可以實現把PCF8591的4路模擬輸入配置成單端模式和差分模式,是配置 AD輸入方式的控制位。單端模式和差分模式的區別。
這裡寫圖片描述

控制位元組的第 2 位是自動增量控制位,自動增量的意思就是,比如我們一共有 4 個通道,當我們全部使用的時候,讀完了通道 0,下一次再讀,會自動進入通道 1 進行讀取,不需要我們指定下一個通道。
注意:由於 A/D 每次讀到的資料,都是上一次的轉換結果,所以在使用自動增量功能的時候,要特別注意,當前讀到的是上一個通道的值。
為了保持程式的通用性,程式碼沒有使用這個功能,而是直接做了一個通用的程式,可以參考一下!

具體實現:
程式在進行 A/D 讀取資料的時候,共使用了兩條程式去讀了 2 個位元組:I2CReadACK(); val = I2CReadNAK(); PCF8591 的轉換時鐘是 I2C 的 SCL,8 個SCL 週期完成一次轉換,所以當前的轉換結果總是在下一個位元組的 8 個 SCL 上才能讀出,因此我們這裡第一條語句的作用是產生一個整體的 SCL 時鐘提供給 PCF8591 進行 A/D 轉換,第二次是讀取當前的轉換結果。如果我們只使用第二條語句的話,每次讀到的都是上一次的轉換結果。

控制位元組的第 0 位和第 1 位就是通道選擇位了,00、01、10、11 代表了從 0 到 3 的一共4 個通道選擇。

第三個位元組 D/A 資料暫存器

如果僅僅使用A/D功能,這一個位元組可不進行控制!

1、ADC實驗中對應的一段核心程式碼!

u8 GetADCValue(u8 ch)
{
    u8 val;

    I2CStart();
    if(I2CWrite(0x48<<1) == 0) //尋器件地址 - 寫
    {
        I2CStop();
        return 0;  //這裡並不像I2C那樣使用break,現在是讀東西,讀不到就得返回0.
    }
    I2CWrite(0x40 | ch);//對應單端模式 - 通道數
    I2CStart();
    I2CWrite((0x48<<1) | 0x01);//尋器件地址 - 讀
    I2CReadACK();
    val = I2CReadNAK();//這次的值,是在下8個SCL輸出,所以先空讀然後再去NAK讀!
    I2CStop();

    return val; 
}

void ValueToString(u8 *str, u8 val)//!!注意這裡的處理技巧,把電壓擴大了10倍!!!
{
    val = (val*50) / 255;  //電壓5V,256個刻度分成255份!
    str[0] = (val/10) + '0';
    str[1] = '.';
    str[2] = (val%10) + '0';
    str[3] = 'V';
    str[4] = '\0';
}

2、DAC當然也有重點!!!

void SetDACOut(u8 val)
{
    I2CStart();
    if(!I2CWrite(0x48<<1))
    {
        I2CStop();
        return;
    }
    I2CWrite(0x40);
    I2CWrite(val);
    I2CStop();
}

void KeyAction(u8 keycode)
{
    static u8 volt = 0;

    if(keycode == 0x26)
    {
        if(volt < 50)
        {
            volt++;
            SetDACOut((volt*255)/50);//輸入數字量,注意區別ADC的公式!
        }
    }   
    else if(keycode == 0x28)
    {
        if(volt > 0)
        {
            volt--;
            SetDACOut((volt*255)/50);//輸入數字量,注意區別ADC的公式!
        }
    }
}

3、DAC做得波形發生器!

u8 code SinWave[] = {  //正弦波波表
    127, 152, 176, 198, 217, 233, 245, 252,
    255, 252, 245, 233, 217, 198, 176, 152,
    127, 102,  78,  56,  37,  21,   9,   2,
      0,   2,   9,  21,  37,  56,  78, 102,
};
u8 code TriWave[] = {  //三角波波表
      0,  16,  32,  48,  64,  80,  96, 112,
    128, 144, 160, 176, 192, 208, 224, 240,
    255, 240, 224, 208, 192, 176, 160, 144,
    128, 112,  96,  80,  64,  48,  32,  16,
};
u8 code SawWave[] = {  //鋸齒波表
      0,   8,  16,  24,  32,  40,  48,  56,
     64,  72,  80,  88,  96, 104, 112, 120,
    128, 136, 144, 152, 160, 168, 176, 184,
    192, 200, 208, 216, 224, 232, 240, 248,
};

.....

void SetWaveFreq(u8 freq)
{
    u32 tmp;

    tmp = (11059200/12) / (freq*32); //計數器計數頻率是波形頻率的32倍!
    tmp = 65536 - tmp;
    T1RH = (u8)(tmp>>8);
    T1RL = (u8)tmp;
    TMOD &= 0x0F;
    TMOD |= 0x10;
    TH1 = T1RH;
    TL1 = T1RL;
    ET1 = 1;
    PT1 = 1; //設定高優先順序!
    TR1 = 1;
}

...

void InterrupTimer1() interrupt 3
{
    static u8 i=0;

    TH1 = T1RH;
    TL1 = T1RL;
    SetDACOut(pWave[i]);
    i++;
    if(i >= 32)
    {
        i=0;
    }
}

上面關鍵是看void SetWaveFreq(u8 freq)的實現!

賽前封裝的PCF8591函式相關

#include "config.h"
#include "i2c.h"

#define VCC 48//電壓是擴大10倍以後的電壓。。注意此處的電壓是實測的電壓值!!!

u8 GetADCValue(u8 ch)
{
    u8 val;

    I2CStart();
    if(!(I2CWrite(0x48<<1)))
    {
        I2CStop();
        return 0;
    }   
    I2CWrite(0x40|ch);
    I2CStart();
    I2CWrite((0x48<<1) | 0x01);
    I2CReadACK();
    val = I2CReadNAK();
    I2CStop();

    val = (val*VCC)/255;//val擴大十倍,手動加小數點

    return val;
}

void SetDACOut(u8 val)//輸入也是一樣的道理,預設輸入擴大十倍,然後處理.
{
    val = (val*255)/VCC;

    I2CStart();
    if(!(I2CWrite(0x48<<1)))
    {
        I2CStop();
        return;
    }   
    I2CWrite(0x40);
    I2CWrite(val);
    I2CStop();
}   

GetADCValue在獲取相關的AD值以後,然後進行了轉換再輸出!

SetDACOut是直接設定相關的DA值,注意輸入的是擴大十倍以後的電壓值!

藍橋板子的AIN×輸入介紹:

AIN0:是接到右邊排針上,可以用杜邦線連外部模擬電壓訊號
AIN1:是接到光敏電阻上
AIN2:放大器的輸出端
AIN3:是接到滑動變阻器Rb2上

注意DA功能輸出在右邊倒數第二個排針引腳上,D/A。
倒數第三個排針是可以接外部任意電壓訊號的口