Python/PySerial, PyModbus

PWM 주파수 조절발생기 SY-LD213과 라즈베리파이 시리얼 통신하기

Juhyuck 2023. 4. 26. 17:34
728x90

라즈베리파이와 주파수 조절 발생기 SY-LD213를 시리얼 통신으로 연결하기

 

SY-LD213은 디바이스마트에서 파는 PWM 주파수 조절발생기이다.

고가의 function generator를 쓸 필요까지 없는 간단한 PWM 신호가 필요할 때 요긴한데, 우선 저렴해서 좋다.

aliexpress에도 비슷한 종류의 제품이 많은데, 우선 바로 사서 해볼 수 있는 디바이스마트에서 구매했다. 가격차이도 별로 안나고.

 

SY-LD213

 

전압 입력, VIN+에 라즈베리파이에서 공급 전원을 넣어주고, VIN- 에는 라즈베리파이의 그라운드 핀을 연결해주면 된다.  나는 라즈베리파이 전원공급을 파워서플라이를 통해서 했기 때문에 라즈베리파이 power 핀과 ground를 서로 연결했다.

 

 

핀 번호로는,

4: 5V PWR

6: GND

8: UART0 TX

10: UART0 RX

 

GPIO 번호로는,

GPIO14: TXD0

GPIO15: RXD0

 

핀 번호 정보는 터미널에서 pinout이라고 치면 예쁘게 나온다.

라즈베리파이 버전이나 기타 정보도 있으니 참조할 수도 있고.

더 자세한 정보는 pinout.xyz를 참조

이제 PWM 주파수 조절발생기...로 불리는 PWM generator... SY-LD213과 통신을 해보자.

 

의외로 간단한 통신이라 적당한 글을 못찾아서 이 글을 쓰는 것이기도 하고.. 여러 글 중에 이 글을 따라서 성공했다.

 

1. 라즈베리파이 패키지나 소프트웨어 레포 등 업데이트

sudo apt-get update
sudo apt-get upgrade

2. 라즈베리파이 설정에서 시리얼 포트 사용 설정

sudo raspi-config

raspi-config에서 3. Interface Options로 들어가서,
I6 Serial Port로 들어가서,
"예"나 "yes"를 눌려준다.영어로는 "ok"일지도?
확인을 누르고,
Finish를 선택해 끝낸다음

sudo reboot

재부팅을 해주면 된다.

 

재부팅 후, 잘 연결되었는지 확인하려면 

dmesg | grep tty

[ttyS0] enabled 를 확인

[ttyS0] enabled를 확인할 수 있다.

(만약, 라즈베피라이 2 이하 혹은 zero를 쓴다면, [ttyAMA0] enabled라고 보인다고 하는데 직접 해보진 않았다.)

 

이제 선 연결도 잘 되었고, Serial Port도 사용하는 것으로 설정하고, ttyS0이 enable 된 것도 확인했다면, 아래 pySerial 라이브러리를 이용해서, python으로 PWM 주파수와 duty를 바꿔줄 수 있다.

 

#!/usr/bin/env python3
import serial

ser = serial.Serial(
        port='/dev/ttyS0', #라즈베리파이2 이하나 제로인 경우 ttyAM0
        baudrate = 9600,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS,
        timeout=1
)

ser.write(b"D100")
ser.close()

이쯤에서 SY-LD213의 specification을 정리하면,

  • 1. Working voltage: 3.3~30V
  • 2. Frequency range: 1Hz~150KHz
  • 3. Frequency accuracy: the accuracy in each range is about 2%
  • 4. Signal load capacity: the output current can be around 5~30ma
  • 5. Output amplitude: The PWM amplitude is equal to the supply voltage
  • 6. Ambient temperature: -20~+70 °C
  • 7. Serial port control (microcontroller TTL level communication)
    • Communication standard : 9600 bps
    • data bits : 8
    • stop bits : 1
    • check digit : none
    • flow control : none
      1. Set the frequency of the PWM
        • “F101”: Set the frequency to 101 HZ (001~999)
        • “F1.05”: Set the frequency to 1.05 KHZ (1.00~9.99)
        • “ F10.5 ”: Set the frequency to 10.5KHZ (10.0~99.9)
        • “F1.0.5”: Set the frequency to 105KHZ (1.0.0~1.5.0)
      2.  Set the duty cycle of the PWM
        • “DXXX”: Set the duty cycle of PWM to XXX ; (001~100)
        • For example, D050 , set the PWM duty cycle to be 50%.
      3. Read the setting parameters
        • Send a " read " string and read the set parameters.
        • Set the success to return: DOWN
        • Setup failed to return: FALL

위 코드로 주파수 설정과 Duty 설정은 잘 되는 것을 확인했으나, setting parameters를 읽는 부분은 실패했다. 

여러 삽질 끝에 알고보니 , "read" string이 아니라 "READ"를 넣었어야 하는 것.

 

그러나, wrtie로 READ를 보내도 될 때도 있고, 결과값이 읽힐때도 있고, 값이 없거나, 데이터 일부가 오는 문제가 있다.

해결해보려고 timeout을 바꿔보고, write와 read 중간에 sleep 시간을 바꿔도 계속 그러는데 디바이스 문제인지..

 

다만, 계속 반복적으로 READ를 보내보니, 10bytes씩 읽을 때 5번에 1번 제대로된 데이터가 오는걸 봐선... .. readline이나 10bytes 이상 read하는 경우는 4번 값을 읽어오고나서 멈춘다.

 

규칙이 있는걸 보면 내가 잘 모르는 이유가 있겠지만... 우선 구현하는 것이 중요하므로, 정상값만 받아내는 방법을 택했다. 

 

정상값은 b'FXXXx\00DXXXx\00'으로 들어오고, 이때 값의 0번째값은 70(ASCII code로 대문자 F 는 70이다)이므로, 

 

import time
import serial

try:
    while True:
        ser = serial.Serial(
            port='/dev/ttyS0',
            baudrate = 9600,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=1
        )
        a = ser.write(b"READ")
        time.sleep(0.1)
        try:
            res = ser.read(12)
            if res[0] == 70:
                res = res.decode()
                freq = res.split('D')[0].split('F')[1]
                duty = res.split(freq)[1].split('D')[1]
                print(f"frequency: {freq}, duty: {duty}")
                break
        except IndexError:
            pass
        ser.close()
except KeyboardInterrupt:
    ser.close()

 

위 코드로 값을 읽을 수 있다. 

 

참고로 주파수와 duty 설정한 다음에도 "DOWN"값을 읽을 수 있는 것 같긴 한데, 어떻게 해도 D가 짤리고 "OWN"만 반환된다. "READ"를 썼을 때도, F가 짤리는 경우가 자주 발생하는데... sleep 타이밍을 조절해서 될 문제는 아닌 것 같고... 잘 모르겠다. 어쨋든 LCD로 값을 모니터링 할 수 있어서 값이 잘 써졌는지는 바로 확인할 수 있으니 우선 굳이 그것까지 신경쓰진 않을 계획. 오히려, 값 write 후 READ해서 잘 세팅되었는지 확인하는게 더 빠를 것 같다. "OWN"값은 20번에 1번정도로 잘 안찍힌다.

DOWN은 어디가고 OWN만 주구장창...

 

마지막으로 내가 쓰려고, Frequency는 고정해서 쓸 테니 Duty 값을 입력값으로 받고, 설정한 후 설정된 값을 읽는 값과 비교하는 코드를 완성했다.

 

import time
import serial

freq = b"F25.0" # range: F001 ~ F1.5.0
duty_cycle = int(input("Enter duty cycle value (0-100): "))
duty_str = "D{}".format(str(duty_cycle).zfill(3))
duty = duty_str.encode("UTF-8")
freq_set = freq.decode().split("F")[1]
duty_set = duty.decode().split("D")[1]

def setSerial(value):
    ser = serial.Serial(
        port='/dev/ttyS0',
        baudrate = 9600,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS,
        timeout=1
        )
    time.sleep(0.1)
    ser.write(value)
    time.sleep(0.1)
    ser.close()

setSerial(duty)

try:
    while True:
        ser = serial.Serial(
            port='/dev/ttyS0',
            baudrate = 9600,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=1
        )

        ser.write(b"READ")
        time.sleep(0.1)
        try:
            res = ser.read(12)
            if res[0] == 70:
                res = res.decode()
                freq_res = res.split("D")[0].split("F")[1].strip()
                duty_res = res.split(freq_set)[1].split("D")[1].strip()
                print(f"Frequency Set: {freq_set}, Read: {freq_res}")
                print(f"DutyCycle Set: {duty_set}, Read: {duty_res}")
                break
        except IndexError:
            pass
        ser.close()
except KeyboardInterrupt:
    ser.close()