การอ่านค่าจากเพาเวอร์มิเตอร์ไฟฟ้าเฟสเดียว: SDM120-Modbus#

บทความนี้กล่าวถึง การทดลองอ่านค่าจากเพาเวอร์มิเตอร์เฟสเดียว รุ่น SDM120 โดยเชื่อมต่อผ่านบัส RS485 และใช้โฟรโตคอล Modbus RTU

Keywords: Single-Phase Power Meter, SDM120M, RS485, Modbus RTU, Python, Arduino Sketch, ESP32-C3


เพาเวอร์มิเตอร์แบบดิจิทัล#

มิเตอร์วัดกำลังไฟฟ้า หรือ "เพาเวอร์มิเตอร์" แบบดิจิทัล (Digital Power Meter) เป็นอุปกรณ์ที่ใช้สำหรับการวัดค่าหรือพารามิเตอร์ต่าง ๆ สำหรับไฟฟ้ากระแสสลับ เช่น

  • ความถี่ (Hz)
  • แรงดันไฟฟ้า (V)
  • กระแสไฟฟ้า (I)
  • ค่าเพาเวอร์แฟคเตอร์ หรือ ค่าตัวประกอบกำลังไฟฟ้า (Power Factor)
  • กำลังไฟฟ้าที่ใช้งานจริง (Real Power: kW)
  • กำลังไฟฟ้าที่ปรากฏ (Apparent Power: kVA)
  • กำลังไฟฟ้ารีแอคทีฟ (Reactive Power: kVAr)
  • พลังงานไฟฟ้าที่ใช้ (Active Energy: kWh)

มิเตอร์ไฟฟ้าประเภทนี้ จำแนกได้ตามจำนวนเฟสไฟฟ้าที่ต้องการวัดทางไฟฟ้า ได้แก่ มิเตอร์แบบเฟสเดียว (Single-Phase Power Meter) และมิเตอร์แบบสามเฟส (3-Phase Power Meter) นอกจากมีหน้าจอแสดงผลแบบ LCD มิเตอร์ไฟฟ้าแบบดิจิทัลในยุคปัจจุบัน ยังรองรับการเชื่อมต่อด้วยโพรโทคอล Modbus RTU และเชื่อมต่อกับระบบบัส RS485 ได้ด้วย

การต่อวงจรไฟฟ้าเพื่อใช้งานมิเตอร์ มีหลายรูปแบบ โดยดูจากความจำเป็นที่ต้องใช้อุปกรณ์อื่นร่วมด้วยหรือไม่ เช่น การใช้หม้อแปลงแรงดันไฟฟ้า (Potential Transformer: PT) และหม้อแปลงกระแสไฟฟ้า (Current Transformer: CT) เพื่อการลดทอนแรงดันไฟฟ้าด้วย PT หรือลดกระแสไฟฟ้าด้วย CT ให้มีค่าต่ำลงและอยู่ในช่วงเหมาะสมกับมิเตอร์ไฟฟ้า

 


มิเตอร์เฟสเดียว: SDM120(M)#

ข้อมูลเชิงเทคนิคเกี่ยวกับมิเตอร์ไฟฟ้ารุ่น SDM120-MODBUS (แบรนด์สินค้า Eastron)

  • เป็นมิเตอร์ไฟฟ้าดิจิทัลแบบเฟสเดียว Single-Phase Digital Power Meter
  • เชื่อมต่อด้วยสาย L (Line) และ N (Neutral)
  • วัดกระแสไฟฟ้าโดยตรง (direct connected) ไม่ได้ใช้หม้อแปลงสำหรับวัดกระแส (Current Transformer: CT)
  • คลาสความแม่นยำสำหรับการวัดแรงดันและกระแสไฟฟ้า: 0.5
  • รองรับแรงดันอินพุต: 176 ~ 276V AC
  • รองรับกระแสไฟฟ้า: 0.25A ~ 5A (45A max.)
  • วัดค่าพลังงาน (kWh) และกำลังไฟฟ้า (kW) ได้ทั้งสองทิศทาง (Bi-directional measurment)
  • อ่านข้อมูลได้ผ่านบัส RS485 ร่วมกับโพรโทคอล Modbus RTU
  • ค่า Baudrate: 2400 (default), 4800, 9600
  • ตั้งค่าหมายเลขอุปกรณ์ (Device Address) ได้ในช่วง 1 ~ 247
  • ให้เอาต์พุตเป็นแบบพัลส์ (Pulsed Outputs)
  • ผ่านมาตรฐาน MID (Measuring Instruments Directive, B and D)
  • สามารถติดตั้งบนรางปีกนกได้ (DIN Rail Mounting)

รูป: SDM120M Digital Power Meter

รูป: การเชื่อมต่อกับมิเตอร์ (Wiring Diagram)

การต่อสายไฟจาก L และ N ไปยังมิเตอร์และโหลดไฟฟ้า

  • 1: สาย L (Line-In) เพื่อให้กระแสวิ่งเข้าไปในตัวมิเตอร์
  • 2 สาย L (Line-Out) เพื่อให้กระแสวิ่งออกจากตัวมิเตอร์ไปยังโหลดไฟฟ้า
  • 3, 4: สาย N (Neutral)
  • 5, 6, 7: เป็นเอาต์พุตแบบพัลส์ Pulse Out 1 (Export kWh), GND, Pulse Out 2 (Import kWh)
  • 8, 9, 10: GND ของวงจร สัญญาณ B และ A ตามลำดับ

รูป: ตัวอย่างการต่อมิเตอร์หลายตัวในระบบบัส RS485 และมีตัวต้านทาน 120 โอห์ม ปิดปลายทั้งสองด้าน

รูป: ตัวอย่างอุปกรณ์ที่ได้นำมาทดลอง

รูป: จอแสดงผล LCD เมื่อกดปุ่มเพื่อแสดงค่าแรงดันไฟฟ้า (AC Voltage) ที่วัดได้ด้วยมิเตอร์

รูป: จอแสดงผล LCD เมื่อกดปุ่มเพื่อแสดงค่า Meter ID (001)

รูป: จอแสดงผล LCD เมื่อกดปุ่มเพื่อแสดงค่า Baudrate (2400: default)

ไฟล์เอกสารสำหรับการใช้งานมิเตอร์

ตามโพรโทคอลของ 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) เขียนค่ารีจิสเตอร์มากกว่าหนึ่งตัว

รีจิสเตอร์ภายในมิเตอร์ SDM120 แบ่งเป็น 2 กลุ่มคือ

  • รีจิสเตอร์อินพุตของมิเตอร์ (Modbus Input Registers) ดังนั้นจึงใช้ Function Code เท่ากับ 0x04 ในการอ่านค่า
  • รีจิสเตอร์สำหรับการตั้งค่าของมิเตอร์ (Modbus Holding Registers) ดังนั้นจึงใช้ Function Code เท่ากับ 0x03 ในการอ่านค่า

รูป: แอดเดรสของรีจิสเตอร์อินพุตของมิเตอร์ (Modbus Input Registers)

จากตารางจะเห็นได้ว่ามีการระบุแอดเดรสในช่วง 30001 ~ 30345 สำหรับรีจิสเตอร์อินพุตแต่ละตัว ให้ดูที่แอดเดรสเริ่มต้น (Start Address Hex) ซึ่งเป็นเลขฐานสิบหก และใช้ Function Code = 0x03 สำหรับการอ่านค่า

  • 0x0000 - 0x0001 หมายถึง รีจิสเตอร์ 2 ตัว (32 บิต) สำหรับแรงดันไฟฟ้า
  • 0x0006 - 0x0007 หมายถึง รีจิสเตอร์ 2 ตัว (32 บิต) สำหรับกระแสไฟฟ้า
  • 0x000C - 0x000D หมายถึง รีจิสเตอร์ 2 ตัว (32 บิต) สำหรับกำลังไฟฟ้าที่ใช้จริง

รูป: แอดเดรสของรีจิสเตอร์สำหรับการตั้งค่าของมิเตอร์ (Modbus Holding Registers)

จากตารางสำหรับรีจิสเตอร์เพื่อการตั้งค่าใช้งาน และใช้ Function Code = 0x4 สำหรับการอ่านค่า

  • 0x0014 ~ 0x0015 หมายถึง รีจิสเตอร์ 2 ตัว (32 บิต) สำหรับหมายเลขอุปกรณ์ (Device Address)
  • 0x001C ~ 0x001D หมายถึง รีจิสเตอร์ 2 ตัว (32 บิต) สำหรับค่า Baudrate (0=2400, 1=4800, 2=9600, ...)

ถ้าต้องการอ่านค่าจากรีจิสเตอร์ เพื่อให้ได้ค่า float (32 บิต)
จะต้องส่งเฟรมข้อมูล (Request Frame) ไปยังมิเตอร์ โดยมีลำดับข้อมูลไบต์ดังนี้

  • Slave Address: แอดเดรสของมิเตอร์ (1 ไบต์)
  • Function Code: โค้ดของฟังก์ชัน (1 ไบต์) เช่น 0x03 หรือ 0x04
  • Start Address (High Byte): แอดเดรสเริ่มต้นของรีจิสเตอร์ที่ต้องการอ่านค่า (ไบต์สูง)
  • Start Address (Low Byte): แอดเดรสเริ่มต้นของรีจิสเตอร์ที่ต้องการอ่านค่า (ไบต์ต่ำ)
  • Number of Registers (High Byte): จำนวนรีจิสเตอร์ที่ต้องการอ่าน (ไบต์สูง)
  • Number of Registers (Low Byte): จำนวนรีจิสเตอร์ที่ต้องการอ่าน (ไบต์ต่ำ)
  • CRC16 (Low Byte): ค่าตัวเลขสำหรับ 16-bit CRC Checksum (ไบต์ต่ำ)
  • CRC16 (High Byte): ค่าตัวเลขสำหรับ 16-bit CRC Checksum (ไบต์สูง)

เฟรมข้อมูลที่ได้รับการตอบกลับมา (Response Frame) หากทำคำสั่งได้ถูกต้อง (เช่น อ่านค่ารีจิเตอร์ 2 ตัว) มีลักษณะดังนี้

  • Slave Address แอดเดรสของมิเตอร์ (1 ไบต์)
  • Function Code: โค้ดของฟังก์ชัน (1 ไบต์) เช่น 0x03 หรือ 0x04
  • Byte Count: จำนวนไบต์ของข้อมูลที่ตามมา
  • First Register (High Byte): ค่าของรีจิสเตอร์ตัวแรก (ไบต์สูง)
  • First Register (Low Byte): ค่าของรีจิสเตอร์ตัวแรก (ไบต์ต่ำ)
  • Second Register (High Byte): ค่าของรีจิสเตอร์ตัวที่สอง (ไบต์สูง)
  • Second Register (Low Byte): ค่าของรีจิสเตอร์ตัวที่สอง (ไบต์ต่ำ)
  • CRC16 (Low Byte): ค่าตัวเลขสำหรับ 16-bit CRC Checksum (ไบต์ต่ำ)
  • CRC16 (High Byte): ค่าตัวเลขสำหรับ 16-bit CRC Checksum (ไบต์สูง)

รูป: เฟรมข้อมูลสำหรับการส่งคำสั่งไปยังมิเตอร์และเฟรมข้อมูลที่ได้รับตอบกลับจากมิเตอร์ เมื่อต้องการอ่านค่าในรีจิสเตอร์

 


ตัวอย่างโค้ด: Python#

โค้ดตัวอย่าง Python ต่อไปนี้สาธิตการอ่านค่าจากมิเตอร์ ตามแอดเดรสของรีจิสเตอร์ต่าง ๆ โดยใช้ไลบรารี MinimalModbus และคอมพิวเตอร์ของผู้ใช้จะต้องเชื่อมต่อ โดยใช้อุปกรณ์ USB-to-RS485 จึงจะสามารถเชื่อมต่อกับบัส RS485 ไปยังมิเตอร์ไฟฟ้าได้ ในโค้ดจะต้องมีการระบุหมายเลขของมิเตอร์ (อยู่ในช่วง 1 ~ 247) พอร์มอนุกรมสำหรับการสื่อสาร เช่น /dev/ttyUSB0 สำหรับ Linux และค่า Baudrate ให้ตรงกับการตั้งค่าของมิเตอร์ เช่น 2400

#!/usr/bin/python

import minimalmodbus # Use the MinimalModbus package

# Set the device address
dev_addr = 1
# Set the serial port
serial_port = '/dev/ttyUSB0'
# Set the baudrate
baudrate = 2400 # default: 2400

rs485 = minimalmodbus.Instrument(serial_port, dev_addr)
rs485.serial.baudrate = baudrate
rs485.serial.bytesize = 8
rs485.serial.parity   = minimalmodbus.serial.PARITY_NONE
rs485.serial.stopbits = 1
rs485.serial.timeout  = 0.5
rs485.debug = False
rs485.mode = minimalmodbus.MODE_RTU

params = [
    {"reg_addr": 0x0000, "name": "Voltage", "unit": "V"},
    {"reg_addr": 0x0006, "name": "Current", "unit": "A"},
    {"reg_addr": 0x000C, "name": "Active Power", "unit": "W"},
    {"reg_addr": 0x0012, "name": "Apparent Power", "unit": "VA"},
    {"reg_addr": 0x0018, "name": "Reactive Power", "unit": "VAr"},
    {"reg_addr": 0x001E, "name": "Power Factor", "unit": "-"},
    {"reg_addr": 0x0046, "name": "Frequency", "unit": "Hz"},
    {"reg_addr": 0x0156, "name": "Total Active Energy", "unit": "kWh"},
]

print( "SDM120 Modbus - Digital Power Meter ")
try:
    for param in params:
        value = rs485.read_float(
                registeraddress=param["reg_addr"], 
                functioncode=4, 
                number_of_registers=2 )
        print(  f'0x{hex(param["reg_addr"])[2:].zfill(4)}:',
                f'{param["name"]:>20s},',
                f'{value:7.3f}', 
                f'[{param["unit"]}]'  )
except Exception as ex:
    print(ex)
finally:
    if rs485.serial:
        rs485.serial.close()
    print('Done..')

คำสั่งของ MinimalMobdus ที่ใช้ในการอ่านค่าจากริจีสเตอร์อินพุต คือ read_float(...) ซึ่งจะต้องระบุแอดเดรสของรีจิสเตอร์ ตามด้วยค่าสำหรับ Function Code เช่น 3 หรือ 4 จำนวนของรีจิสเตอร์ที่ต้องการอ่าน (ถ้าเป็นข้อมูลตัวเลขแบบ float จะต้องอ่าน 2 ริจิสเตอร์ เพื่อให้ได้ 32 บิต) และลำดับของไบต์ข้อมูล (ค่า default คือ 0 ซึ่งหมายถึง Big-endian)

read_float( 
    registeraddress: int, 
    functioncode: int = 3, 
    number_of_registers: int = 2, 
    byteorder: int = 0) → float

รูป: ตัวอย่างข้อความเอาต์พุตจากการรันโค้ดตัวอย่าง Python

 

ตัวอย่างโค้ดถัดไป (ตัดแสดงมาเฉพาะบางส่วนที่สำคัญ) แสดงการใช้คำสั่ง (ใช้ Function Code = 0x03) เพื่ออ่านค่าจากรีจิสเตอร์สำหรับการตั้งค่าใช้งานของมิเตอร์ เช่น หมายเลของอุปกรณ์ (Meter ID) รหัสของมิเตอร์ (Serial Number) เป็นต้น มีดังนี้

# ....
print( "SDM120 Modbus - Digital Power Meter ")

METER_ID_REG_ADDR = 0x0014
METER_SN_REG_ADDR = 0xFC00
BAUDRATE_REG_ADDR = 0x001C

value = rs485.read_float(
                registeraddress=METER_ID_REG_ADDR, 
                functioncode=3, 
                number_of_registers=2 )
meter_id = int(value)
print(f'Meter ID: {meter_id}')

value = rs485.read_long(
                registeraddress=METER_SN_REG_ADDR, 
                functioncode=3)
meter_sn = hex(value)[2:].zfill(8)
print(f'Meter S/N: {meter_sn} (hex)')

value = rs485.read_float(
            registeraddress=BAUDRATE_REG_ADDR, 
            functioncode=3, 
            number_of_registers=2 )
value = int(value)
if value < 5:
    meter_baudrate = (1<<value)*2400
    print(f'Baudrate: {meter_baudrate}')

ถ้าต้องการจะเปลี่ยนค่า Baudrate เช่น จาก 0=2400 ให้เป็น 2=9600 ก็มีตัวอย่างการเขียนโค้ด โดยใช้คำสั่งสำหรับ Function Code = 0x10 ดังนี้

BAUDRATE_REG_ADDR = 0x001C

# Set the baudrate to 9600 (note: 0=2400, 1=4800, 2=9600)
# To enter the setup mode, press the button on the meter for 5 sec.
try:  
    rs485.write_float( registeraddress=BAUDRATE_REG_ADDR, value=2 )
except minimalmodbus.ModbusException as ex:
    print( ex )

ข้อสังเกต: ถ้าต้องการเขียนค่าลงในรีจิสเตอร์ เช่น เพื่อเปลี่ยนค่า Baudrate จะต้องมีการกดปุ่มที่มิเตอร์ค้างไว้ อย่างน้อย 5 วินาที เพื่อเข้าสู่โหมด Setup ก่อนการส่งคำสั่งไปยังมิเตอร์

รูป: จอแสดงผลของมิเตอร์เมื่ออยู่ในโหมด Setup

รูป: การตั้งค่า Baudrate ให้เป็น 9600 ได้สำเร็จแล้ว

 


โค้ดตัวอย่าง: Arduino Sketch#

ในการเขียนโค้ด Arduino Sketch สำหรับบอร์ดไมโครคอนโทรลเลอร์ (เลือกใช้ ESP32-C3) จะต้องใช้ร่วมกับโมดูล Serial-to-RS485 ในตัวอย่างนี้ ได้เลือกใช้ขา Tx=GPIO21 และ Rx=GPIO20 สำหรับวงจร Hardware Serial หมายเลข 0 ของ ESP32-C3 SoC เพื่อนำไปต่อกับโมดูล RS485 Transceiver (ตั้งค่า Baudrate 9600) ขา A และ B ของโมดูล RS485 Transceiver เชื่อมต่อด้วยสายไฟหนึ่งคู่ไปยังช่องสัญญาณ A และ Bsr ของมิเตอร์ไฟฟ้า การเชื่อมต่อกับคอมพิวเตอร์ผู้ใช้ จะใช้วิธี USB-CDC เพื่อการอัปโหลด Arduino Sketch และการรับข้อความจากบอร์ดไมโครคอนโทรลเลอร์

ฟังก์ชัน read_regs(...) ใช้สำหรับการอ่านค่าจากรีจิสเตอร์ขนาด 16 บิต ได้มากกว่าหนึ่งตัว และอีกฟังก์ชัน read_float(...) จะใช้สำหรับการอ่านค่าจากรีจิสเตอร์ตามแอดเดรสของรีจิสเตอร์ที่ต้องการ แต่อ่านรีจิสเตอร์ 2 ตัว เพื่อให้ได้ค่าเป็น float (32 บิต)

ฟังก์ชัน calc_modbus_crc(...) ใช้สำหรับการคำนวณค่า 16-bit CRC และใช้ในการตรวจสอบความถูกต้องสำหรับเฟรมข้อมูลที่มีการส่งไปและได้รับตอบกลับมา

//////////////////////////////////////////////////////////////
// Date: 2024-01-29
// Board: Super-Mini ESP32-C3
// Arduino IDE: v2.2.1
// Arduino ESP32 Core: v3.0.0aplha3
//////////////////////////////////////////////////////////////
#include <HardwareSerial.h>

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

void setup() {
  Serial.begin(115200); // USB-CDC
  while(!Serial);
  RS485.begin(9600); // Set the baudrate for the power meter
  // 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*/ );
  }
  RS485.setRxTimeout(1);
  RS485.flush();
  delay(1000);
  Serial.println("\n\n\n\n\n");
  Serial.flush();
  Serial.println( "Arduino-ESP32C3 Demo..." );
  Serial.println( "Power Meter Reading: Eastron SDM120 Modbus\n");
  Serial.flush();
}

// 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 modbus registers
bool read_regs( byte dev_addr, byte func_code, 
    uint16_t start_reg_addr, size_t num_regs, 
    byte *result, uint32_t delay_ms=1 ) 
{
    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( delay_ms );

    size_t buf_len = 5 +(2*num_regs); 
    byte resp_frame[ buf_len+1 ] = {0};
    size_t resp_frame_len = 0;
#if 1
    resp_frame_len = RS485.readBytes( resp_frame, buf_len );
#else
    uint32_t retries = 100;
    while ( resp_frame_len < buf_len && --retries > 0 ) {
      if ( RS485.available() ) {
        resp_frame[resp_frame_len++] = RS485.read();
      }
      delay(1);
    }
#endif
    if ( resp_frame_len < buf_len ) {
       return false; // error
    }
    uint16_t crc16, expected_crc16;
    byte *crc_bytes = &resp_frame[resp_frame_len-2];
    expected_crc16 = (crc_bytes[1] << 8) | crc_bytes[0];
    crc16 = calc_modbus_crc(resp_frame, resp_frame_len-2);
    if (crc16 == expected_crc16) {
      memcpy( result, resp_frame, resp_frame_len );
      return true; // ok
    } else {
      Serial.print("CRC error: ");
      Serial.print(crc16, HEX);
      Serial.print(" : ");
      Serial.println(expected_crc16, HEX);
      return false; // error
    }
}

bool read_float( 
  uint8_t dev_addr, uint8_t func_code, 
  uint16_t reg_start_addr, float *value ) 
{
  byte result[16] =  {0}; // frame buffer for response
  if (read_regs(dev_addr, func_code, reg_start_addr, 2, result)){
    uint32_t _value = (result[3]<< 24) | (result[4] << 16)
                    | (result[5]<< 8) | result[6];
    *value = *((float *)&_value);
    return true; // success
  }
  *value = 0.0f;
  return false; // error
}

typedef struct {
  uint16_t reg_addr;
  char name[20];
  char unit[4];
} param_t;

param_t params[]= {
  { 0x0000, "Voltage",  "V"         }, 
  { 0x0006, "Current",  "A"         },
  { 0x000C, "Active Power", "W"     },
  { 0x0012, "Apparent Power", "VA"  },
  { 0x0018, "Reactive Power", "VAr" },
  { 0x001E, "Power Factor",  "-"    },
  { 0x0046, "Frequency", "Hz"       },
  { 0x0156, "Total Active Energy", "kWh" },
};

void loop() {  
  uint8_t dev_addr = 0;  
  float value;
  int N = sizeof(params)/sizeof(param_t);
  int index = 0;
  uint8_t func_code = 0x04;
  int attempts = 1;
  while ( index < N ) {
    param_t *p = &params[index];
    if (read_float(dev_addr, func_code, p->reg_addr, &value)) {   
      Serial.printf( "0x%04X: %20s, %7.3f [%s]\n", 
                     p->reg_addr, p->name, value, p->unit );
      index++;
      attempts = 1;
    } else {
      attempts++;
    }
    delay(10);
  }
  Serial.println("============================================");
  Serial.flush();
  delay(2000);
}
//////////////////////////////////////////////////////////////

รูป: ตัวอย่างข้อความเอาต์พุตจากการรันโค้ดตัวอย่าง Arduino Sketch สำหรับบอร์ด ESP32-C3

รูป: ตัวอย่างอุปกรณ์ที่ได้นำมาทดลอง (ใช้บอร์ดไมโครคอนโทรลเลอร์ ESP32-C3 SuperMini)

 


กล่าวสรุป#

บทความนี้ได้นำเสนอการทดลองใช้งานเพาเวอร์มิเตอร์เฟสเดียว รุ่น SDM120 (Brand: Eastron) และตัวอย่างโค้ด Python และ Arduino-ESP32C3 เพื่อสาธิตการอ่านค่าจากมิเตอร์ โดยเชื่อมต่อผ่านบัส RS485 และใช้โพรโทคอล Modbus RTU เพื่อส่งเฟรมข้อมูลสำหรับคำสั่งและข้อมูลตอบกลับ

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

 


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

Created: 2024-01-27 | Last Updated: 2024-01-29