2017年4月25日 星期二

使用 Python 控制 Arduino

本周從市圖借到這本 "Python x Arduino 物聯網整合開發實戰 (碁峰, 曾吉弘譯)",  此書翻譯自 Packt 出版的 "Python Programming for Arduino (Pratik Desai)", 主要是利用 Firmata 通訊協定讓電腦 (Windows, Linux, MacOS) 透過序列埠控制 Arduino, 使用 Python 豐富的函式庫 (視覺化, GUI, 遠端存取等) 實作 Arduino 的物聯網應用, 熟悉 Python 語言的人可駕輕就熟進入 IoT 領域.

Source : CAVEDU

不過這本書所介紹的技術不是要取代 Arduino C 原生程式碼的功能 (也不能), 而只是將 Firmata 協定所寫的韌體燒入 Arduino 當中間人, 讓電腦中的 Python 程式能透過 USB 串列埠與 Arduino 溝通而已. 這種模式下 Arduino 並非獨立運作, 而是接受電腦的 Python 程式控制. 獨立運作的 Arduino 還是必須使用 C 語言編寫原生碼 (韌體), 而非 Firmata.

以下即依照書中描述, 實際測試 Firmata 的功能. Arduino IDE 已經內建了各種應用的 Firmata 韌體, 放置在 "檔案/範例/Firmata" 裡, 測試 Firmata 功能的最簡單方式是開啟 "檔案/範例/Firmata/StandardFirmata" 這個範例程式 :


開啟後不要做任何修改, 直接上傳 Arduino 即可, 編譯後記憶體占用情形如下 (1.8.1 版) :

草稿碼使用了 11134 bytes (36%) 的程式儲存空間。上限為 30720 bytes。
全域變數使用了 1029 bytes (50%) 的動態記憶體,剩餘 1019 bytes 給區域變數。上限為 2048 bytes 。

韌體上傳 Arduino 後, 接下來就是要在電腦端執行 Firmata 測試程式, 連線 Firmata 官網下載 Windows 版的 firmata_test.exe :

# http://www.firmata.org


執行 firmata_test.exe, 點選上方的 Port 選單, 選擇 Arduino 所接的 USB COM 埠 (參考 Arduino Nano 開機測試) :


選定後就會出現 Arduino 每一個 Port 的目前狀態, 其中 6 個 Analog 因為接腳浮接的關係, 其值是隨機的亂數, 因此數值不斷閃爍. 按下 D13 的按鈕切換至 HIGH 狀態時, Arduino 板子上內建的 D13 LED 會亮起來, 切回 LOW 就熄滅, 這就是 Firmata 的主要功能, 即不需要撰寫 Arduino 草稿碼也可以控制 Arduino :


上面的 firmata_test.exe 是現成的電腦主機端軟體, 透過 USB 串列埠與 Arduino 裡的 StandardFirmata 草稿碼互動. 如果要用 Python 自行撰寫程式與 Arduino 程式碼互動, 必須在電腦中安裝 Python 的 pySerial 套件 :

C:\Users\Tony>pip3 install pyserial
Collecting pyserial
  Downloading pyserial-3.3-py2.py3-none-any.whl (189kB)
Installing collected packages: pyserial
Successfully installed pyserial-3.3

如果是離線安裝, 先下載 pyserial 的 whl 或 gz 檔 :

https://pypi.python.org/pypi/pyserial#downloads

C:\Users\Tony>pip3 install d:\python\pyserial-3.3-py2.py3-none-any.whl
Processing d:\python\pyserial-3.3-py2.py3-none-any.whl
Installing collected packages: pyserial
Successfully installed pyserial-3.3

關於 PySerial 函式庫的 API 用法參考 :

http://pyserial.readthedocs.io/en/latest/pyserial_api.html

接下來到 Arduino IDE 開啟 "檔案/範例/01.Basics/DigitReadSerial" 草稿碼 :


其內容如下 :

// digital pin 2 has a pushbutton attached to it. Give it a name:
int pushButton = 2;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

程式只是從 Pin2 接腳讀取按鈕輸入, 然後送到序列埠去, 此程式不用修改直接上傳 Arduino 即可, 我們接下來要在電腦這端使用 Python 程式透過 PySerial 套件讀取 Arduino 送到串列埠的數據. 在 IDLE 輸入如下指令 :

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import serial
>>> s=serial.Serial("com3",9600)
>>> while True:
        print(s.readline())

此程式利用 serial.Serial() 函數建立 com3 (或 COM3, 大小寫無關) 的序列埠物件, 再以此物件的 readline() 讀取連接至 COM3 的 Arduino 序列埠數據. 這時畫面會不斷顯示從序列埠讀取到 0, 這是因為 Arduino 的 Pin2 並未真正接上按鈕之故.


上面是我照書本上的範例所做的測試, 從 Arduiono 輸出數據 (0) 到 USB 串列埠, 再由 Python 程式透過 PySerial 去讀取. 接下來我想反方向測試, 由 Python 程式經序列埠傳送資料給 Arduino. Python 程式如下 :

import serial
s=serial.Serial("com3",9600)
from time import sleep
while True:
s.write('H'.encode())
sleep(1)
s.write('L'.encode())
sleep(1)
此程式從 time 套件匯入 sleep() 函數用來暫停程式執行 (即休眠), 其參數單位為秒, 程式會在無限迴圈中持續對序列埠送出 'H' 與 'L', 間隔一秒, 注意, 因為 Python 3 字串都以 Unicode 表示, 因此在輸出到序列埠時必須呼叫 encode() 將其轉成 byte 編碼. 參考 :

# python3 pySerial TypeError: unicode strings are not supported, please encode to bytes

而 Arduino 端程式則是持續讀取序列埠, 若收到 'H' 就點亮 D13 LED; 否則就熄滅它 :

int LED=13;
void setup() {
  Serial.begin(9600);
  }

void loop() {
  if (Serial.available() > 0) {
    if (Serial.read()=='H') {digitalWrite(LED, HIGH);}
    else {digitalWrite(LED, LOW);}
    }
  delay(1);    
  }

將此 Arduino 草稿碼上傳後, 於 PC 端執行 Python 程式, Arduino 板上的 D13 LED 將亮一秒, 滅一秒反覆交替. 從這個範例可以看出 PySerial 的角色是在 Arduino 與 PC 間透過 USB 串列埠搭起雙向溝通的橋樑. 不過 PySerial 功能較為樸素, 不具有 Firmata 功能, 因此除了 PC 端要寫 Python 程式外, 對於不同的應用, Arduino 端也必須配合不斷改寫上傳, 殊為不便.

有沒有兼具 Firmata 與 PySerial 功能的函式庫, 可以讓 Arduino 端指上傳韌體一次即可, 我們只要專注在 PC 端即可呢? 有的, 那就是 PyFirmata 函式庫, 此函式庫是在 PySerial 基礎上加入 Firmata 功能, 可以用 pip3 install pyfirmata 指令安裝此套件 :

C:\Users\Tony>pip3 install pyfirmata
Collecting pyfirmata
  Downloading pyFirmata-1.0.3-py2.py3-none-any.whl
Requirement already satisfied: pyserial in c:\python36\lib\site-packages (from pyfirmata)
Installing collected packages: pyfirmata
Successfully installed pyfirmata-1.0.3

若需離線安裝, 可先下載 pyfirmata 的 whl 或 gz 檔 :

https://pypi.python.org/pypi/pyFirmata

C:\Users\Tony>pip3 install d:\python\pyFirmata-1.0.3-py2.py3-none-any.whl
Processing d:\python\pyfirmata-1.0.3-py2.py3-none-any.whl
Requirement already satisfied: pyserial in c:\python36\lib\site-packages (from p
yFirmata==1.0.3)
Installing collected packages: pyFirmata
Successfully installed pyFirmata-1.0.3

關於 PyFirmata API 使用說明參考 :

http://pyfirmata.readthedocs.io/en/latest/

欲測試 PyFirmata 功能, 需從 Arduino IDE 的範例中再次上傳 StandardFirmata 草稿碼, 然後就可以在 PC 端用 Python 程式透過序列埠操控 Arduino 了, 程式如下 :

import pyfirmata
from time import sleep
LED=13
PORT="COM3"
board=pyfirmata.Arduino(PORT)
while True:
board.digital[LED].write(1)
sleep(1)
board.digital[LED].write(0)
sleep(1)

這裡要先匯入 pyfirmata 函式庫, 然後呼叫 Arduino() 函數來指定兩方溝通的序列埠名稱, 傳回Arduino 板子物件, 此物件的 digital 串列物件可用來指定 Arduino 腳位, write() 函數可用來輸出準位. 執行後同樣可看到 Arduino 板上的 D13 LED 間隔一秒明滅. 與使用 PySerial 不同的是, 如果要更改應用程式功能, 不需要 (也不可以) 動 Arduino 端韌體 (不須再上傳草稿碼), 全部在電腦端用 Python 程式就可以搞定了.

哇! 這番測試下來發現 Python 這語言真是好用, 甚麼樣功能的函式庫都有高手寫好了, 就怕我們不知道而已. 我在想, 以前 Arduino 要跟電腦互動大都使用 Processing 程式, 有了 PyFirmata 後是否可以用 Python 完全取代 Processing 呢?

參考 :

Arduino:在Windows裡使用Python語言經由Firmata協定控制Arduino開發板
# ARDUINO筆記(十三):使用FIRMATA 協定,ARDUINO 也可以執行 PYTHON

40 則留言 :

半工室 Arduino Python NodeMCU 提到...

請教,利用python獲取氣象局的天氣資料,並且用esp8266將爬到的資料傳給arduino進行控制的可能性有嗎?

小狐狸事務所 提到...

可以的, 利用串列埠 tx/rx 通訊即可. 但不須如此麻煩, EXP8266 有 GPIO 埠可直接進行控制, 只是 3.3V 遇到
5V 系統需使用 LEVEL SHIFTER 轉換位準. ESP-12 模組 GPIO 有 9 支應夠用了.

Unknown 提到...

請問有沒有辦法
把arduino感測器測到的數據透過serial
傳回python程式
保留數據去跟其他參數做比較

小狐狸事務所 提到...

可以的.

廷瑋 提到...

老師謝謝,你的影片我想了很久可是還沒找到答案,的第二個說明,我想讓python, 跟arduino 溝通,arduino 傳給python 文字很順利,python 傳給arduino ,13腳位不閃爍,插上LED才閃,可是亮度不是很理想,謝謝教導

廷瑋 提到...

對不起,我找到答案了謝謝老師

小狐狸事務所 提到...

OK

Unknown 提到...

老師您好,想請問有辦法把arduino 輸出的data傳輸到linux server上做儲存嗎

小狐狸事務所 提到...

需要接網路才行, 例如用 TX/RX 接 ESP8266 上網, 但如果是這樣, 還不如直接用 ESP8266 當微控器, ESP8266 可直接用 Arduino IDE, 參考 :
http://yhhuang1966.blogspot.com/2017/09/arduino-ide-esp8266.html
http://yhhuang1966.blogspot.com/2017/09/arduino-ide-esp8266-led.html

Unknown 提到...

好的,謝謝老師,老師我想請問一下因為原本我預計使用Arduino Uno R3這塊板子透過wifi來實現,相較之下ESP8266會比較適合嗎 謝謝老師

小狐狸事務所 提到...

對, 因為 esp8266/esp32 板子功能都比 arduino 強, 本身就是微控器, 唯一的缺點是 Analog 接腳少, 但可用 pcf8591 模組解決.

Unknown 提到...

了解,謝謝老師您的解說,初步接觸arduino還沒有很熟悉 請見諒,我會再研究看看

小狐狸事務所 提到...

可以先把 arduino 學熟一些再換也不遲

Unknown 提到...

好的 那我應該會先用Uno做實驗,謝謝老師

Unknown 提到...

老師您好,請問該如何在byte裡加入字串格式化%s
ser.write('%s\n'.encode())%option
運算結果為
TypeError: unsupported operand type(s) for %: 'int' and 'str'
字串格式化不適用於byte嗎
還是有其他解決方法?
謝謝老師

小狐狸事務所 提到...

typeerror 表示格式不匹配, 請問 option 是甚麼型別呢

yoyo 提到...
作者已經移除這則留言。
yoyo 提到...

老師你好
想請問
如果用pygame設計小遊戲
然後使用arduino控制它
這是可行的嗎?

小狐狸事務所 提到...

您好, 上面範例都是 PC 端透過 Serial 埠控制 Arduino, 反過來我沒試過耶! 我查一下.

Unknown 提到...

好的 感謝老師!

Unknown 提到...

不好意思 想再請問老師
arduino可以接收得到python傳過去的數據嗎?

小狐狸事務所 提到...

對, 上面的範例就是這樣喔!

Unknown 提到...

謝謝老師

安安 提到...

老師您好若要用Rpi控制Arduino馬達(車),可是這馬達的線不是接到Arduino的角位而是接到了馬達的擴充板(插在Arduino上方類似Arduino擴充板)他連接的方式是透過排線,那這樣怎麼在程式中寫下控制腳位的方法

小狐狸事務所 提到...

Dear 安安, 我的智慧小車上回就是要做這部分就因為忙其他的事而停下來了, 因為還沒實作過所以也不能提供您意見, 殘念 ~~~ 我也希望很快能回來繼續完成這項測試.你可以到圖書館去借楊明豐寫的這本書 :

# ARDUINO自走車最佳入門與應用 : 打造輪型機器人輕鬆學 : 軟硬整合的經典範例, 易學易用的初學指引!
作者 (碁峰)

我前年進行智慧小車製作就是參考這本.

kuan 提到...

老師您好,我現在使用raspberry來控制手臂馬達,在python打code傳到arduino進行控制,這樣有比單單透過python去控制手臂馬達還好嗎?控制馬達的code是要用pwm還是servoangle來寫比較好?謝謝

小狐狸事務所 提到...

您好, 您正在做的實驗也是我很早就想做的, 但至今一直沒時間動手. 雖然沒有足夠經驗, 但我認為不論是直接用樹莓派控制機器手臂還是透過 Arduino, 都必須用驅動板才推得動, 因此我覺得應該直接用樹莓派簡單些. 至於用 pwm 還是 servoangle 我尚未涉獵, 只知道機器手臂應該是用步進伺服馬達控制.

Unknown 提到...

你好,想請問若我想利用python讀取arduino的值傳到MySQL,同時想利用python讀取MySQL值控制arduino腳位的高低電位可以做到嗎?目前我有成功用pyserial將arduino的值讀取到python上且傳入MySQL,但是無法將MySQL的值利用python寫入值傳到arduino上,若單純在arduino的監控視窗做寫入傳送也無法,讀取可以但同時寫入不行的狀況。

小狐狸事務所 提到...

您好, 關於 Python 存取 MySQL 可參考 :
https://yhhuang1966.blogspot.com/2018/05/python-mysql.html

匿名 提到...

請問能python讀取一個道路檢測的影像檔. 讓 arduino 用 sg90 去跟著影像轉動嗎?

小狐狸事務所 提到...

Hi, 雖然沒做過, 但我覺得這應該可以.

匿名 提到...

本來要用raspberry pi 做 但是跑起來影片檔太慢,才想說用python+arduino 去做做看!

小狐狸事務所 提到...

Pi 4 效能應該不錯, 跑影片會很慢嗎?

匿名 提到...

我是用pi4 2g的 跑起來車道影像20秒的影片,pi4跑起來要12分鐘超跑完 ,所以我用sg90根本看不太出來效果

小狐狸事務所 提到...

2G RAM 有點少ㄟ, 我上回幫人架站用 Pi4 8GB 跑伺服器非常順, 但沒跑影像.

kun2917 提到...

老師您好~

我有上網找了很多資料,但是還是不確定我的問題出在哪裡。
於是在網路看見您的教學跟分享。
不知道是否可以詢問問題。若是打擾之處,還請見諒。

我是使用arduino 利用pyfirmata與python控制。
機器手臂會回傳false訊號給ino,再去做其他控制。
我使用D13腳位去接受false訊號。
D13=board.get_pin("d:13:i")
if D13 == True:
*****
我在print(D13)時,總是Fasle/TRUE亂跑。
我後來是使用連續幾次false,才能當作fasle。
===============================
請問老師是否可以給我一個方向或指導。

小狐狸事務所 提到...

嗨, 信號會亂跑可能是雜訊或接觸不良, 可更換接線試試, 或者改用另一個 gpio 接腳. 另外 gpio 最好啟用 pullup/pulldown 電阻避免雜訊干擾.

kun2917 提到...

老師您好~
感謝妳百忙之中,還回覆我!
===================
1、我找pullup/pulldown的資料試試看。
2、我有換過線、也試過使用,使用按鈕,去開啟LED。但是我在讀取這塊也是會這樣。

所以不知道是不是要在讀取的腳位上增加電阻,還是我使用的語法錯誤。
還是說 我不應該用 true/false 當作判斷式
感謝老師

匿名 提到...

老師您好,我使用了由 Python 程式經序列埠傳送資料給 Arduino的程式,但是python端一直出現COM3"存取被拒"的報錯,後來嘗試使用TTL線配合TX和RX傳送,雖然沒有error但是LED燈也不會閃爍。請問有什麼建議的解決方法嗎?謝謝您。
------------------------Arduino程式如下----------------------------------------------
#include
int LED=13;
SoftwareSerial nSerial(15,14);
void setup() {

nSerial.begin(9600);
}

void loop() {
if (nSerial.available() > 0) {
if (nSerial.read()=='H') {digitalWrite(LED, HIGH);}
else {digitalWrite(LED, LOW);}
}
delay(1);
}

小狐狸事務所 提到...

嗨, 程式碼看起來沒甚麼問題, COM 埠存取被拒表示那個埠被其他程式占用了 (例如 Putty)