diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h new file mode 100644 index 0000000..856af93 --- /dev/null +++ b/src/knx/tp_frame.h @@ -0,0 +1,301 @@ +#pragma once + +#include "cemi_frame.h" +#include +#include +#include + +// Means that the frame is invalid +#define TP_FRAME_FLAG_INVALID 0b10000000 + +// Means that the frame is an extended frame +#define TP_FRAME_FLAG_EXTENDED 0b01000000 + +// Means that the frame has been repeated +#define TP_FRAME_FLAG_REPEATED 0b00100000 + +// Means that the frame comes from the device itself +#define TP_FRAME_FLAG_ECHO 0b00010000 + +// Means that the frame is processed by this device +#define TP_FRAME_FLAG_ADDRESSED 0b00000100 + +// Means that the frame has been acked by this device. +#define TP_FRAME_FLAG_ACKING 0b00000010 + +// Means that the frame has been acked by other (Busmontior) +#define TP_FRAME_FLAG_ACKED 0b00000001 + +class TpFrame +{ + private: + uint8_t *_data; + uint16_t _size; + uint16_t _maxSize; + uint8_t _flags = 0; + + /* + * Sets a few flags based on the control byte + */ + inline void presetFlags() + { + if (isExtended()) + addFlags(TP_FRAME_FLAG_EXTENDED); + + if (isRepeated()) + addFlags(TP_FRAME_FLAG_REPEATED); + } + + public: + /* + * Convert a CemiFrame into a TpFrame + */ + TpFrame(CemiFrame &cemiFrame) + { + _size = cemiFrame.telegramLengthtTP(); + _maxSize = cemiFrame.telegramLengthtTP(); + _data = (uint8_t *)malloc(cemiFrame.telegramLengthtTP()); + cemiFrame.fillTelegramTP(_data); + presetFlags(); + } + + /* + * Create a TpFrame with a reserved space. + * Used for incoming parsing. + */ + TpFrame(uint16_t maxSize = 263) + : _maxSize(maxSize) + { + _data = new uint8_t[_maxSize]; + _size = 0; + } + + /* + * Free the data area + */ + ~TpFrame() + { + free(_data); + } + + /* + * Add a byte at end. + * Used for incoming parsing. + */ + inline void addByte(uint8_t byte) + { + if (!isFull()) + { + _data[_size] = byte; + _size++; + } + + // Read meta data for flags + if (_size == 1) + presetFlags(); + } + + /* + * Current frame size. This may differ from the actual size as long as the frame is not complete. + */ + inline uint16_t size() + { + return _size; + } + + /* + * Returns the assigned flags + */ + inline uint16_t flags() + { + return _flags; + } + + /* + * Adds one or more flags + */ + inline void addFlags(uint8_t flags) + { + _flags |= flags; + } + + /* + * Returns a pointer to the data + */ + inline uint8_t *data() + { + return _data; + } + + /* + * Returns the byte corresponding to the specified position + */ + inline uint8_t data(uint16_t pos) + { + return _data[pos]; + } + + /* + * Resets the internal values to refill the frame. + */ + inline void reset() + { + _size = 0; + _flags = 0; + // It is important to fill the _data with zeros so that the length is 0 as long as the value has not yet been read in. + memset(_data, 0x0, _maxSize); + } + + /* + * Checks whether the frame has been imported completely + */ + inline bool isFull() + { + return _size >= (_size >= 7 ? fullSize() : _maxSize); + } + + /* + * Returns is the frame exteneded or not + */ + inline bool isExtended() + { + return (_data[0] & 0xD3) == 0x10; + } + + /* + * Returns the source + * Assumes that enough data has been imported. + */ + inline uint16_t source() + { + return isExtended() ? (_data[2] << 8) + _data[3] : (_data[1] << 8) + _data[2]; + } + + inline std::string humanSource() + { + uint16_t value = source(); + char buffer[10]; + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + return buffer; + } + + inline std::string humanDestination() + { + uint16_t value = destination(); + char buffer[10]; + if (isGroupAddress()) + sprintf(buffer, "%02i/%02i/%03i", (value >> 11 & 0b1111), (value >> 8 & 0b111), (value & 0b11111111)); + else + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + + return buffer; + } + + /* + * Returns the destination + * Assumes that enough data has been imported. + */ + inline uint16_t destination() + { + return isExtended() ? (_data[4] << 8) + _data[5] : (_data[3] << 8) + _data[4]; + } + + /* + * Returns the payload size (with checksum) + * Assumes that enough data has been imported. + */ + inline uint8_t payloadSize() + { + return isExtended() ? _data[6] : _data[5] & 0b1111; + } + + /* + * Returns the header size + */ + inline uint8_t headerSize() + { + return isExtended() ? 9 : 8; + } + + /* + * Returns the frame size based on header and payload size. + * Assumes that enough data has been imported. + */ + inline uint16_t fullSize() + { + return headerSize() + payloadSize(); + } + + /* + * Returns if the destination is a group address + * Assumes that enough data has been imported. + */ + inline bool isGroupAddress() + { + return isExtended() ? (_data[1] >> 7) & 0b1 : (_data[5] >> 7) & 0b1; + } + + /* + * Calculates the size of a CemiFrame. A CemiFrame has 2 additional bytes at the beginning. + * An additional byte is added to a standard frame, as this still has to be converted into an extendend. + */ + uint16_t cemiSize() + { + return fullSize() + (isExtended() ? 2 : 3); + } + + /** + * Creates a buffer and converts the TpFrame into a CemiFrame. + * Important: After processing (i.e. also after using the CemiFrame), the reference must be released manually. + */ + uint8_t *cemiData() + { + uint8_t *cemiBuffer = (uint8_t *)malloc(cemiSize()); + + // Das CEMI erwartet die Daten im Extended format inkl. zwei zusätzlicher Bytes am Anfang. + cemiBuffer[0] = 0x29; + cemiBuffer[1] = 0x0; + cemiBuffer[2] = _data[0]; + if (isExtended()) + { + memcpy(cemiBuffer + 2, _data, fullSize()); + } + else + { + cemiBuffer[3] = _data[5] & 0xF0; + memcpy(cemiBuffer + 4, _data + 1, 4); + cemiBuffer[8] = _data[5] & 0x0F; + memcpy(cemiBuffer + 9, _data + 6, cemiBuffer[8] + 2); + } + + return cemiBuffer; + } + + /* + * Checks whether the frame is complete and valid. + */ + inline bool isValid() + { + if (!isComplete()) + return false; + + uint8_t sum = 0; + const uint16_t s = fullSize() - 1; + for (uint16_t i = 0; i < s; i++) + sum ^= _data[i]; + return _data[s] == (uint8_t)~sum; + } + + /* + * Checks whether the frame is long enough to match the length specified in the frame + */ + inline bool isComplete() + { + return _size == fullSize(); + } + + inline bool isRepeated() + { + return !(_data[0] & 0b100000); + } +}; \ No newline at end of file diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 1c9f777..271ff6c 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -1,620 +1,908 @@ #include "config.h" #ifdef USE_TP +#pragma GCC optimize("O3") -#include "tpuart_data_link_layer.h" -#include "bits.h" -#include "platform.h" -#include "device_object.h" #include "address_table_object.h" +#include "bits.h" #include "cemi_frame.h" +#include "device_object.h" +#include "platform.h" +#include "tpuart_data_link_layer.h" -#include -#include - -// Activate trace output -//#define DBG_TRACE - -// NCN5120 -//#define NCN5120 +/* + * A new implementation of the tpuart connection. + * Author Marco Scholl + * + * To avoid misunderstandings (also for myself), this is in German, at least for the time being. + */ // services Host -> Controller : // internal commands, device specific -#define U_RESET_REQ 0x01 -#define U_STATE_REQ 0x02 -#define U_SET_BUSY_REQ 0x03 -#define U_QUIT_BUSY_REQ 0x04 -#define U_BUSMON_REQ 0x05 -#define U_SET_ADDRESS_REQ 0xF1 // different on TP-UART -#define U_SET_REPETITION_REQ 0xF2 -#define U_L_DATA_OFFSET_REQ 0x08 //-0x0C -#define U_SYSTEM_STATE 0x0D -#define U_STOP_MODE_REQ 0x0E +#define U_RESET_REQ 0x01 +#define U_STATE_REQ 0x02 +#define U_SET_BUSY_REQ 0x03 +#define U_QUIT_BUSY_REQ 0x04 +#define U_BUSMON_REQ 0x05 +#define U_SET_ADDRESS_REQ 0xF1 // different on TP-UART +#define U_L_DATA_OFFSET_REQ 0x08 //-0x0C +#define U_SYSTEM_MODE 0x0D +#define U_STOP_MODE_REQ 0x0E #define U_EXIT_STOP_MODE_REQ 0x0F -#define U_ACK_REQ 0x10 //-0x17 -#define U_ACK_REQ_NACK 0x04 -#define U_ACK_REQ_BUSY 0x02 -#define U_ACK_REQ_ADRESSED 0x01 -#define U_CONFIGURE_REQ 0x18 -#define U_INT_REG_WR_REQ 0x28 -#define U_INT_REG_RD_REQ 0x38 -#define U_POLLING_STATE_REQ 0xE0 +#define U_ACK_REQ 0x10 //-0x17 +#define U_ACK_REQ_NACK 0x04 +#define U_ACK_REQ_BUSY 0x02 +#define U_ACK_REQ_ADRESSED 0x01 +#define U_POLLING_STATE_REQ 0xE0 -//knx transmit data commands -#define U_L_DATA_START_CONT_REQ 0x80 //-0xBF -#define U_L_DATA_END_REQ 0x40 //-0x7F +// Only on NCN51xx available +#ifdef NCN5120 +#define U_CONFIGURE_REQ 0x18 +#define U_CONFIGURE_MARKER_REQ 0x1 +#define U_CONFIGURE_CRC_CCITT_REQ 0x2 +#define U_CONFIGURE_AUTO_POLLING_REQ 0x4 +#define U_SET_REPETITION_REQ 0xF2 +#else +#define U_MXRSTCNT 0x24 +#endif -//serices to host controller +// knx transmit data commands +#define U_L_DATA_START_REQ 0x80 +#define U_L_DATA_CONT_REQ 0x80 //-0xBF +#define U_L_DATA_END_REQ 0x40 //-0x7F + +// serices to host controller // DLL services (device is transparent) #define L_DATA_STANDARD_IND 0x90 #define L_DATA_EXTENDED_IND 0x10 -#define L_DATA_MASK 0xD3 -#define L_POLL_DATA_IND 0xF0 +#define L_DATA_MASK 0xD3 +#define L_POLL_DATA_IND 0xF0 // acknowledge services (device is transparent in bus monitor mode) -#define L_ACKN_IND 0x00 -#define L_ACKN_MASK 0x33 -#define L_DATA_CON 0x0B -#define L_DATA_CON_MASK 0x7F -#define SUCCESS 0x80 +#define L_ACKN_IND 0x00 +#define L_ACKN_MASK 0x33 +#define L_DATA_CON 0x0B +#define L_DATA_CON_MASK 0x7F +#define SUCCESS 0x80 // control services, device specific -#define U_RESET_IND 0x03 -#define U_STATE_IND 0x07 -#define SLAVE_COLLISION 0x80 -#define RECEIVE_ERROR 0x40 -#define TRANSMIT_ERROR 0x20 -#define PROTOCOL_ERROR 0x10 -#define TEMPERATURE_WARNING 0x08 -#define U_FRAME_STATE_IND 0x13 -#define U_FRAME_STATE_MASK 0x17 -#define PARITY_BIT_ERROR 0x80 +#define U_RESET_IND 0x03 +#define U_STATE_MASK 0x07 +#define U_STATE_IND 0x07 +#define SLAVE_COLLISION 0x80 +#define RECEIVE_ERROR 0x40 +#define TRANSMIT_ERROR 0x20 +#define PROTOCOL_ERROR 0x10 +#define TEMPERATURE_WARNING 0x08 +#define U_FRAME_STATE_IND 0x13 +#define U_FRAME_STATE_MASK 0x17 +#define PARITY_BIT_ERROR 0x80 #define CHECKSUM_LENGTH_ERROR 0x40 -#define TIMING_ERROR 0x20 -#define U_CONFIGURE_IND 0x01 -#define U_CONFIGURE_MASK 0x83 -#define AUTO_ACKNOWLEDGE 0x20 -#define AUTO_POLLING 0x10 -#define CRC_CCITT 0x80 +#define TIMING_ERROR 0x20 +#define U_CONFIGURE_IND 0x01 +#define U_CONFIGURE_MASK 0x83 +#define AUTO_ACKNOWLEDGE 0x20 +#define AUTO_POLLING 0x10 +#define CRC_CCITT 0x80 #define FRAME_END_WITH_MARKER 0x40 -#define U_FRAME_END_IND 0xCB -#define U_STOP_MODE_IND 0x2B -#define U_SYSTEM_STAT_IND 0x4B +#define U_FRAME_END_IND 0xCB +#define U_STOP_MODE_IND 0x2B +#define U_SYSTEM_STAT_IND 0x4B -//tx states -enum { - TX_IDLE, - TX_FRAME, - TX_WAIT_CONN -}; +/* + * NCN51xx Register handling + */ +// write internal registers +#define U_INT_REG_WR_REQ_WD 0x28 +#define U_INT_REG_WR_REQ_ACR0 0x29 +#define U_INT_REG_WR_REQ_ACR1 0x2A +#define U_INT_REG_WR_REQ_ASR0 0x2B +// read internal registers +#define U_INT_REG_RD_REQ_WD 0x38 +#define U_INT_REG_RD_REQ_ACR0 0x39 +#define U_INT_REG_RD_REQ_ACR1 0x3A +#define U_INT_REG_RD_REQ_ASR0 0x3B +// Analog Control Register 0 - Bit values +#define ACR0_FLAG_V20VEN 0x40 +#define ACR0_FLAG_DC2EN 0x20 +#define ACR0_FLAG_XCLKEN 0x10 +#define ACR0_FLAG_TRIGEN 0x08 +#define ACR0_FLAG_V20VCLIMIT 0x04 -//rx states -enum { - RX_WAIT_START, - RX_L_ADDR, - RX_L_DATA, - RX_WAIT_EOP -}; - -#define EOP_TIMEOUT 2 //milli seconds; end of layer-2 packet gap -#ifndef EOPR_TIMEOUT // allow to set EOPR_TIMEOUT externally -#define EOPR_TIMEOUT 8 //ms; relaxed EOP timeout; usally to trigger after NAK -#endif -#define CONFIRM_TIMEOUT 500 //milli seconds -#define RESET_TIMEOUT 100 //milli seconds -#define TX_TIMEPAUSE 0 // 0 means 1 milli seconds - -#ifndef OVERRUN_COUNT -#define OVERRUN_COUNT 7 //bytes; max. allowed bytes in receive buffer (on start) to see it as overrun -#endif - -// If this threshold is reached loop() goes into -// "hog mode" where it stays in loop() while L2 address reception -#define HOGMODE_THRESHOLD 3 // milli seconds - -void TpUartDataLinkLayer::enterRxWaitEOP() +enum { - // Flush input - while (_platform.uartAvailable()) - { - _platform.readUart(); - } - _lastByteRxTime = millis(); - _rxState = RX_WAIT_EOP; + TX_IDLE, + TX_FRAME +}; + +enum +{ + // In diesem Zustand wird auf neue Steuerbefehle gewartet. + RX_IDLE, + + // In diesem Zustand werden alle Bytes als Bytes für ein Frame betrachtet. + RX_FRAME, + + /* + * Dieser Zustand ist speziell und soll verhindern dass Framebytes als Steuerbytes betrachet werden. + * Das Ganze ist leider nötig, weil die verarbeitung nicht syncron verläuft und teilweise durch externe interrupt. + * Dadurch kann die 2,6ms ruhe nicht ermittelt werden um sicher zu sagen das ab jetzt alles wieder Steuercodes sind. + */ + RX_INVALID, + + RX_AWAITING_ACK +}; + +void printFrame(TpFrame *tpframe) +{ + print(tpframe->humanSource().c_str()); + print(" -> "); + print(tpframe->humanDestination().c_str()); + print(" ["); + print((tpframe->flags() & TP_FRAME_FLAG_INVALID) ? 'I' : '_'); // Invalid + print((tpframe->flags() & TP_FRAME_FLAG_EXTENDED) ? 'E' : '_'); // Extended + print((tpframe->flags() & TP_FRAME_FLAG_REPEATED) ? 'R' : '_'); // Repeat + print((tpframe->flags() & TP_FRAME_FLAG_ECHO) ? 'O' : '_'); // My own + print((tpframe->flags() & 0b00001000) ? 'x' : '_'); // Reserve + print((tpframe->flags() & TP_FRAME_FLAG_ADDRESSED) ? 'D' : '_'); // For me + print((tpframe->flags() & TP_FRAME_FLAG_ACKING) ? 'A' : '_'); // ACK recevied + print((tpframe->flags() & TP_FRAME_FLAG_ACKED) ? 'A' : '_'); // ACK sent + print("] "); + printHex("( ", tpframe->data(), tpframe->size(), false); + print(")"); } -void TpUartDataLinkLayer::loop() +/* + * Verarbeitet alle Bytes. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) { - if (!_enabled) - { - if(_waitConfirmStartTime == 0) - { - if (millis() - _lastResetChipTime > 1000) - { - //reset chip every 1 seconds - _lastResetChipTime = millis(); - _enabled = resetChip(); - } - } else { - _enabled = resetChipTick(); - } - } - - if (!_enabled) + if (!isrLock()) return; - // Loop once and repeat as long we have rx data available - do { - // Signals to communicate from rx part with the tx part - uint8_t dataConnMsg = 0; // The DATA_CONN message just seen or 0 + /* + * Manche Platformen untersützen die Erkennung, ob der Hardwarebuffer übergelaufen ist. + * Theoretisch könnte man nun den Buffer verwerfen, aber dann geht ggf. noch ein gültiges Frame verloren. + * Daher wird später im loop nur eine Info ausgegben und im Byteprocessing wird "versucht" noch darauf einzugehen. + */ + if (_platform.overflowUart()) + _rxOverflow = true; -#ifdef KNX_WAIT_FOR_ADDR - // After seeing a L2 packet start, stay in loop until address bytes are - // received and the AK/NAK packet is sent - bool stayInRx = true; -#elif defined(KNX_AUTO_ADAPT) - // After seeing a L2 packet start, stay in loop until address bytes are - // received and the AK/NAK packet is sent, when last loop call delayed - // by more than HOGMODE_THRESHOLD - bool stayInRx = millis() - _lastLoopTime > HOGMODE_THRESHOLD; - _lastLoopTime = millis(); -#else - // After seeing a L2 packet start, leave loop and hope the loop - // is called early enough to do further processings - bool stayInRx = false; -#endif - - // Loop once and repeat as long we are in the receive phase for the L2 address - do { - uint8_t* buffer = _receiveBuffer + 2; - uint8_t rxByte; - switch (_rxState) - { - case RX_WAIT_START: - if (_platform.uartAvailable()) - { - if (_platform.uartAvailable() > OVERRUN_COUNT) - { - print("input buffer overrun: "); println(_platform.uartAvailable()); - enterRxWaitEOP(); - break; - } - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - _lastByteRxTime = millis(); - - // Check for layer-2 packets - _RxByteCnt = 0; - _xorSum = 0; - if ((rxByte & L_DATA_MASK) == L_DATA_STANDARD_IND) - { - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - _RxByteCnt++; //convert to L_DATA_EXTENDED - _convert = true; - _rxState = RX_L_ADDR; -#ifdef DBG_TRACE - println("RLS"); -#endif - break; - } - else if ((rxByte & L_DATA_MASK) == L_DATA_EXTENDED_IND) - { - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - _convert = false; - _rxState = RX_L_ADDR; -#ifdef DBG_TRACE - println("RLX"); -#endif - break; - } - - // Handle all single byte packets here - else if ((rxByte & L_DATA_CON_MASK) == L_DATA_CON) - { - dataConnMsg = rxByte; - } - else if (rxByte == L_POLL_DATA_IND) - { - // not sure if this can happen - println("got L_POLL_DATA_IND"); - } - else if ((rxByte & L_ACKN_MASK) == L_ACKN_IND) - { - // this can only happen in bus monitor mode - println("got L_ACKN_IND"); - } - else if (rxByte == U_RESET_IND) - { - println("got U_RESET_IND"); - } - else if ((rxByte & U_STATE_IND) == U_STATE_IND) - { - print("got U_STATE_IND:"); - if (rxByte & 0x80) print (" SC"); - if (rxByte & 0x40) print (" RE"); - if (rxByte & 0x20) print (" TE"); - if (rxByte & 0x10) print (" PE"); - if (rxByte & 0x08) print (" TW"); - println(); - } - else if ((rxByte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) - { - print("got U_FRAME_STATE_IND: 0x"); - print(rxByte, HEX); - println(); - } - else if ((rxByte & U_CONFIGURE_MASK) == U_CONFIGURE_IND) - { - print("got U_CONFIGURE_IND: 0x"); - print(rxByte, HEX); - println(); - } - else if (rxByte == U_FRAME_END_IND) - { - println("got U_FRAME_END_IND"); - } - else if (rxByte == U_STOP_MODE_IND) - { - println("got U_STOP_MODE_IND"); - } - else if (rxByte == U_SYSTEM_STAT_IND) - { - print("got U_SYSTEM_STAT_IND: 0x"); - while (true) - { - int tmp = _platform.readUart(); - if (tmp < 0) - continue; - - print(tmp, HEX); - break; - } - println(); - } - else - { - print("got UNEXPECTED: 0x"); - print(rxByte, HEX); - println(); - } - } - break; - case RX_L_ADDR: - if (millis() - _lastByteRxTime > EOPR_TIMEOUT) - { - _rxState = RX_WAIT_START; - println("EOPR @ RX_L_ADDR"); - break; - } - if (!_platform.uartAvailable()) - break; - _lastByteRxTime = millis(); - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - - if (_RxByteCnt == 7) - { - //Destination Address + payload available - //check if echo; ignore repeat bit of control byte - _isEcho = (_sendBuffer != nullptr && (!((buffer[0] ^ _sendBuffer[0]) & ~0x20) && !memcmp(buffer + _convert + 1, _sendBuffer + 1, 5))); - - //convert into Extended.ind - if (_convert) - { - buffer[1] = buffer[6] & 0xF0; - buffer[6] &= 0x0F; - } - - if (!_isEcho) - { - uint8_t c = U_ACK_REQ; - - // The bau knows everything and could either check the address table object (normal device) - // or any filter tables (coupler) to see if we are addressed. - - //check if individual or group address - bool isGroupAddress = (buffer[1] & 0x80) != 0; - uint16_t addr = getWord(buffer + 4); - - if (_cb.isAckRequired(addr, isGroupAddress)) - { - c |= U_ACK_REQ_ADRESSED; - } - - // Hint: We can send directly here, this doesn't disturb other transmissions - // We don't have to update _lastByteTxTime because after U_ACK_REQ the timing is not so tight - _platform.writeUart(c); - } - _rxState = RX_L_DATA; - } - break; - case RX_L_DATA: - if (!_platform.uartAvailable()) - break; - _lastByteRxTime = millis(); - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - if (_RxByteCnt == MAX_KNX_TELEGRAM_SIZE - 2) - { - println("invalid telegram size"); - enterRxWaitEOP(); - } - else - { - buffer[_RxByteCnt++] = rxByte; - } - - if (_RxByteCnt == buffer[6] + 7 + 2) - { - //complete Frame received, payloadLength+1 for TCPI +1 for CRC - //check if crc is correct - if (rxByte == (uint8_t)(~_xorSum)) - { - if (!_isEcho) - { - _receiveBuffer[0] = 0x29; - _receiveBuffer[1] = 0; -#ifdef DBG_TRACE - unsigned long runTime = millis(); -#endif - frameBytesReceived(_receiveBuffer, _RxByteCnt + 2); -#ifdef DBG_TRACE - runTime = millis() - runTime; - if (runTime > (OVERRUN_COUNT*14)/10) - { - // complain when the runtime was long than the OVERRUN_COUNT allows - print("processing received frame took: "); print(runTime); println(" ms"); - } -#endif - } - _rxState = RX_WAIT_START; -#ifdef DBG_TRACE - println("RX_WAIT_START"); -#endif - } - else - { - println("frame with invalid crc ignored"); - enterRxWaitEOP(); - } - } - else - { - _xorSum ^= rxByte; - } - break; - case RX_WAIT_EOP: - if (millis() - _lastByteRxTime > EOP_TIMEOUT) - { - // found a gap - _rxState = RX_WAIT_START; -#ifdef DBG_TRACE - println("RX_WAIT_START"); -#endif - break; - } - if (_platform.uartAvailable()) - { - _platform.readUart(); - _lastByteRxTime = millis(); - } - break; - default: - println("invalid _rxState"); - enterRxWaitEOP(); - break; - } - } while (_rxState == RX_L_ADDR && (stayInRx || _platform.uartAvailable())); - - // Check for spurios DATA_CONN message - if (dataConnMsg && _txState != TX_WAIT_CONN) { - println("unexpected L_DATA_CON"); - } - - switch (_txState) - { - case TX_IDLE: - if (!isTxQueueEmpty()) - { - loadNextTxFrame(); - _txState = TX_FRAME; -#ifdef DBG_TRACE - println("TX_FRAME"); -#endif - } - break; - case TX_FRAME: - if (millis() - _lastByteTxTime > TX_TIMEPAUSE) - { - if (sendSingleFrameByte() == false) - { - _waitConfirmStartTime = millis(); - _txState = TX_WAIT_CONN; -#ifdef DBG_TRACE - println("TX_WAIT_CONN"); -#endif - } - else - { - _lastByteTxTime = millis(); - } - } - break; - case TX_WAIT_CONN: - if (dataConnMsg) - { - dataConBytesReceived(_receiveBuffer, _RxByteCnt + 2, (dataConnMsg & SUCCESS)); - delete[] _sendBuffer; - _sendBuffer = 0; - _sendBufferLength = 0; - _txState = TX_IDLE; - } - else if (millis() - _waitConfirmStartTime > CONFIRM_TIMEOUT) - { - println("L_DATA_CON not received within expected time"); - uint8_t cemiBuffer[MAX_KNX_TELEGRAM_SIZE]; - cemiBuffer[0] = 0x29; - cemiBuffer[1] = 0; - memcpy((cemiBuffer + 2), _sendBuffer, _sendBufferLength); - dataConBytesReceived(cemiBuffer, _sendBufferLength + 2, false); - delete[] _sendBuffer; - _sendBuffer = 0; - _sendBufferLength = 0; - _txState = TX_IDLE; -#ifdef DBG_TRACE - println("TX_IDLE"); -#endif - } - break; - } - } while (_platform.uartAvailable()); -} - -bool TpUartDataLinkLayer::sendFrame(CemiFrame& frame) -{ - if (!_enabled) + // verarbeiten daten + uint8_t counter = isr ? 10 : 255; + while (_platform.uartAvailable() && counter > 0) { - dataConReceived(frame, false); - return false; + processRxByte(); + counter--; } - addFrameTxQueue(frame); + isrUnlock(); +} + +/* + * Verarbeitet 1 eigehendes Byte (wenn vorhanden) + */ +void TpUartDataLinkLayer::processRxByte() +{ + int byte = _platform.readUart(); + + // RxBuffer empty + if (byte < 0) + return; + + if (_rxState == RX_FRAME) + { + processRxFrameByte(byte); + } + else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) + { + /* + * Verarbeite ein vorheriges Frame falls noch vorhanden. Das dürfte normal nur im Busmonitor auftreten, weil hier noch auch ein ACK gewartet wird + */ + processRxFrameComplete(); + + _rxFrame->addByte(byte); + + // Provoziere ungültige Frames für Tests + // if (millis() % 20 == 0) + // _rxFrame->addByte(0x1); + + _rxMarker = false; + _rxState = RX_FRAME; + + /* + * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu falls noch ein Ack vom vorherigen Frame gesetzt wurde, + * welches aber ggf nicht rechtzeitig verarbeitet (also nach der Übertragung gesendet wurde) sich nicht auf das neue Frame auswirkt. + * Der Zustand kann beliebig oft gesendet werden. Sobald der Moment gekommen ist, dass ein Ack gesendet wird, schaut TPUart im Buffer + * was der letzte Ackstatus war und sendet diesen. + * + * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + */ + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ); + } + } + else if (_rxState == RX_INVALID || _rxOverflow) + { + /* + * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntest Kommando eintrifft ist wechselt der Zustand in RX_INVALID. + * Ab jetzt muss ich davon ausgehen, dass einen Übertragungsfehler gegeben hat und die aktuellen Bytes ungültig sind. + * Gleiches gilt auch wenn ein HW Overflow erkannt wurde + * + * - Dieser Zustand wird aufgehoben wenn ein Frame "vermutlich" erkannt wurde. (if Abfrage über dieser) + * - Die Zeit des letzten Frames 3ms vorbei ist (erfolt im loop) + * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (passiert hiert) + * + * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes + */ + if (markerMode()) + { + if (!_rxMarker && byte == U_FRAME_END_IND) + { + _rxMarker = true; + } + else if (_rxMarker && byte == U_FRAME_END_IND) + { + // doppeltes byte gefunden also marker zurück setzten - kein Frameende + _rxMarker = false; + } + else if (_rxMarker) + { + // frame ende gefunden. -> RX_IDLE + _rxMarker = false; + _rxState = RX_IDLE; + } + else + { + // print("RX_INVALID: "); + // println(byte, HEX); + } + } + } + else + { + if (byte == U_RESET_IND) + { + // println("U_RESET_IND"); + } + else if ((byte & U_STATE_MASK) == U_STATE_IND) + { + _tpState |= (byte ^ U_STATE_MASK); +#ifndef NCN5120 + /* + * Filtere "Protocol errors" weil auf anderen BCU wie der Siements dies gesetzt, when das timing nicht stimmt. + * Leider ist kein perfektes Timing möglich so dass dieser Fehler ignoriert wird. Hat auch keine bekannte Auswirkungen. + */ + _tpState &= 0b11101000; +#endif + } + else if ((byte & U_CONFIGURE_MASK) == U_CONFIGURE_IND) + { + // println("U_CONFIGURE_IND"); + } + else if (byte == U_STOP_MODE_IND) + { + // println("U_STOP_MODE_IND"); + } + else if ((byte & L_ACKN_MASK) == L_ACKN_IND) + { + /* + * Wenn ein Frame noch nicht geschlossen wurde und ein Ack rein kommt. + * dann setze noch das ACK. + */ + if (_rxFrame->size() > 0) + { + _rxFrame->addFlags(TP_FRAME_FLAG_ACKED); + processRxFrameComplete(); + } + // println("L_ACKN_IND"); + } + else if ((byte & L_DATA_CON_MASK) == L_DATA_CON) + { + if (_txState == TX_FRAME) + { + const bool success = ((byte ^ L_DATA_CON_MASK) >> 7); + uint8_t *cemiData = _txFrame->cemiData(); + CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); + dataConReceived(cemiFrame, success); + free(cemiData); + delete _txFrame; + _txFrame = nullptr; + _txState = TX_IDLE; + } + else + { + // Dieses Byte wurde nicht erwartet, da garnichts gesendet wurde. + _rxUnkownControlCounter++; + _rxState = RX_INVALID; + // println("L_DATA_CON"); + } + } + else if (byte == L_POLL_DATA_IND) + { + // println("L_POLL_DATA_IND"); + } + else if ((byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + // println("U_FRAME_STATE_IND"); + } + else + { + _rxUnkownControlCounter++; + // print("Unknown Controlbyte: "); + // println(byte, HEX); + _rxState = RX_INVALID; + } + } + + _rxLastTime = millis(); +} + +/* + * Verarbeite eigehendes Byte eines Frames + */ +void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) +{ + /* + * Bei aktivem Maker muss das erste U_FRAME_END_IND ignoriert und auf ein Folgebyte gewartet werden. + * Das Folgebyte ist also ausschlaggebend wie dieses Byte zo bewerten ist. + */ + if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) + { + _rxMarker = true; + } + + /* + * Wenn das vorherige Byte ein U_FRAME_END_IND war, und das neue Byte ein U_FRAME_STATE_IND ist, + * dann ist der Empfang sauber abgeschlossen und das Frame kann verarbeitet werden. + */ + else if (_rxMarker && (byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + _rxMarker = false; + processRxFrameComplete(); + + /* + * Setze den Zustand auf RX_IDLE, da durch den Marker sichergestellt ist, + * dass das Frame erfolgreich verarbeitet wurde. Nachfolgende Bytes sind also wieder sauber Steuerbefehle, + * selbst wenn das Frame aufgrund einer ungültigen Checksumme verworfen wurde (was RX_INVAID) bedeuten würde + */ + _rxState = RX_IDLE; + } + + /* + * Dies ist ein hypotetischer Fall, dass die Frames ohne Marker kommen, obwohl der MarkerModus aktiv ist. + * Hier wird der aktuelle Frame abgearbeitet und RX_INVALID gesetzt, da das aktuelle Byte hierbei nicht bearbeitet wird. + * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature). + * TODO vergleiche auf maxSize oder falls vorhanden auf länge aus telegramm -> full() + * TODO Beschriebung anpassen weil es auch bei Byteverlusten auftreten kann + */ + else if (markerMode() && _rxFrame->isFull()) + { + processRxFrameComplete(); + /* + * RX_INVALID weil theoretisch könnte das Frame als gültig verarbeitet worden sein. + * Da aber das aktuelle Byte schon "angefangen" wurde zu verarbeiten, fehlt das in der Verarbeitungskette + * und somit sind die nachfolgenden Bytes nicht verwertbar. + */ + _rxState = RX_INVALID; + } + + /* + * Wenn der Markermodus in aktiv ist, soll das Byte normal verarbeitet werden. + * Wenn der Markermodus aktiv ist, dann darf ein U_FRAME_END_IND Byte nur verarbeitet werden, wenn des vorherige Byte auch ein U_FRAME_END_IND war. + */ + else if (!markerMode() || byte != U_FRAME_END_IND || (byte == U_FRAME_END_IND && _rxMarker)) + { + // Setze den Marker wieder zurück falls aktiv + _rxMarker = false; + // Übernehme das Byte + _rxFrame->addByte(byte); + + // Wenn der Busmonitor gestartet wurde, findet keine Verarbeitung - also auch kein ACKing + if (!_monitoring) + { + // Wenn mehr als 7 bytes vorhanden kann geschaut werden ob das Frame für "mich" bestimmt ist + if (_rxFrame->size() == 7) + { + // Prüfe ob ich für das Frame zuständig bin + if (_forceAck || _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress())) + { + /* + * Speichere die Zuständigkeit dass dieses Frame weiterverarbeitet werden soll. + * Da es keine extra Funktion außer dem isAckRequired gibt, wird das erstmal gleich behandelt. + * Eine spätere Unterscheidung (ggf für Routermodus) muss man dann schauen. + */ + + _rxFrame->addFlags(TP_FRAME_FLAG_ADDRESSED); + + // Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + if (_txState == TX_IDLE) + { + // Speichere das ein Acking erfolgen soll + _rxFrame->addFlags(TP_FRAME_FLAG_ACKING); + + // und im TPUart damit dieser das ACK schicken kann + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED); + } + } + } + +#ifdef USE_TP_RX_QUEUE + // Prüfe nun ob die RxQueue noch Platz hat für Frame + Size (2) + Flags(1) + if (_rxFrame->size() == 8 && (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED)) + { + if (availableInRxQueue() < (_rxFrame->size() + 3)) + { + // Nur wenn ich nicht selber sende + if (_txState == RX_IDLE) + { + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); + } + } + } +#endif + } + } + + /* + * Wenn kein Markermodus aktiv ist, dann muss anhand des Frame geschaut werden, ob es Vollständig ist. + * isFull prüft hier ob die maxSize oder die Längenangabe des Frames überschritten wurde! + * In beiden Fällen muss das Frame verarbeitet werden. + */ + if (!markerMode() && (_rxFrame->isFull())) + { + processRxFrameComplete(); + } +} + +/* + * Verarbeitet das aktuelle Frame und prüft ob dieses vollständig und gültig (checksum) ist. + * Sollte ein Frame vollständig und gültig sein, so wird deses falls für "mich" bestimmt in die Queue gelegt und der Modus ist wieder RX_IDLE. + * Ansonsten wird das Frame als ungültig verworfen und der Zustand ist RX_INVALID, da nicht sichergestellt ist das Folgebytes wieder Steuercodes sind. + * Ausnahme im Markermodus, hier wird der Status RX_INVALID an einer anderen Stelle direkt wieder auf RX_IDLE geändert, weil + * dann ist sicher gestellt, dass das das Frame auf TP Ebene kaputt gegangen ist. + */ +void TpUartDataLinkLayer::processRxFrameComplete() +{ + // Sollte aktuell kein Frame in der Bearbeitung (size == 0) sein, dann breche ab + if (_rxFrame->size() == 0) + return; + + // Ist das Frame vollständig und gültig + if (_rxFrame->isValid()) + { + // Wenn ein Frame gesendet wurde + if (_txState == TX_FRAME) + { + // prüfe ob das Empfangen diesem entspricht + if (!memcmp(_rxFrame->data(), _txFrame->data(), _txFrame->size())) + { + // und markiere das entsprechend + // println("MATCH"); + _rxFrame->addFlags(TP_FRAME_FLAG_ECHO); + } + // Jetzt warte noch auf das L_DATA_CON + } + // wenn das frame für mich ist oder ich im busmonitor modus bin dann möchte ich es weiter verarbeiten + if (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED || _monitoring) + { + /* + * Im Busmonitormodus muss noch auf ein Ack gewartet werden. + * Daher wird hier der Status geändert und vor dem echten abschließen zurück gesprungen. + * Sobald ein weiteres mal aufgerufen wird, (egal ob geackt oder nicht) wird das Frame geschlossen. + */ + if (_monitoring && _rxState != RX_AWAITING_ACK) + { + _rxState = RX_AWAITING_ACK; + return; + } + _rxProcessdFrameCounter++; + } + else + { + // Sonst verwerfe das Paket und gebe den Speicher frei -> da nicht in die Queue gepackt wird + _rxIgnoredFrameCounter++; + } + // Und wieder bereit für Steuercodes + _rxState = RX_IDLE; + } + else + { + /* + * Ist das Frame unvollständig oder ungültig dann wechsle in RX_INVALID Modus da ich nicht unterscheiden kann, + * ob es sich um einen TPBus Error oder ein UART Error oder ein Timming Error handelt. + */ + _rxInvalidFrameCounter++; + _rxFrame->addFlags(TP_FRAME_FLAG_INVALID); + _rxState = RX_INVALID; // ignore bytes + } + +#ifdef USE_TP_RX_QUEUE + pushRxFrameQueue(); +#else + processRxFrame(_rxFrame); +#endif + + // resete den aktuellen Framepointer + _rxFrame->reset(); +} + +/* + * Steckt das zu sendende Frame in eine Queue, da der TpUart vielleicht gerade noch nicht sende bereit ist. + */ +void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) +{ + knx_tx_queue_entry_t *entry = new knx_tx_queue_entry_t(tpFrame); + + if (_txFrameQueue.back == nullptr) + { + _txFrameQueue.front = _txFrameQueue.back = entry; + } + else + { + _txFrameQueue.back->next = entry; + _txFrameQueue.back = entry; + } +} + +void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) +{ + _repetitions = (nack & 0b111) | ((busy & 0b111) << 4); +} + +// Alias +void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) +{ + setRepetitions(nack, busy); +} + +bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) +{ + // if (!_connected) + // { + // dataConReceived(cemiFrame, false); + // return false; + // } + + TpFrame *tpFrame = new TpFrame(cemiFrame); + // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); + pushTxFrameQueue(tpFrame); return true; } -bool TpUartDataLinkLayer::resetChip() +/* + * Es soll regelmäßig der Status abgefragt werden um ein Disconnect des TPUart und dessen Status zu erkennen. + * Außerdem soll regelmäßig die aktuelle Config bzw der Modus übermittelt werden, so dass nach einem Disconnect, + * der TPUart im richtigen Zustand ist. + */ +void TpUartDataLinkLayer::requestState() { - if(_waitConfirmStartTime > 0) return false; - uint8_t cmd = U_RESET_REQ; - _platform.writeUart(cmd); - - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - return true; + if (_rxState != RX_IDLE) + return; - _waitConfirmStartTime = millis(); - return false; + if (_txState != TX_IDLE) + return; + + // Nur 1x pro Sekunde + if ((millis() - _lastStateRequest) < 1000) + return; + + // println("requestState"); + + // Sende Konfiguration bzw. Modus + if (_monitoring) + _platform.writeUart(U_BUSMON_REQ); + else + requestConfig(); + + // Frage status an - wenn monitoring inaktiv + if (!_monitoring) + _platform.writeUart(U_STATE_REQ); + + _lastStateRequest = millis(); } -bool TpUartDataLinkLayer::resetChipTick() -{ - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - { - _waitConfirmStartTime = 0; - return true; - } - else if (millis() - _waitConfirmStartTime > RESET_TIMEOUT) - _waitConfirmStartTime = 0; - - return false; -} - -void TpUartDataLinkLayer::stopChip() +/* + * Sendet die aktuelle Config an den Chip + */ +void TpUartDataLinkLayer::requestConfig() { + // println("requestConfig"); #ifdef NCN5120 - uint8_t cmd = U_STOP_MODE_REQ; - _platform.writeUart(cmd); - while (true) + if (markerMode()) + _platform.writeUart(U_CONFIGURE_REQ | U_CONFIGURE_MARKER_REQ); +#endif + + // Abweichende Config + if (_repetitions != 0b00110011) { - int resp = _platform.readUart(); - if (resp == U_STOP_MODE_IND) - break; +#ifdef NCN5120 + _platform.writeUart(U_SET_REPETITION_REQ); + _platform.writeUart(_repetitions); + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet +#else + _platform.writeUart(U_MXRSTCNT); + _platform.writeUart(((_repetitions & 0xF0) << 1) || (_repetitions & 0x0F)); +#endif } -#endif } -TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject& devObj, - NetworkLayerEntity &netLayerEntity, - Platform& platform, - ITpUartCallBacks& cb, - DataLinkLayerCallbacks* dllcb) - : DataLinkLayer(devObj, netLayerEntity, platform), - _cb(cb), - _dllcb(dllcb) +/* + * Ein vereinfachter Lockmechanismus der nur auf dem gleichen Core funktionert. + * Also perfekt für ISR + */ +bool TpUartDataLinkLayer::isrLock(bool blocking /* = false */) { + if (blocking) + while (_rxProcessing) + ; + else if (_rxProcessing) + return false; + + _rxProcessing = true; + return true; } -void TpUartDataLinkLayer::frameBytesReceived(uint8_t* buffer, uint16_t length) +void TpUartDataLinkLayer::isrUnlock() { - //printHex("=>", buffer, length); -#ifdef KNX_ACTIVITYCALLBACK - if(_dllcb) - _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); -#endif - CemiFrame frame(buffer, length); - frameReceived(frame); + _rxProcessing = false; } -void TpUartDataLinkLayer::dataConBytesReceived(uint8_t* buffer, uint16_t length, bool success) +void TpUartDataLinkLayer::clearUartBuffer() { - //printHex("=>", buffer, length); - CemiFrame frame(buffer, length); - dataConReceived(frame, success); + // Clear rx queue + while (_platform.uartAvailable()) + _platform.readUart(); +} + +void TpUartDataLinkLayer::connected(bool state /* = true */) +{ + if (state) + println("TP is connected"); + else + println("TP is disconnected"); + + _connected = state; +} + +bool TpUartDataLinkLayer::reset() +{ + // println("Reset TP"); + if (!_initialized) + { + _platform.setupUart(); + _initialized = true; + } + + // Wait for isr & block isr + isrLock(true); + + // Reset + _rxIgnoredFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxUnkownControlCounter = 0; + if (_txFrame != nullptr) + _txFrame->reset(); + if (_rxFrame != nullptr) + _rxFrame->reset(); + _rxState = RX_IDLE; + _txState = TX_IDLE; + _connected = false; + _stopped = false; + _monitoring = false; + _rxLastTime = 0; + + clearUartBuffer(); + + _platform.writeUart(U_RESET_REQ); + bool success = false; + + const uint32_t start = millis(); + // During startup answer took up to 2ms and normal 1ms + do + { + const int byte = _platform.readUart(); + if (byte == -1) + continue; // empty + + if (byte & U_RESET_IND) + { + success = true; + break; // next run for U_CONFIGURE_IND + } + + } while (!((millis() - start) >= 10)); + + connected(success); + if (success) + { + _lastStateRequest = 0; // Force + requestState(); + _rxLastTime = millis(); + } + + isrUnlock(); + return success; +} + +void TpUartDataLinkLayer::forceAck(bool state) +{ + _forceAck = true; +} + +void TpUartDataLinkLayer::stop(bool state) +{ + if (!_initialized) + return; + + if (state && !_stopped) + _platform.writeUart(U_STOP_MODE_REQ); + else if (!state && _stopped) + _platform.writeUart(U_EXIT_STOP_MODE_REQ); + + _stopped = state; +} + +void TpUartDataLinkLayer::requestBusy(bool state) +{ + if (state && !_busy) + _platform.writeUart(U_SET_BUSY_REQ); + else if (!state && _busy) + _platform.writeUart(U_QUIT_BUSY_REQ); + + _busy = state; +} + +void TpUartDataLinkLayer::monitor() +{ + if (!_initialized || _monitoring) + return; + + // println("busmonitor"); + _monitoring = true; + _platform.writeUart(U_BUSMON_REQ); } void TpUartDataLinkLayer::enabled(bool value) { - if (value && !_enabled) - { - _platform.setupUart(); + // After an unusual device restart, perform a reset, as the TPUart may still be in an incorrect state. + if (!_initialized) + reset(); - uint8_t cmd = U_RESET_REQ; - _platform.writeUart(cmd); - _waitConfirmStartTime = millis(); - bool flag = false; - - while (true) - { - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - { - flag = true; - break; - } - else if (millis() - _waitConfirmStartTime > RESET_TIMEOUT) - { - flag = false; - break; - } - } - - if (flag) - { - _enabled = true; - print("ownaddr "); - println(_deviceObject.individualAddress(), HEX); - } - else - { - _enabled = false; - println("ERROR, TPUART not responding"); - } - return; - } - - if (!value && _enabled) - { - _enabled = false; - stopChip(); - _platform.closeUart(); - return; - } + stop(!value); } bool TpUartDataLinkLayer::enabled() const { - return _enabled; + return _initialized && _connected; +} + +/* + * Hier werden die ausgehenden Frames aus der Warteschlange genomnmen und versendet. + * Das passiert immer nur einzelnd, da nach jedem Frame, gewartet werden muss bis das Frame wieder reingekommen ist und das L_DATA_CON rein kommt. + * + */ +void TpUartDataLinkLayer::processTxQueue() +{ + // Diese Abfrage ist vorsorglich. Eigentlich sollte auch schon parallel gestartet werden können. + if (_rxState != RX_IDLE) + return; + + // + if (_txState != TX_IDLE) + return; + + if (_txFrameQueue.front != nullptr) + { + knx_tx_queue_entry_t *entry = _txFrameQueue.front; + _txFrameQueue.front = entry->next; + + if (_txFrameQueue.front == nullptr) + { + _txFrameQueue.back = nullptr; + } + + _txFrame = entry->frame; +#ifdef DEBUG_TP_FRAMES + print("Outbound: "); + printFrame(_txFrame); + println(); +#endif + _txState = TX_FRAME; + _txLastTime = millis(); + + delete entry; + processTxFrameBytes(); + } +} + +/* + * Prüfe ob ich zu lange keine Daten mehr erhalten habe und setzte den Status auf nicht verbunden. + * Im normalen Modus wird jede Sekunde der Status angefragt. Daher kann hier eine kurze Zeit gewählt werden. + * Im Monitoringmodus gibt es eigentlichen Frames, daher ist hier eine längere Zeit verwendet. + * Dennoch kommt es bei größeren Datenmengen zu vermuteten Disconnect, daher wird auch noch die RxQueue beachtet. + */ +void TpUartDataLinkLayer::checkConnected() +{ + if (!isrLock()) + return; + + const uint32_t current = millis(); + + if (_connected) + { + // 5000 instead 3000 because siemens tpuart + const uint32_t timeout = _monitoring ? 10000 : 5000; + + if ((current - _rxLastTime) > timeout) + { + connected(false); + } + } + else + { + if (_rxLastTime > 0 && (current - _rxLastTime) < 1000) + connected(); + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::loop() +{ + if (!_initialized) + return; + + if (_rxOverflow) + { + println("TPUart overflow detected!"); + _rxOverflow = false; + } + + if (_tpState) + { + print("TPUart state error: "); + println(_tpState, 2); + _tpState = 0; + } + + processRx(); +#ifdef USE_TP_RX_QUEUE + processRxQueue(); +#endif + + requestState(); + processTxQueue(); + checkConnected(); + + /* + * Normal ist alles so gebaut worde, dass anhand der Bytes entschieden wird wann ein Frame "fertig" ist oder nicht. + * Dennoch gibt es Situationen, wo ein Frame nicht geschlossen wurde, weil Bytes verloren geangen sind oder wie beim Busmonitor + * bewusst auf ein Ack gewartet werden muss. + */ + // if (!_platform.uartAvailable() && _rxFrame->size() > 0 && (millis() - _rxLastTime) > 2) + // { + // processRxFrameComplete(); + // } +} + +void TpUartDataLinkLayer::rxFrameReceived(TpFrame *tpFrame) +{ + uint8_t *cemiData = tpFrame->cemiData(); + CemiFrame cemiFrame(cemiData, tpFrame->cemiSize()); + // printHex(" TP<: ", tpFrame->data(), tpFrame->size()); + // printHex(" CEMI<: ", cemiFrame.data(), cemiFrame.dataLength()); + +#ifdef KNX_ACTIVITYCALLBACK + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); +#endif + + frameReceived(cemiFrame); + free(cemiData); } DptMedium TpUartDataLinkLayer::mediumType() const @@ -622,94 +910,246 @@ DptMedium TpUartDataLinkLayer::mediumType() const return DptMedium::KNX_TP1; } -bool TpUartDataLinkLayer::sendSingleFrameByte() +/* + * Hiermit kann die Stromversorgung des V20V (VCC2) + */ +#ifdef NCN5120 +void TpUartDataLinkLayer::powerControl(bool state) { - uint8_t cmd[2]; + _platform.writeUart(U_INT_REG_WR_REQ_ACR0); + if (state) + _platform.writeUart(ACR0_FLAG_DC2EN | ACR0_FLAG_V20VEN | ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); + else + _platform.writeUart(ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); +} +#endif - uint8_t idx = _TxByteCnt >> 6; +bool TpUartDataLinkLayer::processTxFrameBytes() +{ + // println("processTxFrameBytes"); - if (_sendBuffer == NULL) - return false; - - if (_TxByteCnt < _sendBufferLength) + /* + * Jedes Frame muss mit einem U_L_DATA_START_REQ eingeleitet werden und jedes Folgebyte mit einem weiteren Positionsbyte (6bit). + * Da das Positionsbyte aus dem U_L_DATA_START_REQ + Position besteht und wir sowieso mit 0 starten, ist eine weitere + * Unterscheidung nicht nötig. + * + * Das letzte Byte (Checksumme) verwendet allerdings das U_L_DATA_END_REQ + Postion! + * Außerdem gibt es eine weitere Besondertheit bei Extendedframes bis 263 Bytes lang sein können reichen die 6bit nicht mehr. + * Hier muss ein U_L_DATA_OFFSET_REQ + Position (3bit) vorangestellt werden. Somit stehten 9bits für die Postion bereit. + */ + for (uint16_t i = 0; i < _txFrame->size(); i++) { - if (idx != _oldIdx) + uint8_t offset = (i >> 6); + uint8_t position = (i & 0x3F); + + if (offset) { - _oldIdx = idx; - cmd[0] = U_L_DATA_OFFSET_REQ | idx; - _platform.writeUart(cmd, 1); + // position++; + _platform.writeUart(U_L_DATA_OFFSET_REQ | offset); } - if (_TxByteCnt != _sendBufferLength - 1) - cmd[0] = U_L_DATA_START_CONT_REQ | (_TxByteCnt & 0x3F); + if (i == (_txFrame->size() - 1)) // Letztes Bytes (Checksumme) + _platform.writeUart(U_L_DATA_END_REQ | position); else - cmd[0] = U_L_DATA_END_REQ | (_TxByteCnt & 0x3F); + _platform.writeUart(U_L_DATA_START_REQ | position); - cmd[1] = _sendBuffer[_TxByteCnt]; -#ifdef DBG_TRACE - print(cmd[1], HEX); -#endif - - _platform.writeUart(cmd, 2); - _TxByteCnt++; + _platform.writeUart(_txFrame->data(i)); } - - // Check for last byte send - if (_TxByteCnt >= _sendBufferLength) - { - _TxByteCnt = 0; + #ifdef KNX_ACTIVITYCALLBACK - if(_dllcb) - _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); #endif - return false; - } + return true; } -void TpUartDataLinkLayer::addFrameTxQueue(CemiFrame& frame) +TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject &devObj, + NetworkLayerEntity &netLayerEntity, + Platform &platform, + ITpUartCallBacks &cb, + DataLinkLayerCallbacks *dllcb) + : DataLinkLayer(devObj, netLayerEntity, platform), + _cb(cb), + _dllcb(dllcb) { - _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; - tx_frame->length = frame.telegramLengthtTP(); - tx_frame->data = new uint8_t[tx_frame->length]; - tx_frame->next = NULL; - frame.fillTelegramTP(tx_frame->data); - - if (_tx_queue.back == NULL) - { - _tx_queue.front = _tx_queue.back = tx_frame; - } - else - { - _tx_queue.back->next = tx_frame; - _tx_queue.back = tx_frame; - } + _rxFrame = new TpFrame(MAX_KNX_TELEGRAM_SIZE); } -bool TpUartDataLinkLayer::isTxQueueEmpty() +/* + * Liefert die Anzahl der Frames, die nicht verarbeitet werden konnte. + */ +uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() { - if (_tx_queue.front == NULL) - { - return true; - } + return _rxInvalidFrameCounter; +} + +/* + * Liefert die Anzahl der Frames, welche gültig und für das Geräte bestimmt sind + */ +uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() +{ + return _rxProcessdFrameCounter; +} + +/* + * Liefert die Anzahl der Frames, welche gültig aber nicht f+r das Gerät bestimmt sind + */ +uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() +{ + return _rxIgnoredFrameCounter; +} + +/* + * Liefert die Anzahl der gezählten Steuerbytes, welche nicht erkannt wurden + */ +uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() +{ + return _rxUnkownControlCounter; +} + +bool TpUartDataLinkLayer::isConnected() +{ + return _connected; +} + +bool TpUartDataLinkLayer::isStopped() +{ + return _stopped; +} + +bool TpUartDataLinkLayer::isBusy() +{ + return _busy; +} + +bool TpUartDataLinkLayer::isMonitoring() +{ + return _monitoring; +} + +bool TpUartDataLinkLayer::markerMode() +{ + if (_monitoring) + return false; + +#ifdef NCN5120 + // return true; +#endif + return false; } -void TpUartDataLinkLayer::loadNextTxFrame() +void TpUartDataLinkLayer::processRxFrame(TpFrame *tpFrame) { - if (_tx_queue.front == NULL) + if (_monitoring) { - return; + print("Monitor: "); + printFrame(tpFrame); + println(); } - _tx_queue_frame_t* tx_frame = _tx_queue.front; - _sendBuffer = tx_frame->data; - _sendBufferLength = tx_frame->length; - _tx_queue.front = tx_frame->next; + else if (tpFrame->flags() & TP_FRAME_FLAG_INVALID) + { + print("\x1B["); + print(31); + print("m"); + print("Invalid: "); + printFrame(tpFrame); + print("\x1B["); + print(0); + println("m"); + } + else if (tpFrame->flags() & TP_FRAME_FLAG_ADDRESSED) + { +#ifdef DEBUG_TP_FRAMES + print("Inbound: "); + printFrame(tpFrame); + println(); +#endif + rxFrameReceived(tpFrame); + } +} - if (_tx_queue.front == NULL) +#ifdef USE_TP_RX_QUEUE +/* + * This method allows the processing of the incoming bytes to be handled additionally via an interrupt (ISR). + * The prerequisite is that the interrupt runs on the same core as the knx.loop! + * + * Bei einem RP2040 wo beim "erase" eines Blocks auch der ISR gesperrt sind, + * kann zwischern den Erases die Verarbeitung nachgeholt werden. So wird das Risiko von Frameverlusten deutlich minimiert. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRxISR)() +{ + processRx(true); +} + +/* + * Steckt das empfangene Frame in eine Queue. Diese Queue ist notwendig, + * weil ein Frame optional über ein ISR empfangen werden kann und die verarbeitung noch normal im knx.loop statt finden muss. + * Außerdem ist diese Queue statisch vorallokierte, da in einem ISR keine malloc u.ä. gemacht werden kann. + */ +void TpUartDataLinkLayer::pushRxFrameQueue() +{ + if (availableInRxQueue() < (_rxFrame->size() + 3)) + return; + + // Payloadsize (2 byte) + pushByteToRxQueue(_rxFrame->size() & 0xFF); + pushByteToRxQueue(_rxFrame->size() >> 8); + // Paylodflags (1 byte) + pushByteToRxQueue(_rxFrame->flags()); + + for (size_t i = 0; i < _rxFrame->size(); i++) { - _tx_queue.back = NULL; + pushByteToRxQueue(_rxFrame->data(i)); } - delete tx_frame; + + asm volatile("" ::: "memory"); + _rxBufferCount++; +} + +void TpUartDataLinkLayer::processRxQueue() +{ + if (!isrLock()) + return; + + while (_rxBufferCount) + { + const uint16_t size = pullByteFromRxQueue() + (pullByteFromRxQueue() << 8); + TpFrame *tpFrame = new TpFrame(size); + tpFrame->addFlags(pullByteFromRxQueue()); + + for (uint16_t i = 0; i < size; i++) + { + tpFrame->addByte(pullByteFromRxQueue()); + } + + processRxFrame(tpFrame); + delete tpFrame; + asm volatile("" ::: "memory"); + _rxBufferCount--; + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::pushByteToRxQueue(uint8_t byte) +{ + _rxBuffer[_rxBufferFront] = byte; + _rxBufferFront = (_rxBufferFront + 1) % MAX_RX_QUEUE_BYTES; +} + +uint8_t TpUartDataLinkLayer::pullByteFromRxQueue() +{ + uint8_t byte = _rxBuffer[_rxBufferRear]; + _rxBufferRear = (_rxBufferRear + 1) % MAX_RX_QUEUE_BYTES; + return byte; +} + +uint16_t TpUartDataLinkLayer::availableInRxQueue() +{ + return ((_rxBufferFront == _rxBufferRear) ? MAX_RX_QUEUE_BYTES : (((MAX_RX_QUEUE_BYTES - _rxBufferFront) + _rxBufferRear) % MAX_RX_QUEUE_BYTES)) - 1; } #endif + +#endif \ No newline at end of file diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index c2e46f3..da81aa4 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -3,14 +3,26 @@ #include "config.h" #ifdef USE_TP -#include #include "data_link_layer.h" +#include "tp_frame.h" +#include #define MAX_KNX_TELEGRAM_SIZE 263 +#ifndef MAX_RX_QUEUE_BYTES +#define MAX_RX_QUEUE_BYTES MAX_KNX_TELEGRAM_SIZE + 50 +#endif + +// __time_critical_func fallback +#ifndef ARDUINO_ARCH_RP2040 +#define __time_critical_func(X) X +#define __isr +#endif + +void printFrame(TpFrame* tpframe); class ITpUartCallBacks { -public: + public: virtual ~ITpUartCallBacks() = default; virtual bool isAckRequired(uint16_t address, bool isGrpAddr) = 0; }; @@ -24,56 +36,129 @@ class TpUartDataLinkLayer : public DataLinkLayer TpUartDataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform, ITpUartCallBacks& cb, DataLinkLayerCallbacks* dllcb = nullptr); - - void loop(); void enabled(bool value); bool enabled() const; DptMedium mediumType() const override; + bool reset(); + void monitor(); + void stop(bool state); + void requestBusy(bool state); + void forceAck(bool state); + void setRepetitions(uint8_t nack, uint8_t busy); + // Alias + void setFrameRepetition(uint8_t nack, uint8_t busy); + bool isConnected(); + bool isMonitoring(); + bool isStopped(); + bool isBusy(); + +#ifdef USE_TP_RX_QUEUE + void processRxISR(); +#endif +#ifdef NCN5120 + void powerControl(bool state); +#endif + + uint32_t getRxInvalidFrameCounter(); + uint32_t getRxProcessdFrameCounter(); + uint32_t getRxIgnoredFrameCounter(); + uint32_t getRxUnknownControlCounter(); + uint8_t getMode(); private: - bool _enabled = false; - uint8_t* _sendBuffer = 0; - uint16_t _sendBufferLength = 0; - uint8_t _receiveBuffer[MAX_KNX_TELEGRAM_SIZE]; - uint8_t _txState = 0; - uint8_t _rxState = 0; - uint16_t _RxByteCnt = 0; - uint16_t _TxByteCnt = 0; - uint8_t _oldIdx = 0; - bool _isEcho = false; - bool _convert = false; - uint8_t _xorSum = 0; - uint32_t _lastByteRxTime; - uint32_t _lastByteTxTime; - uint32_t _lastLoopTime; - uint32_t _waitConfirmStartTime = 0; - uint32_t _lastResetChipTime = 0; - - struct _tx_queue_frame_t + // Frame + struct knx_tx_queue_entry_t { - uint8_t* data; - uint16_t length; - _tx_queue_frame_t* next; + TpFrame* frame; + knx_tx_queue_entry_t* next = nullptr; + + knx_tx_queue_entry_t(TpFrame* tpFrame) + : frame(tpFrame) + { + } }; - struct _tx_queue_t + // TX Queue + struct knx_tx_queue_t { - _tx_queue_frame_t* front = NULL; - _tx_queue_frame_t* back = NULL; - } _tx_queue; + knx_tx_queue_entry_t* front = nullptr; + knx_tx_queue_entry_t* back = nullptr; + } _txFrameQueue; - void addFrameTxQueue(CemiFrame& frame); - bool isTxQueueEmpty(); - void loadNextTxFrame(); - bool sendSingleFrameByte(); + TpFrame* _txFrame = nullptr; + TpFrame* _rxFrame = nullptr; + + volatile bool _stopped = false; + volatile bool _connected = false; + volatile bool _monitoring = false; + volatile bool _busy = false; + volatile bool _initialized = false; + + volatile uint32_t _stateTime = 0; + volatile uint8_t _rxState = 0; + volatile uint8_t _txState = 0; + volatile uint32_t _rxProcessdFrameCounter = 0; + volatile uint32_t _rxInvalidFrameCounter = 0; + volatile uint32_t _rxIgnoredFrameCounter = 0; + volatile uint32_t _rxUnkownControlCounter = 0; + volatile bool _rxMarker = false; + volatile bool _rxOverflow = false; + volatile uint8_t _tpState = 0x0; + volatile uint32_t _txLastTime = 0; + volatile uint32_t _rxLastTime = 0; + volatile bool _forceAck = false; + + inline bool markerMode(); + + /* + * bits + * + * 5-7 Busy (Default 11 = 3) + * 0-3 Nack (Default 11 = 3) + */ + volatile uint8_t _repetitions = 0b00110011; + + // to prevent parallel rx processing by isr (when using) + volatile bool _rxProcessing = false; + + volatile uint32_t _lastStateRequest = 0; + + // void loadNextTxFrame(); + inline bool processTxFrameBytes(); bool sendFrame(CemiFrame& frame); - void frameBytesReceived(uint8_t* buffer, uint16_t length); + void rxFrameReceived(TpFrame* frame); void dataConBytesReceived(uint8_t* buffer, uint16_t length, bool success); - void enterRxWaitEOP(); - bool resetChip(); - bool resetChipTick(); - void stopChip(); + + void processRx(bool isr = false); + void checkConnected(); + void processRxByte(); + void processTxQueue(); + void processRxFrameComplete(); + inline void processRxFrame(TpFrame* tpFrame); + void pushTxFrameQueue(TpFrame* tpFrame); + void requestState(); + void requestConfig(); + inline void processRxFrameByte(uint8_t byte); + +#ifdef USE_TP_RX_QUEUE + // Es muss ein Extended Frame rein passen + 1Byte je erlaubter ms Verzögerung + volatile uint8_t _rxBuffer[MAX_RX_QUEUE_BYTES] = {}; + volatile uint16_t _rxBufferFront = 0; + volatile uint16_t _rxBufferRear = 0; + volatile uint8_t _rxBufferCount = 0; + + void pushByteToRxQueue(uint8_t byte); + uint8_t pullByteFromRxQueue(); + uint16_t availableInRxQueue(); + void pushRxFrameQueue(); + void processRxQueue(); +#endif + + inline bool isrLock(bool blocking = false); + inline void isrUnlock(); + inline void clearUartBuffer(); + inline void connected(bool state = true); ITpUartCallBacks& _cb; DataLinkLayerCallbacks* _dllcb;