2013/09/17

Arduino練習:傾斜感測器Tilt ball switch

我向Adafruit買了個Tilt ball switch,可用來偵測是否傾斜。金屬小管子有兩隻腳,在管子內有顆金屬球,當管子成直立狀態,也就是腳朝下時,金屬球會落下接觸管子內的兩個接觸點接通兩隻腳,形成通路,若管子傾斜到一定程度後,就會斷路。

這是Tilt ball switch,tilt意思是傾斜、以管子裡的ball球控制、可當做switch開關使用。傾斜時可聽到裡頭球撞擊管子的聲音。


底下是接線圖,其實就跟一般開關沒什麼兩樣。管子的一隻腳接10k歐姆電阻後接5V,這隻腳也接Arduino的數位腳位2,另一隻腳接地。


以下是軟體部分。

int pin = 2;
int ledpin = 13;

void setup()
{
  pinMode(pin, INPUT);   
  pinMode(ledpin, OUTPUT);
}

void loop()
{
  int status;
  status = digitalRead(pin);
  digitalWrite(ledpin, status);
  delay(100);
}

按照這份電路圖與這支程式,平常直立時,形成通路,所以腳位2會讀到LOW,於是LED不亮。若傾斜球開關,裡頭的球離開兩個接觸點,變成斷路,腳位2會讀到HIGH,於是點亮LED。

根據規格書,起作用的傾斜角度是30度,但實際狀況仍須試驗後才知道。這種感測器很便宜,號稱是窮人的加速度感測器,但用起來也不盡理想,並非從某個角度以上都是通路,而從該角度以下都是斷路,實際使用時,管子內的小球可能會亂跑,形成類似一般機械開關的彈跳(bounce)現象。

到YouTube觀看實際使用時的影片

這篇寫的有點簡略,關於「開關」,請參考我之前寫的「Arduino練習:以開關切換LED明滅狀態」。

78 comments:

  1. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. 請問您使用哪個藍牙模組?
      譬如Bluetooth Modem - BlueSMiRF Silver
      https://www.sparkfun.com/products/10269

      Delete
  2. http://goods.ruten.com.tw/item/show?21204112856596
    我用的是這個

    ReplyDelete
    Replies
    1. 找不到什麼資料耶。

      應該都是透過序列埠TX,傳送AT指令,
      需查詢文件才知道查詢信號強度的指令是哪個,
      然後從序列埠讀取回應資料並解析,抽出需要的部份。

      你說「AT指令還會包含MAC位址,要怎麼單只把訊號強度值...」,你已經得到回應了嗎?那麼該回應資料的格式是什麼呢?

      Delete
  3. +INQ:1234:56:0,1F1F,-53
    後面的53是訊號強度,但它一次都要顯示這一整行,我只想把後面的53存入變數來計算。

    ReplyDelete
    Replies
    1. 這應該是ASCII字串吧,有一定的格式,
      你可以丟進產生String物件,http://arduino.cc/en/Reference/StringObject

      然後使用lastIndexOf找出最後一個「,」的索引值,以+INQ:1234:56:0,1F1F,-53來說會是19,
      然後用substring擷取出「-53」,參數應該要丟入19+1,也就是substring(20),
      就可以得到字串-53,
      再用toInt()轉成int,就可以純入一般int變數了。

      順帶一問,為什麼您不一開始就說明使用哪個Bluetooth模組、以及已經取得的資料「+INQ:1234:56:0,1F1F,-53」呢?

      Delete
    2. 因為我本來是想直接抓晶片的腳位來截取數值,不想使用指令,我實驗後發現指令很容易因為存取多筆資料而混亂,會出現很多亂碼。
      謝謝版主的建議,我來試試看!

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. 試試看吧

      void setup()
      {
      Serial.begin(115200);

      char data[] = "+INQ:1234:56:0,1F1F,-53";
      char temp[100];
      Serial.print("Original data is ");
      Serial.println(data);
      String s = String(data);
      s.toCharArray(temp, 100);
      Serial.print("data in String object is ");
      Serial.println(temp);

      int idx = s.lastIndexOf(',');
      Serial.print("index of , is ");
      Serial.println(idx);f

      String s2 = s.substring(idx+1);
      s2.toCharArray(temp, 100);
      Serial.print("the piece of data we want is ");
      Serial.println(temp);

      int rst = s2.toInt();

      Serial.print("finally we got ");
      Serial.println(rst);
      }

      void loop()
      {
      }

      Delete
    2. 謝謝版主
      不過這個程式的功能好像沒辦法抓到一直持續輸出的信號,

      我接收到的資料是這樣無限不停重複的串列埠資料
      如下:
      +INQ:84:00:d2:0e:58:49,5a0204,-51
      +INQ:84:00:d2:0e:58:49,5a0204,-54
      +INQ:84:00:d2:0e:58:49,5a0204,-47
      +INQ:84:00:d2:0e:58:49,5a0204,-47
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQE
      +INQS
      +INQ:84:00:d2:0e:58:49,5a0204,-45
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-47
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQE
      +INQS
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-47
      +INQ:84:00:d2:0e:58:49,5a0204,-49
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQ:84:00:d2:0e:58:49,5a0204,-46
      +INQ:84:00:d2:0e:58:49,5a0204,-48
      +INQE
      +INQS

      Delete
    3. char temp[100];
      請問這是甚麼意思???

      Delete
    4. 我知道你會不停地收到資料,但我已經寫好解析的部份,不是嗎?
      你只要把資料丟進String,然後就可以用我寫的程式碼拿出「-46」那部分。

      不過,除了+INQ:84:00:d2:0e:58:49,5a0204,-48這種格式的資料,你還會收到其他資料,我寫的程式並沒有判斷這點。

      Delete
    5. 可以請您幫我解釋一下嗎?
      把資料丟進String??
      怎麼做?

      Delete
    6. 咳咳,所以說請看看C/C++的書籍。

      試試看吧

      #define SERIAL_BAUDRATE 115200
      #define MAX 100
      #define EXTRACT_ERROR -32768

      int extract(const char *data)
      {
      char temp[MAX];
      Serial.print("Original data is ");
      Serial.println(data);

      String s = String(data);
      s.toCharArray(temp, MAX);
      Serial.print("data in String object is ");
      Serial.println(temp);

      if(s.startsWith("+INQ:")){
      int idx = s.lastIndexOf(',');
      Serial.print("index of , is ");
      Serial.println(idx);

      String s2 = s.substring(idx+1);
      s2.toCharArray(temp, MAX);
      Serial.print("the piece of data we want is ");
      Serial.println(temp);

      int rst = s2.toInt();
      Serial.print("finally we got ");
      Serial.println(rst);
      return rst;
      }

      return EXTRACT_ERROR;
      }

      void setup()
      {
      Serial.begin(SERIAL_BAUDRATE);
      }

      void loop()
      {
      char data0[] = "+INQ:84:00:d2:0e:58:49,5a0204,-47";
      char data1[] = "+INQ:1234:56:0,1F1F,-53";
      char data2[] = "+INQ:84:00:d2:0e:58:49,5a0204,-48";
      char data3[] = "+INQE";
      char data4[] = "+INQS";

      int rst[5];
      rst[0] = extract(data0);
      rst[1] = extract(data1);
      rst[2] = extract(data2);
      rst[3] = extract(data3);
      rst[4] = extract(data4);

      for(int i = 0; i < 5; i++){
      if(rst[i] != EXTRACT_ERROR){
      Serial.print("rst of data");
      Serial.print(i);
      Serial.print(" is ");
      Serial.println(rst[i]);
      }
      else{
      Serial.print("rst of data");
      Serial.print(i);
      Serial.println(" is not valid");
      }
      }

      delay(3000);
      }

      我也貼在網路上了
      http://pastebin.com/ksnSZa94

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. 我找不到你這個藍牙模組的datasheet。
      不知道它是怎麼運作。

      Delete
    2. 請問您來自中國大陸嗎?姓名只有兩個字。

      中秋佳節耶,怎麼在這裡打發時間啊,呵呵:)。

      Delete
    3. 偶素歹灣郎
      宅男一枚

      Delete
    4. 啊,這樣啊,抱歉弄錯了。

      Delete
  6. char temp[100];
    宣告陣列,內含元素的型別為char,共有100個。

    如果您看不懂這些程式碼的話,可能需要找本C語言的書看一看。
    另外,Arduino的程式碼會用到一點點的C++,String就是個C++物件。

    ReplyDelete
    Replies
    1. yehnan哥,您好。
      最近有一項棘手的任務想請教,需求功能如下:
      DIO總共14個,扣掉前面2個(pin0,1)為傳輸用,
      將其餘12個分為前6個(pin2~7)為DI,後6個(pin8~13)為DO。
      但我要能從PC端下command控制,command格式為:
      "W","XX","Y" → "XX"為"0~5",代表pin8~13;"Y"為"0"或"1",代表OFF/ON ("W"大小寫皆可)
      "R","ZZ" → "ZZ"為"0~5",代表pin2~7 ("r"大小寫皆可)

      例如:
      輸入"W,2,1",pin10為ON,回傳"OK." → 此為DO command.
      輸入"R,3",回傳pin5的狀態(0或1) → 此為DI command.

      我目前用Switch..case..架構,但遇到瓶頸試不出來,
      故請問yehnan哥了。

      我的email:pfrhsd@gmail.com

      謝謝!

      Delete
    2. 嗯,就是經由序列埠、讀寫Arduino的腳位狀態。

      什麼瓶頸?
      你只有描述需求,並沒有說明問題。

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. 前輩
    為什麼您藍芽的鮑率都要設定在115200,和9600有甚麼不同嗎?
    在網路上的範例程式都是以9600居多。

    ReplyDelete
    Replies
    1. 我程式碼裡的115200是Arduino Uno與電腦之間的序列傳輸速率。

      很多模組預設為9600,你這個藍牙模組也是。

      Delete
  10. YA~~
    我剛剛找到了這個藍芽的說明文件
    http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Wireless/Bluetooth/CSR-BC417-datasheet.pdf

    ReplyDelete
    Replies
    1. 恭喜,慢慢看吧。這不是我本行。

      Delete
  11. 請問前輩
    下面這個
    +INQ:84:00:d2:0e:58:49,5a0204,-48
    最後一個 - 的索引值是多少?
    該怎麼算?

    ReplyDelete
    Replies
    1. 如果這是ASCII,那麼一個字元應該佔一個byte,會在char陣列裡佔一個位置,索引值從0開始算。+的索引值是0,I的索引值是1,依此類推。

      Delete
  12. 前輩
    如果我要用藍芽的訊號強度來決定第9腳PWM的電壓高低該怎麼做呢??
    請前輩指示

    ReplyDelete
  13. 拿到藍芽訊號強度後,並且查出藍芽訊號強度的最大與最小值,
    然後使用map函式,
    http://arduino.cc/en/Reference/Map
    將強度轉成0~255之間的值,因為Arduino的類比輸出為8位元,
    再用analogWrite將值輸出到第9腳,
    http://arduino.cc/en/Reference/AnalogWrite

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. 不行。還是請你自己想吧。orz

      先試著玩玩analogWrite控制LED的亮度,

      然後丟幾個值到map裡去,用看看,

      查詢藍牙模組的文件,查出強度的最大與最小值,
      然後再寫程式吧。

      Delete
  14. 其實我是不知道該怎麼把藍芽的訊號強度抓出來,並利用它的強弱來控制東西。
    懇請前輩賜教
    我沒別人可以問了

    ReplyDelete
    Replies
    1. 強度?之前不是已經寫好程式抓出來了嗎?

      如何利用強弱控制東西,就是我前幾則說的map與analogWrite。

      建議您看看Arduino入門書吧,譬如
      http://yehnan.blogspot.tw/2013/04/arduinoarduino.html

      Delete
  15. 那本書我看過了,我不是完全不懂啦!!
    是有一些關鍵的問題導致程式沒辦法組合起來,
    前輩上一個寫給我的程式裡,強度是存在哪個變數?
    有點看不懂

    ReplyDelete
  16. 把類似+INQ:84:00:d2:0e:58:49,5a0204,-48的字串傳給extract函式,就會回傳強度,是個int。

    ReplyDelete
  17. extract = map(extract, 0, 1023, 0, 179);
    這樣對嗎?

    ReplyDelete
    Replies
    1. 不對。

      你為什麼不編譯看看、執行看看呢?

      Delete
  18. 直接告訴我嘛
    這對您來說又沒甚麼
    我已經弄了快兩個月了

    ReplyDelete
  19. 不想這麼做。
    加油。

    ReplyDelete
  20. 怎麼這樣....
    好人做到底,不要只幫一半嘛~

    ReplyDelete
  21. 我認為我只幫到5%而已,遠遠不足一半。

    關於以藍芽訊號強度控制第9腳PWM的電壓高低,我也已經說明該做的事情了。

    從+INQ:84:00:d2:0e:58:49,5a0204,-48擷取出藍芽訊號強度後,儲存成int變數,並且查出藍芽訊號強度的最大與最小值,然後使用map函式http://arduino.cc/en/Reference/Map
    將強度轉成0~255之間的值(因為Arduino的類比輸出為8位元),

    再用analogWrite將該值輸出到第9腳,
    http://arduino.cc/en/Reference/AnalogWrite

    加油。

    ReplyDelete
  22. 從+INQ:84:00:d2:0e:58:49,5a0204,-48擷取出藍芽訊號強度後,儲存成int變數,並且查出藍芽訊號強度的最大與最小值,然後使用map函式http://arduino.cc/en/Reference/Map
    將強度轉成0~255之間的值(因為Arduino的類比輸出為8位元),
    這些簡單的我早會了......
    我是不知道您的程式是如何運作的而已,
    我本來是想說請您用控制PWM的方式來講解,
    版主可能誤會我的意思了,我不是想拿現成的程式來用。

    ReplyDelete
  23. ...用analogWrite將值輸出到第9腳...

    analogWrite就是控制PWM的函式。
    http://arduino.cc/en/Reference/AnalogWrite

    ReplyDelete
    Replies
    1. 這我會啦
      這麼簡單

      Delete
    2. Great, problem solved.

      Delete
  24. 還沒解決啦!
    我看不懂您的程式,可以請您解釋一下這個程式的輸出和輸入是哪一個嗎?
    Ps.這個程式是您寫的嗎?還是網路上抓的?如果是網路上抓的,可以告訴我網址嗎?

    ReplyDelete
  25. extract函式是我寫的,
    參數是指向字串"+INQ:84:00:d2:0e:58:49,5a0204,-47"的指標,
    回傳int(訊號強度,也就是-47的部份)。

    譬如
    char data[] = "+INQ:84:00:d2:0e:58:49,5a0204,-47";
    int rst = extract(data);

    那麼rst就會存有-47。

    ReplyDelete
    Replies
    1. ok
      明白了
      我要問的就是這個
      感謝前輩

      Delete
    2. 請問前輩
      char data[] = "+INQ:84:00:d2:0e:58:49,5a0204,-47";

      沒辦法接收我的訊號,我要怎麼把訊號輸入進去??

      Delete
    3. 因為訊號的值是會變動的,char data[] = 該怎麼接收會變動的資料呢?

      Delete
    4. 把你拿到的訊號,應該是個字串吧,譬如"+INQ:84:00:d2:0e:58:49,5a0204,-47",
      儲存成char陣列,或是用char指標指向該字串,
      然後就可以呼叫extract。

      如果拿到字串時已經是個char *,那就可以丟進extract了。

      如果不是,那是什麼呢?一次收到一個字元嗎?那就將字元一個個放進char陣列裡,收集完全後再呼叫extract。

      Delete
    5. 版主您寫的程式只是一直在抓char data[] = "+INQ:84:00:d2:0e:58:49,5a0204,-47";
      裡面的-47,並沒有接收到藍芽的訊號再貼出,是不是搞錯了??

      Delete
    6. 我並不知道你抓到的藍牙訊號是什麼,你必須自己將訊號弄成字串放進char data[]裡。

      Delete
    7. 儲存訊號的變數沒辦法放進char data[]裡??

      Delete
    8. 啥?

      你抓到的藍牙訊號是什麼東西?是個ASCII字串吧?儲存在哪裡?

      Delete
  26. 您是不是不太懂我的問題?

    ReplyDelete
  27. 沒錯。

    您也聽不懂我的回答,呵呵。

    ReplyDelete
  28. 原來如此.....難怪那個程式..........
    我去問別人好了,這幾天叨擾了.........

    ReplyDelete
  29. void setup()
    {
    Serial.begin(115200);

    char data[] = "+INQ:1234:56:0,1F1F,-53";
    char temp[100];
    Serial.print("Original data is ");
    Serial.println(data);
    String s = String(data);
    s.toCharArray(temp, 100);
    Serial.print("data in String object is ");
    Serial.println(temp);

    int idx = s.lastIndexOf(',');
    Serial.print("index of , is ");
    Serial.println(idx);f

    String s2 = s.substring(idx+1);
    s2.toCharArray(temp, 100);
    Serial.print("the piece of data we want is ");
    Serial.println(temp);

    int rst = s2.toInt();

    Serial.print("finally we got ");
    Serial.println(rst);
    }

    void loop()
    {
    }


    char data[]寫在setup..........
    後面還寫 = "+INQ:1234:56:0,1F1F,-53";
    原來您真的不會.......
    浪費我好多天............

    ReplyDelete
    Replies
    1. 您又沒有告訴我藍牙訊號從何而來,
      也只告訴我得到的是"+INQ:1234:56:0,1F1F,-53",
      我當然也只能在程式碼裡寫死,
      我給的是個extract函式,傳入符合"+INQ:1234:56:0,1F1F,-53"格式的訊號,便可抽取出最後代表訊號強度的數字(-53),
      這不就是你最初的問題嗎?

      Delete
    2. > char data[]寫在setup..........

      我也只能寫死在程式裡,

      您最初的問題是「要怎麼單只把訊號強度值存入Arduino的變數裡?」,

      > +INQ:1234:56:0,1F1F,-53
      > 後面的53是訊號強度,但它一次都要顯示這一整行,
      > 我只想把後面的53存入變數來計算。

      我寫了個extract函式,的確把你給的"+INQ:1234:56:0,1F1F,-53"分析後抓出最後那個數字並存成變數了。

      Delete
    3. 嗯,不管如何。請問問其他人吧,說不定一下子就解決了。

      Wish you luck.

      Delete
    4. 訊號當然是會一直變的..............這是基本...........

      Delete
    5. This comment has been removed by the author.

      Delete
    6. 呃,我知道訊號會變,所以?

      之前說的「
      我知道你會不停地收到資料,但我已經寫好解析的部份,不是嗎?
      你只要把資料丟進String,然後就可以用我寫的程式碼拿出「-46」那部分。


      我寫了解析訊號字串取得強度的部份,至於其他部分,還請自己動手。
      既然會從串列埠接收資料,那就在每當接收到完整的一份資料後,進行解析,判斷並取出強度的部份。

      Delete
    7. 啊,你的確說過會從串列埠不停地取得資料。

      Delete
    8. This comment has been removed by the author.

      Delete
  30. 關於你手上的問題,我重新讀過一遍所有的留言後,根據知道的東西,可行方案應如下:

    想要達成的目標是不斷讀取藍牙訊號的強度,隨之調整LED亮度。
    會從序列埠收到類似+INQ:84:00:d2:0e:58:49,5a0204,-47的字串,我假設這是ASCII字串,不僅有此格式的字串,也會有其他格式的字串,譬如+INQE與+INQS。
    不過我不確定藍牙模組是否會自動不斷送出含有強度的字串,還是要下達指令後才會回傳。
    在Arduino程式碼裡的loop(),撰寫程式從序列埠接收字串,應該要根據某種條件判斷該字串是否完整,完整收到字串後,應該存在char陣列裡。
    然後便可丟給抽取強度部分的函式,之中要作判斷,判斷該字串是不是含有強度的那種字串,然後取出強度,是個int值。
    有了強度值後,必須根據強度的最大最小值,利用map函式轉成介於0~255(8位元)的值,再經由analogWrite輸出到LED的腳位,控制LED亮度。

    我沒用過這個藍牙模組,沒有讀過(也不想)研讀該藍牙模組的datasheet,僅根據留言裡的資訊,拼湊出上述或許可行的作法。

    最後希望您能早日解決問題。

    或許可到Arduino的論壇詢問,例如http://www.robofun.net/forum/index.php,或者可向原賣家詢問有無範例程式碼。

    ReplyDelete
  31. This comment has been removed by the author.

    ReplyDelete
  32. = =...看過上面對話感覺很無言...還是別造口業好了。
    葉桑也太熱心了點,請他把你的blog讀完,或順便推一下自己寫的書就好啦...哈哈。
    在下最近才剛接觸MCU且只會寫一點VB,上個月買零件從PIC開始玩起(便宜的12F677)但處處碰壁(因為資源較少),後來在您的blog上發現全open的Arduino便跳坑買了片Mega2560,正在逐步建立起各sensor的code準備用在自家的開心農場。

    ReplyDelete
    Replies
    1. 我沒玩過PIC。
      如果到書店看看微控制器的中文書的話,8051書最多,PIC與Arduino的書非常非常少。但若是英文書的話,都已經相當豐富。另外,聽說PIC在大陸也滿紅的。

      Arduino的程式是C語言、加上極少的C++。

      Good luck, happy playing Arduino.

      Delete
  33. 藍芽可以讀字串,然後用switch去動作嗎? 因為有時傳的資料顯示出並不是完整一串,是有切割過的,但是如果把她串接出來的話,要如何用switch或字串比對(strcmp)?? 然而資料會保留不會被覆蓋掉

    ReplyDelete
    Replies
    1. > 用switch去動作嗎?
      啥?

      > 並不是完整一串,是有切割過的
      因為接收時並不一定會收到你想像中的「完整」一串。
      須自己判斷,串連起來,找出你所謂的完整的資料。

      用strcmp比較字串。

      Delete
    2. 我想要放到陣列裡的字元比較完後,使led亮或暗,接著把陣列清空後,再繼續讀取新的資訊放到陣列內,再使led暗或亮,但是不知怎清掉並讀取新資訊,下面是我寫的程式碼
      int LED = 13;
      int i = 0;
      char ary[128] = {0};
      void setup() {
      pinMode(LED, OUTPUT);
      Serial.begin(9600); // Arduino起始鮑率:9600
      Serial1.begin(9600); // HC-06 出廠的鮑率:每個藍牙晶片的鮑率都不太一樣,請務必確認
      digitalWrite(LED, LOW);
      }
      char key[] = "led0";
      char key1[] = "led1";
      void loop() {
      while (Serial1.available() > 0) {
      char c = Serial1.read();
      ary[i] = c;
      i++;
      }
      if (strcmp(ary, key) == 0) {
      digitalWrite(LED, LOW);
      }
      if (strcmp(ary, key1) == 0) {
      digitalWrite(LED, HIGH);
      }
      }

      Delete
    3. 大概在loop()裡加上i = 0;
      或許就可以了。
      或許。

      Delete