2016年7月30日 星期六

使用Python透過Bluetooth與Arduino連結


Ref : http://arbu00.blogspot.tw/2016/07/pythonbluetootharduino.html

Ref : http://gsyan888.blogspot.tw/2014/03/arduino-hc-05.html

Ref : http://gsyan888.blogspot.tw/2015/03/arduino-hc-05-master-and-slave.html

Ref :

這篇文章示範如何使用Python透過Bluetooth跟Arduino連結.並使用Python建立出來的圖形介面來控制Arduino上LED 的ON/OFF.
 底下實驗概念接線圖

   Python 因為是跨平台的程式語言所以也執行在windows或是Linux,底下範例是直接使用一般電腦及windows 10去做示範.Python原始程式碼只要稍微修改一下也可以改成使用在安裝Linux的系統上,如樹梅派.



底下是之前USB2Serial範例的介紹連結:
http://arbu00.blogspot.tw/2016/07/pythonarduinoled.html




    底下所建立出來的圖形介面如下圖所示,
當按下按鈕<check>去跟Arduino確認是否已經連線成功.
按下按鈕<ButtonA>則會傳送字元'a' 到arduino當指令去啟動LED跑馬燈1
按下按鈕<ButtonB>則會傳送字元'b' 到arduino當指令去啟動LED跑馬燈2
按下按鈕<ButtonC>則會傳送字元'c' 到arduino當指令去啟動LED閃燈的動作.
按下按鈕<Exit>則會傳送'ESC' 鍵字元到arduino當指令去結束視窗跟所有動作.
當成是執行時必須先按下<Check>Button 再reset Arduino才能順執行以確保連線成功.


video 連結  
https://www.youtube.com/watch?v=M8hEMtvtui8



# -*- coding:utf-8 -*-
from time import sleep
import serial

import tkinter
Ready_flag=0
##==============================================================================
ser =serial.Serial("COM9", 9600, timeout=2) # Establish the connection on a specific port
##==============================================================================
##===============For Linux=========================================================
#ser =serial.Serial("/dev/ttyACM0", baudrate=9600, timeout=2) # Establish the connection on a specific port
##==============================================================================
def SerialWrite(command):
 ser.write(command)
 rv=ser.readline()
 #print (rv) # Read the newest output from the Arduino
 print (rv.decode("utf-8"))
 sleep(1) # Delay for one tenth of a second
 ser.flushInput()
##===When button A be pressed then Send 'a' to arduino============
def SendCmdA():
 global Ready_flag
 if Ready_flag==1:
  Arduino_cmd='a'
  cmd=Arduino_cmd.encode("utf-8")
  SerialWrite(cmd)
  #Show to LabelA----------------
  LabelA.config(text="Send the command 'a' to Arduino")
  LabelA.update_idletasks()
  Tkwindow.update()
 else:
  LabelA.config(fg='red',)
  LabelA.config(text="Arduino not ready!Please check it again")
  LabelA.update_idletasks()
  Tkwindow.update()
##===When button A be pressed then Send 'a' to arduino============
def SendCmdB():
 global Ready_flag
 if Ready_flag==1:
  Arduino_cmd='b'
  cmd=Arduino_cmd.encode("utf-8")
  SerialWrite(cmd)
  #Show to LabelA----------------
  LabelA.config(text="Send the command 'b' to Arduino")
  LabelA.update_idletasks()
  Tkwindow.update()
 else:
  LabelA.config(fg='red',)
  LabelA.config(text="Arduino not ready!Please check it again")
  LabelA.update_idletasks()
  Tkwindow.update()
##===When button A be pressed then Send 'a' to arduino============
def SendCmdC():
 global Ready_flag
 if Ready_flag==1:
  Arduino_cmd='c'
  cmd=Arduino_cmd.encode("utf-8")
  SerialWrite(cmd)
  #Show to LabelA----------------
  LabelA.config(text="Send the command 'c' to Arduino")
  LabelA.update_idletasks()
  Tkwindow.update()
 else:
  LabelA.config(fg='red',)
  LabelA.config(text="Arduino not ready!Please check it again")
  LabelA.update_idletasks()
  Tkwindow.update()
  
##==Serial connect and Get arduino  Ready================
def Serial_Connect():
 global Ready_flag
 for i in range (1,10):
  print("Checking...")
  #Show to LabelA----------------
  Str_index=""
  str_check="th Checking...\n"
  Str_index=str(i)
  Label_msg="The " +Str_index +str_check
  LabelA.config(fg='green',)
  LabelA.config(text=Label_msg)
  LabelA.update_idletasks()
  buttonStart.config(state="disabled")
  Tkwindow.update()
   
  rv=ser.readline()
  #Debug print (rv) # Read the newest output from the Arduino
  print (rv.decode("utf-8"))
  ser.flushInput()
  sleep(0.5) # Delay
  Str_Message=rv.decode("utf-8")
  #Debug print(Str[0:5])
  if Str_Message[0:5]=="Ready":
   Ready_flag=1
   print("Get Arduino Ready !")
   #Show to LabelA----------------
   LabelA.config(text="Get Arduino connecting Ready !")
   LabelA.update_idletasks()
   Tkwindow.update()
   break
  elif  i==9:
   Ready_flag=0
   LabelA.config(fg='red',)
   LabelA.config(text="Can't connnect to Arduino successfully!\
                               \nPlease press 'check' button then reset Arduino to try again!")
   buttonStart.config(state="active")
   LabelA.update_idletasks()
   Tkwindow.update()
   sleep(1) # Delay
  
##------------------------------------------------------
##==Serial connect Exit================
def All_Exit():
 print("Exit.....")
 #Show to LabelA----------------
 LabelA.config(text="Exit.....")
 LabelA.update_idletasks()
 Tkwindow.update()
 sleep(1)
 chr_num = 27  ##ESC
 cmd=(chr(chr_num).encode('utf-8'))
 SerialWrite(cmd)
 ser.close()
 Tkwindow.destroy() # Kill the root window!
##------------------------------------------------------
Tkwindow=tkinter.Tk()
Tkwindow.title("Using Python to Control Arduino LED ON/OFF")
Tkwindow.minsize(600,400)
LabelA=tkinter.Label(Tkwindow,
        bg='white',
        fg='blue',
        text="Press 'Check' Button then reset Arduino to connect!",
        width=80,
        height=10,
        justify=tkinter.LEFT
        )
LabelA.pack(side=tkinter.TOP)
buttonA=tkinter.Button(Tkwindow,
         anchor=tkinter.S,
         text="Button A",
         width=10,
         height=1,
         command=SendCmdA)
buttonA.pack(side=tkinter.LEFT)
buttonB=tkinter.Button(Tkwindow,
         anchor=tkinter.S,
         text="Button B",
         width=10,
         height=1,
         command=SendCmdB)
buttonB.pack(side=tkinter.LEFT)
buttonC=tkinter.Button(Tkwindow,
         anchor=tkinter.S,
         text="Button C",
         width=10,
         height=1,
         command=SendCmdC)
buttonC.pack(side=tkinter.LEFT)
buttonStart=tkinter.Button(Tkwindow,
         anchor=tkinter.S,
         text="Check",
         width=10,
         height=1,
         command=Serial_Connect)
buttonStart.pack(side=tkinter.RIGHT)
buttonEnd=tkinter.Button(Tkwindow,
         anchor=tkinter.S,
         text="Exit",
         width=10,
         height=1,
         command=All_Exit)
buttonEnd.pack(side=tkinter.RIGHT)
Tkwindow.mainloop()



<Arduino source code>


//*****************************************************************************
//*****************************************************************************
//ArbuluckyChat  V1.0
//阿布拉機的3D列印與機器人
//
//2016/07/ˇ0 Writen By Ashing Tsai
//
//******************************************************************************
#include <softwareserial>
const int LedPin12=12;
const int LedPin11=11;
const int LedPin10=10;
//USB-Serial String Str01="";
SoftwareSerial BT(2,3);    //RX,TX
String ReadBTString="";      
   
void setup() {
    BT.begin(9600); 
    BT.println("Ready"); // print "Ready" once
 //USB-Serial Serial.begin(115200); // set the baud rate
 //USB-Serial Serial.println("Ready"); // print "Ready" once
   pinMode(LedPin12,OUTPUT);
   pinMode(LedPin11,OUTPUT);
   pinMode(LedPin10,OUTPUT);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,LOW);
}
void loop() {
 /*USB-Serial+
  if (Serial.available())
   Str01="";
   delay(1);
   while(Serial.available())
    {
      Str01+=(char)Serial.read();
    }
    Serial.println(Str01); // send the data back in a new line so that it is not all one long line
}
 USB-Serial-*/
//BT+========================
  if(BT.available()) { 
        ReadBTString="";
        while(BT.available()) { 
                ReadBTString+=(char)BT.read();
                delay(1);  
        }
      BT.println(ReadBTString);
  }
//BT-========================
if (ReadBTString[0]=='a')
{
   digitalWrite(LedPin12,HIGH);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,LOW);
   delay(500);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,HIGH);
   digitalWrite(LedPin10,LOW);
   delay(500);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,HIGH);
   delay(500);
}
if (ReadBTString[0]=='b')
{
   digitalWrite(LedPin12,HIGH);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,LOW);
   delay(100);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,HIGH);
   digitalWrite(LedPin10,LOW);
   delay(100);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,HIGH);
   delay(100);
}
if (ReadBTString[0]=='c')
{
   digitalWrite(LedPin12,HIGH);
   digitalWrite(LedPin11,HIGH);
   digitalWrite(LedPin10,HIGH);
   delay(300);
   digitalWrite(LedPin12,LOW);
   digitalWrite(LedPin11,LOW);
   digitalWrite(LedPin10,LOW);
   delay(300);
}
}
   
</softwareserial>



======================================================================

它和電腦或是 Android 的藍芽配對連線以後,電腦或是裝置上就會多一個 serial port 可以通訊,Scratch 或是 Android Apps 就可以透過這個無線的通道和 Arduino 傳送資料。

不過,要開始玩 S4A 之前,因為這個改過的 Scratch 內定是用 38400 的 baud rate 來和 serial port 連線,而 HC-05 預設的 baud rate 卻是 9600,因此,新買的 HC-05 藍芽模組,第一關是要能進入 AT command mode 去修改它的設定值。

如果有 USB to TTL ,可以利用它直接和 HC-05 連接,除了 Vcc , GND, Key(接5V), 兩個裝置的 RXD → TXD, TXD→RXD。

下面介紹第二種方法,以 Arduino 的第 9, 10, 11 pins 和 5V, GND 五個腳位來達到相同的目的。

硬體的部份

=====================================================================
HC-05 和 Arduino 接線的腳位對應如下:
HC-05 VCC → Arduino 5V
HC-05 GND → Arduino GND
HC-05 TXD → Arduino pin 10
HC-05 RXD → Arduino pin 11
HC-05 KEY → Arduino pin 9

/*
AUTHOR: Hazim Bitar (techbitar)
DATE: Aug 29, 2013
LICENSE: Public domain (use at your own risk)
CONTACT: techbitar at gmail dot com (techbitar.com)
*/
#include <SoftwareSerial.h>
SoftwareSerial BTSerial(10, 11); // RX | TX
void setup()
{
  pinMode(9, OUTPUT);  // this pin will pull the HC-05 pin 34 (key pin) HIGH to switch module to AT mode
  digitalWrite(9, HIGH);
  Serial.begin(9600);
  Serial.println("Enter AT commands:");
  BTSerial.begin(38400);  // HC-05 default speed in AT command more
}
void loop()
{
  // Keep reading from HC-05 and send to Arduino Serial Monitor
  if (BTSerial.available())
    Serial.write(BTSerial.read());
  // Keep reading from Arduino Serial Monitor and send to HC-05
  if (Serial.available())
    BTSerial.write(Serial.read());
}

=================================================================
我們先將 Arduino 以 USB 接上電腦,打開靭體程式編寫工具,將上面的程式碼貼上、儲存後,上載到 Arduino 中。

讓 HC-05 進入 AT command mode

如果 HC-05 和 Arduino 已按照前述的腳位接好線,並且將前述的靭體上載到 Arduino 中了。就可以依照下面的程序連線並讓 HC-05 進入 AT command mode:



  • 先拔掉 Arduino 的 USB 線,也拔掉 HC-05 VCC 和 Arduino 5V 相連的這條線。
  • 將 Arduino 的 USB 線插入電腦。
  • 開啟終端機軟體(例如 Windows 的超級終端機或是 Putty ......),以

    每秒傳輸位元: 9600
    資料位元:8
    同位檢查:無
    停止位元:1

    的設定來和 Arduino 的 serial port 連線,成功的話,應該可以在終端機軟體的畫面中看到「Enter AT commands:」的訊息。
  • 將 HC-05 VCC 和 Arduino 5V 的線重新接上。
  • 供電後的 HC-05 應該是以慢速(約兩秒一次)閃爍 LED,這表示它已進入 AT command mode,等候我們輸入 AT 指令。
啟動「超級終端機」新增連線
選取 Arduino 的 serial port
傳輸速率設為 9600
成功和 Arduino 連線

HC-05 進入 AT command mode 以後,我們可以先輸入「AT」,按完 Enter 鍵後,應該可以看到畫面回應了「OK」,如果沒訊息,先檢查一下硬體的部份是否有按腳位接對了,再依前述的程序重新來一次。

如果畫面上的「OK」一直重覆的出現,停不下來(我被它給嚇壞了,以為買到了有問題的東西),只要每次按 Enter 鍵時快速的多按一下(也就是連按兩次 Enter 鍵),或是用鍵盤按「Ctrl  C」,它就不會瘋狂的回應你了。這個問題應該是 HC-05 的 AT 指令最後要送出「\r\n」,而我們按下 Enter 鍵時,終端機的預設值並沒有送出「換行」字元所致。以 Windows 的「超級終端機」來說,我們可以多設定以下的內容來避免前述的情形:




終端機中如果在「ASCII設定」中多點選「行尾傳送換行符號」就可以滿足 HC-05 AT 指令後需要加上「\r\n」的需求了。

如果用的是 Arduino 內建的終端機,就在下方多選個「Both NL & CR」,讓在在每道指令後頭都自動加上「\r\n」。




HC-05 的 AT 指令

除了「AT」這個指令外,接下來,我們先查詢一下 HC-05 目前的連線速率,在終端機中輸入底下的指令並按 Enter 鍵:

AT+UART?

如果都沒更改過,預設值的回應可能是:

+UART:9600,0,0

我們想將速率換成 S4A 的 36800 ,輸入以下的指令,並按 Enter 鍵:

AT+UART:38400,0,0

成功的話,HC-05 應該會回應「OK」。


除此之外,可能還有其它 AT command 可以執行的,基本的格式是:

  • 查詢:以問號 (?) 結尾。
  • 設定:將前述的問號換成冒號 (:) ,其後再接要設定的新內容。


例如:

  • AT+VERSION?
  • AT+NAME?
  • AT+ADDR?
  • AT+PSWD?

上面分別是查詢版本(VERSION)、藍芽裝置名稱(NAME)、藍芽裝置位址(ADDR)、配對時的密碼(PSWD)。

相對應的設定指令變成:

  • AT+NAME:xxxxx
  • AT+PSWD:xxxxx
xxxxx 為想自訂的內容。

還有其它的 AT 指令,有興趣可以找 HC-05 的手冊來研究吧!


設定 HC-05


首先,當然是要以 AT command mode ,先設定一下兩片 HC-05,主要是讓它們扮演不同的角色:slave 和 master (出廠時,預設是 slave);除此之外, 通訊時,UART 的 baud rate 也要設為一樣......。

假設兩片 HC-05 要以 115200 的 baud rate 通訊,並且只鎖定對方連線,可能要進行以下的設定:

在當 slave 的 HC-05 必須執行的 AT command :
  • AT+UART=115200,0,0
  • AT+CMODE=0
  • AT+ROLE=0
  • AT+ADDR?

如果「AT+ADDR?」回應的內容是:

+ADDR:14:1:61757

要記下 +ADDR: 後面的那串數字「14:1:61757」(slave 的 address),設定 master 時會用到。

在當 master 的 HC-05 必須執行的 AT command :
  • AT+UART=115200,0,0
  • AT+CMODE=0
  • AT+ROLE=1
  • AT+BIND=14,1,61757   
    (注意:14,1,61757 請自行置換成您查到的 slave address)

照上面的 AT commands 來看,slave 和 master 不同的地方在後兩道指令:

  • 「AT+ROLE=」用來設定模組的角色是 slave ( 0 ) ,還是 master ( 1 )。
  • 「AT+ADDR?」用來查詢 client 的 address。
  • 「AT+BIND=」用來指定要主動連哪一個 address 的 slave。


特別注意:「AT+ADDR?」查到的 address 中是用冒號「:」當分隔符號,而在 BIND 指令中用的卻是用逗號「,」當分隔符號哦!


經過前述的設定後,照說,當 master 找到 slave 時,就會自動連線了。接著就可以利用兩片 HC-05 上的 TXD 和 RXD 來傳送及接收資料。

Arduino 接線


HC-05 要怎麼和 Arduino 連接呢?其實兩片 HC-05 交換資料和一片 HC-05 與其它藍牙裝置交換資料,接線的方法並沒有什麼不同,最重要的是前一步驟,利用 AT command 要將「UART」、「ROLE」及 master 的「BIND」設對啦!

我們可以選擇用 Arduino 的 D0、D1 來連接 HC-05,或是利用 Arduino 的 D10、D11,以 SoftwareSerial 的方式來連接 HC-05,腳位對應如下:

方法一 (HC-05 TXD、RXD接在D10、D11):
  • HC-05 TXD ----- Arduino D0
  • HC-05 RXD ----- Arduino D1

方法二 (用 SoftwareSerial,HC-05 TXD、RXD接在D10、D11):
  • HC-05 TXD ----- Arduino D10
  • HC-05 RXD ----- Arduino D11


如果使用方法二,記得要導入 SoftwareSerial 的函數庫,在程式的最前面加入:

#include <SoftwareSerial.h>

並建立一個「SoftwareSerial」的物件才能使用,而方法一只要直接用「Serial」即可。


Arduino 程式


我寫了個測試的程式,在插 slave HC-05 的 Arduino 加一片 LCD(1602A),當 master 那塊 Arduino 傳來字串,slave 接收到後,將字串顯示在 LCD 上

slave 端程式碼(HC-05 TXD、RXD接在D10、D11)

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
SoftwareSerial BTSerial(10, 11); // HC-05的TXD,RXD腳位
LiquidCrystal_I2C lcd(0x27,16,2);  // 設定 LCD
void setup()
{
  lcd.init();            // initialize the lcd
  lcd.backlight();
  BTSerial.begin(115200);
}
void loop()
{
  // 檢查是否有資料傳來
  if (BTSerial.available()) {
    delay(100);  //稍候一下,讓資料都到
    lcd.clear(); //清除 LCD 畫面
    // 讀取所有資料,並顯示在 LCD
    while (BTSerial.available() > 0) {
      lcd.write( BTSerial.read() );
    }
  }
}

上面程式中,關於 HC-05 的指令如下:

  • 建立SoftwareSerial : SoftwareSerial BTSerial(10, 11)
  • 設定鮑率:BTSerial.begin(115200);
  • 檢查是否有資料傳來:BTSerial.available()
  • 讀取傳來的資料:BTSerial.read() 

有「read」,當然就可以使用「write」來將資料傳送給 master 端。


master 端程式碼(HC-05 TXD、RXD接在D10、D11)

#include <SoftwareSerial.h>
SoftwareSerial BTSerial(10, 11); // HC-05的TXD,RXD腳位
void setup() {
  BTSerial.begin(115200);
  //稍等一秒後再送資料給遠方
  delay(1000);
  BTSerial.write("^^ I Love You ^^");
}
void loop()
{
  //
}

這裡只簡單的用「write」來送出字串給遠端的 HC-05,想接收遠端送來的資料,當然就使用「read」囉!


哈~將 LCD  裝在之前 3D printer 做的履帶車上,用手機遙控,開到老婆面前後,再打出字幕,不賴吧!討了老婆歡心之後,應該可以提高對我老是花很多時間在研究如何「玩耍」的耐受度吧?!



========================================================================