From efe2c46d329ee13f348d215a3326e9b4c0bbc9c0 Mon Sep 17 00:00:00 2001 From: Waldemar Porscha Date: Tue, 16 Jan 2024 00:39:26 +0100 Subject: [PATCH 01/22] New implementation of Uninitialized state for group object - old implementation is still there for compatibility reasons - new implementation reflects the case "uninitialized while transmitting" - Just states "Updated", "WriteRequest" and "Ok" remove uninitialized state --- src/knx/group_object.cpp | 29 ++++++++++++++++++++--------- src/knx/group_object.h | 19 +++++++++++++++++-- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 6898ba5..cf3ad24 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -12,7 +12,8 @@ GroupObjectTableObject* GroupObject::_table = 0; GroupObject::GroupObject() { _data = 0; - _commFlag = Uninitialized; + _commFlagEx.uninitialized = true; + _commFlagEx.commFlag = Uninitialized; _dataLength = 0; #ifndef SMALL_GROUPOBJECT _updateHandler = 0; @@ -22,7 +23,7 @@ GroupObject::GroupObject() GroupObject::GroupObject(const GroupObject& other) { _data = new uint8_t[other._dataLength]; - _commFlag = other._commFlag; + _commFlagEx = other._commFlagEx; _dataLength = other._dataLength; _asap = other._asap; #ifndef SMALL_GROUPOBJECT @@ -75,7 +76,7 @@ bool GroupObject::readEnable() return false; // we forbid reading of new (uninitialized) go - if (_commFlag == Uninitialized) + if (_commFlagEx.uninitialized) return false; return bitRead(ntohs(_table->_tableData[_asap]), 11) > 0; @@ -157,22 +158,29 @@ size_t GroupObject::asapValueSize(uint8_t code) ComFlag GroupObject::commFlag() { - return _commFlag; + return _commFlagEx.commFlag; } void GroupObject::commFlag(ComFlag value) { - _commFlag = value; + _commFlagEx.commFlag = value; + if (value == WriteRequest || value == Updated || value == Ok) + _commFlagEx.uninitialized = false; +} + +bool GroupObject::initialized() +{ + return !_commFlagEx.uninitialized; } void GroupObject::requestObjectRead() { - _commFlag = ReadRequest; + _commFlagEx.commFlag = ReadRequest; } void GroupObject::objectWritten() { - _commFlag = WriteRequest; + _commFlagEx.commFlag = WriteRequest; } size_t GroupObject::valueSize() @@ -274,8 +282,11 @@ void GroupObject::valueNoSend(const KNXValue& value) void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) { - if (_commFlag == Uninitialized) - _commFlag = Ok; + if (_commFlagEx.uninitialized) + { + _commFlagEx.commFlag = Ok; + _commFlagEx.uninitialized = false; + } KNX_Encode_Value(value, _data, _dataLength, type); } diff --git a/src/knx/group_object.h b/src/knx/group_object.h index 88ce274..cf46307 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -7,7 +7,7 @@ class GroupObjectTableObject; -enum ComFlag +enum ComFlag : uint8_t { Updated = 0, //!< Group object was updated ReadRequest = 1, //!< Read was requested but was not processed @@ -18,6 +18,16 @@ enum ComFlag Uninitialized = 6 //!< uninitialized Group Object, its value is not valid }; +// extended ComFlag: Uninitialized it not handled correctly as ComFlag +// it might be in state Transmitting during a ReadRequest on startup while value is still not valid +// we use MSB to store Uninitialized and keep the size of GroupObject the same saving memory ressources +// the old Uninitialized handling is still there for compatibility reasons. +struct ComFlagEx +{ + bool uninitialized : 1; + ComFlag commFlag : 7; +}; + class GroupObject; #ifndef HAS_FUNCTIONAL @@ -96,6 +106,11 @@ class GroupObject */ void commFlag(ComFlag value); + /** + * Check if the group object contains a valid value assigned from bus or from application program + */ + bool initialized(); + /** * Request the read of a communication object. Calling this function triggers the * sending of a read-group-value telegram, to read the value of the communication @@ -236,7 +251,7 @@ class GroupObject size_t asapValueSize(uint8_t code); size_t goSize(); uint16_t _asap = 0; - ComFlag _commFlag = Uninitialized; + ComFlagEx _commFlagEx; uint8_t* _data = 0; uint8_t _dataLength = 0; #ifndef SMALL_GROUPOBJECT From c6368db8a3d17c78fd97f7e93012de9b111f8697 Mon Sep 17 00:00:00 2001 From: Waldemar Porscha Date: Thu, 18 Jan 2024 22:43:31 +0100 Subject: [PATCH 02/22] Finalize uninitialized handling --- src/knx/group_object.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index cf3ad24..30420e3 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -175,12 +175,12 @@ bool GroupObject::initialized() void GroupObject::requestObjectRead() { - _commFlagEx.commFlag = ReadRequest; + commFlag(ReadRequest); } void GroupObject::objectWritten() { - _commFlagEx.commFlag = WriteRequest; + commFlag(WriteRequest); } size_t GroupObject::valueSize() @@ -283,10 +283,7 @@ void GroupObject::valueNoSend(const KNXValue& value) void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) { if (_commFlagEx.uninitialized) - { - _commFlagEx.commFlag = Ok; - _commFlagEx.uninitialized = false; - } + commFlag(Ok); KNX_Encode_Value(value, _data, _dataLength, type); } From f5f45e2ce1dccdecd1a057a636fafae3752b8785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cornelius=20K=C3=B6pp?= Date: Sun, 13 Aug 2023 22:01:45 +0200 Subject: [PATCH 03/22] Allow Setting Value of GroupObject (KO) with Checking Change after Conversion by `valueNoSendCompare(..)` * Return if Value was Changed * Always Set the First Value * Copy on Changes Only * Make Comparison Independent of Sending * Extend Doc of `valueNoSendCompare(..)`; Add Note for using `valueNoSend(..)` --- src/knx/group_object.cpp | 24 ++++++++++++++++++++++++ src/knx/group_object.h | 13 +++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 30420e3..80865f4 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -287,3 +287,27 @@ void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) KNX_Encode_Value(value, _data, _dataLength, type); } + +bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) +{ + if (_commFlag == Uninitialized) + { + // always set first value + this->valueNoSend(value, type); + return true; + } + else + { + // convert new value to given dtp + uint8_t newData[_dataLength]; + memset(newData, 0, _dataLength); + KNX_Encode_Value(value, newData, _dataLength, type); + + // check for change in converted value / update value on change only + const bool dataChanged = memcmp(_data, newData, _dataLength); + if (dataChanged) + memcpy(_data, newData, _dataLength); + + return dataChanged; + } +} \ No newline at end of file diff --git a/src/knx/group_object.h b/src/knx/group_object.h index cf46307..38b8291 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -181,6 +181,19 @@ class GroupObject * The parameters must fit the group object. Otherwise it will stay unchanged. */ void valueNoSend(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and update if necessary. + * Use this method only, when the value change is relevant, otherwise valueNoSend(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed + */ + bool valueNoSendCompare(const KNXValue& value, const Dpt& type); + /** * set the current value of the group object. * @param value the value the group object is set to From 1f426e9203dcb4dd25c891a6321d64df20fb8431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cornelius=20K=C3=B6pp?= Date: Mon, 22 Jan 2024 20:47:59 +0100 Subject: [PATCH 04/22] Update for New Uninitialized-Handling --- src/knx/group_object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 80865f4..0abbfc0 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -290,7 +290,7 @@ void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) { - if (_commFlag == Uninitialized) + if (_commFlagEx.uninitialized) { // always set first value this->valueNoSend(value, type); From defffbd5731c59b59f58b929f6186021d0776d8d Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Tue, 30 Jan 2024 10:49:26 +0100 Subject: [PATCH 05/22] fix build pipeline errors custom_hwids.py was never executed in the ci pipeline, but the missing script was just a warning. With Plattform I/O Core 6.1.11 missing scripts are errors, so the pipeline fails. --- examples/knx-usb/platformio-ci.ini | 5 +++-- examples/knx-usb/platformio.ini | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/knx-usb/platformio-ci.ini b/examples/knx-usb/platformio-ci.ini index 60cc010..dc8e232 100644 --- a/examples/knx-usb/platformio-ci.ini +++ b/examples/knx-usb/platformio-ci.ini @@ -14,8 +14,9 @@ framework = arduino ; VID must be changed to some known KNX Manufacturer ; so that the KNX USB interface gets recognized by ETS -extra_scripts = pre:custom_hwids.py -board_build.usb_product="KNX RF - USB Interface" +; not possible within ci +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" lib_deps = SPI diff --git a/examples/knx-usb/platformio.ini b/examples/knx-usb/platformio.ini index 2d7a710..b227cc3 100644 --- a/examples/knx-usb/platformio.ini +++ b/examples/knx-usb/platformio.ini @@ -28,7 +28,7 @@ board_build.usb_product="KNX RF - USB Interface" lib_deps = SPI Adafruit TinyUSB Library@0.7.1 - https://github.com/thelsing/FlashStorage.git + ;https://github.com/thelsing/FlashStorage.git knx build_flags = From 9d7c8acde66ab523b28841bd242c75c6166c4ed9 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Tue, 13 Feb 2024 09:38:51 +0100 Subject: [PATCH 06/22] update library.properties to V2.0.0 --- library.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library.properties b/library.properties index 441ebef..980e8ea 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=knx -version=1.2.0 -author=Thomas Kunze -maintainer=Thomas Kunze +version=2.0.0 +author=Thomas Kunze et al. +maintainer=OpenKNX Team sentence=knx stack paragraph= category=Communication -url=https://github.com/thelsing/knx +url=https://github.com/OpenKNX/knx architectures=* -includes=knx.h +includes=knx.h \ No newline at end of file From cacbd9f1751e3fbf20f0ca518f0702489cec4bc8 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Mon, 26 Feb 2024 12:40:29 +0100 Subject: [PATCH 07/22] add rp2040 build environment to knx-demo example for both example project and ci --- examples/knx-demo/platformio-ci.ini | 17 +++++++++++++++++ examples/knx-demo/platformio.ini | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/examples/knx-demo/platformio-ci.ini b/examples/knx-demo/platformio-ci.ini index 2f0b04c..0ea2778 100644 --- a/examples/knx-demo/platformio-ci.ini +++ b/examples/knx-demo/platformio-ci.ini @@ -79,3 +79,20 @@ build_flags = -DMASK_VERSION=0x07B0 -Wno-unknown-pragmas -DUSE_DATASECURE + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower + +lib_deps = + knx + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas diff --git a/examples/knx-demo/platformio.ini b/examples/knx-demo/platformio.ini index f47cade..0db86f5 100644 --- a/examples/knx-demo/platformio.ini +++ b/examples/knx-demo/platformio.ini @@ -146,3 +146,26 @@ build_flags = -Wno-unknown-pragmas extra_scripts = ../scripts/stm32rdu.py + + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower +; We consider that the this projects is opened within its project directory +; while working with VS Code. + + +lib_deps = + knx=file://../../../knx + +lib_ldf_mode = deep+ + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas \ No newline at end of file From 03f55f5503759061800063814dbb5f357e281b44 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Mon, 26 Feb 2024 23:55:38 +0100 Subject: [PATCH 08/22] Makes the data link layer accessible from outside --- src/knx/bau07B0.cpp | 3 +++ src/knx/bau07B0.h | 3 ++- src/knx/bau091A.cpp | 7 +++++++ src/knx/bau091A.h | 2 ++ src/knx/bau27B0.cpp | 4 +++- src/knx/bau27B0.h | 1 + src/knx/bau2920.cpp | 7 +++++++ src/knx/bau2920.h | 2 ++ src/knx/bau57B0.cpp | 3 +++ src/knx/bau57B0.h | 3 ++- 10 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp index c68ecb2..c30906b 100644 --- a/src/knx/bau07B0.cpp +++ b/src/knx/bau07B0.cpp @@ -151,4 +151,7 @@ bool Bau07B0::isAckRequired(uint16_t address, bool isGrpAddr) return false; } +TpUartDataLinkLayer* Bau07B0::getDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayer; +} #endif diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h index b9c1e6c..b87ca6d 100644 --- a/src/knx/bau07B0.h +++ b/src/knx/bau07B0.h @@ -15,7 +15,8 @@ class Bau07B0 : public BauSystemBDevice, public ITpUartCallBacks, public DataLin void loop() override; bool enabled() override; void enabled(bool value) override; - + + TpUartDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau091A.cpp b/src/knx/bau091A.cpp index 2465bc4..202315f 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -167,4 +167,11 @@ bool Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) return false; } +IpDataLinkLayer* Bau091A::getPrimaryDataLinkLayer() { + return (IpDataLinkLayer*)&_dlLayerPrimary; +} + +TpUartDataLinkLayer* Bau091A::getSecondaryDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayerSecondary; +} #endif diff --git a/src/knx/bau091A.h b/src/knx/bau091A.h index 606fbda..59b8151 100644 --- a/src/knx/bau091A.h +++ b/src/knx/bau091A.h @@ -18,6 +18,8 @@ class Bau091A : public BauSystemBCoupler, public ITpUartCallBacks, public DataLi bool enabled() override; void enabled(bool value) override; + IpDataLinkLayer* getPrimaryDataLinkLayer(); + TpUartDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp index 6180539..e11cde7 100644 --- a/src/knx/bau27B0.cpp +++ b/src/knx/bau27B0.cpp @@ -181,5 +181,7 @@ void Bau27B0::domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCo { } - +RfDataLinkLayer* Bau27B0::getDataLinkLayer() { + return (RfDataLinkLayer*)&_dlLayer; +} #endif // #ifdef USE_RF diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h index 222a848..b5d5e81 100644 --- a/src/knx/bau27B0.h +++ b/src/knx/bau27B0.h @@ -22,6 +22,7 @@ class Bau27B0 : public BauSystemBDevice bool enabled() override; void enabled(bool value) override; + RfDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau2920.cpp b/src/knx/bau2920.cpp index f5e5367..3ed7827 100644 --- a/src/knx/bau2920.cpp +++ b/src/knx/bau2920.cpp @@ -154,4 +154,11 @@ void Bau2920::loop() BauSystemBCoupler::loop(); } +TpUartDataLinkLayer* Bau2920::getPrimaryDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayerPrimary; +} + +RfDataLinkLayer* Bau2920::getSecondaryDataLinkLayer() { + return (RfDataLinkLayer*)&_dlLayerSecondary; +} #endif diff --git a/src/knx/bau2920.h b/src/knx/bau2920.h index 43a5bc5..6c0bbcb 100644 --- a/src/knx/bau2920.h +++ b/src/knx/bau2920.h @@ -22,6 +22,8 @@ class Bau2920 : public BauSystemBCoupler bool enabled() override; void enabled(bool value) override; + TpUartDataLinkLayer* getPrimaryDataLinkLayer(); + RfDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp index 75522aa..2e3b7d2 100644 --- a/src/knx/bau57B0.cpp +++ b/src/knx/bau57B0.cpp @@ -145,4 +145,7 @@ void Bau57B0::loop() #endif } +IpDataLinkLayer* Bau57B0::getDataLinkLayer() { + return (IpDataLinkLayer*)&_dlLayer; +} #endif diff --git a/src/knx/bau57B0.h b/src/knx/bau57B0.h index f6309f9..fb437a1 100644 --- a/src/knx/bau57B0.h +++ b/src/knx/bau57B0.h @@ -15,7 +15,8 @@ class Bau57B0 : public BauSystemBDevice, public DataLinkLayerCallbacks void loop() override; bool enabled() override; void enabled(bool value) override; - + + IpDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); From f5724c64d1366c39f9e710f682ea9c6b8bf7159f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Tue, 27 Feb 2024 16:02:29 +0100 Subject: [PATCH 09/22] Extends the platforms with additional uart methods. --- src/arduino_platform.cpp | 5 +++++ src/arduino_platform.h | 1 + src/knx/platform.cpp | 8 ++++++++ src/knx/platform.h | 2 ++ src/rp2040_arduino_platform.cpp | 5 +++++ src/rp2040_arduino_platform.h | 2 ++ 6 files changed, 23 insertions(+) diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp index 62ab8a9..d23d4c2 100644 --- a/src/arduino_platform.cpp +++ b/src/arduino_platform.cpp @@ -103,6 +103,11 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length) return length; } +void ArduinoPlatform::flushUart() +{ + return _knxSerial->flush(); +} + #ifndef KNX_NO_SPI void ArduinoPlatform::setupSpi() { diff --git a/src/arduino_platform.h b/src/arduino_platform.h index 29d846e..9118920 100644 --- a/src/arduino_platform.h +++ b/src/arduino_platform.h @@ -25,6 +25,7 @@ class ArduinoPlatform : public Platform virtual size_t writeUart(const uint8_t* buffer, size_t size); virtual int readUart(); virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual void flushUart(); //spi #ifndef KNX_NO_SPI diff --git a/src/knx/platform.cpp b/src/knx/platform.cpp index 545e3ad..2b398ae 100644 --- a/src/knx/platform.cpp +++ b/src/knx/platform.cpp @@ -57,6 +57,14 @@ void Platform::closeUart() void Platform::setupUart() {} +bool Platform::overflowUart() +{ + return false; +} + +void Platform::flushUart() +{} + uint32_t Platform::currentIpAddress() { return 0x01020304; diff --git a/src/knx/platform.h b/src/knx/platform.h index b9e4c5c..ddb1a67 100644 --- a/src/knx/platform.h +++ b/src/knx/platform.h @@ -62,6 +62,8 @@ class Platform virtual size_t writeUart(const uint8_t* buffer, size_t size); virtual int readUart(); virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual bool overflowUart(); + virtual void flushUart(); // SPI virtual void setupSpi(); diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index ddc89cb..dce06e8 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -85,6 +85,11 @@ void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) _txPin = txPin; } +bool RP2040ArduinoPlatform::overflowUart() { + SerialUART* serial = dynamic_cast(_knxSerial); + return serial->overflow(); +} + void RP2040ArduinoPlatform::setupUart() { SerialUART* serial = dynamic_cast(_knxSerial); diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index b4e7f8d..4d71862 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -68,6 +68,8 @@ public: // uart void knxUartPins(pin_size_t rxPin, pin_size_t txPin); void setupUart(); + virtual bool overflowUart(); + // unique serial number uint32_t uniqueSerialNumber() override; From 6b2ac7e50bccd468deba31ebafac68be72d0f3c8 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 28 Mar 2024 08:33:15 +0100 Subject: [PATCH 10/22] Adds dma support for rp2040 uart --- src/rp2040_arduino_platform.cpp | 290 ++++++++++++++++++++++++++------ src/rp2040_arduino_platform.h | 33 +++- 2 files changed, 266 insertions(+), 57 deletions(-) diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index dce06e8..691d768 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -4,7 +4,7 @@ Plattform for Raspberry Pi Pico and other RP2040 boards by SirSydom 2021-2022 made to work with arduino-pico - "Raspberry Pi Pico Arduino core, for all RP2040 boards" -by Earl E. Philhower III https://github.com/earlephilhower/arduino-pico +by Earl E. Philhower III https://github.com/earlephilhower/arduino-pico RTTI must be set to enabled in the board options @@ -31,20 +31,77 @@ For usage of KNX-IP you have to define either #include -//Pi Pico specific libs -#include // EEPROM emulation in flash, part of Earl E Philhowers Pi Pico Arduino support -#include // from Pico SDK -#include // from Pico SDK -#include // from Pico SDK +// Pi Pico specific libs +#include // EEPROM emulation in flash, part of Earl E Philhowers Pi Pico Arduino support +#include // from Pico SDK +#include // from Pico SDK +#include // from Pico SDK + +#ifdef USE_KNX_DMA_UART +#include +// constexpr uint32_t uartDmaTransferCount = 0b1111111111; +constexpr uint32_t uartDmaTransferCount = UINT32_MAX; +constexpr uint8_t uartDmaBufferExp = 8u; // 2**BufferExp +constexpr uint16_t uartDmaBufferSize = (1u << uartDmaBufferExp); +int8_t uartDmaChannel = -1; +volatile uint8_t __attribute__((aligned(uartDmaBufferSize))) uartDmaBuffer[uartDmaBufferSize] = {}; +volatile uint32_t uartDmaReadCount = 0; +volatile uint16_t uartDmaRestartCount = 0; +volatile uint32_t uartDmaWriteCount2 = 0; +volatile uint32_t uartDmaAvail = 0; + +// Liefert die Zahl der gelesenen Bytes seit dem DMA Transferstart +inline uint32_t uartDmaWriteCount() +{ + uartDmaWriteCount2 = uartDmaTransferCount - dma_channel_hw_addr(uartDmaChannel)->transfer_count; + return uartDmaWriteCount2; +} + +// Liefert die aktuelle Schreibposition im DMA Buffer +inline uint16_t uartDmaWriteBufferPosition() +{ + return uartDmaWriteCount() % uartDmaBufferSize; +} + +// Liefert die aktuelle Leseposition im DMA Buffer +inline uint16_t uartDmaReadBufferPosition() +{ + return uartDmaReadCount % uartDmaBufferSize; +} + +// Liefert die aktuelle Leseposition als Pointer +inline uint8_t* uartDmaReadAddr() +{ + return ((uint8_t*)uartDmaBuffer + uartDmaReadBufferPosition()); +} + +// Startet den Transfer nach Abschluss neu. +void __time_critical_func(uartDmaRestart)() +{ + // println("Restart"); + uartDmaRestartCount = uartDmaWriteBufferPosition() - uartDmaReadBufferPosition(); + + // wenn uartDmaRestartCount == 0 ist, wurde alles verarbeitet und der read count kann mit dem neustart wieder auf 0 gesetzt werden. + if (uartDmaRestartCount == 0) + { + uartDmaReadCount = 0; + } + + asm volatile("" ::: "memory"); + dma_hw->ints0 = 1u << uartDmaChannel; // clear DMA IRQ0 flag + asm volatile("" ::: "memory"); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, true); +} +#endif #define FLASHPTR ((uint8_t*)XIP_BASE + KNX_FLASH_OFFSET) #ifndef USE_RP2040_EEPROM_EMULATION -#if KNX_FLASH_SIZE%4096 +#if KNX_FLASH_SIZE % 4096 #error "KNX_FLASH_SIZE must be multiple of 4096" #endif -#if KNX_FLASH_OFFSET%4096 +#if KNX_FLASH_OFFSET % 4096 #error "KNX_FLASH_OFFSET must be multiple of 4096" #endif #endif @@ -57,26 +114,27 @@ extern Wiznet5500lwIP KNX_NETIF; #endif RP2040ArduinoPlatform::RP2040ArduinoPlatform() -#ifndef KNX_NO_DEFAULT_UART +#if !defined(KNX_NO_DEFAULT_UART) && !defined(USE_KNX_DMA_UART) : ArduinoPlatform(&KNX_SERIAL) #endif { - #ifdef KNX_UART_RX_PIN +#ifdef KNX_UART_RX_PIN _rxPin = KNX_UART_RX_PIN; - #endif - #ifdef KNX_UART_TX_PIN +#endif +#ifdef KNX_UART_TX_PIN _txPin = KNX_UART_TX_PIN; - #endif - #ifndef USE_RP2040_EEPROM_EMULATION +#endif +#ifndef USE_RP2040_EEPROM_EMULATION _memoryType = Flash; - #endif +#endif } -RP2040ArduinoPlatform::RP2040ArduinoPlatform( HardwareSerial* s) : ArduinoPlatform(s) +RP2040ArduinoPlatform::RP2040ArduinoPlatform(HardwareSerial* s) + : ArduinoPlatform(s) { - #ifndef USE_RP2040_EEPROM_EMULATION +#ifndef USE_RP2040_EEPROM_EMULATION _memoryType = Flash; - #endif +#endif } void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) @@ -85,36 +143,162 @@ void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) _txPin = txPin; } -bool RP2040ArduinoPlatform::overflowUart() { +bool RP2040ArduinoPlatform::overflowUart() +{ +#ifdef USE_KNX_DMA_UART + // during dma restart + bool ret; + const uint32_t writeCount = uartDmaWriteCount(); + if (uartDmaRestartCount > 0) + ret = writeCount >= (uartDmaBufferSize - uartDmaRestartCount - 1); + else + ret = (writeCount - uartDmaReadCount) > uartDmaBufferSize; + + // if (ret) + // { + // println(uartDmaWriteBufferPosition()); + // println(uartDmaReadBufferPosition()); + // println(uartDmaWriteCount()); + // println(uartDmaReadCount); + // println(uartDmaRestartCount); + // printHex("BUF: ", (const uint8_t *)uartDmaBuffer, uartDmaBufferSize); + // println("OVERFLOW"); + // while (true) + // ; + // } + return ret; +#else SerialUART* serial = dynamic_cast(_knxSerial); return serial->overflow(); +#endif } void RP2040ArduinoPlatform::setupUart() { +#ifdef USE_KNX_DMA_UART + if (uartDmaChannel == -1) + { + // configure uart0 + gpio_set_function(_rxPin, GPIO_FUNC_UART); + gpio_set_function(_txPin, GPIO_FUNC_UART); + uart_init(KNX_DMA_UART, 19200); + uart_set_hw_flow(KNX_DMA_UART, false, false); + uart_set_format(KNX_DMA_UART, 8, 1, UART_PARITY_EVEN); + uart_set_fifo_enabled(KNX_DMA_UART, false); + + // configure uart0 + uartDmaChannel = dma_claim_unused_channel(true); // get free channel for dma + dma_channel_config dmaConfig = dma_channel_get_default_config(uartDmaChannel); + channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_8); + channel_config_set_read_increment(&dmaConfig, false); + channel_config_set_write_increment(&dmaConfig, true); + channel_config_set_high_priority(&dmaConfig, true); + channel_config_set_ring(&dmaConfig, true, uartDmaBufferExp); + channel_config_set_dreq(&dmaConfig, KNX_DMA_UART_DREQ); + dma_channel_set_read_addr(uartDmaChannel, &uart_get_hw(uart0)->dr, false); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, false); + dma_channel_set_trans_count(uartDmaChannel, uartDmaTransferCount, false); + dma_channel_set_config(uartDmaChannel, &dmaConfig, true); + dma_channel_set_irq1_enabled(uartDmaChannel, true); + // irq_add_shared_handler(KNX_DMA_IRQ, uartDmaRestart, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); + irq_set_exclusive_handler(KNX_DMA_IRQ, uartDmaRestart); + irq_set_enabled(KNX_DMA_IRQ, true); + } +#else SerialUART* serial = dynamic_cast(_knxSerial); - if(serial) + if (serial) { if (_rxPin != UART_PIN_NOT_DEFINED) serial->setRX(_rxPin); if (_txPin != UART_PIN_NOT_DEFINED) serial->setTX(_txPin); serial->setPollingMode(); + serial->setFIFOSize(64); } _knxSerial->begin(19200, SERIAL_8E1); - while (!_knxSerial) + while (!_knxSerial) ; +#endif } +#ifdef USE_KNX_DMA_UART +int RP2040ArduinoPlatform::uartAvailable() +{ + if (uartDmaChannel == -1) + return 0; + + if (uartDmaRestartCount > 0) + { + return uartDmaRestartCount; + } + else + { + uint32_t tc = dma_channel_hw_addr(uartDmaChannel)->transfer_count; + uartDmaAvail = tc; + int test = uartDmaTransferCount - tc - uartDmaReadCount; + return test; + } +} + +int RP2040ArduinoPlatform::readUart() +{ + if (!uartAvailable()) + return -1; + + int ret = uartDmaReadAddr()[0]; + // print("< "); + // println(ret, HEX); + uartDmaReadCount++; + + if (uartDmaRestartCount > 0) + { + // process previouse buffer + uartDmaRestartCount--; + + // last char, then reset read count to start at new writer position + if (uartDmaRestartCount == 0) + uartDmaReadCount = 0; + } + + return ret; +} + +size_t RP2040ArduinoPlatform::writeUart(const uint8_t data) +{ + if (uartDmaChannel == -1) + return 0; + + // print("> "); + // println(data, HEX); + while (!uart_is_writable(uart0)) + ; + uart_putc_raw(uart0, data); + return 1; +} + +void RP2040ArduinoPlatform::closeUart() +{ + if (uartDmaChannel >= 0) + { + dma_channel_cleanup(uartDmaChannel); + irq_set_enabled(DMA_IRQ_0, false); + uart_deinit(uart0); + uartDmaChannel = -1; + uartDmaReadCount = 0; + uartDmaRestartCount = 0; + } +} +#endif + uint32_t RP2040ArduinoPlatform::uniqueSerialNumber() { - pico_unique_board_id_t id; // 64Bit unique serial number from the QSPI flash + pico_unique_board_id_t id; // 64Bit unique serial number from the QSPI flash noInterrupts(); rp2040.idleOtherCore(); - flash_get_unique_id(id.id); //pico_get_unique_board_id(&id); + flash_get_unique_id(id.id); // pico_get_unique_board_id(&id); rp2040.resumeOtherCore(); interrupts(); @@ -128,7 +312,7 @@ uint32_t RP2040ArduinoPlatform::uniqueSerialNumber() void RP2040ArduinoPlatform::restart() { println("restart"); - watchdog_reboot(0,0,0); + watchdog_reboot(0, 0, 0); } #ifdef USE_RP2040_EEPROM_EMULATION @@ -137,20 +321,20 @@ void RP2040ArduinoPlatform::restart() #ifdef USE_RP2040_LARGE_EEPROM_EMULATION -uint8_t * RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) { - if(size%4096) + if (size % 4096) { println("KNX_FLASH_SIZE must be a multiple of 4096"); fatalError(); } - - if(!_rambuff_initialized) + + if (!_rambuff_initialized) { memcpy(_rambuff, FLASHPTR, KNX_FLASH_SIZE); _rambuff_initialized = true; } - + return _rambuff; } @@ -159,10 +343,10 @@ void RP2040ArduinoPlatform::commitToEeprom() noInterrupts(); rp2040.idleOtherCore(); - //ToDo: write block-by-block to prevent writing of untouched blocks - if(memcmp(_rambuff, FLASHPTR, KNX_FLASH_SIZE)) + // ToDo: write block-by-block to prevent writing of untouched blocks + if (memcmp(_rambuff, FLASHPTR, KNX_FLASH_SIZE)) { - flash_range_erase (KNX_FLASH_OFFSET, KNX_FLASH_SIZE); + flash_range_erase(KNX_FLASH_OFFSET, KNX_FLASH_SIZE); flash_range_program(KNX_FLASH_OFFSET, _rambuff, KNX_FLASH_SIZE); } @@ -172,22 +356,22 @@ void RP2040ArduinoPlatform::commitToEeprom() #else -uint8_t * RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) { - if(size > 4096) + if (size > 4096) { println("KNX_FLASH_SIZE to big for EEPROM emulation (max. 4kB)"); fatalError(); } - - uint8_t * eepromptr = EEPROM.getDataPtr(); - if(eepromptr == nullptr) + uint8_t* eepromptr = EEPROM.getDataPtr(); + + if (eepromptr == nullptr) { EEPROM.begin(4096); eepromptr = EEPROM.getDataPtr(); } - + return eepromptr; } @@ -217,10 +401,10 @@ uint8_t* RP2040ArduinoPlatform::userFlashStart() size_t RP2040ArduinoPlatform::userFlashSizeEraseBlocks() { - if(KNX_FLASH_SIZE <= 0) + if (KNX_FLASH_SIZE <= 0) return 0; else - return ( (KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; + return ((KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; } void RP2040ArduinoPlatform::flashErase(uint16_t eraseBlockNum) @@ -228,7 +412,7 @@ void RP2040ArduinoPlatform::flashErase(uint16_t eraseBlockNum) noInterrupts(); rp2040.idleOtherCore(); - flash_range_erase (KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + flash_range_erase(KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); rp2040.resumeOtherCore(); interrupts(); @@ -247,12 +431,12 @@ void RP2040ArduinoPlatform::flashWritePage(uint16_t pageNumber, uint8_t* data) void RP2040ArduinoPlatform::writeBufferedEraseBlock() { - if(_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) { noInterrupts(); rp2040.idleOtherCore(); - flash_range_erase (KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + flash_range_erase(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); flash_range_program(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), _eraseblockBuffer, flashPageSize() * flashEraseBlockSize()); rp2040.resumeOtherCore(); @@ -281,7 +465,7 @@ void RP2040ArduinoPlatform::macAddress(uint8_t* addr) #if defined(KNX_IP_W5500) addr = KNX_NETIF.getNetIf()->hwaddr; #elif defined(KNX_IP_WIFI) - uint8_t macaddr[6] = {0,0,0,0,0,0}; + uint8_t macaddr[6] = {0, 0, 0, 0, 0, 0}; addr = KNX_NETIF.macAddress(macaddr); #elif defined(KNX_IP_GENERIC) KNX_NETIF.MACAddress(addr); @@ -294,12 +478,12 @@ void RP2040ArduinoPlatform::setupMultiCast(uint32_t addr, uint16_t port) mcastaddr = IPAddress(htonl(addr)); _port = port; uint8_t result = _udp.beginMulticast(mcastaddr, port); - (void) result; + (void)result; - #ifdef KNX_IP_GENERIC - //if(!_unicast_socket_setup) - // _unicast_socket_setup = UDP_UNICAST.begin(3671); - #endif +#ifdef KNX_IP_GENERIC +// if(!_unicast_socket_setup) +// _unicast_socket_setup = UDP_UNICAST.begin(3671); +#endif // print("Setup Mcast addr: "); // print(mcastaddr.toString().c_str()); @@ -318,7 +502,7 @@ bool RP2040ArduinoPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) { // printHex("<- ",buffer, len); - //ToDo: check if Ethernet is able to receive, return false if not + // ToDo: check if Ethernet is able to receive, return false if not _udp.beginPacket(mcastaddr, _port); _udp.write(buffer, len); _udp.endPacket(); @@ -353,12 +537,12 @@ int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) { IPAddress ucastaddr(htonl(addr)); - + // print("sendBytesUniCast to:"); // println(ucastaddr.toString().c_str()); #ifdef KNX_IP_GENERIC - if(!_unicast_socket_setup) + if (!_unicast_socket_setup) _unicast_socket_setup = UDP_UNICAST.begin(3671); #endif @@ -376,5 +560,3 @@ bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8 #endif #endif - - diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index 4d71862..c02c2a7 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -58,6 +58,22 @@ #endif +#if USE_KNX_DMA_UART == 1 +#define KNX_DMA_UART uart1 +#define KNX_DMA_UART_IRQ UART1_IRQ +#define KNX_DMA_UART_DREQ DREQ_UART1_RX +#else +#define KNX_DMA_UART uart0 +#define KNX_DMA_UART_IRQ UART0_IRQ +#define KNX_DMA_UART_DREQ DREQ_UART0_RX +#endif + +#if USE_KNX_DMA_IRQ == 1 +#define KNX_DMA_IRQ DMA_IRQ_1 +#else +#define KNX_DMA_IRQ DMA_IRQ_0 +#endif + class RP2040ArduinoPlatform : public ArduinoPlatform { @@ -67,9 +83,20 @@ public: // uart void knxUartPins(pin_size_t rxPin, pin_size_t txPin); - void setupUart(); - virtual bool overflowUart(); - + void setupUart() override; + bool overflowUart() override; + #ifdef USE_KNX_DMA_UART + int uartAvailable() override; + void closeUart() override; + void knxUart( HardwareSerial* serial) override {}; + HardwareSerial* knxUart() override { return nullptr; }; + size_t writeUart(const uint8_t data) override; + size_t writeUart(const uint8_t* buffer, size_t size) override { return 0; }; + int readUart() override; + size_t readBytesUart(uint8_t* buffer, size_t length) override { return 0; }; + void flushUart() override {}; + #endif + // unique serial number uint32_t uniqueSerialNumber() override; From 1ee78a45d187836cfb838e240b8487a3df80d05f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 28 Mar 2024 08:38:55 +0100 Subject: [PATCH 11/22] Reimplementation of the tpuart data link layer --- src/knx/tp_frame.h | 301 +++++ src/knx/tpuart_data_link_layer.cpp | 1670 ++++++++++++++++++---------- src/knx/tpuart_data_link_layer.h | 163 ++- 3 files changed, 1480 insertions(+), 654 deletions(-) create mode 100644 src/knx/tp_frame.h 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; From 8388c79f9f089847d84fd3ea2f3d3a2990afa862 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 29 Mar 2024 22:31:59 +0100 Subject: [PATCH 12/22] adds gcc optimize for tp frame --- src/knx/tp_frame.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 856af93..32dd13f 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -1,4 +1,5 @@ #pragma once +#pragma GCC optimize("O3") #include "cemi_frame.h" #include From cf1b6bc7accaf47eb4386268f1a412a875e4df59 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 29 Mar 2024 22:32:18 +0100 Subject: [PATCH 13/22] some bugfixes --- src/knx/tpuart_data_link_layer.cpp | 128 ++++++++++++++--------------- src/knx/tpuart_data_link_layer.h | 2 +- 2 files changed, 63 insertions(+), 67 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 271ff6c..3f8a79f 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -169,11 +169,9 @@ void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) _rxOverflow = true; // verarbeiten daten - uint8_t counter = isr ? 10 : 255; - while (_platform.uartAvailable() && counter > 0) + while (_platform.uartAvailable()) { processRxByte(); - counter--; } isrUnlock(); @@ -190,49 +188,28 @@ void TpUartDataLinkLayer::processRxByte() if (byte < 0) return; - if (_rxState == RX_FRAME) + /* + * Wenn ich im RX_INVALID state bin + * und das letzte Byte vor mehr als 2ms verarbeitet wurde (also pause >2ms) + * und keine weiteren Bytes vorliegen, + * dann kann ich den INVALID State verwerfen. + */ + if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) { - 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 - */ + println("Reset RX_INVALID"); 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); - } + _rxState = RX_IDLE; } - else if (_rxState == RX_INVALID || _rxOverflow) + + if (_rxState == RX_INVALID) { /* - * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntest Kommando eintrifft ist wechselt der Zustand in RX_INVALID. + * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntes Kommando eintrifft, 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 + * 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) + * - Die Zeit des letzten Frames 3ms vorbei ist (kann aber nicht garantiert werden, wegen möglichem ISR & DMA. Hier sollte es das Problem aber nicht geben) + * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (Wird hier geprüft) * * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes */ @@ -253,11 +230,38 @@ void TpUartDataLinkLayer::processRxByte() _rxMarker = false; _rxState = RX_IDLE; } - else - { - // print("RX_INVALID: "); - // println(byte, HEX); - } + } + } + else 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 @@ -576,17 +580,17 @@ bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) * 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() +void TpUartDataLinkLayer::requestState(bool force /* = false */) { - if (_rxState != RX_IDLE) - return; + if (!force) + { + if (!(_rxState == RX_IDLE || _rxState == RX_INVALID)) + return; - if (_txState != TX_IDLE) - return; - - // Nur 1x pro Sekunde - if ((millis() - _lastStateRequest) < 1000) - return; + // Nur 1x pro Sekunde + if ((millis() - _lastStateRequest) < 1000) + return; + } // println("requestState"); @@ -720,7 +724,7 @@ bool TpUartDataLinkLayer::reset() if (success) { _lastStateRequest = 0; // Force - requestState(); + requestState(true); _rxLastTime = millis(); } @@ -860,6 +864,7 @@ void TpUartDataLinkLayer::loop() { println("TPUart overflow detected!"); _rxOverflow = false; + _rxState = RX_INVALID; } if (_tpState) @@ -869,7 +874,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - processRx(); + // processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif @@ -877,16 +882,6 @@ void TpUartDataLinkLayer::loop() 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) @@ -1066,7 +1061,8 @@ void TpUartDataLinkLayer::processRxFrame(TpFrame *tpFrame) printFrame(tpFrame); println(); #endif - rxFrameReceived(tpFrame); + if (!(tpFrame->flags() & TP_FRAME_FLAG_ECHO)) + rxFrameReceived(tpFrame); } } diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index da81aa4..bace540 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -137,7 +137,7 @@ class TpUartDataLinkLayer : public DataLinkLayer void processRxFrameComplete(); inline void processRxFrame(TpFrame* tpFrame); void pushTxFrameQueue(TpFrame* tpFrame); - void requestState(); + void requestState(bool force = false); void requestConfig(); inline void processRxFrameByte(uint8_t byte); From 6884734cc3347b83077e74c9cd7c32e2540f33cd Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 30 Mar 2024 06:58:18 +0100 Subject: [PATCH 14/22] fixes the missing processRx() in loop() --- src/knx/tpuart_data_link_layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 3f8a79f..f712fca 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -874,7 +874,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - // processRx(); + processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif From 9d7e29d705c3b9f10280e9b504c09f3155f8742e Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 30 Mar 2024 09:52:15 +0100 Subject: [PATCH 15/22] auto format --- src/knx/tp_frame.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 32dd13f..6647b63 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -2,9 +2,9 @@ #pragma GCC optimize("O3") #include "cemi_frame.h" +#include #include #include -#include // Means that the frame is invalid #define TP_FRAME_FLAG_INVALID 0b10000000 From 7b910eba59a283f4c02b5e7975748804ad814eaf Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 4 Apr 2024 20:51:45 +0200 Subject: [PATCH 16/22] small optimizes --- src/knx/tp_frame.h | 2 +- src/knx/tpuart_data_link_layer.cpp | 56 ++++++++++++++---------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 6647b63..a7475c5 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -67,7 +67,7 @@ class TpFrame TpFrame(uint16_t maxSize = 263) : _maxSize(maxSize) { - _data = new uint8_t[_maxSize]; + _data = (uint8_t *)malloc(_maxSize); _size = 0; } diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index f712fca..e6af0b3 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -123,13 +123,10 @@ enum // 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. - */ + // In diesem Zustand werdem alle Bytes verworfen RX_INVALID, + // Im Monitoring wird noch auf ein ACk gewartet RX_AWAITING_ACK }; @@ -189,9 +186,9 @@ void TpUartDataLinkLayer::processRxByte() return; /* - * Wenn ich im RX_INVALID state bin + * Wenn ich im RX_INVALID Modus bin * und das letzte Byte vor mehr als 2ms verarbeitet wurde (also pause >2ms) - * und keine weiteren Bytes vorliegen, + * und keine weiteren Bytes in der Buffer vorliegen, * dann kann ich den INVALID State verwerfen. */ if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) @@ -208,10 +205,10 @@ void TpUartDataLinkLayer::processRxByte() * 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. * - * - Die Zeit des letzten Frames 3ms vorbei ist (kann aber nicht garantiert werden, wegen möglichem ISR & DMA. Hier sollte es das Problem aber nicht geben) + * - Die Zeit des letzten Frames 3ms vorbei ist und keine Daten mehr im Buffer sind. (Wird übermir geprüft) * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (Wird hier geprüft) * - * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes + * Ansonsten macht dieser Abschnitt nichts und verwirrft damit die ungültigen Bytes */ if (markerMode()) { @@ -252,12 +249,12 @@ void TpUartDataLinkLayer::processRxByte() _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. + * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu, falls noch ein Ack vom vorherigen Frame gesetzt ist, + * zurück gesetzt wird. Das passiert wenn die Verarbeitung zu stark verzögert ist (z.B. weil kein DMA/IRQ genutzt wird). + * Das ACK kann beliebig oft gesendet werden, weil es in der BCU nur gespeichert wird und erst bei bedarf genutzt / gesendet wird. * - * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt. Ggf ignoriert die BCU dies, + * aber ich wollte hier auf sicher gehen. */ if (_txState == TX_IDLE) { @@ -266,6 +263,8 @@ void TpUartDataLinkLayer::processRxByte() } else { + // Hier werden die Commands ausgewertet, falls das noch schon passiert ist. + if (byte == U_RESET_IND) { // println("U_RESET_IND"); @@ -276,7 +275,7 @@ void TpUartDataLinkLayer::processRxByte() #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. + * Leider ist kein perfektes Timing möglich, so dass dieser Fehler ignoriert werden muss. Hat auch keine bekannte Auswirkungen. */ _tpState &= 0b11101000; #endif @@ -350,7 +349,7 @@ 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. + * Das Folgebyte ist also ausschlaggebend wie dieses Byte zu bewerten ist. */ if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) { @@ -377,9 +376,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) /* * 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 + * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature) aber aktiviert wurde. */ else if (markerMode() && _rxFrame->isFull()) { @@ -469,8 +466,8 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) */ void TpUartDataLinkLayer::processRxFrameComplete() { - // Sollte aktuell kein Frame in der Bearbeitung (size == 0) sein, dann breche ab - if (_rxFrame->size() == 0) + // Sollte aktuell kein Frame in der Bearbeitung sein, dann breche ab + if (!_rxFrame->size()) return; // Ist das Frame vollständig und gültig @@ -860,6 +857,10 @@ void TpUartDataLinkLayer::loop() if (!_initialized) return; + /* + * Sollte ein overflow erkannt worden sein, so wechsle in RX_INVALID. + * Das greift aber nur im loop und nicht im ISR. Aber bei Nutzung von ISR und DMA sollte dieser Fall nie passieren. + */ if (_rxOverflow) { println("TPUart overflow detected!"); @@ -874,7 +875,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - processRx(); + // processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif @@ -1112,16 +1113,13 @@ void TpUartDataLinkLayer::processRxQueue() while (_rxBufferCount) { const uint16_t size = pullByteFromRxQueue() + (pullByteFromRxQueue() << 8); - TpFrame *tpFrame = new TpFrame(size); - tpFrame->addFlags(pullByteFromRxQueue()); + TpFrame tpFrame = TpFrame(size); + tpFrame.addFlags(pullByteFromRxQueue()); for (uint16_t i = 0; i < size; i++) - { - tpFrame->addByte(pullByteFromRxQueue()); - } + tpFrame.addByte(pullByteFromRxQueue()); - processRxFrame(tpFrame); - delete tpFrame; + processRxFrame(&tpFrame); asm volatile("" ::: "memory"); _rxBufferCount--; } From e65afebd3ab33bd2e426e8273467e557e67ae8fe Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 5 Apr 2024 00:04:43 +0200 Subject: [PATCH 17/22] reenable sendframe handling reenable rxProcess in loop fixes a memory leak --- src/knx/tpuart_data_link_layer.cpp | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index e6af0b3..479b256 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -560,11 +560,11 @@ void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) { - // if (!_connected) - // { - // dataConReceived(cemiFrame, false); - // return false; - // } + if (!_connected || _monitoring) + { + dataConReceived(cemiFrame, false); + return false; + } TpFrame *tpFrame = new TpFrame(cemiFrame); // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); @@ -686,9 +686,14 @@ bool TpUartDataLinkLayer::reset() _rxInvalidFrameCounter = 0; _rxUnkownControlCounter = 0; if (_txFrame != nullptr) - _txFrame->reset(); + { + _txFrame = nullptr; + delete _txFrame; + } if (_rxFrame != nullptr) + { _rxFrame->reset(); + } _rxState = RX_IDLE; _txState = TX_IDLE; _connected = false; @@ -792,7 +797,6 @@ void TpUartDataLinkLayer::processTxQueue() if (_rxState != RX_IDLE) return; - // if (_txState != TX_IDLE) return; @@ -806,16 +810,23 @@ void TpUartDataLinkLayer::processTxQueue() _txFrameQueue.back = nullptr; } + // free old frame + if (_txFrame != nullptr) + delete _txFrame; + + // use frame from queue and delete queue entry _txFrame = entry->frame; + delete entry; + + _txState = TX_FRAME; + _txLastTime = millis(); + #ifdef DEBUG_TP_FRAMES print("Outbound: "); printFrame(_txFrame); println(); #endif - _txState = TX_FRAME; - _txLastTime = millis(); - delete entry; processTxFrameBytes(); } } @@ -875,7 +886,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - // processRx(); + processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif From 1754e5387b14c08b64941be604971207dc784c9f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Mon, 8 Apr 2024 21:21:30 +0200 Subject: [PATCH 18/22] fixes some problems with txqueue --- src/knx/tpuart_data_link_layer.cpp | 89 +++++++++++++++++++++++------- src/knx/tpuart_data_link_layer.h | 10 +++- 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 479b256..a378083 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -306,13 +306,7 @@ void TpUartDataLinkLayer::processRxByte() 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; + processTxFrameComplete(success); } else { @@ -436,7 +430,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) if (availableInRxQueue() < (_rxFrame->size() + 3)) { // Nur wenn ich nicht selber sende - if (_txState == RX_IDLE) + if (_txState == TX_IDLE) { _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); } @@ -529,6 +523,30 @@ void TpUartDataLinkLayer::processRxFrameComplete() _rxFrame->reset(); } +void TpUartDataLinkLayer::clearTxFrame() +{ + if (_txFrame != nullptr) + { + delete _txFrame; + _txFrame = nullptr; + } +} + +void TpUartDataLinkLayer::clearTxFrameQueue() +{ +} + +void TpUartDataLinkLayer::processTxFrameComplete(bool success) +{ + uint8_t *cemiData = _txFrame->cemiData(); + CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); + dataConReceived(cemiFrame, success); + free(cemiData); + clearTxFrame(); + _txProcessdFrameCounter++; + _txState = TX_IDLE; +} + /* * Steckt das zu sendende Frame in eine Queue, da der TpUart vielleicht gerade noch nicht sende bereit ist. */ @@ -545,6 +563,14 @@ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) _txFrameQueue.back->next = entry; _txFrameQueue.back = entry; } + + if (_txQueueCount > 10) + { + print("_txQueueCount:"); + print(_txQueueCount); + } + _txQueueCount++; + _txFrameCounter++; } void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) @@ -685,17 +711,15 @@ bool TpUartDataLinkLayer::reset() _rxInvalidFrameCounter = 0; _rxInvalidFrameCounter = 0; _rxUnkownControlCounter = 0; - if (_txFrame != nullptr) - { - _txFrame = nullptr; - delete _txFrame; - } + + clearTxFrame(); + clearTxFrameQueue(); + if (_rxFrame != nullptr) { _rxFrame->reset(); } _rxState = RX_IDLE; - _txState = TX_IDLE; _connected = false; _stopped = false; _monitoring = false; @@ -786,6 +810,17 @@ bool TpUartDataLinkLayer::enabled() const return _initialized && _connected; } +/* + * Wenn ein TxFrame gesendet wurde, wird eine Bestätigung für den Versand erwartet. + * Kam es aber zu einem ungültigen Frame oder Busdisconnect, bleibt die Bestätigung aus und der STack hängt im TX_FRAME fest. + * Daher muss nach einer kurzen Wartezeit das Warten beendet werden. + */ +void TpUartDataLinkLayer::clearOutdatedTxFrame() +{ + if (_txState == TX_FRAME && (millis() - _txLastTime) > 1000) + processTxFrameComplete(false); +} + /* * 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. @@ -793,10 +828,6 @@ bool TpUartDataLinkLayer::enabled() const */ 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; @@ -810,9 +841,9 @@ void TpUartDataLinkLayer::processTxQueue() _txFrameQueue.back = nullptr; } - // free old frame - if (_txFrame != nullptr) - delete _txFrame; + _txQueueCount--; + + clearTxFrame(); // use frame from queue and delete queue entry _txFrame = entry->frame; @@ -892,6 +923,7 @@ void TpUartDataLinkLayer::loop() #endif requestState(); + clearOutdatedTxFrame(); processTxQueue(); checkConnected(); } @@ -1015,6 +1047,21 @@ uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() return _rxUnkownControlCounter; } +/* + * Liefert die Anzahl der zusendenden Frames + */ +uint32_t TpUartDataLinkLayer::getTxFrameCounter() +{ + return _txFrameCounter; +} +/* + * Liefert die Anzahl der versendeten Frames + */ +uint32_t TpUartDataLinkLayer::getTxProcessedFrameCounter() +{ + return _txProcessdFrameCounter; +} + bool TpUartDataLinkLayer::isConnected() { return _connected; diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index bace540..24afa1a 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -64,6 +64,8 @@ class TpUartDataLinkLayer : public DataLinkLayer uint32_t getRxProcessdFrameCounter(); uint32_t getRxIgnoredFrameCounter(); uint32_t getRxUnknownControlCounter(); + uint32_t getTxFrameCounter(); + uint32_t getTxProcessedFrameCounter(); uint8_t getMode(); private: @@ -95,19 +97,21 @@ class TpUartDataLinkLayer : public DataLinkLayer 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 uint32_t _txFrameCounter = 0; + volatile uint32_t _txProcessdFrameCounter = 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; + uint8_t _txQueueCount = 0; inline bool markerMode(); @@ -134,6 +138,7 @@ class TpUartDataLinkLayer : public DataLinkLayer void checkConnected(); void processRxByte(); void processTxQueue(); + void clearTxFrameQueue(); void processRxFrameComplete(); inline void processRxFrame(TpFrame* tpFrame); void pushTxFrameQueue(TpFrame* tpFrame); @@ -159,6 +164,9 @@ class TpUartDataLinkLayer : public DataLinkLayer inline void isrUnlock(); inline void clearUartBuffer(); inline void connected(bool state = true); + void clearTxFrame(); + void clearOutdatedTxFrame(); + void processTxFrameComplete(bool success); ITpUartCallBacks& _cb; DataLinkLayerCallbacks* _dllcb; From 97e15c7d9257f081a5280096e1679c0cb63c1425 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Wed, 10 Apr 2024 14:36:19 +0200 Subject: [PATCH 19/22] remove some debug output. adds a tx queue size limit with warning. --- src/knx/tpuart_data_link_layer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index a378083..834c212 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -109,6 +109,8 @@ #define ACR0_FLAG_TRIGEN 0x08 #define ACR0_FLAG_V20VCLIMIT 0x04 +#define MAX_TX_QUEUE 20 + enum { TX_IDLE, @@ -193,7 +195,6 @@ void TpUartDataLinkLayer::processRxByte() */ if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) { - println("Reset RX_INVALID"); processRxFrameComplete(); _rxState = RX_IDLE; } @@ -564,11 +565,6 @@ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) _txFrameQueue.back = entry; } - if (_txQueueCount > 10) - { - print("_txQueueCount:"); - print(_txQueueCount); - } _txQueueCount++; _txFrameCounter++; } @@ -586,8 +582,12 @@ void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) { - if (!_connected || _monitoring) + if (!_connected || _monitoring || _txQueueCount > MAX_TX_QUEUE) { + if (_txQueueCount > MAX_TX_QUEUE) + { + println("Ignore frame because transmit queue is full!"); + } dataConReceived(cemiFrame, false); return false; } From 1ef4856740dc8798aa5f0ef7e4f5d63dfe173830 Mon Sep 17 00:00:00 2001 From: Dom Date: Mon, 22 Apr 2024 13:22:15 +0200 Subject: [PATCH 20/22] Update platformio-ci.ini use newest atmelsam plattform --- examples/knx-usb/platformio-ci.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/knx-usb/platformio-ci.ini b/examples/knx-usb/platformio-ci.ini index dc8e232..6688b72 100644 --- a/examples/knx-usb/platformio-ci.ini +++ b/examples/knx-usb/platformio-ci.ini @@ -8,7 +8,7 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:adafruit_feather_m0] -platform = atmelsam@6.0.1 +platform = atmelsam board = adafruit_feather_m0 framework = arduino From f94bd0b674b3fefec9afbab8d1ad10551086f74d Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sun, 19 May 2024 23:52:21 +0200 Subject: [PATCH 21/22] allow to change MAX_TX_QUEUE --- src/knx/tpuart_data_link_layer.cpp | 2 -- src/knx/tpuart_data_link_layer.h | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 834c212..e74df73 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -109,8 +109,6 @@ #define ACR0_FLAG_TRIGEN 0x08 #define ACR0_FLAG_V20VCLIMIT 0x04 -#define MAX_TX_QUEUE 20 - enum { TX_IDLE, diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index 24afa1a..729e7be 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -8,10 +8,15 @@ #include #define MAX_KNX_TELEGRAM_SIZE 263 + #ifndef MAX_RX_QUEUE_BYTES #define MAX_RX_QUEUE_BYTES MAX_KNX_TELEGRAM_SIZE + 50 #endif +#ifndef MAX_TX_QUEUE +#define MAX_TX_QUEUE 50 +#endif + // __time_critical_func fallback #ifndef ARDUINO_ARCH_RP2040 #define __time_critical_func(X) X From a330699a0e72e9ee92c586e10528799f38f25e13 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Wed, 17 Apr 2024 19:21:27 +0200 Subject: [PATCH 22/22] Add function to group object to send a value only if it was changed. --- src/knx/group_object.cpp | 10 ++++++++++ src/knx/group_object.h | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 0abbfc0..5a9ad28 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -310,4 +310,14 @@ bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) return dataChanged; } +} + +bool GroupObject::valueCompare(const KNXValue& value, const Dpt& type) +{ + if (valueNoSendCompare(value, type)) + { + objectWritten(); + return true; + } + return false; } \ No newline at end of file diff --git a/src/knx/group_object.h b/src/knx/group_object.h index 38b8291..02bd0c3 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -173,6 +173,19 @@ class GroupObject * The parameters must fit the group object. Otherwise it will stay unchanged. */ void value(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and changes the state of the group object to ::WriteRequest if different. + * Use this method only, when the value should not be sent if it was not changed, otherwise value(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed + */ + bool valueCompare(const KNXValue& value, const Dpt& type); + /** * set the current value of the group object. * @param value the value the group object is set to