การสื่อสารผ่านพอร์ทอนุกรม

จาก Theory Wiki
รุ่นแก้ไขเมื่อ 11:21, 2 เมษายน 2565 โดย Chaiporn (คุย | มีส่วนร่วม)
(ต่าง) ←รุ่นแก้ไขก่อนหน้า | รุ่นแก้ไขล่าสุด (ต่าง) | รุ่นแก้ไขถัดไป→ (ต่าง)
ไปยังการนำทาง ไปยังการค้นหา
วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223

แนะนำพอร์ทอนุกรมบนไมโครคอนโทรลเลอร์

ไมโครคอนโทรลเลอร์ส่วนใหญ่ (รวมถึงเบอร์ ATMega168/328 ที่ใช้ในรายวิชา) มีความสามารถในการรับส่งข้อมูลระหว่างอุปกรณ์อื่นและคอมพิวเตอร์ผ่านพอร์ทอนุกรม (serial port) เราจึงสามารถนำคุณสมบัตินี้มาใช้ในการแสดงผลลัพธ์การทำงานของบอร์ดไมโครคอนโทรลเลอร์ที่ให้รายละเอียดมากกว่าการแสดงผลผ่าน LED เพียงอย่างเดียวได้ อีกทั้งสภาพแวดล้อมของ Arduino ยังมีคำสั่งจำพวก Serial.print ที่นำไปใช้ในการส่งข้อความมาแสดงผลบนหน้าจอคอมพิวเตอร์ได้ทันที การสื่อสารแบบอนุกรมนั้นต้องการใช้สายสัญญาณเพียงเส้นเดียวต่อการส่งสัญญาณหนึ่งทิศทาง ดังนั้นการเชื่อมต่ออุปกรณ์เข้าด้วยกันจึงต้องการสายไฟเพียง 3 เส้น เป็นสายสัญญาณสองเส้นเพื่อรับส่งข้อมูลสองทิศทาง และสายอ้างอิงศักย์ไฟฟ้า (หรือกราวนด์) อีกหนึ่งเส้น ตามภาพ

การเชื่อมต่อกับอุปกรณ์อื่นผ่านการสื่อสารแบบอนุกรม

คอมพิวเตอร์รุ่นใหม่โดยเฉพาะอย่างยิ่งคอมพิวเตอร์แบบโน้ตบุ๊กในปัจจุบันมักไม่มีพอร์ทอนุกรมติดมาให้ จึงต้องอาศัยอุปกรณ์เสริมที่เรียกว่า USB to serial adapter หรือ USB-Serial dongle ที่เพิ่มพอร์ทอนุกรมให้กับคอมพิวเตอร์ ตามมาตรฐานการสื่อสารผ่านพอร์ทอนุกรมนั้นมีการใช้หัวเชื่อมต่อแบบ DE-9 ที่มีลักษณะดังภาพ

อะแดปเตอร์ USB-Serial ที่มีหัวเชื่อมต่อแบบ DE-9 ตัวผู้

ในรายวิชา 01204223 เราจะใช้อะแดปเตอร์ USB-Serial ชนิดที่รับข้อมูลจากพอร์ท USB แล้วแปลงเป็นสัญญาณแบบ TTL โดยตรง ทำให้นำไปใช้เชื่อมต่อกับบอร์ดไมโครคอนโทรลเลอร์ได้ทันทีโดยไม่ต้องผ่านหัวเชื่อมต่อแบบ DE-9 ดังภาพ

USB-Serial Dongle ที่ใช้ในรายวิชา

การติดตั้งไดรเวอร์สำหรับอะแดปเตอร์

เครื่องที่ใช้ระบบปฏิบัติการลินุกซ์จะมีไดรเวอร์สำหรับอะแดปเตอร์ติดตั้งมาให้พร้อมใช้งานอยู่แล้ว สำหรับระบบปฏิบัติการอื่น ๆ ให้ดาวน์โหลดและติดตั้งไดรเวอร์จากเว็บ http://www.silabs.com/products/mcu/pages/usbtouartbridgevcpdrivers.aspx

การเชื่อมอะแดปเตอร์เข้ากับบอร์ดไมโครคอนโทรลเลอร์

ตามหลักการแล้วการเชื่อมต่ออุปกรณ์สองชิ้นเข้าด้วยกันผ่านการสื่อสารแบบอนุกรมนั้นทำได้โดยการเชื่อมขา TX (Transmit) เข้ากับขา RX (Receive) ของอุปกรณ์ตรงข้ามให้ครบทั้งสองทิศทางดังรูปด้านบน กรณีที่เราต้องการส่งข้อมูลจากบอร์ดไมโครคอนโทรลเลอร์ไปยังคอมพิวเตอร์เพียงทิศทางเดียวนั้นจะใช้สายเชื่อมเพียงสองเส้น คือ

  • ขา Tx จากบอร์ดไมโครคอนโทรลเลอร์ (ขาเดียวกับ PD1) เชื่อมกับขา Rx ของอะแดปเตอร์
  • ขา GND จากบอร์ดไมโครคอนโทรลเลอร์ เชือมกับขา GND ของอะแดปเตอร์

สังเกตว่าขา Tx และ Rx ของไมโครคอนโทรลเลอร์เป็นขาเดียวกับ PD1 และ PD0 ตามลำดับ จึงต้องมีการบัดกรีคอนเน็คเตอร์แบบ 5x2 ลงไปที่ตำแหน่งพอร์ท D ก่อนจึงจะใช้งานได้

ผังภาพการเชื่อมต่อบอร์ดไมโครคอนโทรลเลอร์เข้ากับอะแดปเตอร์ USB-Serial (รุ่นสีดำ/น้ำเงิน)

อย่างไรก็ตาม อะแดปเตอร์รุ่นสีแดงบางรุ่นจะมีการใช้ขา Tx เป็นขารับสัญญาณ จึงต้องต่อขา Tx (PD1) จากบอร์ดไมโครคอนโทรลเลอร์เข้ากับขา Tx ของอะแดปเตอร์แทน ดังรูป

ผังภาพการเชื่อมต่อบอร์ดไมโครคอนโทรลเลอร์เข้ากับอะแดปเตอร์ USB-Serial (รุ่นสีแดง)

จากนั้นเสียบทั้งบอร์ดไมโครคอนโทรลเลอร์และอะแดปเตอร์ USB-Serial เข้ากับพอร์ท USB ของคอมพิวเตอร์ดังภาพ

ตัวอย่างการเชื่อมต่อบอร์ดไมโครคอนโทรลเลอร์เข้ากับอะแดปเตอร์ USB-Serial

โค้ดเฟิร์มแวร์สำหรับส่งข้อมูลสู่พอร์ทอนุกรม

อุปกรณ์สองฝั่งของการเชื่อมต่อจะสามารถสื่อสารกันได้นั้นต้องมีการกำหนดความเร็วในการรับส่งข้อมูลให้สอดคล้องกัน ในงานทั่วไปที่ไม่ต้องการการแลกเปลี่ยนข้อมูลในปริมาณมากมักจะมีการกำหนดความเร็วไว้ที่ 9,600 บิตต่อวินาที เฟิร์มแวร์ที่พัฒนาด้วยภาษาซีตรง ๆ ต้องมีการตั้งค่าให้กับรีจีสเตอร์ที่เกี่ยวข้องได้แก่ UBRR0H, UBRR0L และ UCSR0C แล้วจึงค่อยป้อนข้อมูลที่จะส่งทีละไบท์ให้กับรีจีสเตอร์ UDR0 รายละเอียดสามารถดูเพิ่มเติมได้จากดาต้าชีตของไมโครคอนโทรลเลอร์เบอร์ ATMega168

เพื่อความสะดวกผู้สอนได้เตรียมโค้ดเบื้องต้นเอาไว้สำหรับส่งข้อความออกทางพอร์ท UART ด้วยฟังก์ชัน printf() ดาวน์โหลดโค้ดได้จากลิ้งค์ https://www.cpe.ku.ac.th/~cpj/204223/avr-serial.zip แตกไฟล์และนำไฟล์ serial.c มาคอมไพล์ร่วมกับโค้ดเฟิร์มแวร์ โค้ดตัวอย่างด้านล่างจะส่งข้อความ Hello, Serial ออกไปยังพอร์ทอนุกรมพร้อมกับ LED สีเขียวบนบอร์ดหลักกระพริบทุก ๆ ครึ่งวินาที

#include <avr/io.h>
#include <util/delay.h>

#include "serial.h"
 
int main()
{
  serial_init();
  DDRD |= (1<<PD3);  // ให้ PD3 เป็นเอาท์พุท
 
  for (;;)
  {
    printf("Hello, Serial\n");
    PORTD ^= (1<<PD3);   // ใช้ตัวดำเนินการ XOR เพื่อสลับลอจิกของขา PD3
    _delay_ms(500);
  }
}

กรณีที่พัฒนาเฟิร์มแวร์ด้วย Arduino เราสามารถเรียกใช้คำสั่ง Serial.begin ในการตั้งอัตราการส่งข้อมูล และคำสั่ง Serial.print เพื่อพิมพ์ข้อความที่ต้องการออกทางพอร์ท UART ตัวอย่างด้านล่างให้พฤติกรรมเดียวกับกับโค้ดภาษาซีข้างต้น

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

void loop()
{
  Serial.println("Hello, Serial");
  PORTD ^= (1<<PD3);  // ใช้ตัวดำเนินการ XOR เพื่อสลับลอจิกของขา PD3
  delay(500);
}

ทดลองคอมไพล์และอัพโหลดเฟิร์มแวร์เข้าสู่ไมโครคอนโทรลเลอร์ เชื่อมสายสัญญาณจากบอร์ดไมโครคอนโทรลเลอร์เข้ากับอะแดปเตอร์ USB-Serial และเสียบอะแดปเตอร์เข้ากับพอร์ท USB ของเครื่องคอมพิวเตอร์ จากนั้นเลือกเมนู Tools → Serial Port และเลือกชื่อพอร์ทที่ปรากฏขึ้นมา (บนลินุกซ์มักปรากฏเป็นชื่อ /dev/ttyUSB0)

การเลือกพอร์ทอนุกรมที่ต้องการเชื่อมต่อใน Arduino IDE

เลือกเมนู Tools → Serial Monitor ดังแสดง

การเปิดโปรแกรม Serial Monitor

ให้แน่ใจว่าอัตราการส่งข้อมูลถูกตั้งไว้ที่ 9600 baud (หมายถึง 9600 บิตต่อวินาที) หน้าจอ Serial Monitor ควรปรากฏผลลัพธ์เป็นข้อความ Hello, Serial ทุก ๆ ครึ่งวินาที พร้อมกับ LED สีเขียวบนบอร์ดหลักกระพริบเป็นจังหวะเดียวกัน

ผลลัพธ์จากหน้าจอของ Serial Monitor

ปัญหาที่อาจพบ

  • Arduino IDE บน Ubuntu ไม่แสดงรายการพอร์ทอนุกรมในเมนู Tools → Serial Port
สาเหตุมักเกิดจากผู้ใช้ไม่ได้รับสิทธิ์ในการเข้าใช้พอร์ทอนุกรม แก้ไขโดยการระบุให้บัญชีผู้ใช้อยู่ในกลุ่ม dialout โดยใช้คำสั่ง
sudo adduser <ชื่อบัญชี> dialout
จากนั้นให้ล็อกเอาท์และล็อกอินกลับเข้ามาใหม่
  • Arduino IDE บน MAC OS X ไม่แสดงรายการพอร์ทอนุกรมในเมนู Tools → Serial Port
ติดตั้งไดรเวอร์สำหรับอะแดปเตอร์ USB-serial จากเว็บ http://www.silabs.com/products/mcu/pages/usbtouartbridgevcpdrivers.aspx
  • สายไฟที่ใช้เชื่อมบอร์ดไมโครคอนโทรลเลอร์กับอะแดปเตอร์ยาวไม่พอ เนื่องจากพอร์ท USB อยู่ห่างกันเกินไป
มีทางเลือกในการแก้ปัญหาดังนี้
  • ใช้สาย USB ที่ปลายด้านหนึ่งเป็นตัวผู้และอีกปลายเป็นตัวเมียเพื่อช่วยเสริมความยาว
  • ใช้อุปกรณ์ USB hub
  • ในกรณีฉุกเฉินให้ถอดบอร์ดไมโครคอนโทรลเลอร์ออกหลังจากอัพโหลดเฟิร์มแวร์ แล้วเสียบอะแดปเตอร์แทนที่ในพอร์ท USB เดียวกัน จากนั้นป้อนไฟเลี้ยงให้กับบอร์ดไมโครคอนโทรลเลอร์ผ่านทางอะแดปเตอร์ USB-serial โดยเชื่อมขา VCC ของอุปกรณ์ทั้งคู่เข้าด้วยกัน ถอดอะแดปเตอร์ออกและเสียบบอร์ดไมโครคอนโทรลเลอร์กลับที่เดิมเมื่อต้องการอัพโหลดเฟิร์มแวร์ใหม่
การจ่ายไฟเลี้ยงให้กับบอร์ดไมโครคอนโทรลเลอร์ผ่านอะแดปเตอร์ USB-serial

โปรแกรมทางฝั่งคอมพิวเตอร์

โปรแกรมที่ใช้รับข้อมูลผ่านพอร์ทอนุกรมทางฝั่งคอมพิวเตอร์สามารถเขียนขึ้นได้จากภาษาใดก็ได้ที่มีไลบรารีสำหรับเชื่อมต่อกับพอร์ทอนุกรม ในวิกินี้จะยกตัวอย่างสองภาษาคือภาษาจาวาและภาษาไพทอน

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

ภาษาจาวาไม่ได้มีไลบรารีสำหรับสื่อสารกับพอร์ทอนุกรมมาให้ตั้งแต่แรก จึงต้องมีการติดตั้งเพิ่มเติม ไลบรารีที่นิยมใช้กันได้แก่ RXTX โค้ดตัวอย่างด้านล่างรอรับข้อมูลจากพอร์ทอนุกรมที่ระบุไ้ว้ในคอมมานด์ไลน์อาร์กิวเมนต์ และพิมพ์อักขระที่รับมาได้ทั้งหมดออกมาทางหน้าจอ

import java.io.InputStream;
import gnu.io.*;
 
public class SerialMonitor {
 
    public static void main(String[] args) throws Exception
    {
        if (args.length != 1)
        {
            System.err.println("Usage: java SerialMonitor <serial-dev>");
            System.exit(1);
        }

        String dev = args[0];

        System.out.println("Monitoring serial stream on " + dev);
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(dev);
        if (portIdentifier.isCurrentlyOwned()) 
        {
            System.out.println( "Error: Port is currently in use" );
        }
        else
        {
            int timeout = 2000;
            CommPort commPort = portIdentifier.open("serial",timeout);
 
            if (commPort instanceof SerialPort) 
            {
                SerialPort serialPort = (SerialPort) commPort;
                serialPort.setSerialPortParams(9600,
                        SerialPort.DATABITS_8,
                        SerialPort.STOPBITS_1,
                        SerialPort.PARITY_NONE );
 
                InputStream in = serialPort.getInputStream();
                while (true) 
                {
                    int data = in.read();
                    if (data < 0) break;
                    System.out.print((char)data);
                }
            }
        }
    }
}

ศึกษาขั้นตอนการติดตั้งไลบรารี RXTX ได้จากวิกิ การติดตั้งไลบรารี RXTX

ตัวอย่างโปรแกรมภาษาไพทอน

เช่นเดียวกับภาษาจาวา ภาษาไพทอนไม่ได้มีไลบรารีสำหรับสื่อสารกับพอร์ทอนุกรมมาให้เช่นกัน ใช้คำสั่ง apt-get หรือ pip เพื่อติดตั้งไลบรารี pyserial ดังนี้

sudo apt-get install python-serial

หรือ

sudo pip install pyserial

โค้ดตัวอย่างด้านล่าง (สำหรับไพทอน 3.x) รอรับข้อมูลจากพอร์ทอนุกรมที่ระบุผ่านคอมมานด์ไลน์อาร์กิวเมนต์ และพิมพ์อักขระที่รับมาได้ทั้งหมดออกมาทางหน้าจอ

import sys
import serial
 
if len(sys.argv) != 2:
    print("Usage: %s <serial-dev>" % sys.argv[0], file=sys.stderr)
    exit(1)

dev = sys.argv[1]
print("Monitoring serial input stream on %s" % dev)
ser = serial.Serial(dev)
while True:
    data = ser.read()
    print(data.decode("utf8"),end="")
    sys.stdout.flush()   # force output to display

ข้อมูลเพิ่มเติม