การอ่านค่าจากเพาเวอร์มิเตอร์ไฟฟ้าสามเฟส: CJ-3D3YS (ZGCJ)#
บทความนี้กล่าวถึง ตัวอย่างการเขียนโปรแกรม Arduino Sketch และใช้งานบอร์ดไมโครคอนโทรลเลอร์ Arduino ESP32 เพื่ออ่านค่าจากมิเตอร์วัดกำลังไฟฟ้าแบบ 3 เฟส รุ่น CJ-3D3YS (Brand: ZGCJ) โดยใช้โพรโทคอล RS485 / Modbus RTU
Keywords: 3-Phase Power Meter, Arduino Sketch, ESP32-C3, RS485, Modbus RTU
▷ เพาเวอร์มิเตอร์แบบดิจิทัล#
มิเตอร์วัดกำลังไฟฟ้า หรือ "เพาเวอร์มิเตอร์" แบบดิจิทัล (Digital Power Meter) เป็นอุปกรณ์ที่ใช้สำหรับการวัดค่าหรือพารามิเตอร์ต่าง ๆ สำหรับไฟฟ้ากระแสสลับ เช่น
- ความถี่ (Hz)
- แรงดันไฟฟ้า (V)
- กระแสไฟฟ้า (I)
- ค่าเพาเวอร์แฟคเตอร์ หรือ ค่าตัวประกอบกำลังไฟฟ้า (Power Factor)
- กำลังไฟฟ้าที่ใช้งานจริง (Real Power: kW)
- กำลังไฟฟ้าที่ปรากฏ (Apparent Power: kVA)
- กำลังไฟฟ้ารีแอคทีฟ (Reactive Power: kVAR)
- พลังงานไฟฟ้าที่ใช้ (Energy: kWh)
มิเตอร์ไฟฟ้าประเภทนี้ จำแนกได้ตามจำนวนเฟสไฟฟ้าที่ต้องการวัดทางไฟฟ้า ได้แก่ มิเตอร์แบบเฟสเดียว (Single-Phase Power Meter) และมิเตอร์แบบสามเฟส (3-Phase Power Meter) นอกจากมีหน้าจอแสดงผลแบบ LCD มิเตอร์ไฟฟ้าแบบดิจิทัลในยุคปัจจุบัน ยังรองรับการเชื่อมต่อด้วยโพรโทคอล Modbus RTU และเชื่อมต่อกับระบบบัส RS485 ได้ด้วย
การต่อวงจรไฟฟ้าเพื่อใช้งานมิเตอร์ มีหลายรูปแบบ โดยดูจากความจำเป็นที่ต้องใช้อุปกรณ์อื่นร่วมด้วยหรือไม่ เช่น การใช้หม้อแปลงแรงดันไฟฟ้า (Potential Transformer: PT) และหม้อแปลงกระแสไฟฟ้า (Current Transformer: CT) เพื่อการลดทอนแรงดันไฟฟ้าด้วย PT หรือลดกระแสไฟฟ้าด้วย CT ให้มีค่าต่ำลงและอยู่ในช่วงเหมาะสมกับมิเตอร์ไฟฟ้า
▷ มิเตอร์สามเฟส: CJ-3D3YS#
เพาเวอร์มิเตอร์ที่ได้นำมาทดลองใช้งานเพื่อการสาธิตการเขียนโปรแกรมและการเชื่อมต่อสื่อสารข้อมูล คือ โมเดล CJ-3D3YS (แบรนด์สินค้า ZGCJ)
รูป: ZGCJ CJ-3D3YS
- เป็นเพาเวอร์มิเตอร์แบบสามเฟส ผลิตในประเทศจีน และจำหน่ายโดยบริษัท Shanghai Chujing Electric Co., Ltd.
- สามารถเชื่อมต่อกับ RS485 และสื่อสารข้อมูลด้วยโพรโทคอล Modbus RTU
- รองรับการตั้งค่า Baudrate ได้ในช่วง 1200 ถึง 9600 (สูงสุด)
- รองรับการวัดแรงดันไฟฟ้าในช่วง 450V (AC) ต่อเฟส และกระแสไฟฟ้าไม่เกิน 5A ต่อเฟส
- คลาสของความแม่นยำ (Accuracy Class): 0.5
- ใช้แรงดันไฟเลี้ยง (Power Supply) ได้ในช่วง AC/DC 85V ~ 265V
โดยต่อเข้าที่ช่อง (หมายเลข) ต่อไปนี้
- L (1)
- N (2)
- ช่องสำหรับวัดกระแสไฟฟ้าแต่ละเฟส (Current Signal Inputs: Iin)
- Ia⭑ (4) และ Ia (5) — กระแสไหลเข้าและออกสำหรับเฟส a
- Ib⭑ (6) และ Ib (7) — กระแสไหลเข้าและออกสำหรับเฟส b
- Ic⭑ (8) และ Ia (9) — กระแสไหลเข้าและออกสำหรับเฟส c
- ช่องสำหรับวัดแรงดันไฟฟ้าแต่ละเฟส (Voltage Signal Inputs: Vin)
- L1 (11) และ N (14) — แรงดันไฟฟ้าเฟส a
- L2 (12) และ N (14) — แรงดันไฟฟ้าเฟส b
- L3 (13) และ N (14) — แรงดันไฟฟ้าเฟส c
- ช่องสัญญาณสำหรับ RS485
- A (58)
- B (59)
การต่อวงจรเพื่อใช้งานมิเตอร์ ได้เลือกรูปแบบ 3-phase, 4-wire (ไม่มีการต่อหม้อแปลงภายนอก PT และ CT)
รูป: ช่องเสียบสายไฟที่อยู่ด้านหลังของมิเตอร์ไฟฟ้า
รูป: การเชื่อมต่อสายไฟสำหรับมิเตอร์ไฟฟ้า
▷ โค้ดตัวอย่าง#
โค้ด Arduino Sketch ต่อไปนี้ สาธิตการอ่านค่าจากรีจิสเตอร์บางตัวภายในมิเตอร์ไฟฟ้า และใช้สำหรับบอร์ดไมโครคอนโทรลเลอร์ ESP32-C3
ในตัวอย่างนี้ ได้เลือกใช้ขา Tx=GPIO21 และ Rx=GPIO20 สำหรับ วงจร Hardware Serial หมายเลข 0 ของ ESP32-C3 เพื่อนำไปต่อกับโมดูล RS485 Transceiver (ตั้งค่า Baudrate 9600)
ขา A และ B ของโมดูล RS485 Transceiver เชื่อมต่อด้วยสายไฟหนึ่งคู่ไปยังช่องสัญญาณ A และ B ของมิเตอร์ไฟฟ้า
การเชื่อมต่อกับคอมพิวเตอร์ผู้ใช้ จะใช้วิธี USB-CDC เพื่อการอัปโหลด Arduino Sketch และการรับข้อความจากบอร์ดไมโครคอนโทรลเลอร์
รีจิสเตอร์ที่เก็บค่าต่าง ๆ ของมิเตอร์และได้จากการวัดปริมาณทางไฟฟ้ามีขนาด 16 บิต การอ่านค่าจากรีจิสเตอร์ทำได้โดยใช้คำสั่ง Function Code 0x03 ตามโพรโทคอล Modbus RTU
เมื่ออ่านค่าด้วยฟังก์ชัน read_short(...)
จะได้ค่าเป็นเลขจำนวนเต็ม 16-bit integer (short)
และจะต้องมีการหารด้วย 10.0, 100.0 หรือ 1000.0 แล้วแต่กรณี
เพื่อให้ได้ค่าเป็นเลขทศนิยม
ฟังก์ชัน scan_regs(...)
สามารถใช้สำหรับการอ่านค่าจากรีจิสเตอร์ในช่วงแอดเดรสที่กำหนด
เช่น ในช่วง 0x0010
ถึง 0x0040
และแสดงค่าตัวเลขจำนวนเต็มของแต่ละรีจิสเตอร์
//////////////////////////////////////////////////////////////
// Date: 2024-01-25
// Board: Super-Mini ESP32-C3
// Arduino IDE: v2.2.1
// Arduino ESP32 Core: v3.0.0alpha3
//////////////////////////////////////////////////////////////
#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 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=GPIO10 and TX=GPIO9 for SuperMini ESP32C3 Board
RS485.setPins( 10 /*RX*/, 9 /*TX*/ );
}
RS485.setRxTimeout(1);
RS485.flush();
delay(1000);
Serial.println("\n\n\n\n\n");
Serial.println("Arduino-ESP32C3 Demo...");
Serial.println("Power Meter Reading: ZGCJ CJ-3D3YS\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_short(
uint8_t dev_addr, uint16_t reg_start_addr, uint16_t *value )
{
uint8_t func_code = 0x03;
byte result[16] = {0}; // frame buffer for response
if ( read_regs(dev_addr, func_code, reg_start_addr, 1, result) ) {
uint32_t _value = (result[3] << 8) | result[4];
*value = _value;
return true; // success
}
*value = 0;
return false; // error
}
void scan_regs( uint8_t dev_addr, uint16_t start_reg, uint16_t end_reg ) {
uint16_t value;
RS485.setRxTimeout(100);
for ( uint16_t addr=start_reg; addr <= end_reg; addr += 1 ) {
if ( read_short(dev_addr, addr, &value) ) {
Serial.printf( "RegAddr=0x%04X: %lu\n", addr, value);
} else {
Serial.printf( "RegAddr=0x%04X: ------\n", addr );
}
delay(100);
}
}
// The register addresses were found by an experimental method
// (no manual available).
#define UA_REG_ADDR (0x0025)
#define UB_REG_ADDR (0x0026)
#define UC_REG_ADDR (0x0027)
#define UAB_REG_ADDR (0x0028)
#define UBC_REG_ADDR (0x0029)
#define UCA_REG_ADDR (0x002A)
#define IA_REG_ADDR (0x002B)
#define IB_REG_ADDR (0x002C)
#define IC_REG_ADDR (0x002D)
#define TOTAL_P_REG_ADDR (0x0031)
#define TOTAL_Q_REG_ADDR (0x0035)
#define TOTAL_S_REG_ADDR (0x003D)
#define PFA_REG_ADDR (0x0036)
#define PFB_REG_ADDR (0x0037)
#define PFC_REG_ADDR (0x0038)
#define FREQ_REG_ADDR (0x003E)
typedef struct {
uint16_t reg_addr;
uint16_t decimals;
char name[8];
char unit[4];
} param_t;
param_t params[]= {
{ UA_REG_ADDR, 10, "Ua", "V" },
{ UB_REG_ADDR, 10, "Ub", "V" },
{ UC_REG_ADDR, 10, "Uc", "V" },
{ UAB_REG_ADDR, 10, "Uab", "V" },
{ UBC_REG_ADDR, 10, "Ubc", "V" },
{ UCA_REG_ADDR, 10, "Uca", "V" },
{ IA_REG_ADDR, 1000, "Ia", "A" },
{ IB_REG_ADDR, 1000, "Ib", "B" },
{ IC_REG_ADDR, 1000, "Ic", "C" },
{ PFA_REG_ADDR, 1000, "PFa", "-" },
{ PFB_REG_ADDR, 1000, "PFb", "-" },
{ PFC_REG_ADDR, 1000, "PFc", "-" },
{ TOTAL_P_REG_ADDR, 100, "P", "W" },
{ TOTAL_Q_REG_ADDR, 100, "Q", "VA" },
{ TOTAL_S_REG_ADDR, 100, "S", "VAR" },
{ FREQ_REG_ADDR, 100, "Freq", "Hz" },
};
void loop() {
uint8_t dev_addr = 1;
uint16_t value;
//scan_regs( dev_addr, 0x0010, 0x0040 );
int N = sizeof(params)/sizeof(param_t);
int index = 0;
int attempts = 1;
while ( index < N ) {
param_t *p = ¶ms[index];
if ( read_short(dev_addr, p->reg_addr, &value) ) {
String fmt_str = "RegAddr=0x%04X: %4s, ";
if (p->decimals==10) {
fmt_str += "%6.1f";
} else if (p->decimals==100) {
fmt_str += "%6.2f";
} else if (p->decimals==1000) {
fmt_str += "%6.3f";
} else {
fmt_str += "%f";
}
fmt_str += " [%s] (#%d)\n";
Serial.printf( fmt_str.c_str(),
p->reg_addr, p->name,
value/((float)p->decimals),
p->unit, attempts );
index++;
attempts = 1;
} else {
attempts++;
}
delay(80);
}
Serial.println("====================================");
Serial.flush();
delay(2000);
}
//////////////////////////////////////////////////////////////
เนื่องจากไม่มีระบบไฟฟ้าสามเฟสให้ทดลอง จึงใช้เพียงหนึ่งเฟสเท่านั้น โดยต่อเข้าที่ L1 และ N และนำไปต่อกับโหลดไฟฟ้า เช่น หลอดไฟ
รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับจากบอร์ด ESP32-C3 แสดงค่าจากการวัดปริมาณทางไฟฟ้าด้วยเพาเวอร์มิเตอร์
รูป: ตัวอย่างการแสดงค่าที่วัดได้บนจอ LCD (แรงดันไฟฟ้าของแต่ละเฟส)
รูป: บอร์ด ESP32-C3 Super-Mini และโมดูล Serial-to-RS485
รูป: อุปกรณ์ที่ได้นำมาทดลอง
▷ กล่าวสรุป#
บทความนี้ได้นำเสนอการทดลองใช้งานเพาเวอร์มิเตอร์สามเฟสแบบดิจิทัล รุ่น ZGCJ J-3D3YS และนำเสนอโค้ดตัวอย่างเพื่อใช้งานบอร์ดไมโครคอนโทรลเลอร์ Arduino ESP32 ให้สามารถอ่านค่าจากมิเตอร์ได้ โดยเชื่อมต่อผ่านบัส RS485 และใช้โพรโทคอล Modbus RTU เพื่อส่งเฟรมข้อมูลสำหรับคำสั่งและข้อมูลตอบกลับ
บทความที่เกี่ยวข้อง
- การใช้งานโมดูลสื่อสาร RS485 Transceiver
- การใช้งานโมดูล XY-MD02 Temperature & Humidity Sensor (RS485 Modbus RTU)
- การอ่านค่าจากเพาเวอร์มิเตอร์ไฟฟ้าสามเฟส: ZM194-D9Y (ZJZM)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Created: 2024-01-25 | Last Updated: 2024-01-27