강좌 전체보기

.

.

시리얼 통신을 이용한 센서장치 – 라즈베리파이 연결

아두이노를 다뤄보신 분은 시리얼(Serial, UART) 통신이 익숙하실 겁니다. 아두이노가 PC와 데이터를 주고 받기 위해 사용하는 통신 방법이 시리얼 통신이라 디버깅 등의 목적으로 시리얼 모니터를 빈번히 사용하니까요. 물론 PC 측에서는 USB를 사용하기 때문에 중간에 시리얼-USB를 변환해주는 칩이 하나가 필요합니다. 이 칩을 보통 USB to UART 또는 FTDI 라고 부르는데 아두이노 그리고 우리가 실습에 사용할 ESP32 Dev 보드는 이 칩을 내부에 포함하고 있습니다. 그래서 USB 케이블만 PC에 연결하면 바로 사용할 수 있습니다.

라즈베리파이에 연결할 때도 USB 케이블을 이용해 USB 포트에 연결해 사용할 수 있습니다. 하지만 라즈베리파이에는 시리얼 통신용 핀이 이미 할당되어 있기 때문에 이걸 이용하면 FTDI 모듈 없이 통신을 할 수 있습니다.

이번 파트에서는 아두이노 보드(센서장치)를 라즈베리파이(서버)의 시리얼 통신 핀과 연결해서 두 보드 간 데이터 송수신이 가능하도록 만들어 보겠습니다.

먼저 라즈베리파이와 아두이노를 아래와 같이 연결합니다.

  

  • 아두이노 –> 라즈베리파이
  • D2 –> TX (GPIO14)
  • D3 –> RX (GPIO15)

아두이노는 시리얼 통신을 위해 필요한 TX/RX(전송/수신) 핀이 D0, D1 핀에 할당되어 있습니다. 하지만 이 핀은 PC와 통신을 하는데 사용해야 하기 때문에, 디지털 핀 2개를 이용해 시리얼 통신이 가능하도록 해주는 SoftwareSerial 이란 방법을 사용합니다. 여기서는 SoftwareSerial 핀으로 D2(RX), D3(TX) 핀을 사용하므로, 각각 라즈베리파이의 TX-RX 핀으로 연결해주면 됩니다. 시리얼 통신은 송수신 핀인 TX, RX 핀이 서로 엇갈리도록 연결해야 합니다.

연결은 이걸로 끝입니다. 아두이노에는 아래 코드를 올립니다.

#include <SoftwareSerial.h>

SoftwareSerial swSerial(2, 3);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  swSerial.begin(9600);

  Serial.println("Hello~!");
}

void loop() {
  // put your main code here, to run repeatedly:
  if (swSerial.available()) {
    Serial.write(swSerial.read());
  }
  if (Serial.available()) {
    swSerial.write(Serial.read());
  }
}

.

Serial 클래스는 USB 로 연결된 PC와의 통신을 의미합니다. swSerial 은 D2/D3로 연결된 라즈베리파이와의 시리얼 통신을 의미합니다. 따라서 위 코드는 PC에서 받은 데이터가 있으면, 1 바이트를 읽어서, 라즈베리파이로 보내고 마찬가지의 작업을 반대로도 한다는 의미입니다.

  • Serial.available() –> PC에서 받은 데이터가 있는지 시리얼 버퍼를 확인합니다. 받은 데이터가 있다면 0보다 큰 값이 리턴됩니다.
  • Serial.read() –> PC에서 시리얼 통신으로 받은 데이터 중 1 바이트를 읽어옵니다.
  • swSerial.write() –> 데이터를 SoftwareSerial로 연결된 라즈베리파이에 전송합니다.
  • 반대 방향으로도 같은 패턴의 작업을 해서 [라즈베리파이 — 아두이노 — PC] 간에 데이터를 주고 받도록 해준겁니다.

아두이노는 준비가 끝났습니다. 스케치 업로드 후 시리얼 모니터를 켜두면 됩니다.

라즈베리파이에서 시리얼 통신을 사용하기 위해서는 사전에 설정해야 할게 있습니다.

  • sudo apt-get install python-serial
    ==> 파이썬에서 시리얼 통신을 제어하기 위한 라이브러리
  • sudo nano config.txt
    enable_uart = 0 ==> enable_uart = 1 로 변경

ttyAMA0 를 삭제합니다.

커널 로그 출력 포트 삭제합니다.

  • sudo systemctl stop serial-getty@ttyAMA0.service
  • sudo systemctl disable serial-getty@ttyAMA0.service

재부팅

  • sudo reboot

이제 라즈베리파이 임의의 디렉터리에 아래 파이썬 코드를 올립니다.

그리고 아래 명령으로 실행하면 됩니다. (execute 권한이 없는 경우 두 파일에 실행 권한을 주세요.)

  • sudo python3 serialcomm.py

라즈베리파이에서 파이썬 파일을 실행하면 시리얼 통신으로 들어오는 데이터를 감지하는 SerialThread 가 실행됩니다. 아두이노에서 데이터를 받으면 해당 문자열을 출력하고 다시 아두이노로 돌려보내줍니다.

정상적으로 데이터를 주고 받는지 확인하기 위해 아두이노 – 시리얼모니터에 문자열을 입력해보세요. 입력한 문자열이 라즈베리파이에도 전달되고, 입력한 문자 그대로 다시 수신해서 보여집니다. 그리고 5초 간격으로 hello 문자열도 계속 수신이 될겁니다.

라즈베리파이에서 Serial 통신을 파이썬으로 제어하는 코드를 살펴보겠습니다. 시리얼 통신으로 들어오는 데이터를 모니터링 하는, 가장 중요한 클래스를 포함한 SerialThread.py 를 보면 됩니다.

# Serial
BAUDRATE=9600
......
class SerialThread(threading.Thread):
    def __init__(self):
        ......
    def connect(self):
        ......
        try:
            self.ser = serial.Serial('/dev/ttyS0', baudrate=BAUDRATE,
                                    parity=serial.PARITY_NONE,
                                    stopbits=serial.STOPBITS_ONE,
                                    bytesize=serial.EIGHTBITS)
            print('\tPort ttyS0 is opened')
        except serial.SerialException as ex:
            print(ex)

    def run(self):
        ......
        while True:
            try:
                # HW UART monitoring
                while self.ser is not None:
                    data = self.ser.read(self.ser.in_waiting or 1)    # Read bytes
                    if data:
                        print("received: " + str(data, 'utf-8'))
                        self.send(data)

            except Exception as e:
                ......

    def send(self, byte_array):
        self.ser.write(byte_array)
        pass

    def close(self):
        ......

.

SerialThread 인스턴스를 생성하면 가장 먼저 connect() 함수를 호출해줘야 합니다. 그래야 시리얼 포트(ttyS0)를 열 수 있습니다. 여기서 BAUDRATE 값을 주의하세요. BAUDRATE 는 통신 속도라 보면 됩니다. 아두이노에서 9600 을 사용했기 때문에 여기서도 9600을 사용합니다.

.

    def connect(self):
        ......
        try:
            self.ser = serial.Serial('/dev/ttyS0', baudrate=BAUDRATE,
                                    parity=serial.PARITY_NONE,
                                    stopbits=serial.STOPBITS_ONE,
                                    bytesize=serial.EIGHTBITS)

.

ttyS0 (시리얼 포트)가 정상적으로 열리면 파이썬 코드 실행 시작점인 serialcomm.py 에서 SerialThread를 start() 하게 됩니다. 그러면 run() 함수의 while() 반복문이 무한루프를 돕니다. 여기서 ser.read() 함수를 이용해 데이터 들어온게 있는지 확인하고, 들어온 데이터가 있으면 콘솔에 출력한 뒤, send() 함수를 이용해 받은 데이터를 아두이노에게 돌려줍니다.

    def run(self):
        ......
        while True:
            try:
                # HW UART monitoring
                while self.ser is not None:
                    data = self.ser.read(self.ser.in_waiting or 1)    # Read bytes
                    if data:
                        print("received: " + str(data, 'utf-8'))
                        self.send(data)

.

파이썬 코드 실행 시작점인 serialcomm.py 에서는 SerialThread 를 생성, 실행하고 5초 간격으로 “hello” 메시지를 전송하도록 되어 있습니다.

......
def send_msg():
    t_ser.send(bytes("hello".encode()))
    t_timer = threading.Timer(5.0, send_msg)
    t_timer.start()

# Connect serial
t_ser = SerialThread()
t_ser.connect()

# Start main loop
if __name__ == '__main__':
    try:
        # Start serial monitor thread
        t_ser.setDaemon(True)
        t_ser.start()
        # Start serial TX timer
        send_msg()

        # Main loop
        while True:
            continue

    except KeyboardInterrupt:
        print('    KeyboardInterrupt!!! Ending main loop')
        # Quit gracefully
        finalize()
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)

.

다음 강좌에서는 시리얼- USB 통신을 이용해서 센서장치(아두이노)와 모바일 폰(안드로이드)을 연결해 보겠습니다.

.

강좌 전체보기

.