類比/數位轉換:

對於任何的科學實驗儀器,或者製程機台,或是自動化控制,通常會有兩個非常重要的介面:

第一個就是 Digital I/O,用來感測或控制 on/off 的訊號。例如控制 Relay 繼電器的 On/Off 來進一步控制某個元件通電運作否?或是控制電磁氣動閥允許壓縮空氣通過否?來更進一步帶動氣壓缸動作之類的(因為壓縮空氣可以帶來極大的能量,所以很多大型的真空閥或是那些只有兩種狀態的機械動作都是由壓縮空氣去驅動!) 或是感測光遮動,磁遮動開關是否感測到機械手臂或門窗已抵達特定位置,繼電器開關是否已經開路或斷路 ... etc 。

第二個就是 AD/DA 類比世界(Analog)跟數位世界(Digital)的轉換介面,它可以把我們生活中處處可見的類比訊號,例如溫度,例如壓力,例如光強度,例如電路裡的電壓,... 適當的轉換成數位化的數值,這樣以數位訊號來運作的電腦或是微處理器才能更近一步對這些數據進行處理(AD)。或反方向,中央處理器透過演算法算出一個數位的數值後,將這個「數位」的數值等比例地轉換成一個類比的電壓值,進一步的去控制那些以類比方式運作的電路元件(DA)。

有了以上的兩個介面,數位電腦就有了可以跟我們世界「真實」接觸的眼睛跟耳朵了。電腦能真實感受跟偵測真實世界的各種物理量的數值跟資訊後,才能談處理,才能談智慧,跟所謂的「控制」。也才能談所謂的「資訊」管理,「資訊」科學。

所以來對第二個這麼重要的介面,利用樹莓派 RaspberryPi 跟 FORTH 做個練習吧!這是儀器控制跟製程機台控制的基礎囉!就用 LM35 這顆迷人的類比溫度感測器吧!來利用它來感測溫度,再將類比電壓值轉成數位訊號給我們去處理囉。來做個可以感測 0 - 100 C 解析度 0.1C 的溫度計吧!

這篇用到 gFORTH 語言跟 wiringPi  C語言的函式庫,還不熟悉的請看我這篇 BLOG 的軟體環境安裝篇

同時要提醒的,這篇有用到樹莓派 SPI 通訊的硬體,別忘了到設定去開啟 SPI 的權限喲。否則 wiringPi 會無法使用 SPI 介面

IMG_4214.png

 

MCP-3004/3008類比數位轉換器

樹莓派 RaspberryPi 40 Pins 的 GPIO 已經具有第一個 Digital I/O的介面,可惜的是缺少了第二個介面。只有數位介面,這樣可是嚴重缺點,是完全不足以跟外界真實世界中的類比世界溝通的。其中數位對類比的轉換介面 DAC 比較沒問題些,因為樹莓的GPIO支援 PWM的脈衝數位輸出,簡單加上 RC 低通濾波器電路就可以將 PWM 數位輸出轉換成類比輸出囉。

但是類比轉數位的介面 ADC 這可就是完全無法取巧的真功夫了,沒了這個功能可是非常傷腦筋的耶!所幸,樹莓派有 SPI 介面,有一款 MCP-3004/3008 的這個類比數位轉換器 ADC 是用 SPI 介面來溝通的!而且它有 10 bits 非常夠用的解析度,四個或八個多頻道的類比數位轉換,加上合理的價格,所以這款 MCP-3004/3008 便成為在樹莓派上最受歡迎及跟樹莓派合作愉快的一款 ADC。

在樹莓派 Python 或是 C++ 等各種語言的嵌入式控制的範例裡面,處處有這顆 ADC 的身影。

筆者有大概看了一下,不管是 Python 或 C++, 大家在跟 MCP-3004/3008 合作時,其實都是呼叫別人已經準備好的函式。這就是主流語言的好處囉,資源總是最多。不過沒關係的,我們的 FORTH 是很威的,沒有現成的函式可以用,那我們就來自己寫一個囉,不會很難的。這個跟 MCP-3004/3008 溝通的函式庫完成後,我們的 FORTH 在樹莓派上就更威囉,就完全打通 ADC 這個非常重要的關節,這樣所有嵌入式控制的應用,就更完全沒有問題囉。

MCP3004/3008 詳細規格於此!(按我點入MCP3004/3008規格書.)

 

MCP-3004/3008類比數位轉換器特色 

根據規格書,來認識一下這顆重要的 ADC 吧! 其特色

- 高達 10 bits (1024) 的解析度,及多達四個頻道(3004) 或八個頻道(3008) 的充裕ADC應用。

- 支援單一輸入(single) 或差動輸入(differential) 兩種彈性類比輸入應用。

- 電源(VDD)要求 2.7V 到 5.5V,所以可以直接用在 3.3V 或是 5V 的邏輯系統。

- SPI 介面數位輸出,取樣速度由 SPI 的時脈來決定。電源(VDD) 5V 下ADC 取樣率最高 200 ksp ,電源(VDD) 2.7V 下ADC 取樣率最高 75 ksp。

- CMOS 製程電路,低功率消耗。典型待機電流 5nA,最高不會超過 2uA。5V下的工作電流最高不會超過 500uA

 

MCP-3004/3008 Pins 腳定義及外型

MCP3004 3008 Pins.png

 

MCP-3004/3008 Pins 腳位解說

MCP 3004 3008 Pin Table.png

VDD: 就電源供應,從 2.7V 到 5.5V。

DGND: 數位訊號系統接地。數位跟類比系統獨立分開的接地,可以減少ADC運作過程類比電路的雜訊干擾。

AGND: 類比訊號系統接地。

CH0 - CH7:  ADC 類比訊號輸入,假如類比輸入訊號源不是低阻抗的話,建議加上緩衝放大器來改善,否則轉換會失真。也建議加上低通濾波器,來消除高頻雜訊及改善 aliasing 現象。

CLK: 就 SPI 時脈訊號 SCLK,因為 MCP-3004/3008 也利用這個時脈來執行 ADC 轉換,時脈訊號頻率不可以低於 10kHz,否則內部運作的電容器會無法正確工作而得到錯誤的轉換資料(Linerity Error)。

DIN: 就 SPI 訊號 MOSI

DOUT: 就 SPI 訊號 MISO

CS: 就 SPI 訊號 CS (或 SS)

VREF: 類比轉數位訊號的參考類比訊號,這個腳位的類比電壓值決定了轉換後每一 bit 數位訊號所代表的電壓. (解析度),公式如下

ADC resolution.png

例如,

假如給 VREF = 5.0V,這時候ADC轉換完畢每一 bit 的解析度就是 5 Volt /1024 = 4.883 mV

假如給 VREF = 3.3V,這時候ADC轉換完畢每一 bit 的解析度就是 3.3 Volt /1024 = 3.223 mV

 

ADC 轉換後的數位結果由下面公式所示

Dout Calculations.png

 

 

MCP-3004/3008 SPI 訊息格式

要來寫能和 MCP3004/3008 透過SPI溝通的FORTH 程式,當然要先搞懂 MCP-3004/3008 的SPI訊息規格為何囉。

MCP3004 3008 SPI Message.png

這裡解說如下:

1. 整個跟 MCP3004/3008 溝通 SPI 傳送的訊息資料共 3 bytes

2. 第一個 byte 的最低位元放 start bit,當 CS 訊號為 LOW 時,MCP3004/3008 會開始 SPI 通訊。當它看到 start bit 出現時,晶片開始運作。

3. start bit 後,需要立刻接續的4個 bits 是 SGL/DIFF, D2, D1, D0,MCP3004/3008 會透過這四個 bits 的指示,決定後續整個ADC轉換的動作。所以這4個 bits 需放在第二個 byte 的高位元。SGL/DIFF 告訴 MCP3004/3008 是在單一輸入模式(single) 還是差動輸入模式 (differential), D2.D1.D0 這三個 bits 剛好是 0 ... 7 的類比頻道編號(CH0 ... CH7),用來指示想要轉換的類比頻道是哪一個。

4. 第三個 byte MCP3004/3008 會直接忽視

5. 收到 start bit, SGL/DIFF, D2, D1, D0 這5個 bits 之後,空一個 bit之後,3004/3008 開始回傳轉換後的結果,先一個 null=0 bit, 然後依序是 B9, B8, B7, B6, B5, B4, B3, B2, B1, B0 總共 10 bits 類比數位轉換後的結果。

MPC3008 SPI Message format.png

MCP3008 總共有八個類比頻道。一般大家都是採用單一輸入(single) 模式,假如你有差動輸入模式的需求的話,可以犧牲掉4個轉換頻道,改用差動輸入的方式輸入。這時候要將 Single/Diff 這個 bit 設為 0, 告訴 MCP3008 將模式切換成差動模式。此時會犧牲掉四個頻道,真正可以使用的頻道只剩下四個,  而給MCP3008的SPI訊息指令 D2.D1.D0 除了標定頻道外也指定(+), (-) 差動輸入的腳位為何種組合。

 

MCP3008 溝通函式庫

先解說一下 RaspberryPi 裡面 wiringPi 的 SPI 傳送指令 wiringPiSPIDataRW

這個指令需要三個參數:  <CS0 or CS1> <Messages Address> <Messages Length, bytes>  wiringPiSPIDataRW

1. SPI Chip Select (SS) 的 port: RaspberryPi 有兩個 CS0 (CE0, 物理接腳 Pin24) 跟 CS1 (CE1, 物理接腳 Pin26) ,來當作 SPI 協定裡 CS (SS) 訊號的選擇。參數給0代表訊號接在物理接腳 Pin24, CE0。 參數給1代表訊號接在物理接腳 Pin26, CE1。

2. 訊息的位址: 要傳送的資料放在哪裡啊?

3. 跟訊息的大小 (bytes): 要傳送的資料總共有多少 bytes 啊?

得到這三個參數後, wiringPiSPIDataRW 就會啟動 SPI 進行傳送。因為 SPI 是雙向的,MOSI 每傳一 bit 的同時,MISO就接收ㄧ bit. 所以傳了 n個bytes 的資料也同時會接收 n個bytes 的資料。wiringPiSPIDataRW 會將回傳所接收的資料回填到相同的你所給的傳送訊息位址。所以對相同位置直接存取就可以得到回傳資料。

4. 這個指令執行完畢後會回傳一個數字,猜測應該是成功的 bytes 數,直接 drop 就可以囉!

 

配上上面所說的訊息格式,所以跟 MCP3008 溝通這部分的 FORTH 函式庫程式碼如下

 

先建個 3 bytes 大小的 SPI 訊息暫存區 SPIMessage

create SPIMessage  3 chars allot  

 

定義一下低位元組的第四 bit 是 sgl/diff bit,當1的時候是 single mode,當0的時候是 differential mode

8      constant  single        ( single mode, 8 = B00001000)
0      constant  differential  ( differential mode, 0 = B00000000)

 

定義常數變數 SglDiffBit ,及模式切換指令 3008MODE

預設是 single mode, 假如要改成 differential mode 可以用 differential 3008MODE 這個指令來切換。目前採用什麼模式,資料存放在常數變數 SglDiffBit 中。

single    value  SglDiffBit    ( default is single mode)

: 3008MODE   ( single/differential --)
   to SglDiffBit
;

 

下面是利用 SPI 傳輸來跟 MCP3008 溝通讀取類比轉換成 10 bits 數位資料的核心指令 ReadADC

: ReadADC ( ch 頻道編號 -- data 10bits 資料)
   
   SPIMessage 3 erase    先將3bytes的SPI暫存區清空
   
   
   建造 3 bytes MCP3008 SPI 訊息

   
   1 SPIMessage c!       第1 byte,填 start bit = B00000001 = 1

                         第2 byte,填 sgl/diff bit + channel no.
   7 and                 遮罩 7 = B00000111 , 限制頻道一定是 0...7 之間的數字
   SglDiffBit   or       將 sgl/diff bit 給合併進去
   4 LSHIFT              sgl/diff bit + channel no. 是在高位元組,所以左移四位
   SPIMessage char+   c! 將這個第2 byte,填入SPI暫存區

 

                         第3 byte,MPC3008直接忽視,所以什麼都不用做


   要求 wiringPi 透過 SPI 介面傳送 SPIMessage 暫存區裡 3bytes 的資料
   SPIChipSelect SPIMessage 3 wiringPiSPIDataRW   drop


   將回傳回來,放在 SPIMessage 的 3bytes 資料轉換成 10bits ADC資料
   SPIMessage char+           第1 byte,沒用,直接忽視
   dup          c@  8 LSHIFT  第2 byte,這是 10bits裡的高位元組,所以左移八位
   swap char+   c@            第3 byte,這是 10bits裡的低位元組
   or                         合併 第2,第3 byte,形成完整 10 bits資料
;

 

所以就完成了整個跟 MCP3008 溝通的函式庫

這個函式庫用

<baud rate> SetupSPI 來設定 SPI 傳送的速度跟起始 SPI 傳送埠

0 (or 1) to SPIChipSelect 來選擇 SPI 傳送 CS (or SS) 的埠 (CS0 or CS1)

differential (or single) 3008MODE 來設定 MCP3008 的輸入模式 ( differential or single)

0 (or 1,2,3,4,5,6,7) ReadADC 來啟動轉換並送回由頻道 0 (or 1,2,3,4,5,6,7) 類比數位轉換後的 10bits 結果

 

 

LM35 類比溫度感測器

LM35 是德儀TI所開發的半導體溫度感測器,廠商的完整規格書在這裡

它的特色:

- 類比溫度訊號輸出 Vout,直接矯正為 10 mV/C ,每攝氏1度輸出 10 mV 類比電壓的良好線性輸出結果 (例如輸出 273mV 代表目前溫度為 27.3 C),所以可以很方便的跟各種類比電路或數位電路來搭配。(簡單接上 500mV 規格的類比電壓表就可以做成類比溫度計了!)

- 保證精度為 0.5 C (25C下),非線性變異約為 +/- 0.25C,可量測溫度從 -55C 到 +150C,無需任何校正。

- 電源Vs要求 4V 到 20V, 消耗電流 < 60 uA, 低功率消耗,工作時僅自身發熱溫度上升 0.08C

- 低阻抗輸出, 1mA 的負載下阻抗為 0.1 ohm

- 成本便宜。可應用在:電源供應器,電池管理,... 各種溫度量測的場合,也適合遠端量測使用。

 

筆者買到的 LM35,Pin 腳定義跟外型如下圖

LM35 Pins.png

 

電路規劃

這裏 LM35 很簡單,每1C 溫度會輸出 10mV 的電壓。這裏我們希望製作 0 - 100C 的溫度計,所以 LM35 會輸出 0 - 1000mV = 0 - 1V 的電壓!

再來就是 MCP3008 囉,這個 ADC 轉換的電壓可以透過 Vref 的電壓來調整。因為我們的 RaspberryPi 是個 3.3V 的邏輯系統,MCP3008 透過 SPI 來跟我們的 RaspberryPi GPIO 溝通,所以也必需是 3.3V 的邏輯系統。所以MCP3008 VDD 的電源供應必須是 3.3V 的。為了解析度,及便利性所以 Vref 也選擇使用 3.3V

因為 LM35 在 100C 的時候會輸出 1V 的電壓,所以此時的 ADC 數值為 1024 * 1V / 3.3V = 310 ADC unit.

可以發現 1024 沒有完整的用到,這時候解析度剩下 100C / 310 ADC unit = 0.32 C/ADC unit,所以每一單位的 ADC unit 代表 0.32 C 度。這樣解析度太低了,螢幕上溫度的數值會以 0.32C 的離散數字跳動。我們希望 1024 ADC unit,每一個都充分使用,這樣我們就可以把解析度推向 100C / 1024 ADC unit = 0.098 C/ADC unit。這樣就可以有 0.1C 的溫度解析度了,0.1度的跳動看起來也比較自然些!

所以這時候需要一個線性放大電路,放大倍數要設計大概是 3.3 倍,這樣就可以把 LM35 0 - 100C 所傳出來的 0 - 1V 轉換成 0 - 3.3V ,因為 Vref 是 3.3V,所以 1024 * Vin / Vref = 1024 就可以充分利用每一 bit 的解析度。

這裡採用 LM358 這顆低電壓的雙運算放大器 IC 來組成正向放大電路,來做這樣的線性放大工作。

正向放大電路的計算,利用 +/- 兩端虛接地的特性,可以很快地計算出正向放大電路的增益

linear amp circuit.png

 

增益 Vout/Vin = 1 + Rf / Rg

查了一下手上電阻盤上的電阻的所有可能組合,最後選擇 Rf = 22K ohm, Rg = 10K ohm 的這個組合

所以 增益 Vout/Vin = 1 + 22K / 10K = 3.22 ,比預期的 3.33 略低些.

 

最後整個電路如下

LM35 MCP3008 Circuit.png

 

實體接法如下

LM35 MCP3008 Physical Circuit.png

實際照片

HipstamaticPhoto-570282172.521609.JPG

HipstamaticPhoto-570282183.276393.JPG

HipstamaticPhoto-570282198.583663.jpg

 

 

 

程式解說

有了 ReadADC 指令來讀取 MCP3008,其餘的的就簡單了!

 

FORTH 的整數哲學

正統的 FORTH,對浮點運算是十分不屑一顧的。因為FORTH 有一套非常成熟對於固定整數 (fixed-point integer) 運算的計算方法,可以透過整數運算來得到跟浮點運算一致的結果。所以像這種儀器控制,或機台的控制,因為所有數字的範圍都是可以預測的,所以小數點有沒有,或是在什麼位置就一點都不重要了!

所以在這裡,我們不用浮點運算,而是採用 fixed-point integer 的固定整數運算。大家也可以看看經典 FORTH 程式如何處理這個在其他語言看來似乎是不可能的任務,但在 FORTH 裡面其實是很成熟的議題跟手法。

這個議題的關鍵就是一個叫做比例縮放的 */ 指令 ( n n1 n2 -- n*n1/n2) 這個指令需要三個堆疊數字,會將堆疊上最後兩個數字當分數,第三個數字乘上第二數字然後除以第一個數字。也就是說將第三數字依照第二第一個數字所組成的「分數」進行比例縮放,然後放回堆疊上。

而這個指令對第三個數字跟第二個數字相乘時,內部會用雙整數的結果暫存以防止數字相乘後整個爆掉,此雙整數結果除以第一個單整數分母數字後,再轉回單整數的結果。這樣可以確保整個整數縮放的最終結果絕對是精確的。透過這個指令,就可以控制整數於股掌之間。

另外一個議題就是小數點的位置,關鍵是為什麼需要小數點呢??仔細思考後,其實你會發現小數點是不必要的。有效位數只要夠,根本不用擔心小數點這件事。程式設計師要自己去控制自己想要的小數點位數跟位置啦!

例如:  12.345 * 1.05 請問等於多少? (浮點運算算出來是 12.96225)

在 FORTH 裡面,你可以說 1234500 就是 12.345 , 後五位就是小數點的位數。 所以 FORTH 來算就是 

1234500  105 100 */ 

結果是 1296225 結果跟浮點運算出來的結果是一模一樣的。後面五位是小數點位數,程式設計師要列印數字結果的時候自己知道,這時候再把小數點加上去就好囉,這就是 fixed-point integer 算數的精義!

 

線性補償修正

來解說一下,因為類比電路跟類比數位轉換,難免會有誤差。第一個誤差來自於運算放大器的線性放大電路,那個增益很難是每個電路都一模一樣。另外一個是 Vref,你也很難要求電源剛好是 3.3V 的電壓。所以有兩種作法來修正,第一種是在電路上 Rf 或 Rg 加上微調電阻讓增益可以微調,虛接地那裡加上零點補償電路等... 這是比較麻煩的電路解法。

另外一種方法比較簡單,ADC 完後程式內部利用 scalar 跟 offset 的數值運算把它補償回去。這裏採用這個方法,這樣需要的話就可以真實校正到完全準確的溫度計來使用

公式如下圖所示,修正後的數值  T' = scalar * T + offset

scalar and offset.png

 

這段程式碼採用 FORTH 特有的 create does> 來實作

LinearCalibrate 是個軟體套版 (有點像物件裡的類別) 使用時要給它三個數字 (scalar分子, scalar分母, offset) ,前兩個數字是斜率 scalar (分數的形式),第三個數字是 offset

透過這個LinearCalibrate軟體套版所定義出來的指令執行時就會根據這三個數字,對堆疊最上面的數字執行 x' = scalar分子 / scalar分母 * x + offset 的修正

 

語法:

例如想要修正 5% 的斜率量,可以用 105 100 0 LinearCalibrate Calibration5% 來定義一個叫做 Calibration5% 的指令來執行這樣的修正

例如想要修正 5% 的斜率量再加上 offset 的數值 12,可以用 105 100 12 LinearCalibrate Calibration5%+12 來定義一個叫做 Calibration5%+12 的指令來執行這樣的修正

 

 

: LinearCalibrate  ( x -- x')   
   翻譯時區: 將 offset, scalar分母, scalar分子 依序編入字典
   create   , ( offset)  ,  ( scalar分母)  , ( scalar分子)

   執行時區: 將 offset, scalar分母, scalar分子取出

            並執行 x' = x * scalar分子/scalar分母 + offset 運算
   does>  dup @  >r      ( offset)
          cell+ dup   @  ( scalar分母)
          swap cell+  @  ( scalar分子)
          swap */   ( x*scalar分子/scalar分母)    r> ( offset)   +
;

 

這矯正程序是預留的,所以暫時 scalar = 1, offset = 0

1 1 0 LinearCalibrate CalibrateCH0

 

LM35 溫度數值的還原

溫度讀取指令 LM35 在這,會直接回傳 0 - 100C 兩位小數的攝氏溫度。

利用 0 ReadADC 將 MCP3008 頻道0 的 ADC 值轉換出來,乘上 100 留下兩位有效小數點位數供後面運算。 CalibrateCH0 對讀進來的 DAC 數位訊號做可能的線性補償,抵銷任何可能的類比電路誤差。DeLM358Amplfer 把 DAC 數值縮放一次,消除掉 LM358Amplifer 放大倍數的影響。 最後 ADC>DegreeC 將 ADC 數值正確轉換成有兩位小數的攝氏溫度。

: LM35 ( -- 100*Temperature)   \ temperature unit: degree-C
   0 ReadADC  100 *  
   CalibrateCH0 DeLM358Amplifer ADC>DegreeC  
;   

 

DeLM358Amplfer 會算出電路上 LM358 正向放大器的增益,把 ADC 數值縮放回未增益前的值。

增益由 Rf, Rg 兩個電阻所決定,一個  Rf = 22K ohm, 另一個 Rg = 10K ohm,用個雙整數常數定義一下這兩個電阻常數。用的時候,這兩個單整數的電阻常數會自動推上堆疊,非常方便。

22 ( Rf) 10 ( Rg)  2constant  Resistances

 

根據公式 gain = Vout/Vin = 1 + Rf/Rg = (Rg + Rf) / Rg

所以

: DeLM358Amplifer ( x -- x')
   Resistances       ( Rf Rg)
   tuck +            ( Rf Rg+Rf)
   */                 計算 x*Rf/(Rg+Rf) 
;

 

ADC>DegreeC 將 ADC 數值正確轉換成有兩位小數的攝氏溫度。

一些關於我們這個ADC系統的計算常數

3300  constant VoltageRef    MCP3008 Vref 我們用的是 3300 mV 的電壓
1024  constant ResolutionADC 10 bits = 1024 的解析度
10    constant mV/DegreeC    LM35 的輸出, 10 mV = 1 度的攝氏溫度

 

根據這些常數,將 ADC 的數值轉換成真實物理溫度數值。  Temperature = ( ADC-unit * Vref / 1024 ) /  10

: ADC>DegreeC ( ADC-unit -- degree-C)
   VoltageRef ResolutionADC  */   
   mV/DegreeC /
;

 

最後的主程式

R% 是個四捨五入的指令,四捨五入後將兩位小數變成一位小數

: R%  5 + 10 /  ;

 

主程式起始一下 SPI 介面,利用 LM35 指令來轉換讀取 LM35 的兩位小數類比溫度值。 R% 把這個數字四捨五入裁減成一位小數。 最後用 <# # #S #> 這一系列 FORTH特有的數字轉字串的指令將數字正確轉成字串印出。

解說如下,

<# 開始起始做堆疊上的數字轉字串轉換, 但 <# 要求雙整數,所以我們先放個 0 讓溫度整數變成雙整數再開始 <# 的轉換。

先轉換小數點下一位的數字,所以放個 # 來轉換一位數字。

再來要放個小數點了,所以 [char] . hold 來放小數點

剩下有幾位數字,就轉換幾位,所以用 #S

最後用 #> 來收尾,將字串準備好成 counted string 的形式,然後用 type 來將轉換好的字串印出來。

 

 

: go
   ( initiate wiringPi GPIO with BCM Numbering)
   SetupGpioBcmNumbering
   
   ( initiate SPI communication port)
   4000000 SetupSPI
   0 to SPIChipSelect
   Begin
      50 delay
      LM35
      ." The Temperature is ... "  R%
      0 <# # [char] . hold #s #> type  ."  C"  cr
   Again
;

 

整個執行的結果

 

 

 

最後,完整原始程式列表

\
\ MCP3008 AD converter communication library
\   2019.1.24  Frank Lin
\

include wiringPi.fs

 

8      constant  single        ( single mode, 8 = B00001000)
0      constant  differential  ( differential mode)

single    value  SglDiffBit    ( default is single mode)

\
\ Syntax:  
\          singe 3008MODE   ( default)
\   differential 3008MODE       
\


: 3008MODE   ( single/differential --)
   to SglDiffBit
;


: SetupGpioBcmNumbering 
   wiringPiSetupGpio     abort" Something went wrong in GPIO..." 
;


0 value SPIChipSelect ( there are two SPI enable port for RaspberryPi, 0 or 1)

: SetupSPI ( baudrate ---)
   SPIChipSelect swap  wiringPiSPISetup  -1 =
   abort" Something went wrong in SPI device..."
;


create SPIMessage  3 chars allot  ( MCP3008 uses 24 bits data for SPI communication)


: ReadADC ( ch -- data)
   
   SPIMessage 3 erase     ( clean up SPI Message)
   
   
   \ build command for MCP3008
   
   1 SPIMessage c!       ( #0, start bit)

                         ( #1, sgl/diff bit + channel no.)
   7 and                 ( channel no. must be within 0 - 7, 7 = B00000111)
   SglDiffBit   or       ( merge with sgl/diff bit)
   4 LSHIFT              ( left shift 4 bits, follow the format of datasheet)
   SPIMessage char+   c!
   
      
   \ initiate SPI transferring
   SPIChipSelect SPIMessage 3 wiringPiSPIDataRW   drop


   \ reassemble 2 bytes data sent from MCP3008 to correct ADC data.
   SPIMessage char+   
   dup          c@  8 LSHIFT     ( #1, high byte)
   swap char+   c@               ( #2, low byte)
   or                            ( merge to one)
;


\ Words for this library
\
\ 0 or 1  to SPIChipSelect       ( to select which ChipSelect be used)
\ <baudrate>    SetupSPI         ( initiate SPI port with speed <baudrate>)
\ single        3008MODE         ( set mode to single mode)
\ differential  3008MODE         ( set mode to differential mode)
\ <ch>   ReadADC                 ( read value from ADC channel <ch>)

 


true
[if]


\
\ Test Code for LM35 Temperature Sensor
\   2019.1.27  Frank Lin 
\


: LinearCalibrate  ( x -- x')   
\ x' = x * scalar1/scalar2 + offset, to compensate error from ADC
   create   , ( offset)  ,  ( scalar2)  , ( scalar1)
   does>  dup @  >r      ( offset)
          cell+ dup   @  ( scalar2)
          swap cell+  @  ( scalar1)
          swap */   ( x*scalar1/scalar2)    r> ( offset)   +
;

\ Syntax:
\  105 100  12  Linear  CalibrateCH1  ( x' = x*105/100 + 12)
\  1000 CalibrateCH1                  ( result to 1062)

 

\ some constants

3300  constant VoltageRef      ( 3300 mV system on RaspberryPi for MCP3008)
1024  constant ResolutionADC   ( 10 bits = 1024)
10    constant mV/DegreeC      ( LM35 output, 10 mV = 1 DegreeC)

22 ( Rf) 10 ( Rg)  2constant  Resistances

1 1 0 LinearCalibrate CalibrateCH0


: DeLM358Amplifer ( x -- x')
   Resistances       ( Rf Rg)
   tuck +            ( Rf Rg+Rf)
   */                \ x*Rf/(Rg+Rf) 
;

: ADC>DegreeC ( ADC-unit -- degree-C)
   VoltageRef ResolutionADC  */   
   mV/DegreeC /
;


: LM35 ( -- 100*Temperature)   \ temperature unit: degree-C
   0 ReadADC  100 *  
   CalibrateCH0 DeLM358Amplifer ADC>DegreeC  
;   
   
: R%  5 + 10 /  ;


: go
   ( initiate wiringPi GPIO with BCM Numbering)
   SetupGpioBcmNumbering
   
   ( initiate SPI communication port)
   4000000 SetupSPI
   0 to SPIChipSelect
   Begin
      50 delay
      LM35
      ." The Temperature is ... "  R%
      0 <# # [char] . hold #s #> type  ."  C"  cr
   Again
;


[then]

 

xxxx

arrow
arrow

    ohiyooo2 發表在 痞客邦 留言(0) 人氣()