Implement HoymilesRadio_CMT

This commit is contained in:
Thomas Basler 2023-03-07 18:48:24 +01:00
parent 8927b8374a
commit 90c689a41a
7 changed files with 693 additions and 193 deletions

View File

@ -4,212 +4,124 @@
#include "cmt2300a_defs.h" #include "cmt2300a_defs.h"
#include <stdint.h> #include <stdint.h>
/* [CMT Bank] */ /* [CMT Bank] with RSSI offset of +- 0 (and 13dBm) */
static uint8_t g_cmt2300aCmtBank_default[CMT2300A_CMT_BANK_SIZE] = { static uint8_t g_cmt2300aCmtBank[CMT2300A_CMT_BANK_SIZE] = {
0x00, 0x00,
0x66, 0x66,
0xEC, 0xEC,
0x1D, 0x1C,
0x70, 0x70,
0x80, 0x80,
0x14, 0x14,
0x08, 0x08,
0x91, 0x11,
0x02, 0x02,
0x02, 0x02,
0xD0, 0x00,
};
/* [CMT Bank] with RSSI offset of +- 0 and 13dBm*/
static uint8_t g_cmt2300aCmtBank_RSSI_0[CMT2300A_CMT_BANK_SIZE] = {
0x00,
0x66,
0xEC,
0x1C,
0x70,
0x80,
0x14,
0x08,
0x11,
0x02,
0x02,
0x00,
}; };
/* [System Bank] */ /* [System Bank] */
static uint8_t g_cmt2300aSystemBank[CMT2300A_SYSTEM_BANK_SIZE] = { static uint8_t g_cmt2300aSystemBank[CMT2300A_SYSTEM_BANK_SIZE] = {
0xAE, 0xAE,
0xE0, 0xE0,
0x35, 0x35,
0x00, 0x00,
0x00, 0x00,
0xF4, 0xF4,
0x10, 0x10,
0xE2, 0xE2,
0x42, 0x42,
0x20, 0x20,
0x0C, 0x0C,
0x81, 0x81,
};
/* [Frequency Bank] 868 MHz (default) */
static uint8_t g_cmt2300aFrequencyBank_868[CMT2300A_FREQUENCY_BANK_SIZE] = {
0x42,
0xCF,
0xA7,
0x8C,
0x42,
0xC4,
0x4E,
0x1C,
};
/* [Frequency Bank] 863 MHz (EU) */
static uint8_t g_cmt2300aFrequencyBank_863[CMT2300A_FREQUENCY_BANK_SIZE] = {
0x42,
0x6D,
0x80,
0x86,
0x42,
0x62,
0x27,
0x16,
}; };
/* [Frequency Bank] 860 MHz */ /* [Frequency Bank] 860 MHz */
static uint8_t g_cmt2300aFrequencyBank_860[CMT2300A_FREQUENCY_BANK_SIZE] = { static uint8_t g_cmt2300aFrequencyBank[CMT2300A_FREQUENCY_BANK_SIZE] = {
0x42, 0x42,
0x32, 0x32,
0xCF, 0xCF,
0x82, 0x82,
0x42, 0x42,
0x27, 0x27,
0x76, 0x76,
0x12, 0x12,
}; };
/* [Data Rate Bank] */ /* [Data Rate Bank] */
static uint8_t g_cmt2300aDataRateBank[CMT2300A_DATA_RATE_BANK_SIZE] = { static uint8_t g_cmt2300aDataRateBank[CMT2300A_DATA_RATE_BANK_SIZE] = {
0xA6, 0xA6,
0xC9, 0xC9,
0x20, 0x20,
0x20, 0x20,
0xD2, 0xD2,
0x35, 0x35,
0x0C, 0x0C,
0x0A, 0x0A,
0x9F, 0x9F,
0x4B, 0x4B,
0x29, 0x29,
0x29, 0x29,
0xC0, 0xC0,
0x14, 0x14,
0x05, 0x05,
0x53, 0x53,
0x10, 0x10,
0x00, 0x00,
0xB4, 0xB4,
0x00, 0x00,
0x00, 0x00,
0x01, 0x01,
0x00, 0x00,
0x00, 0x00,
};
/* [Baseband Bank] - default */
static uint8_t g_cmt2300aBasebandBank_default[CMT2300A_BASEBAND_BANK_SIZE] = {
0x12,
0x1E,
0x00,
0xAA,
0x06,
0x00,
0x00,
0x00,
0x00,
0xD6,
0xD5,
0xD4,
0x2D,
0x01,
0x1F,
0x00,
0x00,
0x00,
0x00,
0x00,
0xC3,
0x00,
0x00,
0x60,
0xFF,
0x00,
0x00,
0x1F,
0x10,
}; };
/* [Baseband Bank] - EU */ /* [Baseband Bank] - EU */
static uint8_t g_cmt2300aBasebandBank_EU[CMT2300A_BASEBAND_BANK_SIZE] = { static uint8_t g_cmt2300aBasebandBank[CMT2300A_BASEBAND_BANK_SIZE] = {
0x12, 0x12,
0x1E, 0x1E,
0x00, 0x00,
0xAA, 0xAA,
0x06, 0x06,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x48, 0x48,
0x5A, 0x5A,
0x48, 0x48,
0x4D, 0x4D,
0x01, 0x01,
0x1D, // default should be 0x1F (32 bytes length), but 0x45 is 0x01 -> variable length, so this value is ignored 0x1F,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0xC3, 0xC3,
0x00, 0x00,
0x00, 0x00,
0x60, 0x60,
0xFF, 0xFF,
0x00, 0x00,
0x00, 0x00,
0x1F, 0x1F,
0x10, 0x10,
};
/* [Tx Bank] 20 dBm */
static uint8_t g_cmt2300aTxBank_20dBm[CMT2300A_TX_BANK_SIZE] = {
0x70,
0x4D,
0x06,
0x00,
0x07,
0x50,
0x00,
0x8A,
0x18,
0x3F,
0x7F,
}; };
/* [Tx Bank] 13 dBm */ /* [Tx Bank] 13 dBm */
static uint8_t g_cmt2300aTxBank_13dBm[CMT2300A_TX_BANK_SIZE] = { static uint8_t g_cmt2300aTxBank[CMT2300A_TX_BANK_SIZE] = {
0x70, 0x70,
0x4D, 0x4D,
0x06, 0x06,
0x00, 0x00,
0x07, 0x07,
0x50, 0x50,
0x00, 0x00,
0x42, 0x42,
0x0C, 0x0C,
0x3F, 0x3F,
0x7F, 0x7F,
}; };
#endif #endif

View File

@ -24,18 +24,22 @@ void HoymilesClass::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pin
_pollInterval = 0; _pollInterval = 0;
_radioNrf.reset(new HoymilesRadio_NRF()); _radioNrf.reset(new HoymilesRadio_NRF());
_radioNrf->init(initialisedSpiBus, pinCE, pinIRQ); _radioNrf->init(initialisedSpiBus, pinCE, pinIRQ);
_radioCmt.reset(new HoymilesRadio_CMT());
_radioCmt->init();
} }
void HoymilesClass::loop() void HoymilesClass::loop()
{ {
HOY_SEMAPHORE_TAKE(); HOY_SEMAPHORE_TAKE();
_radioNrf->loop(); _radioNrf->loop();
_radioCmt->loop();
if (getNumInverters() > 0) { if (getNumInverters() > 0) {
if (millis() - _lastPoll > (_pollInterval * 1000)) { if (millis() - _lastPoll > (_pollInterval * 1000)) {
static uint8_t inverterPos = 0; static uint8_t inverterPos = 0;
if (_radioNrf->isIdle()) { if (_radioNrf->isIdle() && _radioCmt->isIdle()) {
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos); std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
if (iv != nullptr) { if (iv != nullptr) {
_messageOutput->print("Fetch inverter: "); _messageOutput->print("Fetch inverter: ");
@ -89,11 +93,11 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, u
{ {
std::shared_ptr<InverterAbstract> i = nullptr; std::shared_ptr<InverterAbstract> i = nullptr;
if (HMS_4CH::isValidSerial(serial)) { if (HMS_4CH::isValidSerial(serial)) {
i = std::make_shared<HMS_4CH>(_radioNrf.get(), serial); i = std::make_shared<HMS_4CH>(_radioCmt.get(), serial);
} else if (HMS_2CH::isValidSerial(serial)) { } else if (HMS_2CH::isValidSerial(serial)) {
i = std::make_shared<HMS_2CH>(_radioNrf.get(), serial); i = std::make_shared<HMS_2CH>(_radioCmt.get(), serial);
} else if (HMS_1CH::isValidSerial(serial)) { } else if (HMS_1CH::isValidSerial(serial)) {
i = std::make_shared<HMS_1CH>(_radioNrf.get(), serial); i = std::make_shared<HMS_1CH>(_radioCmt.get(), serial);
} else if (HM_4CH::isValidSerial(serial)) { } else if (HM_4CH::isValidSerial(serial)) {
i = std::make_shared<HM_4CH>(_radioNrf.get(), serial); i = std::make_shared<HM_4CH>(_radioNrf.get(), serial);
} else if (HM_2CH::isValidSerial(serial)) { } else if (HM_2CH::isValidSerial(serial)) {

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "HoymilesRadio_NRF.h" #include "HoymilesRadio_NRF.h"
#include "HoymilesRadio_CMT.h"
#include "inverters/InverterAbstract.h" #include "inverters/InverterAbstract.h"
#include "types.h" #include "types.h"
#include <Print.h> #include <Print.h>
@ -35,6 +36,7 @@ public:
private: private:
std::vector<std::shared_ptr<InverterAbstract>> _inverters; std::vector<std::shared_ptr<InverterAbstract>> _inverters;
std::unique_ptr<HoymilesRadio_NRF> _radioNrf; std::unique_ptr<HoymilesRadio_NRF> _radioNrf;
std::unique_ptr<HoymilesRadio_CMT> _radioCmt;
SemaphoreHandle_t _xSemaphore; SemaphoreHandle_t _xSemaphore;

View File

@ -0,0 +1,495 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "HoymilesRadio_CMT.h"
#include "Hoymiles.h"
#include "crc.h"
#include <FunctionalInterrupt.h>
#include <cmt2300a.h>
#include <cmt2300a_params.h>
#define CMT2300A_ONE_STEP_SIZE 2500 // frequency channel step size for fast frequency hopping operation: One step size is 2.5 kHz.
#define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset
#define HOY_BASE_FREQ 860000000 // Hoymiles base frequency for CMD56 channels is 860.00 MHz
#define HOY_BOOT_FREQ 868000000 // Hoymiles boot/init frequency after power up inverter
String HoymilesRadio_CMT::cmtChToFreq(const uint8_t channel)
{
return String((HOY_BASE_FREQ + (cmtBaseChOff860 + channel) * FH_OFFSET * CMT2300A_ONE_STEP_SIZE) / 1000000.0, 2) + " MHz";
}
void HoymilesRadio_CMT::cmtSwitchChannel(const uint8_t channel)
{
yield();
CMT2300A_SetFrequencyChannel(channel);
yield();
cmtActualCh = channel;
// Hoymiles.getMessageOutput()->println("[cmtSwitchChannel] switched channel to " + cmtGetActFreq());
}
uint8_t HoymilesRadio_CMT::cmtFreqToChan(const String func_name, const String var_name, const uint32_t freq_kHz)
{
if ((freq_kHz % 250) != 0) {
Hoymiles.getMessageOutput()->println(func_name + " " + var_name + " " + String(freq_kHz / 1000.0, 3) + " MHz is not divisible by 250 kHz!");
return 0xFF; // ERROR
}
const uint32_t min_Freq_kHz = (HOY_BASE_FREQ + (cmtBaseChOff860 >= 1 ? cmtBaseChOff860 : 1) * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000; // frequency can not be lower than actual initailized base freq
const uint32_t max_Freq_kHz = (HOY_BASE_FREQ + 0xFE * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000; // =923500, 0xFF does not work
if (freq_kHz < min_Freq_kHz || freq_kHz > max_Freq_kHz) {
Hoymiles.getMessageOutput()->println(func_name + " " + var_name + " " + String(freq_kHz / 1000.0, 2) + " MHz is out of Hoymiles/CMT range! (" + String(min_Freq_kHz / 1000.0, 2) + " MHz - " + String(max_Freq_kHz / 1000.0, 2) + " MHz)");
return 0xFF; // ERROR
}
if (freq_kHz < 863000 || freq_kHz > 870000)
Hoymiles.getMessageOutput()->println(func_name + " !!! caution: " + var_name + " " + String(freq_kHz / 1000.0, 2) + " MHz is out of EU legal range! (863 - 870 MHz)");
return (freq_kHz * 1000 - HOY_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET - cmtBaseChOff860; // frequency to channel
}
bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_freq_kHz)
{
const uint8_t toChannel = cmtFreqToChan("[cmtSwitchDtuFreq]", "to_freq_kHz", to_freq_kHz);
if (toChannel == 0xFF)
return false;
cmtSwitchChannel(toChannel);
return true;
}
bool HoymilesRadio_CMT::cmtConfig(void)
{
#ifdef ENABLE_ANTENNA_SWITCH
/* If you enable antenna switch, GPIO1/GPIO2 will output RX_ACTIVE/TX_ACTIVE,
and it can't output INT1/INT2 via GPIO1/GPIO2 */
CMT2300A_EnableAntennaSwitch(0);
#else
/* Config GPIOs */
CMT2300A_ConfigGpio(
CMT2300A_GPIO3_SEL_INT2);
/* Config interrupt */
CMT2300A_ConfigInterrupt(
CMT2300A_INT_SEL_TX_DONE, /* Config INT1 */
CMT2300A_INT_SEL_PKT_OK /* Config INT2 */
);
#endif
/* Enable interrupt */
CMT2300A_EnableInterrupt(
CMT2300A_MASK_TX_DONE_EN | CMT2300A_MASK_PREAM_OK_EN | CMT2300A_MASK_SYNC_OK_EN | CMT2300A_MASK_CRC_OK_EN | CMT2300A_MASK_PKT_DONE_EN);
/* Disable low frequency OSC calibration */
// CMT2300A_EnableLfosc(FALSE);
CMT2300A_SetFrequencyStep(100); // set FH_OFFSET to 100 (frequency = base freq + 2.5kHz*FH_OFFSET*FH_CHANNEL)
/* Use a single 64-byte FIFO for either Tx or Rx */
CMT2300A_EnableFifoMerge(true);
/* Go to sleep for configuration to take effect */
if (!CMT2300A_GoSleep())
return false; // CMT2300A not switched to sleep mode!
return true;
}
bool HoymilesRadio_CMT::cmtSwitchInvAndDtuFreq(const uint64_t inv_serial, const uint32_t from_freq_kHz, const uint32_t to_freq_kHz)
{
const uint8_t fromChannel = cmtFreqToChan("[cmtSwitchInvAndDtuFreq]", "from_freq_kHz", from_freq_kHz);
const uint8_t toChannel = cmtFreqToChan("[cmtSwitchInvAndDtuFreq]", "to_freq_kHz", to_freq_kHz);
if (fromChannel == 0xFF || toChannel == 0xFF)
return false;
cmtSwitchChannel(fromChannel);
cmtTx56toCh = toChannel;
// CMD56 for inverter frequency/channel switch
cmtTxBuffer[0] = 0x56;
// cmtTxBuffer[1-4] = last inverter serial
// cmtTxBuffer[5-8] = dtu serial
cmtTxBuffer[9] = 0x02;
cmtTxBuffer[10] = 0x15;
cmtTxBuffer[11] = 0x21;
cmtTxBuffer[12] = (uint8_t)(cmtBaseChOff860 + toChannel);
cmtTxBuffer[13] = 0x14;
cmtTxBuffer[14] = crc8(cmtTxBuffer, 14);
Hoymiles.getMessageOutput()->print("TX CMD56 ");
Hoymiles.getMessageOutput()->print(cmtChToFreq(cmtActualCh));
Hoymiles.getMessageOutput()->print(" --> ");
dumpBuf("", cmtTxBuffer, 15);
cmtTxLength = 15;
cmtTxTimeout = 100;
cmtNextState = CMT_STATE_TX_START;
//_busyFlag = true;
return true;
}
enumCMTresult HoymilesRadio_CMT::cmtProcess(void)
{
enumCMTresult nRes = CMT_BUSY;
switch (cmtNextState) {
case CMT_STATE_IDLE: {
nRes = CMT_IDLE;
break;
}
case CMT_STATE_RX_START: {
CMT2300A_GoStby();
CMT2300A_ClearInterruptFlags();
/* Must clear FIFO after enable SPI to read or write the FIFO */
CMT2300A_EnableReadFifo();
CMT2300A_ClearRxFifo();
if (!CMT2300A_GoRx())
cmtNextState = CMT_STATE_ERROR;
else
cmtNextState = CMT_STATE_RX_WAIT;
cmtRxTimeCount = CMT2300A_GetTickCount();
cmtRxTimeout = 200;
break;
}
case CMT_STATE_RX_WAIT: {
#ifdef ENABLE_ANTENNA_SWITCH
if (CMT2300A_MASK_PKT_OK_FLG & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG)) /* Read PKT_OK flag */
#else
if (_packetReceived) /* Read INT2, PKT_OK */
#endif
{
Hoymiles.getMessageOutput()->println("Interrupt received");
_packetReceived = false; // reset interrupt
cmtNextState = CMT_STATE_RX_DONE;
}
if ((CMT2300A_GetTickCount() - cmtRxTimeCount) > cmtRxTimeout)
cmtNextState = CMT_STATE_RX_TIMEOUT;
break;
}
case CMT_STATE_RX_DONE: {
CMT2300A_GoStby();
bool isLastFrame = false;
uint8_t state = CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
if ((state & 0x1b) == 0x1b) {
cmtRxTimeoutCnt = 0;
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
fragment_t f;
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
CMT2300A_ReadFifo(&f.len, 1); // first byte in FiFo is length
f.channel = cmtActualCh;
f.rssi = CMT2300A_GetRssiDBm();
if (f.len > MAX_RF_PAYLOAD_SIZE)
f.len = MAX_RF_PAYLOAD_SIZE;
CMT2300A_ReadFifo(f.fragment, f.len);
if (f.fragment[9] & 0x80) // last frame detection for end Rx
isLastFrame = true;
_rxBuffer.push(f);
} else {
Hoymiles.getMessageOutput()->println("Buffer full");
}
} else if ((state & 0x19) == 0x19)
Hoymiles.getMessageOutput()->println("[CMT_STATE_RX_DONE] state: " + String(state, HEX) + " (CRC_ERROR)");
else
Hoymiles.getMessageOutput()->println("[CMT_STATE_RX_DONE] wrong state: " + String(state, HEX));
CMT2300A_ClearInterruptFlags();
CMT2300A_GoSleep();
if (isLastFrame) // last frame received
cmtNextState = CMT_STATE_IDLE;
else
cmtNextState = CMT_STATE_RX_START; // receive next frame(s)
nRes = CMT_RX_DONE;
break;
}
case CMT_STATE_RX_TIMEOUT: {
CMT2300A_GoSleep();
Hoymiles.getMessageOutput()->println("RX timeout!");
cmtNextState = CMT_STATE_IDLE;
// send CMD56 after 3 Rx timeouts
if (cmtRxTimeoutCnt < 2)
cmtRxTimeoutCnt++;
else {
uint32_t invSerial = cmtTxBuffer[1] << 24 | cmtTxBuffer[2] << 16 | cmtTxBuffer[3] << 8 | cmtTxBuffer[4]; // read inverter serial from last Tx buffer
cmtSwitchInvAndDtuFreq(invSerial, HOY_BOOT_FREQ / 1000, CMT_WORK_FREQ);
}
nRes = CMT_RX_TIMEOUT;
break;
}
case CMT_STATE_TX_START: {
CMT2300A_GoStby();
CMT2300A_ClearInterruptFlags();
/* Must clear FIFO after enable SPI to read or write the FIFO */
CMT2300A_EnableWriteFifo();
CMT2300A_ClearTxFifo();
CMT2300A_WriteReg(CMT2300A_CUS_PKT15, cmtTxLength); // set Tx length
/* The length need be smaller than 32 */
CMT2300A_WriteFifo(cmtTxBuffer, cmtTxLength);
if (!(CMT2300A_ReadReg(CMT2300A_CUS_FIFO_FLAG) & CMT2300A_MASK_TX_FIFO_NMTY_FLG))
cmtNextState = CMT_STATE_ERROR;
if (!CMT2300A_GoTx())
cmtNextState = CMT_STATE_ERROR;
else
cmtNextState = CMT_STATE_TX_WAIT;
cmtTxTimeCount = CMT2300A_GetTickCount();
break;
}
case CMT_STATE_TX_WAIT: {
// #ifdef ENABLE_ANTENNA_SWITCH
if (CMT2300A_MASK_TX_DONE_FLG & CMT2300A_ReadReg(CMT2300A_CUS_INT_CLR1)) /* Read TX_DONE flag */
// #else
// if(CMT2300A_ReadGpio1()) /* Read INT1, TX_DONE */
// #endif
{
cmtNextState = CMT_STATE_TX_DONE;
}
if ((CMT2300A_GetTickCount() - cmtTxTimeCount) > cmtTxTimeout)
cmtNextState = CMT_STATE_TX_TIMEOUT;
break;
}
case CMT_STATE_TX_DONE: {
CMT2300A_ClearInterruptFlags();
CMT2300A_GoSleep();
if (cmtTx56toCh != 0xFF) {
cmtSwitchChannel(cmtTx56toCh);
cmtTx56toCh = 0xFF;
cmtNextState = CMT_STATE_IDLE;
} else
cmtNextState = CMT_STATE_RX_START; // receive answer
nRes = CMT_TX_DONE;
break;
}
case CMT_STATE_TX_TIMEOUT: {
CMT2300A_GoSleep();
Hoymiles.getMessageOutput()->println("TC timeout!");
if (cmtTx56toCh != 0xFF) {
cmtTx56toCh = 0xFF;
cmtNextState = CMT_STATE_IDLE;
}
cmtNextState = CMT_STATE_IDLE;
nRes = CMT_TX_TIMEOUT;
break;
}
case CMT_STATE_ERROR: {
CMT2300A_SoftReset();
CMT2300A_DelayMs(20);
CMT2300A_GoStby();
cmtConfig();
cmtNextState = CMT_STATE_IDLE;
nRes = CMT_ERROR;
break;
}
default:
break;
}
return nRes;
}
void HoymilesRadio_CMT::init()
{
_dtuSerial.u64 = 0;
uint8_t tmp;
CMT2300A_InitSpi();
if (!CMT2300A_Init()) {
Hoymiles.getMessageOutput()->println("CMT2300A_Init() failed!");
return;
}
/* config registers */
CMT2300A_ConfigRegBank(CMT2300A_CMT_BANK_ADDR, g_cmt2300aCmtBank, CMT2300A_CMT_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_SYSTEM_BANK_ADDR, g_cmt2300aSystemBank, CMT2300A_SYSTEM_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_FREQUENCY_BANK_ADDR, g_cmt2300aFrequencyBank, CMT2300A_FREQUENCY_BANK_SIZE); // cmtBaseChOff860 need to be changed to the same frequency for channel calculation
CMT2300A_ConfigRegBank(CMT2300A_DATA_RATE_BANK_ADDR, g_cmt2300aDataRateBank, CMT2300A_DATA_RATE_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_BASEBAND_BANK_ADDR, g_cmt2300aBasebandBank, CMT2300A_BASEBAND_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_TX_BANK_ADDR, g_cmt2300aTxBank, CMT2300A_TX_BANK_SIZE);
cmtBaseChOff860 = (860000000 - HOY_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET;
// xosc_aac_code[2:0] = 2
tmp = (~0x07) & CMT2300A_ReadReg(CMT2300A_CUS_CMT10);
CMT2300A_WriteReg(CMT2300A_CUS_CMT10, tmp | 0x02);
if (!cmtConfig()) {
Hoymiles.getMessageOutput()->println("cmtConfig() failed!");
return;
}
attachInterrupt(digitalPinToInterrupt(CMT_PIN_GPIO3), std::bind(&HoymilesRadio_CMT::handleIntr, this), RISING);
cmtSwitchDtuFreq(CMT_WORK_FREQ); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched
_ChipConnected = true;
Hoymiles.getMessageOutput()->println("CMT init successful");
}
void HoymilesRadio_CMT::loop()
{
enumCMTresult mCMTstate = cmtProcess();
if (mCMTstate != CMT_RX_DONE) { // Perform package parsing only if no packages are received
if (!_rxBuffer.empty()) {
fragment_t f = _rxBuffer.back();
if (checkFragmentCrc(&f)) {
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
if (nullptr != inv) {
// Save packet in inverter rx buffer
Hoymiles.getMessageOutput()->print("RX ");
Hoymiles.getMessageOutput()->print(cmtChToFreq(f.channel));
Hoymiles.getMessageOutput()->print(" --> ");
dumpBuf("", f.fragment, f.len);
Hoymiles.getMessageOutput()->print("| ");
Hoymiles.getMessageOutput()->print(f.rssi);
Hoymiles.getMessageOutput()->println(" dBm");
inv->addRxFragment(f.fragment, f.len);
} else {
Hoymiles.getMessageOutput()->println("Inverter Not found!");
}
} else {
Hoymiles.getMessageOutput()->println("Frame kaputt"); // ;-)
}
// Remove paket from buffer even it was corrupted
_rxBuffer.pop();
}
}
if (_busyFlag && _rxTimeout.occured()) {
Hoymiles.getMessageOutput()->println("RX Period End");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
if (nullptr != inv) {
CommandAbstract* cmd = _commandQueue.front().get();
uint8_t verifyResult = inv->verifyAllFragments(cmd);
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
Hoymiles.getMessageOutput()->println("Nothing received");
// sendLastPacketAgain();
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Retransmit timeout");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
Hoymiles.getMessageOutput()->println("Packet handling error");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult > 0) {
// Perform Retransmit
Hoymiles.getMessageOutput()->print("Request retransmit: ");
Hoymiles.getMessageOutput()->println(verifyResult);
sendRetransmitPacket(verifyResult);
} else {
// Successful received all packages
Hoymiles.getMessageOutput()->println("Success");
_commandQueue.pop();
_busyFlag = false;
}
} else {
// If inverter was not found, assume the command is invalid
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
_commandQueue.pop();
_busyFlag = false;
}
} else if (!_busyFlag) {
// Currently in idle mode --> send packet if one is in the queue
if (!_commandQueue.empty()) {
CommandAbstract* cmd = _commandQueue.front().get();
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
if (nullptr != inv) {
inv->clearRxFragmentBuffer();
sendEsbPacket(cmd);
} else {
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
_commandQueue.pop();
}
}
}
}
bool HoymilesRadio_CMT::isIdle()
{
return !_busyFlag;
}
bool HoymilesRadio_CMT::isConnected()
{
return _ChipConnected;
}
void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleIntr()
{
_packetReceived = true;
}
void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract* cmd)
{
cmd->incrementSendCount();
cmd->setRouterAddress(DtuSerial().u64);
Hoymiles.getMessageOutput()->print("TX ");
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
Hoymiles.getMessageOutput()->print(" ");
Hoymiles.getMessageOutput()->print(cmtChToFreq(cmtActualCh));
Hoymiles.getMessageOutput()->print(" --> ");
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
memcpy(cmtTxBuffer, cmd->getDataPayload(), cmd->getDataSize());
cmtTxLength = cmd->getDataSize();
cmtTxTimeout = 100;
cmtNextState = CMT_STATE_TX_START;
_busyFlag = true;
_rxTimeout.set(cmd->getTimeout());
}

View File

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HoymilesRadio.h"
#include "TimeoutHelper.h"
#include "commands/CommandAbstract.h"
#include "types.h"
#include <Arduino.h>
#include <memory>
#include <queue>
// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
/* CMT states */
typedef enum {
CMT_STATE_IDLE = 0,
CMT_STATE_RX_START,
CMT_STATE_RX_WAIT,
CMT_STATE_RX_DONE,
CMT_STATE_RX_TIMEOUT,
CMT_STATE_TX_START,
CMT_STATE_TX_WAIT,
CMT_STATE_TX_DONE,
CMT_STATE_TX_TIMEOUT,
CMT_STATE_ERROR,
} enumCMTstate;
/* CMT process function results */
typedef enum {
CMT_IDLE = 0,
CMT_BUSY,
CMT_RX_DONE,
CMT_RX_TIMEOUT,
CMT_TX_DONE,
CMT_TX_TIMEOUT,
CMT_ERROR,
} enumCMTresult;
class HoymilesRadio_CMT : public HoymilesRadio {
public:
void init();
void loop();
bool isIdle();
bool isConnected();
private:
void ARDUINO_ISR_ATTR handleIntr();
void sendEsbPacket(CommandAbstract* cmd);
volatile bool _packetReceived = false;
std::queue<fragment_t> _rxBuffer;
TimeoutHelper _rxTimeout;
bool _busyFlag = false;
bool _ChipConnected = false;
String cmtChToFreq(const uint8_t channel);
void cmtSwitchChannel(const uint8_t channel);
uint8_t cmtFreqToChan(const String func_name, const String var_name, const uint32_t freq_kHz);
bool cmtSwitchDtuFreq(const uint32_t to_freq_kHz);
bool cmtConfig(void);
bool cmtSwitchInvAndDtuFreq(const uint64_t inv_serial, const uint32_t from_freq_kHz, const uint32_t to_freq_kHz);
enumCMTresult cmtProcess(void);
enumCMTstate cmtNextState = CMT_STATE_IDLE;
uint8_t cmtTxBuffer[32];
uint8_t cmtTxLength = 0;
uint32_t cmtRxTimeout = 200;
uint32_t cmtTxTimeout = 200;
uint32_t cmtRxTimeCount = 0;
uint32_t cmtTxTimeCount = 0;
uint8_t cmtBaseChOff860; // offset from initalized CMT base frequency to Hoy base frequency in channels
uint8_t cmtActualCh; // actual used channel, should be stored per inverter und set before next Tx, if hopping is used
uint8_t cmtTx56toCh = 0xFF; // send CMD56 active to Channel xx, inactive = 0xFF
uint8_t cmtRxTimeoutCnt = 0; // Rx timeout counter !!! should be stored per inverter !!!
};

View File

@ -16,5 +16,6 @@ typedef struct {
uint8_t fragment[MAX_RF_PAYLOAD_SIZE]; uint8_t fragment[MAX_RF_PAYLOAD_SIZE];
uint8_t len; uint8_t len;
uint8_t channel; uint8_t channel;
int8_t rssi;
bool wasReceived; bool wasReceived;
} fragment_t; } fragment_t;

View File

@ -61,6 +61,7 @@ build_flags = ${env.build_flags}
-DCMT_PIN_CS=5 -DCMT_PIN_CS=5
-DCMT_PIN_FCS=4 -DCMT_PIN_FCS=4
-DCMT_PIN_GPIO3=15 -DCMT_PIN_GPIO3=15
-DCMT_WORK_FREQ=865000
[env:olimex_esp32_poe] [env:olimex_esp32_poe]