├── README.md ├── assets └── images │ ├── SDM630_Sprungantwort_Plotter.PNG │ └── ZeroExportController_Overview.PNG └── code └── GTIL2_SDM630_Example.ino /README.md: -------------------------------------------------------------------------------- 1 | # SDM630-zero-export-controller-Arduino 2 | Arduino code for a zero export controller 3 | --------------------------------------------------------- 4 | ![Overview](/assets/images/ZeroExportController_Overview.PNG) 5 | This project describes an Arduino code for a zero export controller. The code controls the output power of a grid tie solar inverter based on the power measurements of a 3-phase energy meter. 6 | The main controller consists of an Arduino Nano with a Max485 RS485-interface HW-97 module. The 3-phase energy meter is a SDM630 (Modbus ID:1, baudrate: 9600). The grid tie inverter is a SUN GTIL2 1000/2000 with the RS485 interface pcb (Modbus ID:4) of this project: https://github.com/trucki-eu/RS485-Interface-for-Sun-GTIL2-1000 7 | 8 | Function of the code: 9 | --------------------- 10 | The Arduino Nano fetches the actual power of the SDM630 energy meter AND(!) the SUN GTIL2 every 500ms. A delay of 200ms is needed between the two read commands. It seems to be a peculiarity of the used Modbus Master library. The new output power for the SUN GTIL2 inverter is calculated by the sum of the SDM630 power and the actual SUN GTIL2 output power. To be sure not to export any power to the grid, the code will subtract 50W (grid_offset) from the sum. 11 | 12 | Especially, heating devices like an oven are switching high loads very often. As the inverter doesn't know the future, it would export power to the grid every time the oven shuts off. To reduce such grid exports, the calculated inverter power is averaged over 60 values (avgcnt). This makes the controller slow, but reduces the grid export substantially. If the controller reads a grid power from the SDM630 energy meter which is smaller than 25W (grid_min) it will correct the inverter output power immediately. 13 | 14 | To give the SUN GTIL2 grid tie inverter time to reach the requested output power, new values are transmitted to the inverter only every 5000ms if the received grid power from the SMD630 is higher than 75W (grid_max). Between 25W (grid_min) to 75W (grid_max) the inverter output is not corrected. 15 | 16 | Arduino Serial Plotter: 17 | ----------------------- 18 | To visualize grid and inverter power, the received measurements of the SDM630 energy meter and the SUN GTIL display output power are transmitted via the serial interace of the Arduino Nano in a CSV format: 'PA: xx, GTIL: xx' . 19 | The Arduino Serial Plotter will visualize the CSV values as two charts: 20 | 21 | ![Arduino Serial Plotter](/assets/images/SDM630_Sprungantwort_Plotter.PNG) 22 | 23 | The chart above shows the power taken from the grid in blue and the inverter output in brown. Until x:600 the grid power is 25W-75W and the inverter output is slowly rising at about 200W. At x:600 an additional load with 250W is switched on. This increases the inverter output until (x:745) the grid power is back to 25W-75W. From x:745 to x:845 the system is stable. At x:845 the additional load (250W) is switched off and the inverter reduces is output quickly. For 5s there is about 250W exported to the grid. 24 | 25 | SDM630 settings: 26 | ---------------- 27 | To check if your SDM630 is working at ID:1 with 9600baud enter the settings menu by pressing the "E"-Button for 3s. The default password is "1000". With button "P/M" you can scroll through the settings. "Addr" must be "001" and "bAUd" : "9k6". Press the "E"-Button again for 3s to leave the settings menu. 28 | 29 | RS485 termination: 30 | ------------------ 31 | You should use i.e. a 120Ohm resistor on each end of the RS485 bus. As my bus is very short I successfully tested the setup without termination resistors. 32 | -------------------------------------------------------------------------------- /assets/images/SDM630_Sprungantwort_Plotter.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trucki-eu/SDM630-zero-export-controller-Arduino-/fe54cadc85379193b80e660bb9327497faef5d8d/assets/images/SDM630_Sprungantwort_Plotter.PNG -------------------------------------------------------------------------------- /assets/images/ZeroExportController_Overview.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trucki-eu/SDM630-zero-export-controller-Arduino-/fe54cadc85379193b80e660bb9327497faef5d8d/assets/images/ZeroExportController_Overview.PNG -------------------------------------------------------------------------------- /code/GTIL2_SDM630_Example.ino: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------------------------------- 2 | This project describes an Arduino code for a zero export controller. The code controls the 3 | output power of a grid tie solar inverter based on the power measurements of a 3-phase 4 | energy meter. The main controller consists of an Arduino Nano with a Max485 RS485-interface 5 | HW-97 module. The 3-phase energy meter is a SDM630 (Modbus ID:1, baudrate: 9600). The grid 6 | tie inverter is a SUN GTIL2 1000/2000 with the RS485 interface pcb (Modbus ID:4) of this 7 | project: https://github.com/trucki-eu/RS485-Interface-for-Sun-GTIL2-1000 8 | 9 | Connect Arduino->MAX485, D8(RX)-> RO, D9(TX)->DI , D2(TE)->DE/RE 10 | SDM630 settings: ModbusID=1, baudrate=9600 11 | SUN GTIL2 RS485 interface pcb ModbusID=4 12 | 13 | Further documentation to this project can be found at: 14 | https://github.com/trucki-eu/SDM630-zero-export-controller-Arduino- 15 | 16 | */ 17 | //------------------------------------------------------------------------------------------- 18 | #include //Use for Atmega328p 19 | AltSoftSerial mySerial(8, 9); // RX, TX //Use for Atmega328p SoftwareSerial for Modbus RS485 20 | 21 | #include "ModbusMaster.h" //https://github.com/4-20ma/ModbusMaster - by DocWalker 22 | #define MAX485_DE 2 //DE Pin of Max485 Interface 23 | ModbusMaster sdm630_node; 24 | ModbusMaster gtil_node; 25 | 26 | //SDM630 Modbus values 27 | const uint8_t smd630_id = 1; //Modbus ID of SDM630 smartmeter 28 | float sdm630_p1 = 0; //SDM630 power phase 1 [W] 29 | float sdm630_p2 = 0; //SDM630 power phase 2 [W] 30 | float sdm630_p3 = 0; //SDM630 power phase 3 [W] 31 | float sdm630_pa = 0; //SDM630 power sum all [W] 32 | 33 | //SUN GTIL2 Modbus values 34 | const uint8_t gtil_id = 4; //Modbus ID of Sun GTIL2 solar inverter 35 | uint16_t gtil_set_ac_power = 0; //Sun GTIL2 AC_Setpoint [W*10] 36 | uint16_t gtil_ac_power = 0; //Sun GTIL2 AC_Display output [W*10] 37 | uint16_t gtil_vgrid = 0; //Sun GTIL2 grid voltage [V*10] 38 | uint16_t gtil_vbat = 0; //Sun GTIL2 battery voltage [V*10] 39 | uint16_t gtil_dac = 0; //Sun GTIL2 DAC value 40 | uint16_t gtil_cal_step = 0; //Sun GTIL2 calibration step 41 | 42 | //Zero Export variables 43 | const uint8_t grid_offset = 50; //Power [W] which you still want to get from the grid 44 | const uint8_t grid_min = 25; //Minimum power [W] from grid 45 | const uint8_t grid_max = 75; //Maximum power [W] from grid 46 | float avgPower = 0; //Averaged desired power [W](grid+inverter) 47 | const uint8_t avgcnt = 60; //number of averaged values 48 | uint16_t t2np = 5000; //give inverter time before sending next value (time2next Power) = 5000ms 49 | 50 | unsigned long previousMillis_modbus = 0; //Counter to next modbus read (every 1300ms) 51 | unsigned long previousMillis_t2np = 0; //Counter to next inverter power (every 5000ms) 52 | 53 | void preTransmission() {digitalWrite(MAX485_DE, 1);} 54 | void postTransmission(){digitalWrite(MAX485_DE, 0);} 55 | //----------------------------------------------------------------------------------------------------------------- 56 | float reform_uint16_2_float32(uint16_t u1, uint16_t u2){ 57 | //convert SDM630 2xuint16_t to float 58 | uint32_t num = ((uint32_t)u1 & 0xFFFF) << 16 | ((uint32_t)u2 & 0xFFFF); 59 | float numf; 60 | memcpy(&numf, &num, 4); 61 | return numf; 62 | } 63 | //----------------------------------------------------------------------------------------------------------------- 64 | int read_sdm630() { 65 | //Modbus read SDM630 p1-p3 66 | if (sdm630_node.readInputRegisters(0x000C, 6) == sdm630_node.ku8MBSuccess) { 67 | sdm630_p1 = reform_uint16_2_float32(sdm630_node.getResponseBuffer(0),sdm630_node.getResponseBuffer(1)); 68 | sdm630_p2 = reform_uint16_2_float32(sdm630_node.getResponseBuffer(2),sdm630_node.getResponseBuffer(3)); 69 | sdm630_p3 = reform_uint16_2_float32(sdm630_node.getResponseBuffer(4),sdm630_node.getResponseBuffer(5)); 70 | sdm630_pa = sdm630_p1 + sdm630_p2 + sdm630_p3; 71 | } else return 0; 72 | return 1; 73 | } 74 | //----------------------------------------------------------------------------------------------------------------- 75 | int read_gtil() { 76 | //Modbus read Sun GTIL2 (AC Setpoint and AC output from display) 77 | if (gtil_node.readInputRegisters(0, 2) == sdm630_node.ku8MBSuccess) { 78 | gtil_set_ac_power = gtil_node.getResponseBuffer(0); 79 | gtil_ac_power = gtil_node.getResponseBuffer(1); 80 | //gtil_vgrid = node.getResponseBuffer(2); 81 | //gtil_vbat = node.getResponseBuffer(3); 82 | //gtil_dac = node.getResponseBuffer(4); 83 | //gtil_cal_step = node.getResponseBuffer(5); 84 | } else return 0; 85 | return 1; 86 | } 87 | //----------------------------------------------------------------------------------------------------------------- 88 | int write_gtil(float set_ac_power) { 89 | //Modbus write ac_setpoint power to Sun GTIL2 90 | if (set_ac_power < 0) set_ac_power = 0; 91 | uint16_t U16_set_ac_power = (uint16_t)set_ac_power*10; 92 | if (gtil_node.writeSingleRegister(0,U16_set_ac_power) == gtil_node.ku8MBSuccess) return 1; 93 | return 0; 94 | } 95 | //----------------------------------------------------------------------------------------------------------------- 96 | void UpdateSerialPlotter() { 97 | //Serial output to PC 98 | Serial.print("PA:"); Serial.print(sdm630_pa); Serial.print(','); //AC power sum from SDM630 99 | Serial.print("GTIL:"); Serial.print((float)(gtil_ac_power/10)); Serial.print(','); //AC power from GTIL display 100 | //Serial.print("Set_AC:"); Serial.print((float)(gtil_set_ac_power/10)); Serial.print(','); //GTIL AC setpoint 101 | //Serial.print("AVG:"); Serial.print(avgPower); Serial.print(','); //avgPower 102 | Serial.println(); 103 | } 104 | //----------------------------------------------------------------------------------------------------------------- 105 | void setup() { 106 | Serial.begin(9600); //open serial port for serial debug output 107 | 108 | pinMode(MAX485_DE, OUTPUT); //Init DE Pin of Max485 Interface as output 109 | digitalWrite(MAX485_DE, 0); 110 | 111 | mySerial.begin(9600); //SoftwareSerial for Modbus RS485 112 | sdm630_node.begin(smd630_id, mySerial); //open Modbus node to sdm630 113 | sdm630_node.preTransmission(preTransmission); 114 | sdm630_node.postTransmission(postTransmission); 115 | 116 | gtil_node.begin(gtil_id, mySerial); //open Modbus node to GTIL2 117 | gtil_node.preTransmission(preTransmission); 118 | gtil_node.postTransmission(postTransmission); 119 | 120 | Serial.println("Start"); 121 | } 122 | //----------------------------------------------------------------------------------------------------------------- 123 | void loop() { 124 | if ( (millis()-previousMillis_modbus) >= 500){ //enter this IF-Routine every 500ms 125 | if(read_sdm630()){ //Modbus read SDM630 126 | delay(200); //Modbus Master lib seems to need a delay(200) between two different nodes read 127 | if (read_gtil()){ //Modbus read SUN GTIL2 128 | float sumPower = ((float)gtil_ac_power/10) + sdm630_pa - grid_offset; //calculate desired power (GTIL2+grid-grid_offset) 129 | avgPower = (((avgcnt-1)*avgPower) + sumPower)/avgcnt; //calculate avgPower from 59*old_values + 1*new_value 130 | if ( (sdm630_pa < grid_min) || ((sdm630_pa > grid_max) && (millis()-previousMillis_t2np > t2np)) ){ //if (gridgrid_max) AND Time2NextPower(5s) ->Update GTIL2 Power 131 | if (sdm630_pa < grid_min) avgPower=sumPower; //if power from grid < grid min -> correct inverter power instantly 132 | if (!write_gtil(avgPower) ) Serial.println("Error writing SUN GTIL2"); //Modbus write SUN GTIL 133 | previousMillis_t2np = millis(); //set next time to set inverter power 134 | } 135 | }else Serial.println("Error reading SUN GTIL2"); //Error Modbus read GTIL2 136 | }else Serial.println("Error reading SDM630"); //Error Modbus read SDM630 137 | UpdateSerialPlotter(); //Serial print values for Arduino serial plotter 138 | previousMillis_modbus = millis(); //set next time to run this IF-routine again 139 | } 140 | } 141 | //----------------------------------------------------------------------------------------------------------------- 142 | --------------------------------------------------------------------------------