This project allows you to control an XY-SK120 power supply (and compatible models) over Modbus RTU using a Seeed XIAO ESP32S3.
- Serial monitor control interface
- Memory group management
- Direct register access for debugging
- Web interface (coming soon)
- Connect the XIAO ESP32S3 to the XY-SK120 using the TTL interface:
- XIAO TX pin → XY-SK120 RX pin
- XIAO RX pin → XY-SK120 TX pin
- XIAO GND → XY-SK120 GND
on
- Turn output ONoff
- Turn output OFFset V I
- Set voltage (V) and current (I)status
- Display current statusinfo
- Display device informationconfig
- Display current configurationsave
- Save current config to NVSreset
- Reset config to defaultshelp
- Show help message
mem N
- Display memory group N (0-9)call N
- Call memory group N (1-9) to active memorysave2mem N
- Save current settings to memory group N (1-9)setmem N param value
- Set specific parameter in memory group N- Parameters: v(voltage), i(current), p(power), ovp, ocp, opp, oah, owh, uvp, ucp
The project supports direct register access for debugging and advanced usage:
read addr count
- Read 'count' registers starting at address 'addr'write addr value
- Write 'value' to register at address 'addr'writes addr v1 v2 ...
- Write multiple values to consecutive registers
Examples:
read 0x0000 1
- Read the voltage setting register (result shows different scaling factors)write 0x0000 1250
- Set voltage to 12.50V (1250/100)read 0x0002 3
- Read output voltage, current, and power
Addresses can be provided in decimal or hexadecimal (with 0x prefix).
The power supply uses the following register map (partial list):
Address | Description | Unit | Scaling | R/W |
---|---|---|---|---|
0x0000 | Voltage setting | V | /100 | R/W |
0x0001 | Current setting | A | /1000 | R/W |
0x0002 | Output voltage | V | /100 | R |
0x0003 | Output current | A | /1000 | R |
0x0004 | Output power | W | /100 | R |
0x0012 | Output on/off | 0/1 | - | R/W |
For a more complete register map, see the XY-SKxxx library documentation.
This project uses PlatformIO. To build and upload:
pio run -t upload
The project uses Tailwind CSS for styling. To build the CSS:
-
Make sure you have Node.js and npm installed
-
Run the build script:
# Make the script executable (first time only) chmod +x build-css.sh # Run the build script ./build-css.sh
-
For development with auto-reloading:
npm run watch:css
This will generate the data/css/tailwind.css
file used by the application.
This guide outlines the complete process of discovering, implementing, and exposing new features in the XY-SKxxx library.
The first step in adding a new feature is discovering which Modbus registers control it:
-
Access the Debug Menu via Serial Monitor:
menu 5
-
Scan Register Ranges:
scan 0x0000 0x0030
This examines registers in blocks. For example, configuration registers are typically found in 0x0000-0x0030, while protection settings often appear in 0x0050-0x0080.
-
Record Initial Values: Take note of all current register values before making any changes.
-
Change Settings via Device Screen:
- Modify the setting you're investigating through the device's physical interface
- Make small, known changes (e.g., change MPPT from OFF to ON)
-
Rescan Registers to Detect Changes:
scan 0x0000 0x0030
Compare with your initial values to identify which register changed.
-
Validate with Direct Register Read/Write:
read 0x001F # Example: reading MPPT enable register
-
Test Writing to Register:
write 0x001F 1 # Example: enabling MPPT
Verify the change takes effect on the device display.
Update XY-SKxxx.h
with the new register definitions:
// Add to register definitions section
#define REG_MPPT_ENABLE 0x001F // MPPT enable/disable, 2 bytes, 0 decimal places, unit: 0/1
#define REG_MPPT_THRESHOLD 0x0020 // MPPT threshold percentage, 2 bytes, 2 decimal places, unit: ratio (0.00-1.00)
Add private member variables to store cached values:
// Add to private section of XY_SKxxx class
bool _mpptEnabled; // MPPT enable state cache
float _mpptThreshold; // MPPT threshold cache
Add method declarations to the public interface:
// Add to public section of XY_SKxxx class
bool setMPPTEnable(bool enabled);
bool getMPPTEnable(bool &enabled);
bool setMPPTThreshold(float threshold);
bool getMPPTThreshold(float &threshold);
Create implementation in an appropriate file (e.g., XY-SKxxx-settings.cpp
):
/**
* Enable or disable MPPT (Maximum Power Point Tracking)
*
* @param enabled true to enable, false to disable
* @return true if successful
*/
bool XY_SKxxx::setMPPTEnable(bool enabled) {
waitForSilentInterval();
uint8_t result = modbus.writeSingleRegister(REG_MPPT_ENABLE, enabled ? 1 : 0);
_lastCommsTime = millis();
if (result == modbus.ku8MBSuccess) {
_mpptEnabled = enabled; // Update cache
return true;
}
return false;
}
// Implement remaining methods...
Modify the cache update methods (e.g., in XY-SKxxx-cache.cpp
):
bool XY_SKxxx::updateCalibrationSettings(bool force) {
// ...existing code...
// Read MPPT enable state
delay(_silentIntervalTime * 2);
result = modbus.readHoldingRegisters(REG_MPPT_ENABLE, 1);
if (result == modbus.ku8MBSuccess) {
_mpptEnabled = (modbus.getResponseBuffer(0) != 0);
}
// Read MPPT threshold
delay(_silentIntervalTime * 2);
result = modbus.readHoldingRegisters(REG_MPPT_THRESHOLD, 1);
if (result == modbus.ku8MBSuccess) {
_mpptThreshold = modbus.getResponseBuffer(0) / 100.0f;
}
// ...existing code...
}
Make getters use the cache:
bool XY_SKxxx::getMPPTEnable(bool &enabled) {
// Try from cache first
if (updateCalibrationSettings(false)) {
enabled = _mpptEnabled;
return true;
}
// If cache failed, read directly
// ...direct reading code...
}
Update the relevant menu display function (e.g., menu_settings.cpp
):
void displaySettingsMenu() {
// ...existing code...
Serial.println("mppt [on/off] - Enable/disable MPPT (Maximum Power Point Tracking)");
Serial.println("mpptthr [value] - Set MPPT threshold (0-100%, default 80%)");
// ...existing code...
}
Implement the command handlers:
void handleSettingsMenu(const String& input, XY_SKxxx* ps, XYModbusConfig& config) {
// ...existing code...
else if (input.startsWith("mppt ")) {
String subCmd = input.substring(5);
subCmd.trim();
if (subCmd == "on") {
if (ps->setMPPTEnable(true)) {
Serial.println("MPPT enabled");
} else {
Serial.println("Failed to enable MPPT");
}
} else if (subCmd == "off") {
if (ps->setMPPTEnable(false)) {
Serial.println("MPPT disabled");
} else {
Serial.println("Failed to disable MPPT");
}
} else {
Serial.println("Invalid option. Use 'on' or 'off'");
}
}
// ...handle other commands...
}
Update status display to show the new feature:
void displayDeviceStatus(XY_SKxxx* ps) {
// ...existing code...
// Read MPPT status and threshold
bool mpptEnabled;
if (ps->getMPPTEnable(mpptEnabled)) {
Serial.print("MPPT Status: ");
Serial.println(mpptEnabled ? "ENABLED" : "DISABLED");
if (mpptEnabled) {
float mpptThreshold;
if (ps->getMPPTThreshold(mpptThreshold)) {
Serial.print("MPPT Threshold: ");
Serial.print(mpptThreshold * 100, 0);
Serial.println("%");
}
}
}
// ...existing code...
}
-
Compile and Flash:
pio run --target upload
-
Test via Serial Monitor:
status # Verify the feature appears in status output menu 4 # Go to settings menu mppt on # Enable MPPT mpptthr 85 # Set threshold to 85% status # Confirm changes are reflected
-
Verify on Device: Confirm the settings changed on the device's physical display.
Note: The serial monitor interface is just one example of how to expose the library features. The same library methods can be used in other interfaces such as web APIs, MQTT clients, or custom applications. The core XY-SKxxx library is designed to be interface-agnostic.