使用Python的pySerial模組進行序列通訊:連接電腦與Arduino和MicroPython

連接ESP8266控制板和電腦的方式有兩種:

  • Wi-Fi無線網路:把ESP8266當作感測器網路前端設備,傳送資料到網站伺服器;或者當作網站伺服器,提供API讓前端網頁取得感測數據。
  • 序列通訊埠:透過USB序列埠跟電腦或者手機連線。

使用Wi-Fi連線顯然比較符合ESP8266的天性,但是對於沒有內建Wi-Fi的MicroPython控制板或者Arduino板,USB是最普遍的選擇。本文將介紹使用Python程式與Arduino和MicroPython(ESP8266板)進行序列通訊的方法。

USB序列連接ESP8266

使用pySerial進行序列通訊

Python的序列埠通訊套件叫做pySerial,請在終端機透過pip進行安裝:

安裝pySerial

pySerial提供初始化序列埠、傳送和接收序列數據的指令,像read(), readline()和write()基本指令名稱和語法,跟MicroPython的UART模組一樣。一開始,我們先從Arduino傳送序列資料給Python接收,來認識pySerial模組的用法。

底下是每隔一秒傳送“hello!”訊息的Arduino程式:

unsigned long previousMillis = 0;
const long interval = 1000;    // 設定間隔時間,1000ms
char chr;

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

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    Serial.println("hello!");
  }
}

底下是採用pySerial模組接收序列資料的Python程式碼,它將不停地接收與顯示收到的序列資料,直到你按下Ctrl和C鍵為止。筆者假設Arduino所在的序列埠名稱是COM6,請自行修改。macOS系統的通訊埠名稱類似這樣:/dev/tty.usbmodem231

import serial  # 引用pySerial模組

COM_PORT = 'COM6'    # 指定通訊埠名稱
BAUD_RATES = 9600    # 設定傳輸速率
ser = serial.Serial(COM_PORT, BAUD_RATES)   # 初始化序列通訊埠

try:
    while True:
        while ser.in_waiting:          # 若收到序列資料…
            data_raw = ser.readline()  # 讀取一行
            data = data_raw.decode()   # 用預設的UTF-8解碼
            print('接收到的原始資料:', data_raw)
            print('接收到的資料:', data)

except KeyboardInterrupt:
    ser.close()    # 清除序列通訊物件
    print('再見!')

連接Arduino,再執行上面的Python程式,將能收到來自Arduino的訊息。原始資料是byte格式

終端機接收Arduino序列訊息

使用Python序列程式控制Arduino或MicroPython板的LED

本單元的Python程式將在終端機顯示:「按1開燈、按2關燈、按e關閉程式」;若用戶按下1,Python將透過序列埠傳遞代表開燈的b’LED_ON\n’訊息;若按下2,則送出代表開燈的b’LED_OFF\n’訊息,這兩個訊息都是筆者自訂的,訊息內容必須是byte格式。

電腦端的Python序列通訊程式碼如下:

import serial
from time import sleep
import sys

COM_PORT = 'COM8'  # 請自行修改序列埠名稱
BAUD_RATES = 9600
ser = serial.Serial(COM_PORT, BAUD_RATES)

try:
    while True:
        # 接收用戶的輸入值並轉成小寫
        choice = input('按1開燈、按2關燈、按e關閉程式  ').lower()

        if choice == '1':
            print('傳送開燈指令')
            ser.write(b'LED_ON\n')  # 訊息必須是位元組類型
            sleep(0.5)              # 暫停0.5秒,再執行底下接收回應訊息的迴圈
        elif choice == '2':
            print('傳送關燈指令')
            ser.write(b'LED_OFF\n')
            sleep(0.5)
        elif choice == 'e':
            ser.close()
            print('再見!')
            sys.exit()
        else:
            print('指令錯誤…')

        while ser.in_waiting:
            mcu_feedback = ser.readline().decode()  # 接收回應訊息並解碼
            print('控制板回應:', mcu_feedback)
            
except KeyboardInterrupt:
    ser.close()
    print('再見!')

使用Arduino Uno進行序列通訊

接收來自電腦Python的序列訊息,開、關第13腳的LED的Arduino程式碼如下:

#define LED 13
String str;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    // 讀取傳入的字串直到"\n"結尾
    str = Serial.readStringUntil('\n');

    if (str == "LED_ON") {           // 若字串值是 "LED_ON" 開燈
        digitalWrite(LED, HIGH);     // 開燈
        Serial.println("LED is ON"); // 回應訊息給電腦
    } else if (str == "LED_OFF") {
        digitalWrite(LED, LOW);
        Serial.println("LED is OFF");
    }
  }
}

使用MicroPython(Wemos D1 mini板)進行序列通訊

底下是在ESP8266控制板(D1 mini)執行microPython,接收序列訊息的程式:

from machine import Pin
from machine import UART

com = UART(0, 9600)
com.init(9600)

led = Pin(2, Pin.OUT, value=1)

print('MicroPython Ready...')

while True:
    choice = com.readline()
    
    if choice == b'LED_ON\n':
        led.value(0)
        com.write(b'LED is ON!\n')  # 回應訊息給電腦端的Python
    elif choice == b'LED_OFF\n':
        led.value(1)
        com.write(b'LED is OFF!\n')

筆者將此程式命名成ser_test.py檔,再使用ampy工具上傳到D1 mini板。上傳完畢後,請先按一下D1 mini板的Reset(重置)鍵。

接著使用WebREPL連線到D1 mini板,透過import ser_test執行序列通訊測試程式,相關操作說明請參閱《超圖解 Python 物聯網實作入門》第七章「序列埠通信」7-21頁。

WebREPL介面

執行此Python序列通訊程式的操作畫面如下:

Python序列通訊程式的操作畫面

Posts created 470

7 thoughts on “使用Python的pySerial模組進行序列通訊:連接電腦與Arduino和MicroPython

  1. 請問一下,我在這個程式中加入split()
    希望能夠控制碼能夠更加多元
    但是他都沒反應,但我用putty進入到micropython中測試split()這個函式
    可是卻是可以用的,不知道哪邊有錯呢?

    a.py
    import serial
    from time import sleep
    import sys

    COM_PORT = ‘COM3’ # 請自行修改序列埠名稱
    BAUD_RATES = 9600
    ser = serial.Serial(COM_PORT, BAUD_RATES)

    try:
    while True:
    # 接收用戶的輸入值並轉成小寫
    choice = input(‘按1開燈、按2關燈、按e關閉程式 ‘).lower()

    if choice == ‘1’:
    print(‘傳送開燈指令’)
    ser.write(b’LED_ON,2\n’) # 訊息必須是位元組類型
    sleep(2) # 暫停0.5秒,再執行底下接收回應訊息的迴圈
    elif choice == ‘2’:
    print(‘傳送關燈指令’)
    ser.write(b’LED_OFF,1\n’)
    sleep(2)
    elif choice == ‘e’:
    ser.close()
    print(‘再見!’)
    sys.exit()
    else:
    print(‘指令錯誤…’)

    except KeyboardInterrupt:
    ser.close()
    print(‘再見!’)

    main1.py
    from machine import Pin
    from machine import UART

    com = UART(0, 9600)
    com.init(9600)

    led = Pin(2, Pin.OUT, value=1)

    print(‘MicroPython Ready…’)

    while True:
    choice = com.readline()
    choice1=choice.split(b’,’)
    if choice1[0] == b’LED_ON’:
    led.value(0)
    elif choice1[0] == b’LED_OFF’:
    led.value(1)

    1. 可以,5-26有split()的語法示範,7-25頁的GPS資料解析也有用到split()。應該是你忘記考量’\n’字元,請嘗試解碼後再分割:

      choice, num = com.readline().split(b’\n’)[0].decode().split(‘,’)

      thanks,
      jeffrey

    2. 我有考量到\n
      因為他在後面
      所以 並不影響choice1[0]
      同樣的方式
      a=b’LED_ON,22,3\n’
      b=a.split(b’,’)
      if b[0]==b’LED_ON’:
      print(’12’)
      在putty是可行的
      但是 如果用在老師提供的方法執行 卻是不可行
      甚至是加入decode我都試過 但都不行
      所以我在想 micropython可能有bug

    3. 我剛剛實機測試了一下,發現是因為缺少判斷傳入值為None的情況:

      while True:
          ser_in = com.readline()
          if ser_in is not None:
              # 假設傳入值格式為:b'LED_ON,12'
              choice, num = ser_in.split(b'\n')[0].decode().split(',')
          else:
              continue
          
          if choice == 'LED_ON':
              led.value(0)
              msg = 'LED is ON, value=' + num + '\n'
              com.write(msg.encode())
          elif choice == 'LED_OFF':
              led.value(1)
              msg = 'LED is OFF, value=' + num + '\n'
              com.write(msg.encode())
      

      thanks,
      jeffrey

  2. 老師您好!請問一下ser.write(b’LED_ON\n’) 中的LED_ON\n,我想要改成變數形式改如何做?謝謝老師

  3. 想請問在read,write之間有什麼常見的問題嗎?
    我在micropython on esp32做了以下測試後,渴望您的見解
    1.板子:com.write(b”xxx”) -> 電腦:ser.readline().decode()
    這是成功的
    但反向時
    2.電腦:ser.write(b”xxx”) ->板子:com.readline()
    收不到資料,都是None

    但是,不用readline()接收,空的main,單純開webrepl,在>>>列上是有收到ser.write()過來的資料的…
    感激不盡

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top