การใช้งานโมดูล XY-MD02 Temperature & Humidity Sensor (RS485 Modbus RTU)#

บทความนี้กล่าวถึงการทดลองใช้งานโมดูล XY-MD02 Sensor (ผลิตจากประเทศจีน) ซึ่งเป็นเซนเซอร์วัดอุณหภูมิ (Temperature) และความชื้นสัมพัทธ์ (Relative Humidity) เชื่อมต่อด้วยบัส RS485 และใช้โพรโทคอล Modbus RTU ในการสื่อสารข้อมูล

Keywords: Temperature & Relative Humidity Sensor, RS485, Modbus RTU, Python Programming


XY-MD02#

ข้อมูลเชิงเทคนิคเกี่ยวกับโมดูล XY-MD02

  • ใช้แรงดันไฟเลี้ยงได้ในช่วง 5V ~ 30V (DC)
  • มีไอซีลดระดับและควบคุมแรงดันให้ได้ 3.3V
  • มีกล่องพลาสติกยึดได้กับรางปีกนก (DIN Rail)
  • มีคอนเนกเตอร์เป็นแบบ Terminal Blocks: B-, A+, GND(-), VCC(+)
  • เชื่อมต่อด้วยบัส RS485 และใช้ไอซี RS485 Transceiver (3.3V) และทำหน้าที่เป็นอุปกรณ์ RS485 Slave
  • สื่อสารข้อมูลด้วยสองวิธี
    • Modbus RTU (Remote Terminal Unit) RS485 Protocol
    • Serial ASCII Command เช่น คำสั่ง READ, PARAM เป็นต้น
  • อัตราการส่งข้อมูลแบบบิตอนุกรม: 9600 (default) และ 19200 (max.)
    • Data Bits: 8
    • Parity Bit: No
    • Stop Bit: 1
  • ตั้งค่าหมายเลขอุปกรณ์ได้ในช่วง 1 ~ 247 (default: 1)
  • ใช้ไอซีของบริษัท Sensirion
  • Temperature Range: -40 ~ 125 °C
  • Humidity Range: 0 ~ 100 %RH
  • SHT20 (I2C):
    • Temperature Precision: +/-0.3 ℃
    • Humidity Precision: +/-3 %RH
  • SHTC3 (I2C):
    • Temperature Precision: +/-0.2 ℃
    • Humidity Precision: +/-2 %RH
  • SHT40 (I2C):
    • Temperature Precision: +/-0.2 ℃
    • Humidity Precision: +/-2 %RH

โมดูล XY-MD02 (SHT40) มีการใช้ไอซี เช่น

  • HK32F030M MCU:
    • Arm Cortex-M0, 32MHz, 16KB flash, 2KB / 4MB SRAM
  • SHT40:
    • I2C with Pullup: 4.7k Ohm (on SCL and SDA line)
  • SP3485E
    • Half-Duplex RS-485 Transceiver
    • SOIC-8
  • L5J / TPL820F33–89:
    • 3.3V LDO Voltage Regulator
    • Current Output: 180mA (max.) @3.3V
    • Wide Input Voltage Range: 3.6V to 42V
    • SOT-89–3

รูป: แผงวงจรภายในโมดูล XY-MD02

รูป: ไอซี SHT40 บนแผงวงจรของโมดูล

 


RS485 Interfacing#

ในการใช้งานโมดูล XY-MD02 กับคอมพิวเตอร์ จะต้องมีอุปกรณ์ USB-to-RS485 Transceiver แต่ถ้าจะใช้กับบอร์ดไมโครคอนโทรลเลอร์ จะต้องมีโมดูล RS485 Transceiver ที่รับส่งข้อมูลแบบบิตอนุกรม (Serial)

โมดูล RS485 Transceiver แบ่งได้เป็น 2 กลุ่มตามลักษณะการใช้งานขา I/O สำหรับการเชื่อมต่อกับไมโครคอนโทรลเลอร์

  • ประเภทแรกมีขา DE, /RE, DI, RO
    • VCC, GND (Supply Voltage)
    • A+, B- (RS485 Differential Signal Pair)
    • DE (Driver Enable, Active-High)
    • /RE (Receiver Enable, Active-Low)
    • DI (Driver Input)
    • RO (Receiver Output)
  • ประเภทที่สองมีขา TX, RX และเนื่องจากว่า มีวงจรควบคุมการเปลี่ยนทิศทางการรับส่งข้อมูลโดยอัตโนมัติ (Automatic Flow Control) จึงใช้งานได้สะดวกกว่า เหมือนการสื่อสารข้อมูลกับโมดูล Serial (TTL Logic) โดยทั่วไป
    • VCC, GND (Supply Voltage)
    • A+, B- (RS485 Differential Signal Pair)
    • TX (Serial Out)
    • RX (Serial In)

แนะนำให้ใช้งานโมดูลประเภทที่สอง เนื่องจากใช้งานได้ง่ายกว่า

รูป: ตัวอย่างโมดูล RS485 Transceiver "HW-97" (MAX485, 5V)

รูป: ตัวอย่างโมดูล RS485 Transceiver "HW-0519" (MAX485, 5V)

รูป: ตัวอย่างโมดูล RS485 Transceiver (MAX1348, 3.3V ~ 5V)

รูป: การใช้แรงดันไฟเลี้ยง (5V หรือสูงกว่า)

รูป: ตัวอย่างการเชื่อมต่อโมดูล XY-MD02 ในระบบบัส RS485

 


Modbus RTU over RS485#

โดยทั่วไปแล้ว การสื่อสารข้อมูลด้วยโพรโทคอล Modbus มี 3 รูปแบบ

  • Modbus over Serial
    • Modbus RTU (binary) โดยใช้ RS485 เป็น Serial / Physical Layer
    • Modbus ASCII เหมือน Modbus RTU แต่ข้อมูลเป็น ASCI (Text-based)
  • Modbus over TCP โดยใช้ระบบเครือข่ายและสื่อสารด้วย TCP/IP

โมดูล XY-MD02 สื่อสารด้วยโพรโทคอล Modbus RTU ซึ่งจะต้องมีการส่งเฟรมหรือชุดข้อมูลไบต์ออกไป (Request Frame) และรับข้อมูลที่ตอบกลับมา (Response Frame) ทั้งการส่งและการรับเฟรมข้อมูล จะต้องมีการตรวจสอบความผิดพลาดด้วย CRC (Cyclic Redundancy Check) จำนวน 2 ไบต์ ที่อยู่สองไบต์ท้ายของเฟรมข้อมูล

คอมพิวเตอร์หรือไมโครคอนโทรลเลอร์จะทำหน้าที่เป็น Modbus Master และอุปกรณ์เซนเซอร์ XY-MD02 จะทำหน้าที่เป็น Modbus Slave เชื่อมต่อกันด้วยระบบบัส RS485

ตามโพรโทคอลของ Modbus RTU การเขียนหรืออ่านข้อมูลรีจิสเตอร์ แต่ละตัวมีขนาด 2 ไบต์ หรือ 16 บิต ( Big-endian encoding) มีการจำแนกประเภทหรือแบ่งกลุ่มซึ่งจะใช้ คำสั่ง Function Code (FC) แตกต่างกัน เช่น

  • Input Registers เป็นรีจิสเตอร์สำหรับข้อมูล (ขนาด 2 ไบต์) ที่ใช้เป็นอินพุตของโมดูลหรือมีการเปลี่ยนแปลงได้
    • ใช้คำสั่ง FC = 0x04 (Read Input Registers) อ่านค่าจากรีจิสเตอร์หนึ่งตัว (หรือมากกว่าหนึ่งตัวแต่มีแอดเดรสต่อเนื่องกัน)
  • Holding Registers เป็นรีจิสเตอร์สำหรับข้อมูล (ขนาด 2 ไบต์) เช่น การตั้งค่าการใช้งานสำหรับโมดูล
    • ใช้คำสั่ง FC = 0x03 (Read Multiple Holding Registers) อ่านค่าจากรีจิสเตอร์หนึ่งตัว (หรือมากกว่าหนึ่งตัวแต่มีแอดเดรสต่อเนื่องกัน)
    • ใช้คำสั่ง FC = 0x06 (Write Single Holding Register) เขียนค่าลงในรีจิสเตอร์หนึ่งตัว
    • ใช้คำสั่ง FC = 0x10 (Write Multiple Holding Registers) เขียนค่าลงในรีจิสเตอร์มากกว่าหนึ่งตัว

โมดูล XY-MD02 มีรีจิสเตอร์ดังนี้

Type Name Size (bytes) Register Address
Input Reg. Temperature 2 0x0001
Input Reg. Humidity 2 0x0002
Holding Reg. Device Address 2 0x0101
Holding Reg. Baud Rate 2 0x0102
Holding Reg. Temperature Correction 2 0x0103
Holding Reg. Humidity Correction 2 0x0104
  • ค่าอุณหภูมิ และ ความชื้นสัมพัทธ์ อ่านได้จากรีจิสเตอร์ตามแอดเดรส 0x0001 และ 0x0002 ตามลำดับ ค่าตัวเลขที่อ่านได้เป็น Signed 16-bit Integer และจะต้องนำไปหารด้วย 10.0
  • รีจิสเตอร์ตามแอดเดรส 0x0101 ใช้สำหรับการตั้งค่าหรือตรวจสอบหมายเลขอุปกรณ์
  • รีจิสเตอร์ตามแอดเดรส 0x0102 ใช้สำหรับการตั้งค่าหรือตรวจสอบค่า Baudrate
  • รีจิสเตอร์ตามแอดเดรส 0x0103 ใช้สำหรับการตั้งค่าหรือตรวจสอบค่าชดเชยความผิดพลาดสำหรับอุณหภูมิ (เป็นค่าที่ใช้ในการบวกหรือลบ อยู่ในช่วง -10 ~ +10 °C)
  • รีจิสเตอร์ตามแอดเดรส 0x0104 ใช้สำหรับการตั้งค่าหรือตรวจสอบค่าชดเชยความผิดพลาดสำหรับความชื้น (เป็นค่าที่ใช้ในการบวกหรือลบ อยู่ในช่วง -10 ~ +10 %RH)

ตัวอย่างเฟรมข้อมูลสำหรับการส่งไปยังโมดูลเซนเซอร์ ตามลำดับมีดังนี้

  • แอดเดรสของโมดูล (Device Address) ที่ทำหน้าที่เป็น RS485 Slave (1~247): 1 ไบต์
  • รหัสสำหรับฟังก์ชัน (Function Code) เช่น 0x03 หรือ 0x04: 1 ไบต์
  • แอดเดรสเริ่มต้นของรีจิสเตอร์ที่ต้องการอ่านข้อมูล: 2 ไบต์ (16-bit integer)
  • จำนวนรีจิสเตอร์ที่ต้องการอ่าน: 2 ไบต์ (16-bit integer)
  • ตัวเลขสำหรับการตรวจสอบความผิดพลาด (CRC): 2 ไบต์ (16-bit integer)

และเฟรมข้อมูลสำหรับการตอบกลับ

  • แอดเดรสของโมดูลที่ทำหน้าที่เป็น RS485 Slave และได้ตอบกลับมา: 1 ไบต์
  • รหัสสำหรับฟังก์ชัน: 1 ไบต์
  • จำนวนไบต์ของข้อมูลที่ตามมา: N ไบต์
  • ข้อมูลจากรีจิสเตอร์
  • ตัวเลขสำหรับการตรวจสอบความผิดพลาด: 2 ไบต์

ตัวอย่างเฟรมข้อมูล

รูป: ตัวอย่างเฟรมข้อมูลสำหรับการส่งออกไปโดย RS485 Master และเฟรมข้อมูลการตอบกลับจาก RS485 Slave (อ้างอิงจากเอกสารของผู้ผลิต)

รูป: ตัวอย่างการใช้คำสั่ง 0x04 สำหรับการอ่านค่าจากรีจิสเตอร์ที่เก็บค่าอุณหภูมิ (แอดเดรส: 0x0001) และการตอบกลับมาจากโมดูลเซนเซอร์

รูป: ตัวอย่างการใช้คำสั่ง 0x04 สำหรับการอ่านค่าจากรีจิสเตอร์ที่เก็บค่าความชื้นสัมพัทธ์ (แอดเดรส: 0x0002) และการตอบกลับมา

สำหรับการใช้คำสั่งและเฟรมข้อมูลที่เกี่ยวข้อง สามารถศึกษาได้จากเอกสารคู่มือการใช้งาน (PDF files): (1 | 2)

 


Python Coding Using PySerial#

การเขียนโค้ดด้วย Python เพื่อเชื่อมต่อกับโมดูล XY-MD02 จะต้องใช้ไลบรารี PySerial (ติดตั้งด้วยคำสั่ง pip3 install pyserial)

โค้ดต่อไปนี้สาธิตการส่งคำสั่งเพื่ออ่านค่าจากรีจิสเตอร์-อินพุต ได้แก่ อุณหภูมิและความชื้นสัมพัทธ์ รวมถึงการอ่านค่าจากรีจิสเตอร์ที่เก็บค่าสำหรับการตั้งค่าใช้งานของโมดูล การอ่านค่าจากรีจิสเตอร์ทีละตัว จะใช้ฟังก์ชัน read_single_reg(...)

import time
import struct
from datetime import datetime
import serial

DEV_ADDR     = 0x01
SERIAL_PORT  = '/dev/ttyUSB0'
#SERIAL_PORT = 'COM54'
BAUDRATE     = 9600

# Calculate the CRC of a Modbus RTU response.
def calc_modbus_crc(data):
    crc = 0xFFFF
    for x in data:
        crc ^= x
        for _ in range(8):
            if crc & 1:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return crc

def read_single_reg( ser, dev_addr, func_code, reg_addr ):
    reg_addr_hi = (reg_addr >> 8) & 0xff
    reg_addr_lo = reg_addr & 0xff
    frame = [dev_addr, func_code, reg_addr_hi, reg_addr_lo, 0x00, 0x01]
    crc = calc_modbus_crc( frame ) 
    frame.append( crc & 0xff )
    frame.append( (crc >> 8) & 0xff )
    ser.write( bytes(frame) )
    time.sleep(0.1)
    resp_frame = ser.read(10)
    expected_crc = int.from_bytes(resp_frame[-2:], byteorder='little')
    calculated_crc = calc_modbus_crc(resp_frame[:-2])
    if calculated_crc == expected_crc:
        return resp_frame
    else:
        print( 'CRC error: ', calculated_crc, expected_crc )
        return None

# Open the serial port
ser = serial.Serial(port=SERIAL_PORT, baudrate=BAUDRATE, timeout=0.1)

holding_regs = [0x0101, 0x0102, 0x0103, 0x0104]
names = [ 'Device address: {}', 
          'Baudrate: {}',
          'Temperature Correction: {}',
          'Humudity Correction: {}' ]
values = []
for reg in holding_regs:
    resp = read_single_reg( ser, DEV_ADDR, 0x03, reg )
    if resp is not None:
        values.append( struct.unpack('>h', resp[3:5])[0] )

for i,kv in enumerate(zip(names,values)):
    if i==2 or i==3:
        print ( kv[0].format(kv[1]/10.0) )
    else:
        print ( kv[0].format(kv[1]) )

input_regs = [0x0001, 0x0002]
names = [ 'Temperature: {:.1f} deg.C', 'Humidity: {:.1f} %RH' ]

try:
    while True:
        values = []
        for reg in input_regs:
            resp = read_single_reg( ser, DEV_ADDR, 0x04, reg )
            if resp is not None:
                value = struct.unpack('>h', resp[3:5])[0]
                values.append( value/10.0 )
        ts = datetime.now().replace(microsecond=0)
        print( f"Timestamp:", ts )
        for name, value in zip(names,values):
            print( name.format( value ) )
        print(40*'-')
        time.sleep(4.0)
except KeyboardInterrupt:
    pass
finally:
    if ser:
        ser.close()

รูป: การเขียนโค้ดและรันโค้ดด้วย VS Code IDE

รูป: การวัดสัญญาณ A+ และ B- ด้วยออสซิลโลสโคป

โค้ดตัวอย่างถัดไปสาธิต การเขียนค่าลงในรีจิสเตอร์ตามแอดเดรส 0x0103 (Temperature Correction) โดยใช้คำสั่ง Function Code เท่ากับ 0x06 และใช้กับอุปกรณ์หมายเลข 0x01 การเขียนค่าไปยังรีจิสเตอร์หนึ่งตัว จะใช้ฟังก์ชัน write_single_reg(...) ถ้าต้องการให้มีการปรับชดเชยค่า เช่น +0.5 องศาเซลเซียส จะต้องคูณด้วย 10 แล้วแปลงให้เป็นเลข 16-bit signed integer เพื่อเขียนลงในรีจิสเตอร์

import time
import struct
from datetime import datetime
import serial

DEV_ADDR     = 0x01
SERIAL_PORT  = '/dev/ttyUSB0'
BAUDRATE     = 9600

def calc_modbus_crc(data):
    crc = 0xFFFF
    for x in data:
        crc ^= x
        for _ in range(8):
            if crc & 1:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return crc

def write_single_reg( ser, dev_addr, func_code, reg_addr, value ):
    reg_addr_hi = (reg_addr >> 8) & 0xff
    reg_addr_lo = reg_addr & 0xff
    value_hi  = (value >> 8) & 0xff
    value_lo  = value & 0xff
    frame = [dev_addr, func_code, reg_addr_hi, reg_addr_lo, 
             value_hi, value_lo]
    crc = calc_modbus_crc( frame ) 
    frame.append( crc & 0xff )
    frame.append( (crc >> 8) & 0xff )
    ser.write( bytes(frame) )
    time.sleep(0.1)
    resp_frame = ser.read(10)
    expected_crc = int.from_bytes(resp_frame[-2:], byteorder='little')
    calculated_crc = calc_modbus_crc(resp_frame[:-2])
    if calculated_crc == expected_crc:
        return resp_frame
    else:
        print( 'CRC error: ', calculated_crc, expected_crc )
        return None

# Open the serial port
ser = serial.Serial(port=SERIAL_PORT, baudrate=BAUDRATE, timeout=0.1)

# Set temperature correction (TC) in deg.C, multipled by 10
TC = 0.5
resp = write_single_reg( ser, DEV_ADDR, 0x06, 0x0103, int(TC*10) )
print( resp )

if ser:
    ser.close()

 


Python Coding Using PySerial + MinimalModbus#

โค้ดตัวอย่างถัดไปสาธิตการใช้ไลบรารี MinimalModbus (ติดตั้งโดยทำคำสั่ง $ pip3 install minimalmodbus) เพื่ออ่านค่าอุณหภูมิและความชื้นสัมพัทธ์ จากอุปกรณ์ที่มีแอดเดรส 0x01

import time
import minimalmodbus

# Set the slave address
DEV_ADDR = 1
# Specify the serial port to be used
PORT_NAME = '/dev/ttyUSB0'

FUNC_CODE = 4
REG_ADDR_TEMP  = 1
REG_ADDR_HUMID = 2

# MODBUS device initialization
device = minimalmodbus.Instrument(PORT_NAME, DEV_ADDR, debug=True)

# MODBUS device connection settings
device.serial.baudrate = 9600
device.serial.bytesize = 8
device.serial.parity   = minimalmodbus.serial.PARITY_NONE
device.mode            = minimalmodbus.MODE_RTU
device.serial.stopbits = 1
device.serial.timeout  = 0.1

while True:
    try: 
        # Reg. address (1 or 2), number of decimals (1), function code (4)
        temp  = device.read_register(REG_ADDR_TEMP, 1, FUNC_CODE )
        humid = device.read_register(REG_ADDR_HUMID, 1, FUNC_CODE )
        print(40*"-")
        print("Temperature:", temp)
        print("Humidity:", humid)
    except IOError:
        print("Failed to read from the device!")
    time.sleep(4.0)

รูป: การสาธิตการทำงานของโค้ดตัวอย่างที่ใช้ไลบรารี MinimalModbus


Arduino ESP32(C3) with ModbusRTUMaster Library#

โค้ดตัวอย่างต่อไปนี้สาธิตการใช้งานไลบรารี ModbusRTUMaster ร่วมกับบอร์ดไมโครคอนโทรลเลอร์ ESP32C3 และเขียนโปรแกรมด้วย Arduino ESP32 Core (v3.0) ในโค้ดตัวอย่างได้เลือกใช้ HardwareSerial ระหว่าง Serial0 หรือ Serial1 และต้องมีการกำหนดขา Rx/Tx เพื่อนำไปเชื่อมต่อกับโมดูล XY-017 Serial-RS485 Transceiver ซึ่งสามารถใช้แรงดันไฟเลี้ยง 3.3V ได้

ข้อสังเกต: โมดูล XY-017 มีขา TXD เป็นอินพุต และมีขา RXD เป็นเอาต์พุต

#include <HardwareSerial.h>
#include <ModbusRTUMaster.h> // https://github.com/CMB27/ModbusRTUMaster

#define HW_SERIAL (0)
HardwareSerial RS485( HW_SERIAL ); // Use Hardware Serial 0 or 1
ModbusRTUMaster modbus( RS485 );

void setup() {
  Serial.begin(115200); // USB-CDC
  while(!Serial);
  Serial.flush();
  // Set Tx/Rx pins for RS485-serial
  if (HW_SERIAL==0) {
    // Default pins for Serial0: RX=GPIO20, TX=GPIO21
    RS485.setPins( 20 /*RX*/, 21 /*TX*/ );
  } else {
    // Default pins for Serial1: RX=GPIO18, TX=GPIO19
    // Use RX=10 and TX=9 for Super-Mini ESP32C3 Board.
    RS485.setPins( 10 /*RX*/, 9 /*TX*/ );
  }
  // Set baudrate for RS485-serial
  modbus.begin( 9600 );
}

void loop() {
  static uint8_t dev_addr = 1;
  uint16_t regValues[2] = {0xffff,0xffff};
  // device address, starting address of input registers, 
  // number of input registers to read
  modbus.readInputRegisters( dev_addr, 0x0001, regValues, 2 ); 
  if  ( regValues[0] != 0xffff ) {
    float temp  = ((int16_t)regValues[0])/10.0f;
    float humid = ((int16_t)regValues[1])/10.0f;
    Serial.println(String("XY-MD02 Reading, Device Address: ") + dev_addr);
    Serial.println(String("- Temperature : ") + temp + String(" deg.C")); 
    Serial.println(String("- Humidity    : ") + humid + String(" %RH\n")); 
  } else {
    Serial.println("Sensor reading errors!");
  }
  Serial.flush();
  if ((dev_addr += 1) > 2) {
    dev_addr = 1;
  }  
  delay(2000);
}

รูป: การสาธิตการทำงานของโค้ด Arduino Sketch ที่ใช้ไลบรารี ModbusRTUMaster

รูป: โมดูล XY-017 และตัวอย่างผังวงจร

รูป: ตัวอย่างการเชื่อมต่อระหว่างอุปกรณ์ ได้แก่ Super-Mini ESP32-C3, XY-017 และ XY-MD02

ตัวอย่างการเขียนโค้ดโดยไม่ใช้ไลบรารีสำหรับการอ่านค่าจากรีจิสเตอร์ มีดังนี้

#include <HardwareSerial.h>

#define HW_SERIAL (0)
HardwareSerial RS485( HW_SERIAL ); // Use Hardware Serial 0 or 1

void setup() {
  Serial.begin(115200); // USB-CDC
  while(!Serial);
  Serial.flush();
  RS485.begin(9600);
  // Set Tx/Rx pins for RS485-serial
  if (HW_SERIAL==0) {
    // Default pins for Serial0: RX=GPIO20, TX=GPIO21
    RS485.setPins( 20 /*RX*/, 21 /*TX*/ );
  } else {
    // Default pins for Serial1: RX=GPIO18, TX=GPIO19
    RS485.setPins( 10 /*RX*/, 9 /*TX*/ );
  }
}

// Calculate the CRC of a Modbus RTU response.
uint16_t calc_modbus_crc(const byte* data, size_t len) {
  uint16_t crc = 0xFFFF;
  for (size_t i=0; i < len; i++) {
    crc ^= data[i];
    for (int j=0; j < 8; j++) {
      if (crc & 1) {
        crc >>= 1;
        crc ^= 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }
  return crc;
}

// Function to read a single Modbus register
int read_input_regs( byte dev_addr, byte func_code, 
    uint16_t start_reg_addr, size_t num_regs, byte *result ) 
{
    byte reg_addr_hi = (start_reg_addr >> 8) & 0xFF;
    byte reg_addr_lo = start_reg_addr & 0xFF;
    byte num_regs_hi = (num_regs >> 8) & 0xFF;
    byte num_regs_lo = num_regs & 0xFF;
    byte req_frame[8] = { dev_addr, func_code, reg_addr_hi, reg_addr_lo,
                          num_regs_hi, num_regs_lo };
    size_t req_frame_len = sizeof(req_frame);
    uint16_t crc = calc_modbus_crc(req_frame, req_frame_len-2 );
    req_frame[req_frame_len-2] = crc & 0xff;
    req_frame[req_frame_len-1] = (crc >> 8) & 0xff;
    RS485.write( req_frame, req_frame_len );
    delay(5);

    byte resp_frame[16] = {0};
    size_t resp_frame_len = RS485.readBytes( resp_frame, 16 );
    if (resp_frame_len == 0) {
      return -1; // error
    }
    byte *crc_bytes = &resp_frame[resp_frame_len-2];
    uint16_t expected_crc = (crc_bytes[1] << 8) | crc_bytes[0];
    uint16_t calculated_crc = calc_modbus_crc(resp_frame, resp_frame_len-2);
    if (calculated_crc == expected_crc) {
      memcpy( result, resp_frame, resp_frame_len );
      return 0; // ok
    } else {
      Serial.print("CRC error: ");
      Serial.print(calculated_crc, HEX);
      Serial.print(" : ");
      Serial.println( expected_crc, HEX);
      return -1; // error
    }
}

void loop() {
  static uint8_t dev_addr = 1;
  byte result[16]; // frame buffer for response
  memset(result, 0x00, sizeof(byte)); 
  if ( !read_input_regs( dev_addr, 0x04, 0x0001, 2, result ) ) {
    int16_t temp  = (result[3]<< 8) | result[4];
    int16_t humid = (result[5]<< 8) | result[6];
    Serial.println(String("XY-MD02 Reading, Device Address: ") + dev_addr);
    Serial.println(String("Temperature: ") + temp/10.0f  + " deg.C");
    Serial.println(String("Humidity:    ") + humid/10.0f + " %RH");
  } else {
    Serial.println("Sensor reading errors!");
  }
  Serial.flush();
  if ( (dev_addr += 1) > 2 ) {
     dev_addr = 1;
  }
  Serial.println("----------------");
  delay(2000);
}

 


กล่าวสรุป#

บทความนี้ได้นำเสนอการใช้งานโมดูล XY-MD02 Sensor การเชื่อมต่อกับคอมพิวเตอร์ทางพอร์ต USB รูปแบบของการส่งคำสั่งและการตอบกลับจากโมดูลโดยใช้โพรโทคอล RS485 Modbus RTUS เพื่ออ่านค่าจากเซนเซอร์ และการตั้งค่าการทำงานของโมดูล โดยใช้ภาษา Python และมีตัวอย่างการใช้งานร่วมกับบอร์ดไมโครคอนโทรลเลอร์ เช่น ESP32-C3 และเขียนโค้ดด้วย Arduino ESP32 Core เพื่ออ่านค่าจากโมดูล XY-MD02

บทความที่เกี่ยวข้อง

 


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2024-01-19 | Last Updated: 2024-01-26