一、介紹
?? PCF8591 是單片、單電源低功耗8位CMOS數據采集器件,具有4個模擬輸入(其中一個為電壓模擬輸入)、一個模擬輸出和一個串行I2C總線接口。3個地址引腳A0、A1和A2用于編程硬件地址,允許將最多8個器件連接至I2C總線而不需要額外硬件。器件的地址、控制和數據通過兩線雙向I2C總線傳輸。器件功能包括多路復用模擬輸入、片上跟蹤和保持功能、8位模數轉換和8位數模擬轉換。最大轉換速率取決于I2C 總線的最高速率。
二、組件
★Raspberry Pi 3主板*1
★樹莓派電源*1
★40P軟排線*1
★PCF8591模數轉換器模塊*1
★雙色LED模塊*1
★面包板*1
★跳線若干
三、實驗原理
??PCF8591模塊的工作原理比較復雜,斷斷續續一個多月時間才基本理清,本文也經過多次修改,以后也會不斷回頭補充。對于小白來說,先使用,再明白就可以,不懂原理也不是天大的問題,只要堅持學習,總有一天會恍然大悟,后面也推薦有經典教材供深入學習。若有疑問,歡迎留言,看到了就會回復交流。
1、I2C總線:
??I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。
??主器件用于啟動總線傳送數據,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。在總線上主和從、發和收的關系不是恒定的,而取決于此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件,然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下,機負責產生定時時鐘和終止數據傳送。
??SDA(串行數據線)和SCL(串行時鐘線)都是雙向I/O線,接口電路為開漏輸出,需通過上拉電阻接電源VCC。當總線空閑時.兩根線都是高電平,連接總線的外同器件都是CMOS(Complementary Metal Oxide Semiconductor互補金屬氧化物半導體)器件,輸出級也是開漏電路。在總線上消耗的電流很小,因此,總線上擴展的器件數量主要由電容負載來決定,因為每個器件的總線接口都有一定的等效電容。
??主器件用于啟動總線傳送數據,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。在總線上主和從、發和收的關系不是恒定的,而取決于此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件,然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下,主機負責產生定時時鐘和終止數據傳送。
2、引腳定義:
本模塊左邊和右邊分別外擴2路排針接口,分別說明如下:
右邊JP1, 5對接口:
左排是:
??AOUT 芯片DA輸出接口
??AINO 芯片模擬輸入接口0
??AIN1 芯片模擬輸入接口1
??AIN2 芯片模擬輸入接口2
??AIN3 芯片模擬輸入接口3
右排是:
??GND 接地
??GND 接地
??INPUT2 熱敏電阻接口
??INPUT1 光敏電阻接口
??INPUT0 電位計接口
左邊J1, 4個接口:
??SCL IIC時鐘接口 接樹莓派的scl口(接樹莓派 I2C1 SCL口)
??SDA IIC數字接口 接樹莓派的sda口(接單樹莓派 I2C1 SDA口)
??GND 模塊地 外接地(接樹莓派GND)
??VCC 電源接口 外接3.3v-5v (接樹莓派電源)
這里用的是5V。
對應的端口分別作用如下:
INPUT0端口 用短路帽接上AIN0,選擇0-5V可調電壓接入電路
INPUT1端口 用短路帽接上AIN1,選擇光敏電阻接入電路
INPUT2端口 用短路帽接上AIN2,選擇熱敏電阻接入電路
??PCF8591是具有I2C總線接口的8位A/D及D/A轉換器。有4路A/D轉換輸入,1路D/A模擬輸出。I2C總線是Philips公司推出的串行總線,整個系統僅靠數據線(SDA)和時鐘線(SCL)實現完善的全雙工數據傳輸,即CPU與各個外圍器件僅靠這兩條線實現信息交換。I2C總線系統與傳統的并行總線系統相比具有結構簡單、可維護性好、易實現系統擴展、易實現模塊化標準化設計、可靠性高等優點。
AIN0~AIN3:模擬信號輸入端。
A0~A3:引腳地址端。
VDD、VSS:電源端(2.5~6V)
SDA、SCL:I2C總線的數據線、時鐘線。
OSC:外部時鐘輸入端,內部時鐘輸出端。
EXT:內部、外部時鐘選擇線,使用內部時鐘時EXT接地。
AGND:模擬信號地。
AOUT:D/A轉換輸出端。
VREF:基準電源端。
3、第一字節:器件地址
??PCF8591采用典型的I2C總線接口器件尋址方法,即總線地址由器件地址、引腳地址和方向位組成。飛利蒲公司規定A/D器件地址為1001。引腳地址為A2A1A0,其值由用戶選擇,因此I2C系統中最多可接23=8個具有I2C總線接口的A/D器件。地址的最后一位為方向位R/ ,當主控器對A/D器件進行讀操作時為1,進行寫操作時為0??偩€操作時,由器件地址、引腳地址和方向位組成的從地址為主控器發送的第一字節。
4、第二字節:控制字節
??控制字節用于控制器件的各種功能,如模擬信號由哪幾個通道輸入等??刂谱止澊娣旁诳刂萍拇嫫髦?,總線操作時為主控器發送的第二字節。其格式如下所示:
其中:
D1、D0 兩位是A/D通道編號:00通道0,01通道1,10通道2,11通道3
D2 自動增量選擇(0為禁止自動增量,1為允許自動增量),如果允許自動增量,則在每次A/D轉換后,通道編號會自動遞增。
D3 特征位:固定值為:0。
D5、D4 模擬量輸入選擇:00為四路單端輸入、01為三路差分輸入、10為兩路單端與一路差分輸入、11為兩路差分輸入。
D6 使能模擬輸出AOUT有效(1為有效,0為無效)。
D7 特征位:固定值為:0。
后面的編程會遇到,“bus.write_byte(address,0x40) ” 語句就是發送控制字“0x40”,40就代表控制字“0100 0000”,主要表示模擬輸出有效,四路單端輸入,禁止自動增量,A/D通道為0。
具體如下圖所示:
??當系統為A/D轉換時,模擬輸出允許為0。模擬量輸入選擇位取值由輸入方式決定:四路單端輸入時取00,三路差分輸入時取01,單端與差分輸入時取10,二路差分輸入時取11。最低兩位時通道編號位,當對0通道的模擬信號進行A/D轉換時取00,當對1通道的模擬信號進行A/D轉換時取01,當對2通道的模擬信號進行A/D轉換時取10,當對3通道的模擬信號進行A/D轉換時取11。
??在進行數據操作時,首先是主控器發出起始信號,然后發出讀尋址字節,被控器做出應答后,主控器從被控器讀出第一個數據字節,主控器發出應答,主控器從被控器讀出第二個數據字節,主控器發出應答…一直到主控器從被控器中讀出第n個數據字節,主控器發出非應答信號,最后主控器發出停止信號。
5、A/D轉換應用開發流程
?? 一個A/D轉換的周期的開始,總是在發送有效的讀設備地址給PCF8591之后,A/D轉換在應答時鐘脈沖的后沿被觸發。PCF8591的A/D轉換程序設計流程,可以分為四個步驟:
1--發送寫設備地址,選擇IIC總線上的PCF8591器件。
2--發送控制字節,選擇模擬量輸入模式和通道。
3--發送讀設備地址,選擇IIC總線上的PCF8591器件。
4--讀取PCF8591中目標通道的數據。
(1)、AD的位數:表明這個AD共有2n個刻度,8位AD,輸出的刻度是0~255. 8591就是8為精度的,因此它digtalRead的數據在0-255之間。
(2)、分辨率:就是AD能夠分辨的最小的模擬量變化,假設5.10V的系統用8位的AD采樣,那么它能分辨的最小電壓就是5.10/255=0.02V。
??AD轉換的原理簡單來理解就是通過電路將非電信號轉為電信號,然后通過一個基準電壓(PCF8591的基準電壓是5V),然后判斷這個電信號的電壓高低,然后得到一個0-255(8位精度)的比值。
四、實驗步驟
??第1步:在本實驗中,AIN0(模擬輸入0)端口用于接收來自電位計模塊的模擬信號。AOUT(模擬輸出)用于將模擬信號輸出到雙色LED模塊,以便改變LED的亮度。傳感器上可以看見,可調電阻在傳感器上是標識的是“0”,使用INPUT0端口,用短路帽連接AIN0和INPUT0。
??光敏電阻模塊是INPUT1端口,熱敏電阻模塊是INPUT2端口。
樹莓派 | T型轉接板 | PCF8591模塊 |
---|---|---|
SDA | SDA | SDA |
SCL | SCL | SCL |
5V | 5V | VCC |
GND | GND | GND |
雙色LED模塊 | T型轉接板 | PCF8591模塊 |
---|---|---|
R(紅色端口) | * | AOUT |
GND | GND | GND |
G(綠色端口) | * | * |
??第2步:PCF8591模塊采用的是I2C(IIC)總線進行通信的,但是在樹莓派的鏡像中默認是關閉的,在使用該傳感器的時候,我們必須首先允許IIC總線通信。
??第3步:開始編程。這里先編寫一個PCF8591.py庫文件,后面再編寫一個python程序引入這個庫文件。
??PCF8591.py庫文件就是PCF8591模塊的程序,單獨編寫是為了便于重用。在這個腳本中,我們使用了一個放大器用于模擬輸入和一個LED燈用于模擬輸出,模擬輸入不能超過3.3V!
??該程序也可以單獨運行,用于測試3個電阻模塊的功能。需用短路帽連接AIN0和INPUT0(電位計模塊),連接AIN1和INPUT1(光敏電阻模塊),以及連接AIN2和INPUT2(熱敏電阻模塊)。
??連接LED燈,AIN0(模擬輸入0)端口用于接收來自電位計模塊的模擬信號,AOUT(模擬輸出)用于將模擬信號輸出到雙色LED模塊,以便改變LED的亮度。
#!/usr/bin/env python
#------------------------------------------------------
#
# 您可以使用下面語句將此腳本導入另一個腳本:
# “import PCF8591 as ADC”
#
# ADC.Setup(Address) # 查詢PCF8591的地址:“sudo i2cdetect -y 1”
# i2cdetect is a userspace program to scan an I2C bus for devices.
# It outputs a table with the list of detected devices on the specified bus.
# ADC.read(channal) # Channal范圍從0到3
# ADC.write(Value) # Value范圍從0到255
#
#------------------------------------------------------
#SMBus (System Management Bus,系統管理總線)
import smbus #在程序中導入“smbus”模塊
import time
# for RPI version 1, use "bus = smbus.SMBus(1)"
# 0 代表 /dev/i2c-0, 1 代表 /dev/i2c-1 ,具體看使用的樹莓派那個I2C來決定
bus = smbus.SMBus(1) #創建一個smbus實例
#在樹莓派上查詢PCF8591的地址:“sudo i2cdetect -y 1”
def setup(Addr):
global address
address = Addr
def read(chn): #channel
if chn == 0:
bus.write_byte(address,0x40) #發送一個控制字節到設備
if chn == 1:
bus.write_byte(address,0x41)
if chn == 2:
bus.write_byte(address,0x42)
if chn == 3:
bus.write_byte(address,0x43)
bus.read_byte(address) # 從設備讀取單個字節,而不指定設備寄存器。
return bus.read_byte(address) #返回某通道輸入的模擬值A/D轉換后的數字值
def write(val):
temp = val # 將字符串值移動到temp
temp = int(temp) # 將字符串改為整數類型
# print temp to see on terminal else comment out
bus.write_byte_data(address, 0x40, temp)
#寫入字節數據,將數字值轉化成模擬值從AOUT輸出
if __name__ == "__main__":
setup(0x48)
#在樹莓派終端上使用命令“sudo i2cdetect -y 1”,查詢出PCF8591的地址為0x48
while True:
print '電位計 AIN0 = ', read(0) #電位計模擬信號轉化的數字值
print '光敏電阻 AIN1 = ', read(1) #光敏電阻模擬信號轉化的數字
print '熱敏電阻 AIN2 = ', read(2) #熱敏電阻模擬信號轉化的數字值
tmp = read(0)
tmp = tmp*(255-125)/255+125
# 125以下LED不會亮,所以將“0-255”轉換為“125-255”,調節亮度時燈不會熄滅
write(tmp)
time.sleep(2)
??若想深入學習模/數轉換等模電知識,強烈推薦以下書籍,國外大學最經典模電教程,比國內教程生動有趣更易懂。掃碼購買或者點擊下面的鏈接。
《模擬電子基礎》點擊購買:https://u.dangdang.com/OIVk
??上面的程序中import smbus(System Management BUS,即系統管理總線),SMBUS總線規范是基于I2C的總線規范,但與I2C總線規范也有一定的區別。python smbus 有如下函數:
# Send only the read / write bit
long write_quick(int addr)
# Read a single byte from a device, without specifying a device register.
long read_byte(int addr)
# Send a single byte to a device
long write_byte(int addr, char val)
# Read Byte Data transaction.
long read_byte_data(int addr, char cmd)
# Write Byte Data transaction.
long write_byte_data(int addr, char cmd, char val)
# Read Word Data transaction.
long read_word_data(int addr, char cmd)
# Write Word Data transaction.
long write_word_data(int addr, char cmd, int val)
# Process Call transaction.
long process_call(int addr, char cmd, int val)
#Read Block Data transaction.
long[] read_block_data(int addr, char cmd)
# Write up to 32 bytes to a device. This fucntion adds an initial byte indicating the
# length of the vals array before the valls array. Use write_i2c_block_data instead!
write_block_data(int addr,char cmd,long vals[])
# Block Process Call transaction.
long[] block_process_call(int addr, char cmd, long vals[])
# I2C Access Functions
# Block Read transaction.
long[] read_i2c_block_data(int addr, char cmd)
#Block Write transaction.
write_i2c_block_data(int addr, char cmd, long vals[])
#Code Example
#!/usr/bin/python
import smbus
bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
DEVICE_ADDRESS = 0x15 #7 bit address (will be left shifted to add the read write bit)
DEVICE_REG_MODE1 = 0x00
DEVICE_REG_LEDOUT0 = 0x1d
#Write a single register
bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, 0x80)
#Write an array of registers
ledout_values = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
bus.write_i2c_block_data(DEVICE_ADDRESS, DEVICE_REG_LEDOUT0, ledout_values)
??第4步:編輯運行本次實驗程序。用小平口起子調節藍白色的“103”可變電阻,LED燈的亮度會隨之變化;同時程序運行終端上會不停地打印可變電阻大小A/D轉換后的數字值。
??當然,這里僅僅用短路帽連接AIN0和INPUT0(電位計模塊)就可以了,光敏電阻模塊以及熱敏電阻模塊就不需要短路帽連接了。
#!/usr/bin/env python
import PCF8591 as ADC
def setup():
ADC.setup(0x48)
def loop():
while True:
print ADC.read(0)
#打印電位計電壓大小A/D轉換后的數字值(從AIN0借口輸入的)
#范圍是0~255,0時LED燈熄滅,255時燈最亮
ADC.write(ADC.read(0))
#將0通道輸入的電位計電壓數字值轉化成模擬值從AOUT輸出
#給LED燈提供電源VCC輸入
def destroy():
ADC.write(0)
if __name__ == "__main__":
try:
setup()
loop()
except KeyboardInterrupt:
destroy()