first commit

This commit is contained in:
Jérôme Delacotte
2025-03-06 11:15:32 +01:00
commit 7b30d6e298
5276 changed files with 2108927 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
/**
* @file dcdcmbus.ino
* @brief usage of XY6020L DCDC for simple max power point tracking of
* a solar module driving a electrolytic cell
* Cells voltage start from ~3 V and current will rise up to ~3A at 4 V.
* At ~19 V the 20V pannel has its maximum power.
* Simple I-controler increases the cell voltage till the power consumption
* from the solar panel drives its voltage below 19 V.
Hardware: Arduino Pro Micro Clone from China
*
* @author Jens Gleissberg
* @date 2024
* @license GNU Lesser General Public License v3.0 or later
*/
#include "xy6020l.h"
#define PIN_SOFTWARE_SERIAL_RX2 25
#define PIN_SOFTWARE_SERIAL_TX2 26
// dcdc's MBus is connected to Serial2 of Arduino
xy6020l xy(Serial2, 1);
long ts;
bool boActive;
// solar panel voltage
word vIn;
// cell voltage setpoint
word vOut, vOutMin, vOutMax;
void setup() {
// debug messages via USB
Serial.begin( 115200);
// MBus serial
// Serial2.begin( 115200);
Serial2.begin(115200, SERIAL_8N1, PIN_SOFTWARE_SERIAL_RX2, PIN_SOFTWARE_SERIAL_TX2); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3
ts = millis();
vOutMin= 300; // start with 3,0 V
vOutMax= 450; // max voltage
vIn = 1700;
boActive = false;
xy.setPreset(01);
while(!xy.TxBufEmpty())
xy.task();
xy.setOutput(false);
while(!xy.TxBufEmpty())
xy.task();
}
void loop() {
int vDiff;
char tmpBuf[30]; // text buffer for serial messages
xy.task();
if(xy.HRegUpdated())
{
vIn = xy.getInV();
// 15 V -> undervoltage of solar panel
if(vIn < 1300 )
{
// output off
xy.setOutput(false);
boActive = false;
// reset cell voltage to its min value
vOut= vOutMin;
}
else
{
// use a hyseressis for switch on to avoid to short on pulses
if(vIn > 2100 )
{
boActive= true;
// output on
if(!xy.getOutputOn() )
xy.setOutput(true);
}
}
if(xy.getLockOn() )
xy.setLockOn(false);
if(xy.getProtect()>0)
{
xy.setProtect(0);
}
if(boActive)
{
// target:
// the input voltage must be kept at 18..19 V
// @todo I part depents on voltage difference and slope
vDiff= (int)vIn - (int)1900;
// dead band -> no change
if(vDiff < 70 && vDiff > -100)
vDiff=0;
else
{
if(vDiff > 200)
vDiff=200;
if(vDiff < -200)
vDiff=-200;
if(vDiff > 0)
vOut+= vDiff /10;
else
vOut+= vDiff ;
if(vOut > vOutMax)
vOut = vOutMax;
if(vOut < vOutMin)
vOut = vOutMin;
xy.setCV( vOut );
}
}
// print control results: - switched to save runtime -
//sprintf( tmpBuf, "%d: %d Out=%d\n", vIn, vOut, boActive);
//Serial.print(tmpBuf);
}
}

View File

@@ -0,0 +1,617 @@
// #include <type_traits>
/**
* @file xy6020l.cpp
* @brief UART control access to XY6020L DCDC
*
* This library provides an embedded and simplified ModBus implementation.
* The data type of interfaces to physical values are in decimal, scaled almost in 0.01.
*
* Tested only on a Arduino Pro Micro clone from China
*
* @author Jens Gleissberg
* @date 2024
* @license GNU Lesser General Public License v3.0 or later
*/
#include "Arduino.h"
#include "xy6020l.h"
/**
* @brief: debug message level on serial
* > 1 - timeout comment
* > 2 - HRegs contents
* - rx bytes and HReg of written HReg
* > 9 - rx bytes (basic RS232 communication)
*/
#define __debug__ 0
/** @brief period for reading content of all hold regs, in msec */
// #define PERIOD_READ_ALL_HREGS 100
/** @brief answer timeout of tx message: 4 x 10 ms */
#define PERIOD_TIMEOUT_RESPONSE 4
TxRingBuffer::TxRingBuffer()
{
mpIn = &(mTxBuf[0]);
mpOut= &(mTxBuf[0]);
mIn=0;
}
bool TxRingBuffer::AddTx(txRingEle* pTxEle)
{
bool retVal=true;
char tmpBuf[20];
if( IsFull())
retVal= false;
else
{
mpIn->mHregIdx = pTxEle->mHregIdx;
mpIn->mValue = pTxEle->mValue;
mIn++;
mpIn++;
// wrap around
if(mpIn == &(mTxBuf[TX_RING_BUFFER_SIZE]))
mpIn = &(mTxBuf[0]);
#if __debug__ > 2
sprintf( tmpBuf, "\nRing In: %d\n", mIn);
Serial.print(tmpBuf);
#endif
}
return retVal;
}
bool TxRingBuffer::AddTx(byte hRegIdx, word value)
{
txRingEle TxEle;
TxEle.mHregIdx= hRegIdx;
TxEle.mValue = value;
return AddTx( &TxEle );
}
bool TxRingBuffer::GetTx(txRingEle& TxEle)
{
bool retVal=true;
if( IsEmpty() )
retVal= false;
else
{
TxEle.mHregIdx = mpOut->mHregIdx;
TxEle.mValue = mpOut->mValue;
mIn--;
mpOut++;
// wrap around
if(mpOut == &(mTxBuf[TX_RING_BUFFER_SIZE]))
mpOut = &(mTxBuf[0]);
};
return retVal;
}
xy6020l::xy6020l(Stream& serial, byte adr, byte txPeriod, byte options )
{
mSerial= &serial;
mAdr=adr;
mOptions = options;
mMemory = 255;
mMemoryState= Send;
mRxBufIdx = 0;
mRxFrameCnt=0;
mRxFrameCntLast=0;
mTxBufIdx = 0;
mTxPeriod = txPeriod;
mResponse = None;
mTs = millis();
mTO = mTs;
mTLastTx = mTs;
};
bool xy6020l::ReadAllHRegs(void)
{
bool retValue=false;
if( mTxBufIdx == 0 )
{
SendReadHReg(0, NB_HREGS-1 );
retValue= true;
}
return retValue;
}
bool xy6020l::HRegUpdated(void)
{
bool retValue=false;
if( mRxFrameCnt != mRxFrameCntLast)
{
mRxFrameCntLast= mRxFrameCnt;
retValue=true;
}
return retValue;
};
/** @brief: read register reply, must be decoded to Hregister content */
bool xy6020l::RxDecode03( byte cnt)
{
bool RxOk= true;
char tmpBuf[30];
mRxThis = mRxBuf[0]==mAdr? true:false;
mRxSize = mRxBuf[2];
if (mRxSize > NB_HREGS*2+5)
{
RxOk= false;
}
else
{
for(int i=0; i< mRxSize/2; i++)
{
if(mMemory > 10 )
{
hRegs[i]= (word)mRxBuf[3+2*i] * 256 + (word)mRxBuf[4+2*i];
}
else
{
mMem[i]= (word)mRxBuf[3+2*i] * 256 + (word)mRxBuf[4+2*i];
}
}
};
// ignore CRC
mRxFrameCnt++;
#if __debug__ > 2
sprintf( tmpBuf, "\nDec03:%d: ", mRxBuf[1]);
Serial.print(tmpBuf);
#endif
#if __debug__ > 2
if(mMemory > 10 )
{
for(int i=0; i < NB_HREGS; i++)
{
sprintf( tmpBuf, "%X(%4d) ", i, hRegs[i]);
Serial.print(tmpBuf);
}
}
else
{
for(int i=0; i < NB_MEMREGS; i++)
{
sprintf( tmpBuf, "%X(%4d) ", i, mMem[i]);
Serial.print(tmpBuf);
}
}
Serial.print("\n");
#endif
// reset memory redirection
mMemory=255;
mResponse = None;
return RxOk;
}
bool xy6020l::RxDecode06( byte cnt)
{
bool RxOk= true;
word RegNr;
char tmpBuf[30];
mRxThis = mRxBuf[0]==mAdr? true:false;
if(mRxBuf[1]==0x06)
{
RegNr = (word)mRxBuf[2] *256 + mRxBuf[3];
if (RegNr < NB_HREGS)
{
hRegs[RegNr] = (word)mRxBuf[4] *256 + mRxBuf[5];
}
};
#if __debug__ > 2
sprintf( tmpBuf, "Dec06: %d %d:=%4d\n", mRxBuf[1], RegNr, hRegs[RegNr]);
Serial.print(tmpBuf);
#endif
mResponse = None;
return RxOk;
}
bool xy6020l::RxDecode16( byte cnt)
{
bool RxOk= true;
word RegNr;
char tmpBuf[30];
mRxThis = mRxBuf[0]==mAdr? true:false;
if(mRxBuf[1]==0x10)
{
RegNr = (word)mRxBuf[2] *256 + mRxBuf[3];
};
#if __debug__ > 2
sprintf( tmpBuf, "\nDec16: %d RegStart: 0x%X\n", mRxBuf[1], RegNr);
Serial.print(tmpBuf);
#endif
mResponse = None;
return RxOk;
}
void xy6020l::RxDecodeExceptions(byte cnt)
{
char tmpBuf[30];
if(cnt >= 5)
{
mLastExceptionCode = mRxBuf[2];
// reset memory redirection
mMemory=255;
mResponse = None;
#if __debug__ > 2
sprintf( tmpBuf, "\nException to fct code %d", ( mRxBuf[1] & 0x0F) );
Serial.print(tmpBuf);
switch( mRxBuf[2])
{
case 1: Serial.print(F("\nIllegal Function")); break;
case 2: Serial.print(F("\nIllegal Data Address")); break;
case 3: Serial.print(F("\nIllegal Data Value")); break;
case 4: Serial.print(F("\nSlave Device Failure")); break;
case 5: Serial.print(F("\nAcknowledge")); break;
case 6: Serial.print(F("\nSlave Device Busy")); break;
case 7: Serial.print(F("\nNegative Acknowledge")); break;
case 8: Serial.print(F("\nMemory Parity Error")); break;
case 10:Serial.print(F("\nGateway Path Unavailable")); break;
case 11:Serial.print(F("\nGateway Target Device Failed to Respond")); break;
default:Serial.print(F("\nUnknown Exception Code")); break;
}
#endif
}
}
void xy6020l::task()
{
char tmpBuf[30];
mRxBufIdx=0;
// check rx buffer
#if __debug__ > 9
if(mSerial->available() > 0)
Serial.print("\nRX: ");
#endif
while ( (mSerial->available() > 0) && (mRxBufIdx < sizeof(mRxBuf)) )
{
mRxBuf[ mRxBufIdx] = mSerial->read();
#if __debug__ > 9
sprintf( tmpBuf, "%02X ", mRxBuf[ mRxBufIdx]);
Serial.print(tmpBuf);
#endif
mRxBufIdx++;
// @todo optimize delay time in dependency from baudrate
delayMicroseconds(1000);
}
if(mRxBufIdx > 7)
{
if(mRxBuf[1] & 0x80)
RxDecodeExceptions(mRxBufIdx);
else
{
// mbus as different answer layouts
switch(mRxBuf[1] )
{
case 0x3: RxDecode03(mRxBufIdx); break;
case 0x6: RxDecode06(mRxBufIdx); break;
case 0x10:RxDecode16(mRxBufIdx); break;
}
}
}
// transmits pending ?
if(mResponse== None)
{
// response received -> tx next after pause time
// transmit pause time: mTxPeriod ms !
if (millis() > mTLastTx + mTxPeriod)
{
// something in the txbuffer ? -> send Tx data out
if(mTxBufIdx > 0)
{
#if __debug__ > 9
Serial.print("Send bytes: ");
for(int i=0; i < mTxBufIdx; i++)
{
sprintf( tmpBuf, "%02X ",mTxBuf[i] );
Serial.print(tmpBuf);
}
Serial.print("\n");
#endif
mSerial->write( mTxBuf, mTxBufIdx);
mTxBufIdx=0;
mResponse = Data;
mTLastTx = millis(); // wait from here PERIOD_TX_PAUSE for next transmits
#if __debug__ > 2
Serial.print("Tx Buf send\n");
#endif
}
else
{
// prioritize queued register writes against updating Hregs
// any command queued ?
if( !mTxRingBuffer.IsEmpty() )
{
setHRegFromBuf();
}
else
{
// update all HReg
if(!(mOptions & XY6020_OPT_NO_HREG_UPDATE))
SendReadHReg(0, NB_HREGS-1 );
}
}
}
}
// answer timeout detection
if (millis() > mTO + 10)
{
mTO = millis();
if(mResponse == None)
mCntTO= PERIOD_TIMEOUT_RESPONSE;
else
{
if(mCntTO>0)
mCntTO--;
else
{
// TIME OUT
#if __debug__ > 1
Serial.print(F("\n\n - - TIMEOUT - - *********************************************************************************************************\n"));
//delay(10000);
#endif
mResponse= None;
// reset memory redirection
mMemory=255;
// dummy increment frame counter to release any blocked waiting loop
mRxFrameCnt++;
};
}
}
};
void xy6020l::SendReadHReg( word startReg, word nbRegs)
{
// tx buffer free?
if( mTxBufIdx == 0 )
{
mTxBuf[0]= mAdr;
mTxBuf[1]= 0x03;
mTxBuf[2]= startReg >> 8;
mTxBuf[3]= startReg & 0xFF;
mTxBuf[4]= nbRegs >> 8;
mTxBuf[5]= nbRegs & 0xFF;
CRCModBus(6);
mTxBufIdx=8;
}
}
void xy6020l::SetMemory(tMemory& mem )
{
if( mem.Nr<10)
{
mMemory= mem.Nr;
/** @todo: check memcpy for fast copy */
mMem[HREG_IDX_M_VSET] = mem.VSet;
mMem[HREG_IDX_M_ISET] = mem.ISet;
mMem[HREG_IDX_M_SLVP] = mem.sLVP;
mMem[HREG_IDX_M_SOVP] = mem.sOVP;
mMem[HREG_IDX_M_SOCP] = mem.sOCP;
mMem[HREG_IDX_M_SOPP] = mem.sOPP;
mMem[HREG_IDX_M_SOHPH]= mem.sOHPh;
mMem[HREG_IDX_M_SOHPM]= mem.sOHPm;
mMem[HREG_IDX_M_SOAHL]= (word)(mem.sOAH & 0xFFFF);
mMem[HREG_IDX_M_SOAHH]= (word)(mem.sOAH >> 16);
mMem[HREG_IDX_M_SOWHL]= (word)(mem.sOWH & 0xFFFF);
mMem[HREG_IDX_M_SOWHH]= (word)(mem.sOWH >> 16);
mMem[HREG_IDX_M_SOTP]= mem.sOTP;
mMem[HREG_IDX_M_SINI]= mem.sINI;
// queue cmd for memory write
// only 1 memory write at 1 time !
mTxRingBuffer.AddTx( HREG_IDX_M0 + mem.Nr * HREG_IDX_M_OFFSET, 0 );
}
}
bool xy6020l::GetMemory(tMemory* pMem)
{
bool retVal= false;
switch(mMemoryState)
{
case Send:
if(pMem!= nullptr && (pMem->Nr < 10) )
{
mMemory= pMem->Nr;
SendReadHReg( HREG_IDX_M0 + pMem->Nr * HREG_IDX_M_OFFSET, NB_MEMREGS);
mMemoryState = Wait;
mMemoryLastFrame= mRxFrameCnt;
}
break;
case Wait:
if( mMemoryLastFrame != mRxFrameCnt)
{
//
pMem->VSet = mMem[HREG_IDX_M_VSET];
pMem->ISet = mMem[HREG_IDX_M_ISET];
pMem->sLVP = mMem[HREG_IDX_M_SLVP];
pMem->sOVP = mMem[HREG_IDX_M_SOVP];
pMem->sOCP = mMem[HREG_IDX_M_SOCP];
pMem->sOPP = mMem[HREG_IDX_M_SOPP];
pMem->sOHPh= mMem[HREG_IDX_M_SOHPH];
pMem->sOHPm= mMem[HREG_IDX_M_SOHPM];
pMem->sOAH = mMem[HREG_IDX_M_SOAHL] | ((unsigned long)mMem[HREG_IDX_M_SOAHH])<<16;
pMem->sOWH = mMem[HREG_IDX_M_SOWHL] | ((unsigned long)mMem[HREG_IDX_M_SOWHH])<<16;
pMem->sOTP = mMem[HREG_IDX_M_SOTP];
pMem->sINI = mMem[HREG_IDX_M_SINI];
mMemory = 255;
mMemoryState= Send;
retVal = true;
}
break;
}
return retVal;
}
bool xy6020l::setHReg(byte nr, word value)
{
bool retVal=false;
// tx buffer free?
if(mTxBufIdx == 0)
{
mTxBuf[0]= mAdr;
mTxBuf[1]= 0x06;
mTxBuf[2]= 0;
mTxBuf[3]= nr;
mTxBuf[4]= value >> 8;
mTxBuf[5]= value & 0xFF;
CRCModBus(6);
mTxBufIdx=8;
retVal= true;
}
return (retVal);
}
bool xy6020l::setHRegFromBuf()
{
bool retVal=false;
txRingEle txEle;
int iMem;
// buffer filled ?
if( !mTxRingBuffer.IsEmpty())
{
if(mTxRingBuffer.GetTx(txEle))
{
// check if register needs to be updated at all -> skip transfer to reduce update period of HRegs
if( !( mOptions & XY6020_OPT_SKIP_SAME_HREG_VALUE ) ||
( hRegs[txEle.mHregIdx] != txEle.mValue ) )
{
mTxBuf[0]= mAdr;
mTxBuf[1]= 0x06;
mTxBuf[2]= 0;
mTxBuf[3]= txEle.mHregIdx;
if(txEle.mHregIdx < HREG_IDX_M0)
{
// "normal" hregs
mTxBuf[4]= txEle.mValue >> 8;
mTxBuf[5]= txEle.mValue & 0xFF;
CRCModBus(6);
mTxBufIdx=8;
}
else {
// memory set HRegs
setMemoryRegs(txEle.mHregIdx);
}
}
else
{
#if __debug__ > 2
Serial.print(F("\nSkip HReg Update!\n"));
#endif
}
// else { send nothing };
retVal= true;
}
}
return (retVal);
}
void xy6020l::CRCModBus(int datalen)
{
word crc = 0xFFFF;
for (int pos = 0; pos < datalen; pos++)
{
crc ^= mTxBuf[pos];
for (int i = 8; i != 0; i--)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
crc >>= 1;
}
}
mTxBuf[datalen] = (byte)(crc & 0xFF);
mTxBuf[datalen + 1] = (byte)((crc >> 8) & 0xFF);
}
bool xy6020l::setSlaveAdd( word add)
{
bool retVal= true;
if( setHReg(HREG_IDX_SLAVE_ADD, add & (word)0x00FF ) )
{
// change address only if command could be places in tx buffer !
//mAdr= add;
}
else
retVal=false;
return retVal;
};
void xy6020l::setMemoryRegs(byte HRegIdx)
{
int iMem;
// buffer filled ?
mTxBuf[0]= mAdr;
mTxBuf[1]= 16;
// start address
mTxBuf[2]= 0;
mTxBuf[3]= HRegIdx;
// number of regs to write
mTxBuf[4]= 0;
mTxBuf[5]= 14;
// bytes to write
mTxBuf[6]= 2* 14;
// memory set HRegs
// register are cached in mMem[] array !
for(iMem=0; iMem<NB_MEMREGS; iMem++)
{
mTxBuf[7+iMem*2]= mMem[iMem] >> 8;
mTxBuf[8+iMem*2]= mMem[iMem] & 0xFF;
}
CRCModBus(9+14*2);
mTxBufIdx=11+14*2;
}
void xy6020l::PrintMemory(tMemory& mem)
{
char tmpBuf[50];
Serial.print(F("\nList Memory Content:"));
sprintf( tmpBuf, "\nNr: %d ", mem.Nr ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nV-SET = %d (Voltage setting)", mem.VSet ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nI-SET = %d (Current setting)", mem.ISet ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-LVP = %d (Low voltage protection value)", mem.sLVP ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OVP = %d (Overvoltage protection value)", mem.sOVP ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OCP = %d (Overcurrent protection value)", mem.sOCP ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OPP = %d (Over power protection value)", mem.sOPP ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OHP_H = %d (Maximum output time - hours)", mem.sOHPh ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OHP_M = %d (Maximum output time - minutes)", mem.sOHPm ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OAH = %d (Maximum output charge Ah)", mem.sOAH ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OWH = %d (Maximum output energy Wh)", mem.sOWH ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-OTP = %d (Over temperature protection)", mem.sOTP ); Serial.print(tmpBuf);
sprintf( tmpBuf, "\nS-INI = %d (Power-on output switch)", mem.sINI ); Serial.print(tmpBuf);
Serial.print(F("\n"));
}

296
ESP32_XY6020L_BUS/xy6020l.h Normal file
View File

@@ -0,0 +1,296 @@
/**
* @file xy6020l.h
* @brief UART control access to XY6020L DCDC
*
* This library provides an embedded and simplified ModBus implementation.
* The data type of interfaces to physical values are in decimal, scaled almost in 0.01.
*
* Tested only on a Arduino Pro Micro clone from China
*
* Special thanks to user g-radmac for his discovery of the UART protocol!
* References:
* https://forum.allaboutcircuits.com/threads/exploring-programming-a-xy6020l-power-supply-via-modbus.197022/
* https://www.simplymodbus.ca/FAQ.htm
*
*
* @author Jens Gleissberg
* @date 2024
* @license GNU Lesser General Public License v3.0 or later
*
*/
#ifndef xy6020l_h
#define xy6020l_h
#include "Arduino.h"
// the XY6020 provides 31 holding registers
#define NB_HREGS 31
#define NB_MEMREGS 14
// Holding Register index
// set voltage
#define HREG_IDX_CV 0
// set current
#define HREG_IDX_CC 1
// actual voltage 0,01 V
#define HREG_IDX_ACT_V 2
// actual current 0,01 A
#define HREG_IDX_ACT_C 3
// actual output power 0,1 W
#define HREG_IDX_ACT_P 4
// input voltage 0,01 V
#define HREG_IDX_IN_V 5
// output charge 0,001 Ah
#define HREG_IDX_OUT_CHRG 6
#define HREG_IDX_OUT_CHRG_HIGH 7
// output energy 0,001 Wh
#define HREG_IDX_OUT_ENERGY 8
#define HREG_IDX_OUT_ENERGY_HIGH 9
// on time [h] ??
#define HREG_IDX_ON_HOUR 0x0A
// on time [min]
#define HREG_IDX_ON_MIN 0x0B
// on time [s]
#define HREG_IDX_ON_SEC 0x0C
// temperature 0,1 °C / Fahrenheit ?
#define HREG_IDX_TEMP 0x0D
#define HREG_IDX_TEMP_EXD 0x0E
// key lock changes
#define HREG_IDX_LOCK 0x0F
#define HREG_IDX_PROTECT 0x10
#define HREG_IDX_CVCC 0x11
// output on
#define HREG_IDX_OUTPUT_ON 0x12
#define HREG_IDX_FC 0x13
#define HREG_IDX_MODEL 0x16
#define HREG_IDX_VERSION 0x17
#define HREG_IDX_SLAVE_ADD 0x18
#define HREG_IDX_BAUDRATE 0x19
#define HREG_IDX_TEMP_OFS 0x1A
#define HREG_IDX_TEMP_EXT_OFS 0x1B
#define HREG_IDX_MEMORY 0x1D
// Memory register
#define HREG_IDX_M0 0x50
#define HREG_IDX_M_OFFSET 0x10
#define HREG_IDX_M_VSET 0
#define HREG_IDX_M_ISET 1
#define HREG_IDX_M_SLVP 2
#define HREG_IDX_M_SOVP 3
#define HREG_IDX_M_SOCP 4
#define HREG_IDX_M_SOPP 5
#define HREG_IDX_M_SOHPH 6
#define HREG_IDX_M_SOHPM 7
#define HREG_IDX_M_SOAHL 8
#define HREG_IDX_M_SOAHH 9
#define HREG_IDX_M_SOWHL 10
#define HREG_IDX_M_SOWHH 11
#define HREG_IDX_M_SOTP 12
#define HREG_IDX_M_SINI 13
#define TX_RING_BUFFER_SIZE 16
typedef struct {
byte mHregIdx;
word mValue;
} txRingEle;
class TxRingBuffer
{
private:
txRingEle* mpIn;
txRingEle* mpOut;
int mIn;
txRingEle mTxBuf[TX_RING_BUFFER_SIZE];
public:
TxRingBuffer();
bool IsEmpty() { return (mIn<1);};
bool IsFull() { return (mIn>=TX_RING_BUFFER_SIZE);}
bool AddTx(txRingEle* pTxEle);
bool AddTx(byte hRegIdx, word value);
bool GetTx(txRingEle& pTxEle);
};
typedef struct {
byte Nr;
word VSet;
word ISet;
word sLVP;
word sOVP;
word sOCP;
word sOPP;
word sOHPh;
word sOHPm;
unsigned long sOAH;
unsigned long sOWH;
word sOTP;
word sINI;
} tMemory;
/**
* @class xy6020l
* @brief Class for controlling the XY6020L DCDC converter
*/
/** @brief option flags */
#define XY6020_OPT_SKIP_SAME_HREG_VALUE 1
#define XY6020_OPT_NO_HREG_UPDATE 2
class xy6020l
{
public:
/**
* @brief Constructor requires an interface to serial port
* @param serial Stream object reference (i.e., )
* @param adr slave address of the xy device, can be change by setSlaveAdd command
* @param txPeriod minimum period to wait for next tx message, at times < 50 ms the XY6020 does not send answers
*/
xy6020l(Stream& serial, byte adr=1, byte txPeriod=50, byte options=XY6020_OPT_SKIP_SAME_HREG_VALUE );
/**
* @brief Task method that must be called in loop() function of the main program cyclically.
* It automatically triggers the reading of the Holding Registers each PERIOD_READ_ALL_HREGS ms.
*/
void task(void);
/** @brief requests to read all Hold Registers, reception from XY6020 can be checked via HRegUpdated and data access via read/get methods
@return false if tx buffer is full
*/
bool ReadAllHRegs(void);
/** @brief true if the Hold Regs are read after read all register command, asynchron access */
bool HRegUpdated(void);
/// @name XY6020L application layer: HReg register access
/// @{
//
/** @brief voltage setpoint, LSB: 0.01 V , R/W */
word getCV(void) { return (word)hRegs[ HREG_IDX_CV]; };
bool setCV( word cv) { return mTxRingBuffer.AddTx(HREG_IDX_CV, cv);};
/** @brief constant current setpoint, LSB: 0.01 A , R/W */
word getCC() { return (word)hRegs[ HREG_IDX_CC]; };
bool setCC( word cc) { return mTxRingBuffer.AddTx(HREG_IDX_CC, cc);};
/** @brief actual input voltage , LSB: 0.01 V, readonly */
word getInV() { return (word)hRegs[ HREG_IDX_IN_V ]; };
/** @brief actual voltage at output, LSB: 0.01 V, readonly */
word getActV() { return (word)hRegs[ HREG_IDX_ACT_V]; };
/** @brief actual current at output, LSB: 0.01 A, readonly */
word getActC() { return (word)hRegs[ HREG_IDX_ACT_C]; };
/** @brief actual power at output, LSB: 0.01 W, readonly */
word getActP() { return (word)hRegs[ HREG_IDX_ACT_P]; };
/** @brief actual charge from output, LSB: 0.001 Ah, readonly, with high word because not tested yet */
word getCharge() { return (word)hRegs[ HREG_IDX_OUT_CHRG ] ; };
/** @brief actual energy provided from output, LSB: 0.001 Wh, readonly, with high word because not tested yet */
word getEnergy() { return (word)hRegs[ HREG_IDX_OUT_ENERGY ] ; };
/** @brief actual output time, LSB: 1 h, readonly */
word getHour() { return (word)hRegs[ HREG_IDX_ON_HOUR ] ; };
/** @brief actual output time, LSB: 1 min, readonly */
word getMin() { return (word)hRegs[ HREG_IDX_ON_MIN ] ; };
/** @brief actual output time, LSB: 1 secs, readonly */
word getSec() { return (word)hRegs[ HREG_IDX_ON_SEC ] ; };
/** @brief dcdc temperature, LSB: 0.1°C/F, readonly */
word getTemp() { return (word)hRegs[ HREG_IDX_TEMP ] ; };
/** @brief external temperature, LSB: 0.1°C/F, readonly */
word getTempExt() { return (word)hRegs[ HREG_IDX_TEMP_EXD ]; };
/** @brief lock switch, true = on, R/W */
bool getLockOn() { return hRegs[ HREG_IDX_LOCK]>0?true:false; };
bool setLockOn(bool onState) { return mTxRingBuffer.AddTx(HREG_IDX_LOCK, onState?1:0);};
/** @brief lock switch, true = on, R/W */
word getProtect() { return hRegs[ HREG_IDX_PROTECT]; };
bool setProtect(word state) { return setHReg(HREG_IDX_PROTECT, state );};
/** @brief returns if CC is active , true = on, read only */
bool isCC() { return hRegs[ HREG_IDX_CVCC]>0?true:false; };
/** @brief returns if CV is active , true = on, read only */
bool isCV() { return hRegs[ HREG_IDX_CVCC]<1?true:false; };
/** @brief output switch, true = on, R/W */
bool getOutputOn() { return hRegs[ HREG_IDX_OUTPUT_ON]>0?true:false; };
bool setOutput(bool onState) { return mTxRingBuffer.AddTx(HREG_IDX_OUTPUT_ON, onState?1:0);};
/** @brief set the temperature unit to °C, read not implemended because no use */
bool setTempAsCelsius(void) { return setHReg(HREG_IDX_FC, 0);};
/** @brief set the temperature unit to Fahrenheit, read not implemended because no use */
bool setTempAsFahrenheit(void) { return setHReg(HREG_IDX_FC, 1);};
/** @brief returns the product number, readonly */
word getModel(void) { return (word)hRegs[ HREG_IDX_MODEL ] ; };
/** @brief returns the version number, readonly */
word getVersion(void) { return (word)hRegs[ HREG_IDX_VERSION ] ; };
/** @brief slave address, R/W, take effect after reset of XY6020L ! */
word getSlaveAdd(void) { return (word)hRegs[ HREG_IDX_SLAVE_ADD]; };
bool setSlaveAdd( word add);
/** @brief baud rate , W, no read option because on use
@todo: provide enum for rate number to avoid random/unsupported number */
bool setBaudrate( word rate) { return setHReg(HREG_IDX_BAUDRATE, rate);};
/** @brief internal temperature offset, R/W */
word getTempOfs(void) { return (word)hRegs[ HREG_IDX_TEMP_OFS]; };
bool setTempOfs( word tempOfs) { return setHReg(HREG_IDX_TEMP_OFS, tempOfs );};
/** @brief external temperature offset, R/W */
word getTempExtOfs(void) { return (word)hRegs[ HREG_IDX_TEMP_EXT_OFS]; };
bool setTempExtOfs( word tempOfs) { return setHReg(HREG_IDX_TEMP_EXT_OFS, tempOfs );};
/** @brief Presets, R/W */
word getPreset(void) { return (word)hRegs[ HREG_IDX_MEMORY]; };
bool setPreset( word preset) { return setHReg(HREG_IDX_MEMORY, preset );};
/// @}
bool TxBufEmpty(void) { return ((mTxBufIdx<=0)&&(mTxRingBuffer.IsEmpty()));};
void SetMemory(tMemory& mem);
bool GetMemory(tMemory* pMem);
void PrintMemory(tMemory& mem);
private:
byte mAdr;
byte mOptions;
Stream* mSerial;
byte mRxBufIdx;
unsigned char mRxBuf[60];
byte mRxState;
bool mRxThis;
byte mRxSize;
word mRxFrameCnt;
word mRxFrameCntLast;
byte mLastExceptionCode;
enum Response { None, Confirm, Data };
Response mResponse;
/** @brief rx answer belongs to memory request data
* M0..M9 -> 0..9 ; 255 no memory */
byte mMemory;
long mTs;
long mTO;
long mTLastTx;
byte mCntTO;
byte mTxPeriod;
int mTxBufIdx;
unsigned char mTxBuf[40];
TxRingBuffer mTxRingBuffer;
/** @brief buffer to cache hold regs after reading them at once and to check if update needed for writting regs */
word hRegs[NB_HREGS];
/** @brief 1 cache for memory register */
word mMem[NB_MEMREGS];
enum MemoryState { Send, Wait };
MemoryState mMemoryState;
word mMemoryLastFrame;
bool setHReg(byte nr, word value);
bool setHRegFromBuf(void);
void CRCModBus(int datalen);
void RxDecodeExceptions(byte cnt);
bool RxDecode03( byte cnt);
bool RxDecode06( byte cnt);
bool RxDecode16( byte cnt);
void SendReadHReg( word startReg, word nbRegs);
void setMemoryRegs(byte HRegIdx);
};
#endif