[TH] List the serial ports connected to the RPi with pySerial.

บทความนี้กล่าวถึงการใช้ไลบรารี pySerial ของภาษาไพธอนบนบอร์ด Raspberry Pi หรือ RPi ทั้งรุ่น 3 และ 4 เพื่อเชื่อมต่อกับพอร์ตสื่อสารอนุกรม (Serial Port) ซึ่งตัวบอร์ดสามารถทำได้ 2 ลักษณะคือ ใช้ฮาร์ดแวร์อย่าง ET-CONV10/RS232 HAT ที่ได้เขียนถึงในหนังสือ กับการใช้พอร์ต USB เชื่อมต่อกับตัวแปลงเป็นพอร์ตสื่อสารอนุกรม (USB to Serial Port) ดังภาพที่ 1 โดยบทความนี้เป็นการใช้แบบที่ 2 เพื่อเรียกใช้ pySerial สำหรับตรวจสอบว่ามีพอร์ตอนุกรมเชื่อมต่ออยู่กี่พอร์ตและชื่ออะไรบ้าง ดังตัวอย่างในภาพที่ 8

ภาพที่ 1 การเชื่อมต่อ ET-USB/RS232 Mini เข้ากับพอร์ต USB ของ RPi

บทความนี้ประกอบด้วย 3 ตอน คือ

  1. List the serial ports connected to the RPi with pySerial.
  2. List the serial ports connected to the RPi with pySerial and PyQt5.
  3. LEDs on/off via PyQt5 and serial communication.

ตรวจสอบการเชื่อมต่อ USB

คำสั่งสำหรับการตรวจสอบว่ามีอุปกรณ์ใดบ้างที่เชื่อมต่อกับพอร์ต USB ของ Raspberry Pi มีรูปแบบของคำสั่งดังนี้

lsusb

เมื่อสั่งงาน lsusb กับ Raspberry Pi ที่ไม่ได้เชื่อมต่ออุปกรณ์เพิ่มเติมใด ๆ จะได้ผลลัพธ์ดังภาพที่ 2

ภาพที่ 2 ตัวอย่างผลลัพธ์ของคำสั่ง lsusb เมื่อไม่ได้เชื่อมต่ออุปกรณ์ใด ๆ

เมื่อเสียบ RS232-to-USB เข้ากับพอร์ต USB ดังภาพที่ 1 และสั่ง lsusb อีกครั้งจะได้้ผลลัพธฺดังภาพที่ 3 ซึ่งแสดงรายการอุปกรณ์เชื่อมต่อ Device 005 เป็น ID 0403:6015 Future Technology Devices International, Ltd Bridge ( I2C/SPI/UART/FIFO ) ซึ่งเป็นโมดูลแปลงสัญญาณ RS232-to-USB รุ่น ET-USB/RS232 MINI

ภาพที่ 3 ตัวอย่างผลลัพธ์จาก lsusb เมื่อเชื่อมต่อ ET-USB/RS232 MINI 

นอกจากนี้ เมื่อเชื่อมต่อโมดูลแปลง USB เป็น RS232 เข้ากับพอร์ต จะได้ชื่อของอุปกรณ์เป็นไดเร็กทอรี เช่น /dev/ttyUSB0 เป็นต้น ดังนั้นเมื่อสังงานด้วยคำสั่งต่อไปนี้ จะได้ผลลัพธ์เป็นดังภาพที่ 4 และ 5 สำหรับกรณีที่ยังไม่ได้เสียบ ET-USB/RS232 MINI เข้ากับ RPi และผลลัพธ์หลังจากเสียบเข้ากับพอร์ต USB เป็นที่เรียบร้อยแล้วตามลำดับ

ls  /dev/ttyUSB*
ภาพที่ 4 ผลลัพธ์กรณีที่ไม่พบ /dev/ttyUSB*
ภาพที่ 5 กรณีที่พบ /dev/ttyUSB*

pySerial

ไลบรารี pySerial เป็นไลบรารีสำหรับเชื่อมต่อพอร์ตสื่อสารอนุกรมของภาษาไพธอน ทำให้สามารถสื่อสารอนุกรมกับอุปกรณ์ภายนอกได้ โดยคุณสมบัติของ pySerial เป็นดังนี้

  • รองรับทุกแพล็ตฟอร์ม
  • สามารถกำหนดคุณสมบัติของพิร์ตสื่อสารอนุกรมได้โดยตรงจากคำสั่งของไลบรารี
  • รองรับข้อมูลหลายขนาด (different byte sizes) รองรับค่าบิตสต็อป รองรับการใช้บิตพาริตี และรองรับการควบคุมทิศทางการทำงานผ่านทางบิต RTS/CTS และ/หรือ Xon/Xoff
  • ทำงานได้ทั้งแบบมีการตั้งหรือไม่ตั้งระยะเวลาไทม์เอาต์ (time out)
  • ใช้คำสั่งรับและส่งเป็นคำสั่งเดียวกับไฟล์ คือ read และ write
  • ไลบรารีเขียนด้วยไพธอน
  • เข้ากันได้กับไลบรารี io
  • พอร์ตถูกตั้งค่าให้รับและส่งข้อมูลแบบไบต์

ติดตั้ง

โดยปกติแล้ว pySerial ติดตั้งเป็นไลบรารีหลักตัวหนึ่งของ Python รุ่น 3 จึงไม่จำเป็นต้องติดตั้ง แต่สามารถสั่งการติดตั้ง pySerial ผ่านทาง pip สั่งงานดังรูปแบบคำสั่งต่อไปนี้

pip install pyserial

สำหรับกรณีที่ต้องการติดตั้งแบบอัพเกรดไลบรารีให้เป็นรุ่นใหม่ให้ใช้คำสั่งต่อไปนี้แทนคำสั่งด้านบน

pip install –upgrade pyserial

หลังจากติดตั้งเสร็จสิ้นหรืออัพเกรดเป็นรุ่นใหม่เป็นที่เรียบร้อยให้ตรวจสอบการใช้งานโดยเข้าโปรแกรม python และนำเข้า serial เพื่อแสดงรุ่นของไลบรารีดังภาพที่ 6

ภาพที่ 6 ตัวอย่างผลลัพธ์ของการตรวจสอบรุ่นของ PySerial

การสร้างวัตถุประเภทสื่อสารอนุกรม

การใช้งานไลบรารี pySerial จะต้องสร้างวัตถุประเภทสื่อสารอนุกรมขึ้นมาโดยรูปแบบของการสร้างวัตถุมีดังนี้

วัตถุ = serial.Serial(port=None, baudrate=9600, bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE, timeout=None, xonxoff=False, rtscts=False, write_timeout=None, dsrdtr=False )

โดยที่

  • port ชื่อของพอร์ตที่ต้องการเชื่อมต่อ ซึ่งมีชื่อเรียกแตกต่างกันไปตามระบบปฏิบัติการ เช่น /dev/ttyUSB0 สำหรับระบบปฏิบัติการ Linux หรือ COM4 บนระบบปฏิบัติการ Windows เป็นต้น
  • baudrate เป็นค่าอัตราการสื่อสารในหน่วย bps (bits-per-second) ซึ่งมีค่าได้ดังนี้
    • 50
    • 75
    • 110
    • 134
    • 150
    • 200
    • 300
    • 600
    • 1200
    • 1800
    • 2400
    • 4800
    • 9600
    • 19200
    • 38400
    • 57600
    • 115200
    • 230400
    • 460800
    • 500000
    • 576000
    • 921600
    • 1000000
    • 1152000
    • 1500000
    • 2000000
    • 2500000
    • 3000000
    • 3500000
    • 4000000
  • bytesize คือ ขนาดของข้อมูล ซึ่งมีค่าได้ดังนี้
    • FIVEBITS
    • SIXBITS
    • SEVENBITS
    • EIGHTBITS
  • parity คือ การเปิดใช้งานการตรวจสอบการรับส่งด้วยการใช้พาริตีบิต ซึ่งสามารถกำหนดค่าได้ดังนี้
    • PARITY_NONE
    • PARITY_EVEN
    • PARITY_ODD
    • PARITY_MARK
    • PARITY_SPACE
  • stopbits คือ จำนวนบิตที่ใช้สำหรับเป็นบิตสิ้นสุด ซึ่งมีค่าได้ดังนี้
    • STOPBITS_ONE
    • STOPBITS_ONE_POINT_FIVE
    • STOPBITS_TWO
  • timeout คือ ค่าตัวเลขทศนิยมที่เป็นหน่วยเวลาของการตรวจสอบการเกิดไทม์เอาต์ของการอ่านข้อมูลจากพอร์ตสื่อสาร ดดยมีลักษณะของค่า 3 แบบ คือ
    • None สำหรับให้รอจนกว่าจะมีข้อมูลเข้า
    • 0 สำหรับการทำงานแบบ non-blocking หรือไม่ต้องรอจนกว่าจะมีข้อมูล นั่นหมายความว่าถ้าพบข้อมูลก็อ่านไม่พบก็ข้ามไปไม่ต้องรอ
    • X สำหรับรอจนกว่าจะพบข้อมูลเป็นเวลา X วินาที
  • xonxoff คือ การเปิดหรือปิดการทำควบคุมการรับส่งข้อมูลด้วยซอฟต์แวร์
  • rtscts คือ การเปิดหรือปิดการทำควบคุมการรับส่งข้อมูลด้วยฮาร์ดแวร์ผ่านทางขา RTS/CTS สำหรับการส่งข้อมูล
  • dsrdtr คือ การเปิดหรือปิดการทำควบคุมการรับส่งข้อมูลด้วยฮาร์ดแวร์ผ่านทางขา DSRDTR สำหรับการรับข้อมูล
  • write_timeout คือ การกำหนดค่าไทม์เอาต์กรณีของการส่งข้อมูล ซึ่งปกติจะทำงานแบบ none-blocking หรือส่งเสร็จไม่ต้องรอ

ค่า exception ที่ได้มีดังนี้

  • ValueError พารามิเตอร์ที่กำหนดมีค่าที่ไม่ถูกต้อง
  • SerialException สำหรับกรณีที่ไม่พบอุปกรณ์ที่กำหนดใน port

การเปิดพอร์ต

การเปิดพอร์ตของวัตถุที่สร้างไว้ สามารถใช้งานตามคำสั่งต่อไปนี้

วัตถุ.open()

ปิดการเชื่อมต่อ

กรณีของการปิดการสื่อสารเมื่อไม่ต้องการใช้งานหรือถือครองพอร์ตเอาไว้ ให้เรียกใช้คำสั่งตามรูปแบบต่อไปนี้

วัตถุ.close()

ตรวจสอบสถานะ

การตรวจสอบว่าพอร์ตที่สร้างขึ้นนั้นถูกเปิดใช้งานไปก่อนหรือไม่สามารถใช้คำสั่งตรวจสอบดังนี้

ผลลัพธ์ = วัตถุ.isOpen()

ตัวอย่างการสร้าง เปิด ปิด และตรวจสอบสถานะเป็นดังภาพที่ 7 ซึ่งจะพบว่า หลังจากมีการสร้างวัตถุไปแล้วจะทำให้การเปิดการเชื่อมต่อนั้นทำงานทันที เมื่อสั่ง open() ซ้ำจะเกิดความผิดพลาด

ภาพที่ 7 ตัวอย่างการเปิด/ปิดและตรวจสอบสถานะของพอร์ต

ส่งข้อมูล

คำสั่งสำหรับการนำออกข้อมูลมีรูปแบบการใช้งานดังนี้

วัตถุ.write( ข้อมูล )

กรณีที่ต้องการนำออกข้อมูลจากบัฟเฟอร์ออกไปทั้งหมดเพื่อให้บัฟเฟอร์ว่างใช้คำสั่งดังนี้

วัตถุ.flushOutput()

รับข้อมูล

การรับข้อมูลเข้าสามารถทำโดยคำสั่งตามรูปแบบต่อไปนี้

ข้อมูล = วัตถุ.read()

ข้อมูล = วัตถุ.read( จำนวนไบต์ )

กรณีที่ต้องการล้างค่าของบัฟเฟอร์นำเข้าให้ใช้คำสั่งต่อไปนี้

วัตถุ.flushInput()

การตรวจสอบการรอเพื่อรับข้อมูลสามารถทำโดยใช้คำสั่ง inWaiting() ดังนี้

จำนวนข้อมูล = วัตถุ.inWaiting()

ตัวอย่างโปรแกรม

ขั้นตอนวิธีของโปรแกรมตัวอย่างเป็นดังนี้

  • สร้างวัตถุ
  • กำหนดชื่อพอร์ตให้กับวัตถุ
  • เปิดพอร์ต
    • ถ้า error แสดงว่าไม่มีพอร์ตนั้น
    • ถ้าผ่านแสดงว่าพบพอร์ตตามชื่อที่เปิด
  • ปิดพอร์ต
  • ถ้ายังไม่ครบทุกชื่อให้เปลี่ยนชื่อ และกลับไป 3

ตัวอย่างโปรแกรมต่อไปนี้เป็นการแสดงรายชื่อพอร์ตอนุกรมที่เชื่อมต่อกับบอร์ด Raspberry Pi ผ่านทางพอร์ต USB และตัวอย่างผลลัพธ์ของโปรแกรมเป็นดังภาพที่ 8

import serial
s = serial.Serial()
portNames = [
    "/dev/ttyUSB0",
    "/dev/ttyUSB1",
    "/dev/ttyUSB2",
    "/dev/ttyUSB3",
    "/dev/ttyACM0",
    "/dev/ttyACM1",
    "/dev/ttyACM2",
    "/dev/ttyACM3"
]
for pname in portNames:
    try:
        s.port = pname
        s.open()
        if s.isOpen():
            print("Found {}.".format(pname))
    except:
        pass
print("End of program.")
ภาพที่ 8 ตัวอย่างผลลัพธ์ของการสแกนพอร์ตสื่อสาร

สรุป

จากบทความนี้จะพบว่าการตรวจสอบว่ามีพอร์ตสื่อสารใดบ้างที่ถูกใช้ในการต่อเชื่อมระหว่างอุปกรณ์กับบอร์ด Raspberry Pi นั้นทำได้ด้วยการกำหนดชื่อพอร์ตที่ต้องการตรวจสอบ เมื่อทำการเปิดใช้งานพอร์ตแล้วเกิดความผิดพลาดย่อมแสดงว่าพอร์ตนั้นไม่ได้เชื่อมต่อ ด้วยหลักการนี้จึงถูกนำมาแปลงเป็นการเขียนโปรแกรมตัวอย่าง นอกจากนี้จะพบว่า ถ้าต้องการนำโปรแกรมนี้ไปใช้งานกับระบบปฏิบัติการอื่น ได้แก่ Microsoft Windows หรือ Apple macOS จะต้องเปลี่ยนชื่อของพอร์ตที่ต้องการค้นหาให้เหมาะสมกันด้วย ดังนี้

  • Windows ใช้ชื่อขึ้นต้นเป็น COM
  • Linux จะใช้ชื่อขึ้นต้นด้วย /dev/ttyUSB และ /dev/ttyACM
  • macOS จะใช้ชื่อขึ้นต้นเป็น /dev/cu.

สุดท้ายนี้ หวังว่าบทความนี้คงมีประโยชน์บ้างไม่มากก็น้อย และขอให้สนุกกับการเขียนโปรแกรมครับ

ท่านใดต้องการพูดคุยสามารคอมเมนท์ได้เลยครับ

แหล่งอ้างอิง

  1. PyPi : pyserial
  2. pythonhost.org: Welcome to pySerial’s documentation.

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-10-26, 2022-01-01