[PyQt] Arduino 센서와 Serial communication 구현 (+ Arduino 고유 이름 지정)

이정찬·2022년 5월 19일
0

PyQt

목록 보기
2/6
post-thumbnail

2021년 8월부터 2021년 11월까지 진행된 PyQt를 이용한 스마트팩토리 외주에서 배운 사항들을 정리하기 위해 작성한 글입니다.
Raspberry Pi 3 환경에 Raspbian OS를 설치하여 진행하였습니다.

1. 시리얼 통신이 뭔가요?

하나의 통신선을 이용하여 데이터를 보내는 단방향 통신을 일컫습니다. 미리 지정한 baudrate(초당 비트 전송량)에 따라 값을 단방향으로 전송하게 됩니다.
이번 외주에서는 Arduino 센서에 미리 1초에 한번씩 serial 포트(해당 외주에서는 USB 포트를 사용하였습니다.)를 통해 값을 보내도록 미리 코딩해 두었습니다.

해당 포스트에 작성하는 센서 외에도 2개의 센서와 더 시리얼 통신을 진행했지만, 여기서는 Arduino nano 33 BLE sense와 USB 유선 통신을 하는 방법만을 서술하겠습니다.

2. Arduino 센서 코드 업로드

// noiseSensor.ino
#include <Arduino_HTS221.h>
#include <Arduino_LSM9DS1.h>
#include <PDM.h>

void setup() {
  Serial.begin(9600);
  while(!Serial);

  PDM.onReceive(onPDMdata);

  if (!PDM.begin(1, 16000)) {
    Serial.println("Failed!");
    while(1);
  }

  if (!HTS.begin()) {
    Serial.println("Failed!");
    while(1);
  }

  if (!IMU.begin()) {
    Serial.println("Failed!");
    while(1);
  }
}

...

void onPDMdata() {
  int bytesAvailable = PDM.available();
  PDM.read(Buffer, bytesAvailable);
  Read = bytesAvailable / 2;
}

Arduino 센서의 setup 부분입니다. Arduino nano 33 BLE sense 센서를 이용하였으므로, 공식 독스를 참고하여, 필요한 header 파일을 설치하고, 센서와의 연결을 진행했습니다.
Arduino nano 33 BLE sense 공식 독스

baudrate는 Serial.begin 함수의 매개변수로 보내주며, 9600으로 지정했습니다. 나중에 python에서 값을 받아올 때에도, 9600으로 지정해야 합니다.

// noiseSensor.ino
void loop() {
  float temperature = HTS.readTemperature();
  float humidity = HTS.readHumidity();
  float x, y, z;
  
  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(x, y, z);

    Serial.print(x);
    Serial.print("\t");
    Serial.print(y);
    Serial.print("\t");
    Serial.print(z);
  }
  Serial.print("\t");
  Serial.print(temperature);
  Serial.print("\t");
  Serial.print(humidity); 
  Serial.print("\t");
  Serial.println(Buffer[0]);
}

해당 ino 파일의 loop 부분입니다. 받아온 값을 탭('\t')을 기준으로 구분하여 계속해서 보낼 것입니다.

이렇게 만들어진 파일을 컴파일 후, 센서에 업로드 해주면 성공적으로 값을 시리얼 통신을 통해 받을 수 있습니다.

3. Arduino 센서 고유 이름 지정

라즈베리파이 보드에 센서를 여러개 꽂다 보면, 꽂을 때마다 센서의 이름이 바뀌는 것을 확인할 수 있습니다. 꽂는 순서에 따라서 /dev 내에 여러 파일 이름이 할당되는데, /ttyUSB0, /ttyUSB1 ... 순서로 이름이 할당됩니다. 한 개의 센서만을 이용한다면 상관이 없겠지만, 이번 외주에서는 세 개의 센서를 이용해야 했기 떄문에 센서별로 고정 이름을 할당하는 것이 필수적이었습니다.

lsusb

위 명령어를 이용하면 여러 줄의 결과가 나오는데, 현재 라즈베리파이에 연결된 여러 장치들의 'Vender ID'와 'Product ID'가 나오게 됩니다.

Bus 001 Device 011: ID 0403:6001 FTDI FT232 USB-Serial(UART) IC

예를 들어, 위와 같은 결과가 나온다면, 0403이 Vender ID, 6001이 Product ID가 됩니다. 동일한 품목의 다른 센서를 꽂아도, 동일한 결과가 나올 것입니다.

이제 이 정보를 가지고, 센서에 고유한 이름을 지정해줍니다.

# /etc/udev/rules.d/99-usb-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="{Vender ID}", ATTRS{idProduct}=="{Product ID}", SYMLINK+="{지정할 이름}"

큰따옴표 내부의 중괄호로 지정된 부분만 바꿔서(중괄호 제거) 99-usb-serial.rules 라는 파일에 저장해두고, /etc/udev/rules.d에 저장해주면, 해당 센서와 동일한 센서들은 언제 뺐다가 꽂아도 내가 지정한 이름으로 라즈베리파이가 인식하게 됩니다.

4. PyQt serial communication

길고 긴 준비과정을 거쳐, 이제 드디어 PyQt에서 값을 받아볼 때입니다. 거창한 준비과정에 비해서, Python 코드는 매우 간단합니다.

#noiseValue.py
import serial
...

class NoiseValue(QtCore.QThread):
    def __init__(self):
    	...
        self.ser = serial.Serial('/dev/microphone', config.baudrate, timeout=1)
    ...

값을 받고자 하는 생성자에 serial 라이브러리를 import(conda 또는 pip로 설치 필요)하고, 미리 지정해둔 Arduino 센서의 고유이름과 Arduino 보드에 업로드해둔 baudrate와 동일한 값(해당 코드에선 9600), 마지막으로 값을 불러올 주기를 timeout 매개변수 초 던위로 전달해주면 serial 통신이 정상적으로 진행됩니다.

#noiseValue.py
def run(self):
    while self.runnable and self.ser.isOpen():
                try:
                    serialValue = self.ser.readline().decode()
                except serial.SerialException:
                    continue

                try:
                    values = serialValue.split('\t')
                    ...

이후, 쓰레드로 실행할 run 함수 내부에서 self.ser을 한줄씩 읽어온 후, decode() 함수를 이용해 디코딩합니다. 상기 Arduino 코드에서 '\t'를 기준으로 데이터를 보내도록 업로드를 해놨기 때문에, 보내는 값들을 한 줄에 한 번에 받아서 split 함수로 나눠서 이용합니다.

센서가 연결되지 않았는데 쓰레드가 실행된다면 프로그램이 강제 종료가 될 우려가 있으므로, serial.SerialException으로 예외처리를 진행해 주었습니다.

5. 마치며

Arduino에 관해서는 처음 공부 해봤던 경험이었습니다. 모르는 용어도 너무 많았고, 애초에 너무 생소한 분야였습니다. 그래도 공식 docs와 python 라이브러리의 도움으로 생각보다 빠르게 개발을 진행했습니다. 그래서 이 외주를 진행한 이후에는 정말 뭐든 구글링과 docs만 있다면 개발할 수 있다는 자신감을 얻을 수 있었습니다.
이후에는 센서 연결과 GUI 로딩, 값 전송이 순차적으로 이루어 질 수 있도록 비동기 처리를 진행했는데, 그것도 추후 포스팅 하도록 하겠습니다.

profile
개발자를 꿈꾸는 사람

0개의 댓글