DEV Community

codemee
codemee

Posted on

使用 ADS1115 MicroPython 程式庫讀取 ADC 值的怪現象

同事因為想要在 D1 mini(ESP8266) 上讀取多個通道的ADC 值, 但是 ESP8266 本身只有一個 ADC 通道, 所以接了一塊採用 ADS1115 晶片的擴充板, 讓 ESP8266 可以讀取 4 個通道的 ADC。這個板子因為是使用 I2C 通訊, 而且可以透過特定腳位電位變化變更 4 種 I2C 位址, 所以最多可以同時接上 4 片擴充板讀取 16 個通道的 ADC 值。

這個板子因為非常常見, 所以也可以找到 MicroPython 程式庫, 這一切看似美好, 可是同事實測的時候發現, 依序讀取 0、1、2、3 通道的 ADC 值時, 卻是讀到通道 3、0、1、2 的 ADC 值, 他的程式如下:

from machine import I2C, Pin, Timer
import time
import ads1x15                                # 匯入程式庫

addr1 = 72                                    # 設定 I2C 位址
gain = 1                                      # 設定偵測範圍為 0~4.096V
rate=6                                        # 設定取樣率為 475Hz
i2c = I2C(scl=Pin(5), sda=Pin(4),freq=400000) # 建立 I2C 物件

ads1 = ads1x15.ADS1115(i2c, addr1, gain)      # 建立擴充板物件

def get1vol(cha):                             # 讀取指定通道的 ADC 值
    ads1.set_conv(rate=rate,channel1=cha)     # 設定通道等參數
    raw1 = ads1.read_rev()                    # 讀取 ADC 值
    vol1 = ads1.raw_to_v(raw1)                # 轉換為電壓
    return vol1

while True:
    time.sleep(0.1)                           # 每 01 秒讀取 1 次
    vol1=get1vol(0)                           # 讀取通道 0 ADC 值
    vol2=get1vol(1)                           # 讀取通道 1 ADC 值
    vol3=get1vol(2)                           # 讀取通道 2 ADC 值
    vol4=get1vol(3)                           # 讀取通道 3 ADC 值
    print("%2.2f, %2.2f, %2.2f, %2.2f" % (vol1, vol2, vol3, vol4))
Enter fullscreen mode Exit fullscreen mode

仔細看了一下文件, 發現他所使用的 set_conv() 和 read_rev() 這一對方法的用法是比較奇特的, set_conv() 會設定下一次要讀取的通道編號以及採樣率, read_rev() 則會先取出上一次叫用 read_rev() 時讀取的 ADC 值當作傳回值, 然後依據目前設定的通道編號以及採樣率啟動讀取 ADC 值的程序就返回。也就是說, read_rev() 並不是傳回這一次的 ADC 值。這樣的作法主要是為了節省時間, 在叫用 read_rev() 時可以立即返回, 不用等取樣以及轉換的時間。不過這樣做就會導致如果是要輪流讀取不同通道的 ADC 值時, 會有同事遇到的怪現象, 舉來說來, 同事使用上述程式依序讀取 0、1、2、3 通道, 就會變成這樣:

ads1.rset_conv(rate=rate,channel1=0)
raw1 = ads1.read_rev() # 傳回前一次讀 channel ? 的數值, 然後依據目前設定讀 channel 0 的值
ads1.rset_conv(rate=rate,channel1=1)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 0 的數值, 然後依據目前設定讀 channel 1 的值
ads1.rset_conv(rate=rate,channel1=2)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 1 的數值, 然後依據目前設定讀 channel 2 的值
ads1.rset_conv(rate=rate,channel1=3)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 2 的數值, 然後依據目前設定讀 channel 3 的值
ads1.rset_conv(rate=rate,channel1=0)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 3 的數值, 然後依據目前設定讀 channel 0 的值
ads1.rset_conv(rate=rate,channel1=1)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 0 的數值, 然後依據目前設定讀 channel 1 的值
ads1.rset_conv(rate=rate,channel1=2)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 1 的數值, 然後依據目前設定讀 channel 2 的值
ads1.rset_conv(rate=rate,channel1=3)
raw1 = ads1.read_rev() # 傳回前一次讀 channel 2 的數值, 然後依據目前設定讀 channel 3 的值
...
Enter fullscreen mode Exit fullscreen mode

如果覺得這樣很怪, 可以改用同一程式庫的 read() 方法, 將程式中的 get1vol() 函式改寫成這樣:

def get1vol(cha):                           
    raw1 = ads1.read(rate, cha)               # 讀取指定通道的 ADC 值
    vol1 = ads1.raw_to_v(raw1)                # 轉換為電壓
    return vol1
Enter fullscreen mode Exit fullscreen mode

read() 方法會啟動讀取 ADC 值的程序, 等待取樣完成並且轉換成數位值後才會返回。至於要用哪一種方式, 就看自己的需求囉。

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (4)

Collapse
 
webduinocn profile image
webduino-cn • Edited

我觉得“文件”中的说明很清楚了,您反而要先量测在两个get1vol()的呼叫中,是否遵循rate=6, ie. 475Hz 的样本率 (最好用逻辑分析仪量一下),“文件”中也有采用Timer以及ADC中断方式来读取的例子。

Collapse
 
codemee profile image
codemee

你講的跟我講的不是同一件事, 主要是 read_rev() 本來就是把前一次 ADC 結果返回, 然後啟動下一輪的 ADC 程序, 所以他返回的不是目前的 ADC 值。

Collapse
 
webduinocn profile image
webduino-cn

是的,在文件当中已经写的很清楚了(会这样实现也是这种Sigma-Delta ADC需要时间来量测),是您同事程式码逻辑不符合这个程式库的设计理念。另外,我主要指出在您同事的程式码中,没有配合量测的“延时”,一下子读了4次,ADC的速度一定跟不上,这跟您在REPL手敲的结果是不一样的。

Thread Thread
 
codemee profile image
codemee

他原本的想法就是以為 read_rev() 會測量完取值再返回, 所以才會連續讀四次, 如果是使用 read(), 就沒有等待量測時間這個問題了。

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay