2018年1月22日 星期一

DS18B20-認識CRC(3)

 CRC主題從開始到此篇已進入第三篇,其中有CRC基礎及簡化CRC的詳細步驟,到上一篇將CRC-8的操作步驟規律整理至五步驟,那時,我以為這就是最終的簡化(其實也可以拿來寫程式了),但參考了其他程式庫,才知道還可以更精簡。整理如下!

精簡:提前位移,更好!

其實再簡化的步驟,就只是將向右位移的步驟提前至第二步驟,當計算值經XOR運算後為"0"時,位移值既為最終值,若為"1"時,直接與"10001100"進行XOR的運算既可。
說起來,向右位移的步驟往前,好像也沒什麼,但各位若細細品味,應可體會它對於程式的程序的精簡有不可小看的功效。


CRC-8的概念及程式碼

底下是概念圖及程式碼的整合,上面所說的精簡部份,可參考上一篇"[DS18B20-認識CRC(2)]"的概念圖,必須依初值X="1/0"的狀態與"0x80"與"0x18"進行XOR運算,而精簡後,則只要當初值X為"1"時,與"10001100"(0x8C)進行XOR運算既可,這樣對於程式設計的程序達到了極佳的精簡。

下一步,完整的程式碼!

當初在摸索CRC時,一開始也看不懂,先看CRC數學運算說明,有看沒有懂。再去看程式碼(如上圖),還是看不懂!後來,乾脆就用最笨的方法,進入位元值的角度,用填空格及移位的方式來摸索,這樣才漸漸懂得如何而來,再回頭看數學運算及程式碼,就懂了!

CRC用了三篇來說明,一是因為它扮演著單線數位傳輸的要角,必須懂。二是三篇的過程來說明,是詳細的呈現,我是用何種方法來學習CRC,這是我的「格物」之道!僅供參考!

常見CRC的處理方式還有使用查表法的方式,優點是速度快,但會佔較多的程式碼空間,但其基本仍是依上圖所示!

至此,我們算是完成了CRC的認識及關鍵程式碼的演化程序!

下一步,將並利用它來作參數的調整!







2018年1月15日 星期一

DS18B20-認識CRC(2)

上一篇從基本的CRC認識開始,到簡化的CRC-4 計算中,各位若有依我的建議自已演練一番,應該在過程中會有所領悟,而此篇是我自已的領悟(不是最終答案,只是我自已的思路整理喲!)

CRC-4的規律

下圖是我在簡化的CRC-4中,整理出的運算規律概念圖,以其中一個運算點來呈現CRC-4碼的運算規律,我將其分為五個步驟,各位可以看看先,是否與你的規律相同(※其中A代表位元變數,可能是"1"也可以是"0")


CRC-8的規律

下圖是從CRC-4簡化後,再進行CRC-8運算,整理出的運算規律,同樣將其分為五個步驟,各位可以與CRC-4的一起比較,應可以知曉其基本運算規律相同,只是位元數較長些(※其中X/A/B代表位元變數,可能是"1"也可以是"0")


結語

到目前為止,我們都是用最笨的方式來演練CRC碼的運算,不加入數學運算原理,很笨但若是認真的演練,就能體會【熟】能生【巧】,那麼下一步是什麼?

下一步,從規律中演化出程式碼!

2018年1月13日 星期六

DS18B20-認識CRC(1)

正式進入第六步驟-CRC

上篇說明了,讀取DS18B20中暫存器之數值(共九個字元),然後呢?有兩個選擇,一是再去讀取ROM中的數值?或者,我們深入的探究暫存器中的關鍵值-CRC?前者是讓單pin可連接多個DS18B20的關鍵,但不是此專案的目標,所以先放著。後者,才是通往本專案目標的必經之路。

簡單的來說,CRC是確認你從DS18B20讀取到的數值"於傳輸過程中沒有失真"的關鍵。

掌握了CRC,可以透過它來調整傳輸的參數設定,並能檢測數位傳輸的數值正確性,以防止「傳誤」的狀況產生。

什麼是CRC


【維基百科的解釋】
循環冗餘校驗英語:Cyclic redundancy check,通稱「CRC」)是一種根據網路資料封包或電腦檔案等資料產生簡短固定位數驗證碼的一種雜湊函數,主要用來檢測或校驗資料傳輸或者儲存後可能出現的錯誤。生成的數字在傳輸或者儲存之前計算出來並且附加到資料後面,然後接收方進行檢驗確定資料是否發生變化。(資料來源: https://zh.wikipedia.org/wiki/循環冗餘校驗)

看得懂嗎?我是看不太懂啦!但你若用"CRC"當關鍵字來搜尋相關說明,大部份好心的網友會使用"長除法"來說明如何計算並取得所需之CRC值,但說實在的,我的數學不好,他們說的很清楚,但我還是看不懂!

就我的了解CRC:CRC是透過特定的數值運算規則計算,得到的檢驗碼。在DS18B20的運用中,具體的用法就是暫存器的前八個字元值運算後的CRC碼就置於最後第九個字元。當Arduino讀取了暫存器的九個字元值,透過同樣的CRC運算方式計算前八個字元值,所得之字元值若與讀取到的CRC碼相同,代表此次讀取的資料無誤,反之,則代表此次資料讀取出現錯誤,需捨棄不用。

還是有點硬的說明,不過,沒關係,要使用CRC不見得一定要懂得數學式,我們是要懂得如何使用,而不是去透析一切,底下的影片我認為是認識CRC計算及回推"最直觀"的說明,請耐心觀看!

 YouTube上一個不錯的CRC演示


認識Dallas的CRC-8



CRC碼其實只是種驗證的方法,可依設計者所需來調整位數及算法,以滿足其精確度。上圖是維基百科中所整理常用CRC的部份,其中用紅框圈住的就是Dallas針對1-Wire bus的CRC算法。而下圖就是Datasheet中CRC的操作圖示!

將Datasheet的資料與上述的計算式整合於一起就如同下圖所示:八位元的CRC運算、箭頭是移位方向、XOR(邏輯異或)
其中,圖示中標明的XOR是什麼,其實就是位元的邏輯運算式,常用的符號及運算規則如底下所示:
XOR運算:位元值相同則為"0",相異則為"1"

CRC-8太難,從簡化版的CRC開始

觀看上述影片後,大家應該了解它的運作為何了吧!底下,我將用簡化版的CRC-4(來說明其操作步驟),重點是讓各位了解其運作的順序,用土法煉鋼的笨方法先,先會用,再從中找到規律來簡化。


CRC的練習-0-填零作業

首先,我們要進行運算的數值為"1011",而在作CRC運算前,最先的步驟就是填零,既先將空位及後四碼填值為0

CRC的練習-1-步驟細分解

填零後,就先取出第一個位元值"1"及X4值"0",先作XOR運算(相異得"1"),X2向右位移至X3,再取X1值"0",進行XOR運算(相異得"1")得X2值為1,X0值"0"再向右位移至X1,最後將"1"填入X0值中,既完成了第一步驟的運算。

CRC的練習-1-8-步驟分解

下圖是將運算步驟的圖示表,有點煩,但各位可以自行演算並參照之,很簡單的步驟,但有點煩,建議至少做1~2次。
 如上圖,重覆8次的運算結果,既可得至CRC值為"0001"

 找到規律了嗎?

我們不從數理中去說明,直接用最笨的方法,來作位元的運算及操作,這就是電腦的操作程序,從簡化的例子運算中,若你做過2次以上,那麼你會發覺,CRC運算是十分簡單的步驟,但就是有點煩。簡單是因為規則是別人訂的,我們只要照著操作,就是了。就是步驟很煩,一不小心就會出錯。

下一步,我們就要從CRC的運算中找出規律,再寫程式由電腦來做這簡單又煩雜的工作!

2018年1月9日 星期二

DS18B20-讀取暫存器資料

Yeh,有溫度值了!




小卒過河,過了第一階段,也獲得了溫度值,下一階段的目標是透過CRC驗證碼來確認所獲取的資料是正確無誤的,此階段只有兩個步驟,第一步驟就是獲取暫存器之資料值,第二步驟是計算暫存器資料並驗證CRC碼。

第一步驟簡單,就是將資料依序讀出,再依序放入資料陣列中既可。第二步驟就有點煩,但不難,且容後再詳述。

此篇就針對暫存器讀取的部份作說明。

暫存器上架程式架構

下圖就是圖示:讀取DS18B20暫存器內的資料,並上架(Shelve)到Arduino的流程及程式,依序將9筆資料(byte)讀入,並放入暫存器陣列中,以待其他副程式使用。

獲取溫度及列印暫存器資料程式架構


 下圖是讀取暫存器資料,將資料放入暫存器陣列中,再透過溫度值計算獲得溫度值,再列印出暫存器資料的程式架構。

程式碼及其輸出

 此處只列出新增的輸出程式碼的部份,完整的程式碼,會於下一篇的CRC驗證是詳列,此篇的重點是各位可以印證實際測量之值與Datasheet中的資料是否相符合。

程式碼

const uint8_t  g_dq_pin =7;             //Arduino數位腳位7接到DS18B20
uint8_t scratchpad[9];

#define Skip_ROM   0xcc //用於1對1時,省略每次作ROM序號確認程序
#define Convert_T  0x44 //啟動溫度轉換
#define Read_Scratchpad  0xbe //讀取暫存器值,有9個字元

void setup() {
 Serial.begin(9600);
 Serial.println("<>");
}

void loop() {
 Serial.print("DS18b20's Temperature ->");
 Serial.println(getTempC());
 PrintScratchpad();
 delay(3000);
}

void ShelveData() {
 for(int i = 0; i < 9; ++i)
 {
  scratchpad[i]=ReadByte();
 }

}void PrintScratchpad() {
 Serial.println("");
 Serial.println("DS18b20's scratchpad Data ->");

 Serial.print("LSB -> 0x");
 Serial.print(scratchpad[0],HEX);
 Serial.println("");

 Serial.print("MSB -> 0x");
 Serial.print(scratchpad[1],HEX);
 Serial.println("");

 Serial.print("TH REGISTER -> 0x");
 Serial.print(scratchpad[2],HEX);
 Serial.println("");

 Serial.print("TL REGISTER -> 0x");
 Serial.print(scratchpad[3],HEX);
 Serial.println("");

 Serial.print("Configuration Register -> 0x");
 Serial.print(scratchpad[4],HEX);
 Serial.println("");

 for(int i = 5; i < 8; ++i)
 {
  Serial.print("RESERVED ");
  Serial.print(i-4);
  Serial.print(" -> 0x");
  Serial.print(scratchpad[i],HEX);
  Serial.println("");
 }

 Serial.print("CRC -> 0x");
 Serial.print(scratchpad[8],HEX);
 Serial.println("");
 Serial.println("");
}

輸出結果



輸出解析-精密度

在Datasheet中關於解析度的資料是存放於暫存器的第四位元,由R1(bit 6)及R0(bit 5)兩個位元值來顯示,bit 7值應為"0",若為"1"就代表為測試模式,一般出廠時其位元值是設定為"0"。其他低五位(bit0~bit4)通常為1,可以不用去理它。


我們讀取到暫存器中的第四個位元值(byte)為0x3F(二進位為"00111111"),R1="0",R0="1"代表著此顆DS18B20目前設置的精確度為10位元。

結語

讀取暫存器的資料算是簡單的步驟,但卻是不可或缺,同時可以提供你對於掌握手中DS18B20的特性及處理有一精確的掌握。

有了暫存器資料,下一步,我們就可以來作CRC檢驗了!

2018年1月6日 星期六

DS18B20-溫度值計算

 基本的命令及讀取程序完備後,接下來要進入取值並計算成溫度值的程序。由下面DS18B20的讀取溫度值程序中,可以知道,在Arduino命令DS18B20啟動溫度轉換命令(0x44)後,待待其轉換時間750μs(DS18B20會將溫度值放入暫存區中),再進行重啟、省略ROM確認,再通知DS18B20開始讀取暫存器資料(0xbe),此時DS18B20會依序(從暫存器依序從byte 0開始到byte 8)逐位元(從bit 0到bit 7)傳遞資料給Arduino

而我們會讀取兩次資料,第一次會讀到的Byte為LS Byte(簡寫為LSB)為溫度值的低位元值,再讀一次,會讀到MS Byte(簡稱MSB)為溫度值的高位元值。而溫度值就是MSB+LSB的組合的轉換。

DS18B20的暫存器結構 

 下圖為DS18B20的官方資料,其中左方為暫存器(SCRATCHPAD)結構,及右方EEPROM資料,在此不多介紹EEPROM,簡單的說它就是上下限警告值及位元設定的資料。我們將焦點置於暫存器。
下圖是將官方的暫存器重繪後,並以啟始的狀態呈現。由下圖可看到DS18B20暫存器共有9個byte,但可區分為五個部份


1、溫度值(byte0~1):存放量測到的溫度值,byte 0為溫度值的LSB,而byte 1為溫度值的MSB,以通電後之初值而言,LSB為[01010000]而MSB為[00000101]

2、警示溫度上下限(byte2~3)

3、精確度位元(byte 4): 如下表,使用R1與R0來設定其位元精度(有效值)
4、無效區(byte5~7):未使用

5、CRC檢驗碼(byte 8):通CRC的運算的檢驗碼,可提供Arduino確認資料的正確性。

DS18B20溫測範圍及精度差異

 在了解DS18B20的暫存器後,下圖是DS18B20的量測溫度值、暫存器之二進位元輸出值及十六進位表示值。
由官方資料及表中可知,DS18B20量測的溫度區間為-55~+125℃(-10~85℃時精度為±0.5℃),在12位元的解析度時,其可分辨的溫度最小的量測單位為0.0625℃。

 

由MSB與LSB組成的溫度值

DS18B20的溫度值是用16位元(MSB+LSB)組合,如官方所提供的資料如下:

底下將其區段內容及計算的概念整理之。

溫度值區段


如上圖所示,溫度值位元資料其實是MSB+LSB的組合成十六位元(bit)資料,可區分為符號區、整數值區及精確度區。

符號區(MSB之bit3~bit7):使用5個位元,當溫度值為正時皆為0,而當溫度值為負時則為1,故於程式設計時,只要MSB大於7就意謂著溫度值為負。

整數值區:使用7個位元,MSB之bit 0~2加LSB之bit 4~7組合而成。

精確度區:以12位元為例,正負號(1)+整數值(7)+小數點(4)共計為12位元,而一般作溫度數值計算時,會直接以12位元做計算,再取正確小數點既可。

溫度值計算

 有了上述的基礎知識後,接下來,就是計算的程序,整理如下圖:先判斷正負值,若為負值是做二補數處理並設定負號值,再將兩數結合運算乘於單位量測值0.0625,既得溫度值,若負號值為真,則補上負號,傳回溫度值。
下圖以溫度值-25.0625℃為例(資料於十六位元表示為FE6FH),在12位元的狀態下,其計算的變化:正負號判定、取反值、加1的位元變化(※"~"代表取反值,既0與1互換的動作)。
當初也被LSB負值的計算方式:[取反加一]的為何"加1"所困,後來了解後才知道,因為"0"被算到正值的陣營去了,只好用加1位補足。(詳細的部份,各位可用"二補數"作關鍵字搜索,再細探之)

程式說明

程式中有使用到位元運算反相(~):0與1互換,向左移位(<<)的指令,不難,只是不習慣,可參考上圖的位元變化程序及程式說明,應可掌握

程式碼

float Caculate_Temperature(uint8_t temp_LSB , uint8_t temp_MSB)
{
 boolean  fp_minus = false;        //温度正負標誌:預設為false,因為通常為零度以上
 //當temp_MSB>7代表此溫度為負數值時,測到的數值需要先反相再加 1
 if(temp_MSB > 0x7f)  
 {
  fp_minus = true;      //溫度正負指標,負數時fg_Minus=0
  temp_MSB = ~temp_MSB;     //將temp_MSB中的每一位元反相(0、1互換)
  temp_LSB = ~temp_LSB + 1;
  //將temp_LSB中的每一位元反相(0、1互換),要記得加一,才能正確的反應其值
 }
 //以十六位元空箱來結合MSB及LSB
 uint16_t raw_temp =
     (((uint16_t)temp_MSB) << 8) |   //將MSB先用空的十六位元左移8個位元
     (((uint16_t)temp_LSB));
 //將十六位元的整數值再乘於0.0625的單位值,既得溫度值
 float fp_temp = raw_temp * 0.0625;
 if(fp_minus) {fp_temp = -fp_temp;} //當fp_minus是1,代表是負數,將溫度加上負號
 return fp_temp;
}

結語

此篇資料要花點時間了解,因為包含了DS18B20的硬體、暫存器及位元數值運算、位移等,當初研究時,也不怎麼順利,不過,一去二回,熟了就懂了,但其實只是不習慣而已,只要花點時間,相信就能掌握,而且一旦了解後,你會發覺,好像電子元件的運作就是這麼一回事,很簡單,但這個簡單(Simple)卻不容易(Easy)!

總算完成所有的準備功夫了,下一步,就最讀取溫度值了!

2018年1月2日 星期二

DS18B20-讀取溫度值【整合篇】

萬事俱備,整合吧!

 在上一篇中了解了DS18B20的暫存器架構及溫度值的計算後,本篇就是完成讀取溫度值的最後一步:整合!
底下我們從接線方式、程式架構及程式碼等三個方面依序說明!

接線方式

 再次重申,此程式只適用於Arduino與DS18B20於一對一的連接狀況下喲!此外,請注意,於Arduino的接線pin腳是7不是2,若你的接腳有調整時,請調整程式內的設定!


程式架構

底下之讀取溫度架構圖是關鍵函式-getTemp()的架構解析,而數字所標明的區域為前面所寫的工具副程式:

1、重啟程序(綠色):Arduino將電位拉低並維持480μs,DS18B20應簽低電位值既代表重啟程序OK。

2、命令程序(藍色):命令拆解成為位元,再使用寫位元(Write bit)程序使用DS18B20

3、讀取程序(黃色):讀取DS18B20給予之位元值,並組合成一字元(Byte)

4、計算溫度值(紅色):將讀取程序的位元值(LSB及MSB)轉換為溫度值
 除了整合上述四個工具副程式外,其中,要注意的是等待轉換的時間設定,
不同的位元(9-12位元)精密度有不同的轉換時間如下表所示,在這裡直接設定750μs,可確保其轉換時間充足。


程式碼及輸出

 程式碼有點長,但其實就是初始設定、關鍵程式庫(getTemp()-程式行號為17至31行)及工具程式碼的結合!
 

程式碼

const uint8_t  g_dq_pin =7; //Arduino數位腳位7接到DS18B20
#define Skip_ROM    0xcc //用於1對1時,省略每次作ROM序號確認程序
#define Convert_T   0x44 //啟動溫度轉換
#define Read_Scratchpad  0xbe //讀取暫存器值,有9個字元

void setup() {
 Serial.begin(9600);
 Serial.println("<>");
}

void loop() {
 Serial.print("DS18b20's Temperature ->");
 Serial.println(getTempC());
 delay(1000);
}

float getTempC()
{
 while(!CommandReset());       //重啟成功,再進行動作
 SendCommand(Skip_ROM);  //主機下0xcc命令(1對1的省略ROM確認作業)
 SendCommand(Convert_T);  //0x44啟動溫度轉換
 delay(750);
 //再次重啟,因為要作通訊作業
 while(!CommandReset());          //重啟成功,再進行動作
 SendCommand(Skip_ROM);        //主機下0xcc命令(1對1的省略ROM確認作業)
 SendCommand(Read_Scratchpad);   //0xBE溫度暫存器中的訊息(共9個字元)
 //前兩個字元就是溫度的訊息
 uint8_t LSB = ReceiveData();   //第一個讀到的是低位
 uint8_t MSB = ReceiveData();      //第二個讀到的是高位
 return Caculate_Temperature(LSB,MSB);
}

float Caculate_Temperature(uint8_t temp_LSB , uint8_t temp_MSB)
{
 boolean  fp_minus = false;        //温度正負標誌:預設為false,因為通常為零度以上
 if(temp_MSB > 0x7f)  //當temp_MSB>7代表此溫度為負數值時,測到的數值需要先反相再加 1
 {
  fp_minus = true;     //溫度正負指標,負數時fg_Minus=0
  temp_MSB = ~temp_MSB;     //將temp_MSB中的每一位元反相(0、1互換)
  temp_LSB = ~temp_LSB + 1;
  //將temp_LSB中的每一位元反相(0、1互換),要記得加一,才能正確的反應其值
 }
 //以十六位元空箱來結合MSB及LSB
 uint16_t raw_temp =
     (((uint16_t)temp_MSB) << 8) | //將MSB先用空的十六位元左移8個位元,等於乘256
     (((uint16_t)temp_LSB));
 float fp_temp = raw_temp * 0.0625;
 //將十六位元的整數值再乘於0.0625的單位值,既得溫度值
 if(fp_minus) {fp_temp = -fp_temp;} //當fp_minus是1,代表是負數,將溫度加上負號
 return fp_temp;
}

uint8_t ReceiveData()
{
 uint8_t byte_in=0;
 for(uint8_t i = 0; i < 8; i++)
 {
  //此時所測到的電位,就是此位元的資料
  if(ReadSlot()) {
   //看看此時主機線的電位狀況若為高位,就是1
   bitSet(byte_in, i);     //將byte_in第i個位元值,設置為1
  }
  else {
   bitClear(byte_in, i);   //將byte_in第i個位元值,設置為0
  }
 }
 return (byte_in);
}

uint8_t ReadSlot() {
 delayMicroseconds(1);
 pinMode(g_dq_pin, OUTPUT);       //轉為輸出,可達到高電位
 digitalWrite(g_dq_pin, LOW);   //將電位拉低告訴DS18B20,主機已準備好了
 delayMicroseconds(2);
 pinMode(g_dq_pin, INPUT);    //轉為輸入狀態,同時釋放線路
 delayMicroseconds(10);     //加前面的延時,約於12~13us時取樣
 uint8_t fp=digitalRead(g_dq_pin);
 delayMicroseconds(55);    //加上延時過渡此段作業時間60us
 return fp;
}

void SendCommand(uint8_t instruction)
{
 for(uint8_t i = 0; i < 8; i++) {
  WriteSolt(bitRead(instruction, i));
 }
}

void WriteSolt(uint8_t order_bit)
{
 if(order_bit) {        //當值為1時的處理,
  pinMode(g_dq_pin, OUTPUT);      //先將pin腳改為輸出狀態
  digitalWrite(g_dq_pin, LOW);    //將電位拉低,等於通知DS18B20要do something
  delayMicroseconds(10);       //至少要等待1us,但於15us前轉為高電位
  pinMode(g_dq_pin, INPUT);     //將接收轉成INPUT狀態,轉為高電位
  delayMicroseconds(60);      //加前段的延時至少等待60us過此周期
 }
 else {           //當寫入值為'0'時,Tx拉低電位時段60~120us
  pinMode(g_dq_pin, OUTPUT);    //先轉為輸出狀態
  digitalWrite(g_dq_pin, LOW);   //將電位輸出低電位
  delayMicroseconds(65);         //靜靜的等待DS18B20來讀取資料
  pinMode(g_dq_pin, INPUT);      //釋放電位控制轉回輸入狀態
  delayMicroseconds(5);     //等待上拉電阻將電位復位為HIGH
 }
}

uint8_t CommandReset()
{
 pinMode(g_dq_pin, OUTPUT);
 digitalWrite(g_dq_pin, LOW);
 delayMicroseconds(720);
 pinMode(g_dq_pin, INPUT);
 delayMicroseconds(70);
 uint8_t is_exist = !digitalRead(g_dq_pin);
 delayMicroseconds(410);
 return is_exist;
}

程式輸出 



結語

 歷經了前面淺水區的練功期,再加上此階段的暫存器、溫度值轉換,如今,終於看到溫度值了!不過,高興一下就好!

這樣就結束了嗎?當然沒那麼簡單,讀到溫度值只是此階段的甜頭,但不是終點,因為,溫度值雖然出來了,但我們要保證讀取到DS18B20的溫度值是正確無誤,要達到這個功能就必須掌握DS18B20的檢核功能-CRC碼檢驗,只要通過了檢驗,就代表此數值無誤,我們可以安心使用了!

那麼,就讓我們邁入檢核程式階段吧!

2017年12月30日 星期六

DS18B20-讀取溫度值【準備篇】

恭禧!過了淺水區!

恭禧各位!已經過了淺水區,並撰寫了使用DS18B20最基礎的三項工具程式:重啟程序、寫位元及讀位元程式。

接下來,只要將它們作加強及掌握DS18B20的使用命令、程序、溫度值位置及溫度值轉換後,既可取得DS18B20的溫度值喲!

本篇文章,就是講述溫度值的讀取程序架構,並將所使用的三個主要工具(重啟、命令、讀取)的相應關係及其功能作一說明,並對於讀取、命令的工具程式作必要說明,這樣就完成了讀取DS18B20溫度值的熱身運動了!

讀取溫度值的程序架構

底下這圖就是讀取DS18B20的程序架構圖,請參考!
讀取程序的細部會於後面說明,此架構主要是標明其使用的三項工具的使用順序,以三個顏色區分:
1、重啟程序(綠色):Arduino將電位拉低並維持480μs,DS18B20應簽低電位值既代表重啟程序OK。

2、命令程序(藍色):命令拆解成為位元,再使用寫位元(Write bit)程序使用DS18B20

3、讀取程序(黃色):讀取DS18B20給予之位元值,並組合成一字元(Byte)

此三項程序於前面都有講述,其中,重啟程序已OK!但命令及讀取程序部份需由再作補充說明,故列舉於下!

命令工具:從寫位元(bit)到下命令

 在讀取程式架構中,會使用到的命令主要有三個:省略ROM確認(0xcc)、啟動溫度轉換(0x44)及讀取暫存器資料(0xbe)等。

似乎有點難,但其實就是將十六進位的命令值拆解成8個二進位位元值,再依序給予(Write)DS18B20既可,底下會以省略ROM確認命令(0xcc)說明之。

程式架構

"省略ROM確認"命令的使用必須是Arduino與DS18B20在一對一的狀況,等同於生活中的一對一相親,所以不用使用一對多的狀況,必須別名牌或是編號以作區別。

其命令值以十六進位表示就是0xcc,在二進位的表示為[11001100],而程式的架構就是將指令(instruction)分八次將位元值傳給DS18B20。簡單的說,就是從低位元起,讀位元值、寫位元(WriteSlot)並重覆8次的動作。


程式碼

void SendCommand(uint8_t instruction)
{
 for(uint8_t i = 0; i < 8; i++) {
  WriteSolt(bitRead(instruction, i));
 }
}

void WriteSolt(uint8_t order_bit)
{
 if(order_bit) {        //當值為1時的處理,
  pinMode(g_dq_pin, OUTPUT);      //先將pin腳改為輸出狀態
  digitalWrite(g_dq_pin, LOW);    //將電位拉低,等於通知DS18B20要do something
  delayMicroseconds(10);       //至少要等待1us,但於15us前轉為高電位
  pinMode(g_dq_pin, INPUT);     //將接收轉成INPUT狀態,轉為高電位
  delayMicroseconds(60);      //加前段的延時至少等待60us過此周期
 }
 else {           //當寫入值為'0'時,Tx拉低電位時段60~120us
  pinMode(g_dq_pin, OUTPUT);    //先轉為輸出狀態
  digitalWrite(g_dq_pin, LOW);   //將電位輸出低電位
  delayMicroseconds(65);         //靜靜的等待DS18B20來讀取資料
  pinMode(g_dq_pin, INPUT);      //釋放電位控制轉回輸入狀態
  delayMicroseconds(5);     //等待上拉電阻將電位復位為HIGH
 }
}

讀取資料工具:從讀位元(bit)到讀取字元(Byte)

從讀取溫度值的程式架構中,Arduino進了了重啟、略過ROM序號確認、啟動溫度轉換、等待轉換,再重啟、略過ROM、讀取暫存器資料後,DS18B20已準備好進行資料的交換,此時,讀取資料程序就必須上場了。

程式架構




讀取資料程序的步驟就是先準備一空白字元值(Byte=0),然後從最低位元(bit 0)開始,讀取DS18B20給予的位元值 (1/0),並將其值放入位槽中,並重覆執行到字元結束(bit 7)。

程式說明

程式中除了基礎的迴圈使用外,多了兩個位元命令[bitSet()]及[bitClear()],說明如下:

bitSet(x, n):將字元變數x的第n位元值設置為1

bitClear(x, n):將字元變數x的第n位元值設置為0


※n值-右起第一位為0、第二位為1


程式碼

uint8_t ReceiveData()
{
 uint8_t byte_in=0;
 for(uint8_t i = 0; i < 8; i++)
 {
  //此時所測到的電位,就是此位元的資料
  if(ReadSlot()) {
   //看看此時主機線的電位狀況若為高位,就是1
   bitSet(byte_in, i);     //將byte_in第i個位元值,設置為1
  }
  else {
   bitClear(byte_in, i);   //將byte_in第i個位元值,設置為0
  }
 }
 return (byte_in);
}

uint8_t ReadSlot() {
 delayMicroseconds(1);
 pinMode(g_dq_pin, OUTPUT);       //轉為輸出,可達到高電位
 digitalWrite(g_dq_pin, LOW);   //將電位拉低告訴DS18B20,主機已準備好了
 delayMicroseconds(2);
 pinMode(g_dq_pin, INPUT);    //轉為輸入狀態,同時釋放線路
 delayMicroseconds(10);     //加前面的延時,約於12~13us時取樣
 uint8_t fp=digitalRead(g_dq_pin);
 delayMicroseconds(55);    //加上延時過渡此段作業時間60us
 return fp;
}


 萬事俱備,讀取溫度去!

此篇是熱身準備,將前面的讀、寫位元的基礎工具進化到讀取資料及命令的程序

好了!工具齊全了,下一步驟就是依讀取溫度的步驟組合工具,讀取得溫度值去!

2017年12月26日 星期二

DS18B20-Read-讀個位元

跨入淺水區的第三步


二話不說,在上一步,我們知道如何寫入一個位元值到DS18B20,此篇是告訴你如何讀取DS18B20的位元值,重點仍是時間,特別是位元值"1"的取樣時間,底下將作細部說明!

從DS18B20讀取位元值

 要謮取DS18B20的位元值,最主要的關鍵是對於位元值"1"的取樣時間點的掌握,底下會先說明官方資料的內容,再作區段說明及程式碼步驟分析。

官方資料

讀取位元值的文字說明於DS18B20的Datasheet的第16頁下方(Read Time Slots),有興趣的可參考一下!

Read的時序圖


 上圖為官方文件中的第16頁中,有關讀位元的時序圖,可分為三部份,第三部份是線條示意,就不必特別介紹。

第一部分是DS18B20的時序輸出(左為位元"0"值輸出時序,右為位元"1"值的輸出時序)

第二部份中的長條狀Master(此為Arduino)讀取點。

關鍵是DS18B20給予的位元值的時間點,是在電位拉低後,1~15μs的時間點中,如果位元值為"0"時,問題不大,主要麻煩的是當位元值為"1"時,如何準確的掌握讀取的時間點,才是重點。

官方建議的讀取位元圖

  可能是讀取位元值"1"的時序很重要,當然也可能是上圖畫的不清楚,所以又給了一張[建議主機讀取時序圖],簡單的說啟始時間(Tint)及電位回復高電位(Trc)越短越好,而主機取樣點靠近信號尾端為佳。


讀取位元時序整合及關鍵

 底下將上述兩圖整合,再拆解成七個步驟來解析其讀取位元的順序及重點說明!

時序對照圖

 重點仍是放在當DS18B20給予的位元值為"1"時,如何正確的讀取其值。上圖為整體圖,而下圖為讀取位元值"1"的步驟及時序圖,其中紫色長條為Arduino的取樣時點(意謂著,當Arduino於此時點取樣時,1就是1,0就是0,不會搞錯)

區段概念分析

 在步驟分析之前,先看一下訊息區段,其實可分為四段

1-啟始訊號段:電位由高轉為低電位,等於通知DS18B20準備將位元值作輸出動作)

2-DS18B20接手段:主機放手:主機端放開電位控制,由DS18B20接手控制

3-訊息混亂段:
   位元值為"0"時,DS18B20會讓電位維持在低電位
   位元值為"1"時,DS18B20會放手讓上拉電阻將電位拉至高電位,這段時間(Trc)為混亂段,
   如果Arduino於此段(灰色段)讀取資料會出現問題

4-正確訊息段:上圖綠色的部份就是最好的取樣時段,在此區段取樣可獲得正確的位元值

關鍵步驟說明

程式碼中有附上詳細的說明,在此只提重點

1-確保讀取時序間,要有至少1μs的間隔

2-整個的讀取時序,最好是60μs

3- 步驟3/5/7的時間,可自行調整,如one wire中的設定是3/10/53,而官方建議是4/8/55,都可行,不妨都試試。

程式碼

ReadSlot()副程式中,分為7個步驟,其中fp為取樣值的變數名稱,讀取位元值並傳回。
uint8_t ReadSlot() {
 //步驟01:確保與上一個讀時序有1us的間隔
 delayMicroseconds(1);

 //步驟02:啟始信號--拉低電位
 pinMode(g_dq_pin, OUTPUT);       //轉為輸出,可達到高電位
 digitalWrite(g_dq_pin, LOW);   //將電位拉低告訴DS18B20,主機已準備好了

 //步驟03:保持低電位最少1us
 delayMicroseconds(3);

 //步驟04:釋放線路電位
 pinMode(g_dq_pin, INPUT);    //轉為輸入狀態,同時釋放線路

 //步驟05:等待時間,
 delayMicroseconds(9);     //加前面的延時,於2+9<=11us時取樣為保險值

 //步驟06:讀取slot的電位值
 uint8_t fp=digitalRead(g_dq_pin);

 //Step07:延時動作達到讀時序時段全長為60us
 delayMicroseconds(48);    //1+2+9+48=60us
 return fp;
}

基本功過關了

恭禧!到此,你已掌握了Arduino控制DS18B20的三個最基本工具:重啟、寫入一個位元值、讀取一個位元值。

下一步,我們就可以讀取溫度值了!


2017年12月25日 星期一

DS18B20-Write-寫個位元

跨出第二步



要掌握DS18B20,其實就只要三個基礎程序,第一項就是啟動程序:包含了重置及應答程序,而第二項就是寫位元(Write a bit)及第三項讀位元(Read a bit),有了這三項基本程序就能延伸出所有的後續操作如命令、讀取溫度值等。

啟動程序已在上一篇談過,本篇開始就進入第二項基礎程序寫字元(Write a bit),關鍵還是時序的掌握。

如何寫入一個位元至DS18B20

首先我會先介紹位元與字元的差異,以免混淆觀念,然後就是將官方資料(Datasheet)的資料呈現出來,再附上寫位元的時序分段概念圖,以供各位參考。

 基本概念:位元(bit)與字元(byte)

位元(bit) :目前電腦的最基本的單位就是位元(bit),每個位元,透過電壓(High/Low)的判別,可以儲存(1/0)兩種訊息

字元(byte):字元(byte)就是8個位元的組合,要注意的是位元序號是由0開始,且由右至左的升序,最左的位元為bit 7,由於每個位元有兩種狀態(0/1),因此8個位元組合而成的字元(byte)就有2^8(2的8次方)=256種狀態可區分。

基本應用:透過標準的狀態規定,可以達到訊息的傳遞。以電腦中常用的A,其基本組成如上圖例中的"01000001",而透過ASCII編碼表中,電腦可顯示它為"A"。

官方資料及其時序圖


資料來源:Datasheet之第15頁及第16頁的圖示

寫位元的時序圖

Arduino 寫"0"訊息進位元槽的時序
1-將電位拉高
2-將電位拉低
3-持續於低電位(60~120μS),讓DS18B20採樣讀取時間
4-釋放電位控制(上拉電阻會將其電位拉高)
5-等待間隔:等待上拉電阻將電位復位為High的時間

Arduino 寫"0"訊息進位元槽的時序1-將電位拉高
2-將電位拉低並持續於低電位(1~15μS)至少1μS,且於15μS前要回復高電位
3-轉換為高電位
4-加上前段的延時,至少要等待60μS(DS18B20採樣時間)

※簡單的來說:寫程序都是拉高電位,再拉低電位作開端,
所以整個寫的時序,需注意的是以下兩項重點:
1、程序間隔時間
2、在DS18B20的採樣時間中給予正確的位元訊息


程式碼撰寫

掌握了時序圖後,再配合程式碼中的說明,應該沒什麼問題,此程式碼沒有輸出,需等第三步Read程序的撰寫,才會有具體的輸出顯示。


程式碼


程式碼是一個WriteSolt(uint8_t order_bit)的副程式,會讀取一個位元值(傳值變數是order_bit),副程式依其值(1或0)來作不同的處理程序。
void WriteSolt(uint8_t order_bit)
{
 if(order_bit) {        //當欲寫入的值(變數名稱:order_bit)為1時的處理,
  pinMode(g_dq_pin, OUTPUT);      //先將pin腳改為輸出狀態
  digitalWrite(g_dq_pin, LOW);    //將電位拉低,等於通知DS18B20要do something
  delayMicroseconds(10);       //至少要等待1us,但於15us前轉為高電位
  pinMode(g_dq_pin, INPUT);     //將接收轉成INPUT狀態,轉為高電位
  delayMicroseconds(60);      //加前段的延時至少等待60us過此周期
 }
 else {           //當寫入值為'0'時,Tx拉低電位時段60~120us
  pinMode(g_dq_pin, OUTPUT);    //先轉為輸出狀態
  digitalWrite(g_dq_pin, LOW);   //將電位輸出低電位
  delayMicroseconds(65);         //靜靜的等待DS18B20來讀取資料
  pinMode(g_dq_pin, INPUT);      //釋放電位控制轉回輸入狀態
  delayMicroseconds(5);     //等待上拉電阻將電位復位為HIGH
 }
}

結語 

完成此一步驟,基本上對於DS18B20的時序圖及其英文資訊應該不會太排斥了吧!其實,電子類的操作英文都不會太難,只是我們不常接觸!

時序圖只要掌握到重點,再加上適當的步驟分解,也可以輕易的轉換成程式!

寫入一個位元,沒問題了,下一步應是講寫入一個字元(byte),但其實就是拆解字元命令,再重覆八次的寫入程序,必要但不重要,所以...

下一步,就是讀取一個位元值的程序!


2017年12月20日 星期三

DS18B20-Reset-3-連線檢測

 特務接頭前的觀察要點

一個特務,一定了解執行任何一次的接頭任務前,都要先觀察據點是否正常。意味著,它可能被"破了",若有出現"異常"的訊息,則立馬回報組織。

Arduino與DS18B20的連接檢測

上一篇[DS18B20-Reset-02-啟動程序]中,執行時可以重啟DS18B20,且當收到DS18B20的應答訊息後,會顯示"DS18B20 Exist"的回饋訊息,而當無法重啟DS18B20時,它會顯示"No DS18B20 right here"。
當一切連線正常時,它會如實的執行,不過此程式只能偵測DS18B20的正常反應,卻無法處理另一狀態,就是Arduino與DS18B20斷線時的錯誤回饋,而此篇的程式就是補強此一短處。

而程式設計的關鍵也不難,如Datasheet中第10頁[Hardware Configuration]中所言,就是當Arduino與DS18B20正常連接並外接一上拉電阻時,它會處於高電位的狀態。反之,當未執行任何執行命令時,連線若出現低電位既是異常。

程式設計說明

簡單而言,程式設計的理念就是在執行啟動程序前,先觀察連線的電位狀態一段時間,期間若有任何的低電位出現,就是連線異常

※ 硬體連線資料請參考[DS18B20-Reset-02-啟動程序]

STEP-1-程式架構

 此程式使用了四個副程式,其架構如下圖:
1.TestConnect():測試連線副程式,為此篇的重點,主要是偵測輸入pin腳,當出現低電位時, 就顯示錯誤訊息"DQ_pin no connect",並傳"0"值給ResetResult()副程式,若通過觀察期, 它仍處於高電位,就傳"1"值給ResetResult()副程式。

2.ResetResult():啟動結果副程式,統合連線測試及啟動測試兩功能,但先測連線,再測啟動, 兩者皆OK才傳"1"值給ShowOkInfo(),否則傳"0"值給ShowOkInfo()。

3.isReset():啟動程序副程式,主要功能為傳回重啟狀態值

4.ShowOkInfo():顯示程程式,當一切正常為"Reset OK",有任何異常顯示"Reset Fail"



STEP-2-關鍵程式碼說明 

此次程式的關鍵是測試連線副程式TestConnect(),觀察期間為240μs,每隔4μs觀察一次電位值,觀察60次,中間若有任何一次出現低電位,則傳回"0"值並結束程式,若整個觀察期間皆為高電位,則傳回"1"值。
uint8_t TestConnect()
{
 uint8_t retries = 60;
 //先拉高電位(轉為讀取狀態)
 pinMode(g_dq_pin, INPUT);
 //若是正常就會處於高電位(DS18B20的一般狀態)
 while(!digitalRead(g_dq_pin))
 {
  retries--;
  if(retries == 0) {
   Serial.println("DQ_pin no connect!");
   return 0;
  }
  delayMicroseconds(4);
 }
 return 1;
}

STEP-3-程式碼

#define g_dq_pin 7
void setup(){
	Serial.begin(9600);
	Serial.println("<>");
}

void loop(){
	ShowOkInfo(ResetResult()) ;
	delay(2000);
}

uint8_t ResetResult()
{
	//傳回檢測Arduino與DS18B20連接狀態
	if(!TestConnect()) {return 0;}
	//直接傳回Reset結果,1既存在,0為非
	return isReset();
}

void ShowOkInfo(uint8_t isOk)
{
	if(isOk) {
		Serial.println("Reset OK");
	} else {
		Serial.println("Reset Fail");
	}
}
//測試Arduino與DS18B20的連接狀態並傳回測試值
uint8_t TestConnect()
{
	uint8_t retries = 60;
	//先拉高電位(轉為讀取狀態)
	pinMode(g_dq_pin, INPUT);
	//若是正常就會處於高電位(DS18B20的一般狀態)
	while(!digitalRead(g_dq_pin))
	{
		retries--;
		if(retries == 0) {
			Serial.println("DQ_pin no connect!");
			return 0;
		}
		delayMicroseconds(4);
	}
	return 1;
}
//啟動程序並傳回狀態值
uint8_t isReset() {
	pinMode(g_dq_pin, OUTPUT);
	digitalWrite(g_dq_pin, LOW);
	delayMicroseconds(720);
	pinMode(g_dq_pin, INPUT);
	delayMicroseconds(70);
	uint8_t is_exist = !digitalRead(g_dq_pin);
	delayMicroseconds(410);
	return is_exist;
}

STEP-4-程式輸出

輸出結果中,可看出有成功四次Reset,然後兩次的失敗,因為我將Arduino與DS18B20的連線拔除, 然後,再插回去就顯示重啟正常。


結語

 其實此一功能是整個讀取溫度程序都完成了之後,到[檢錯/中斷設置及類別化(Object)]時才加上去,當初認為是多此一舉,沒必要,但後來考量到整體的檢錯功能時,就知道不加不行,且要能防止系統出問題,需傳回一異常值(-127℃),才能保證系統就算有硬體連線/元件異常時,也不會出大錯。當然,先在此補充了此功能,也讓各位有一個觀念。

接下來,要用實際數值,來檢驗Datasheet的數字是"正確"的嗎?

2017年12月16日 星期六

DS18B20-Reset-02-啟動程序

A特務與D特務的無聲接頭暗號

各位可以將Arduino與DS18B20的訊息交流,想像成兩個「啞巴」特務的接頭暗號
而訊息的交換是透過一個被彈簧繩(上接電阻)拉著的小球高低(電位狀態)來呈現
1-A特務(Arduino)拉低小球至低於80cm(<0.8V)約480秒,再放開
2-小球會被彈簧繩拉回原來的高度250cm(>2.2V)
3-D特務(DS18B20)會於15-60秒內,將小球再拉低60-240秒
當A特務於放開小球後的第70秒內看到小球是低於80cm(低電位)
代表D特務在線並能正常反應!

時序才是重點

由於DS18B20與Arduino只透過單一線傳遞訊息,因此,時序(包含時間及順序)就很重要,所以對於訊息傳遞過程中,什麼時候"發送"、什麼時段"接收",電位狀態就十分重要。底下,就解析其時序。

DS18B20啟動資料

此部份,先從Datasheet中截取關鍵說明及其時序圖,然後再加上自製的圖形及解說,掌握後既可輕鬆的寫出程式碼。

啟動之文字說明

底下為Datasheet第15頁中關於啟動程序(Initialization Procedure)的說明,簡單的說就是Arduino先發出重置(Reset)命令,接下來就是上拉電阻動作,然後是DS18B20的答應作業。
英文不難,各位可以看看先!(請注意電位的變化及時間數字)

啟動之官方時序圖

 上述的文字下方,就附上了DS18B20的初啟時序圖,可對照一下上述的文字參考之!

 時序圖解析

整個啟動程序(Initialization Procedure)主要可分為兩個部份Arduino發送重置(Reset)與DS18B20應答(Presence)脈沖,我將其細分為6個步驟:
一、Arduino發送重置脈沖(請問有人在嗎?)
1-Arduino先將電位拉高
2-再將電位接低
3-持續低電位至少480μ
4-Arduino釋放控制,然後,上接電阻會動作,將電位拉回高電位

二、DS18B20發送應簽脈沖(有,有人在!)
5-DS18B20於Arduino釋放控制開始,於15-60μs的時間後,將電位拉低並維持60-240μs
【Arduino就於此時段讀取電位值既可判斷是否有DS18B20的存在】
●當讀取的電位為高電位時,代表沒有DS18B20或是已故障
○當讀取的電位為低電位時,代表存有DS18B20
6-應答作業完成後,DS18B20釋放控制,上拉電阻將電位拉回至高電位 
※重置脈沖及接收訊息時段,建議都要超過480μs


程式設計步驟

掌握了Arduino與DS18B20之間的初啟程序後,我們底下就將其硬體接線、關鍵程式說明並將其程式碼及結果整理於下:

STEP-1-零配件清單及接線圖

此圖依序為Datasheet上之外部接線圖、材料清單及零件示意圖


此圖為Arduino與DS18B20接線圖及其局部放大,要注意的是DS18B20之接腳的接線:
1- GND 為電源地。
2- DQ 為數位資號輸入/輸出端。
3-VDD 為外接供電電源輸入端。
【注意:我使用的是Pin 7不是Pin 2喲!】

STEP-2-關鍵程式碼說明 

1、釋放電位控制
於上序時序圖中,Step-4為釋放電位控制,對Arduino而言,其程式碼就是
pinMode(DQ_Pin, INPUT);
代表著釋放控制,並將角色轉換為"聽"的狀態

2、讀取的時間點
由於DS18B20會於Arduino釋放電位後開始計時於15-60μs後將電位接低,
乾脆以最久的時間再加10μs來讀取其狀態,最保險!但最好不要超過120μs!

STEP-3-程式碼

//Arduino數位腳位7接到DS18B20的第2腳(DQ),DQ為Data input/output的縮寫
#define DQ_Pin 7            

void setup() {
 Serial.begin(9600);
 char is_exist = 0;  //有無DS18B20之變數,先預設為不存在(0)
 Serial.println("Start Reset DS18B20 in the Wire Bus...");
}

void loop() {
 //Tx階段:Step 1.主機發送重置脈沖
 pinMode(DQ_Pin, OUTPUT);    //將DQ_Pin調整為輸出狀態,自動會回歸HIGH電位
 //Tx階段:Step 2.主機拉低電位
 digitalWrite(DQ_Pin, LOW);  
 //Tx階段:Step 3.主機持續於低電位480us
 delayMicroseconds(480);     
 //Tx階段:Step 4.主機釋放電位控制,轉為輸入狀態
 pinMode(DQ_Pin, INPUT);  //將DQ_Pin設定作數位輸入動作
 //Rx階段:Step 5.讀取DS18B20電位值
 delayMicroseconds(70);  //此值為DS18B20 max回應反應時間60us再加10us

 //將讀取的電位值反轉,所以是低電位為有DS18B20
 char is_exist = !digitalRead(DQ_Pin);

 //Rx階段:Step 6.延時至超過主機接收訊息時段
 delayMicroseconds(410);

 //結果輸出
 if(is_exist) {
  Serial.println("DS18B20 Exist");
 } else {
  Serial.println("No DS18B20 right here");
 }
 delay(2000);
}

STEP-4-結果輸出 


結語

 是的,透過啟動程序的解析及撰寫,我們寫下了一個不用引用任何程式庫,並能正確的讀取DS18B20是否於線上的程式,踏出了第一步。

接下來,要再補充一下,初始之前還要作的一件事!