From fc0153e52a60a375896ae82e69610f6deaa9fb5b Mon Sep 17 00:00:00 2001 From: nanosonde <2073569+nanosonde@users.noreply.github.com> Date: Fri, 25 Oct 2019 16:41:29 +0200 Subject: [PATCH] initial KNX RF S-Mode support --- knx-linux/CMakeLists.txt | 45 +- knx-linux/main.cpp | 4 +- src/arduino_platform.cpp | 40 ++ src/arduino_platform.h | 10 + src/knx/application_layer.cpp | 106 ++++- src/knx/application_layer.h | 8 + src/knx/bau.cpp | 25 +- src/knx/bau.h | 14 +- src/knx/bau07B0.cpp | 6 + src/knx/bau27B0.cpp | 103 +++++ src/knx/bau27B0.h | 30 ++ src/knx/bau57B0.cpp | 6 + src/knx/bau_systemB.cpp | 33 ++ src/knx/bau_systemB.h | 2 + src/knx/cemi_frame.cpp | 151 ++++++- src/knx/cemi_frame.h | 20 +- src/knx/data_link_layer.cpp | 22 +- src/knx/data_link_layer.h | 2 +- src/knx/device_object.cpp | 15 +- src/knx/device_object.h | 5 +- src/knx/interface_object.h | 5 +- src/knx/knx_types.h | 31 +- src/knx/network_layer.cpp | 2 +- src/knx/platform.h | 9 + src/knx/property_types.h | 11 + src/knx/rf_data_link_layer.cpp | 365 +++++++++++++++ src/knx/rf_data_link_layer.h | 58 +++ src/knx/rf_medium_object.cpp | 128 ++++++ src/knx/rf_medium_object.h | 27 ++ src/knx/rf_physical_layer.cpp | 799 +++++++++++++++++++++++++++++++++ src/knx/rf_physical_layer.h | 257 +++++++++++ src/knx_facade.cpp | 25 +- src/knx_facade.h | 45 +- src/linux_platform.cpp | 397 +++++++++++++++- src/linux_platform.h | 19 + 35 files changed, 2761 insertions(+), 64 deletions(-) create mode 100644 src/knx/bau27B0.cpp create mode 100644 src/knx/bau27B0.h create mode 100644 src/knx/rf_data_link_layer.cpp create mode 100644 src/knx/rf_data_link_layer.h create mode 100644 src/knx/rf_medium_object.cpp create mode 100644 src/knx/rf_medium_object.h create mode 100644 src/knx/rf_physical_layer.cpp create mode 100644 src/knx/rf_physical_layer.h diff --git a/knx-linux/CMakeLists.txt b/knx-linux/CMakeLists.txt index 5a9ba65..d29ed91 100644 --- a/knx-linux/CMakeLists.txt +++ b/knx-linux/CMakeLists.txt @@ -9,7 +9,8 @@ add_executable(knx-linux ../src/knx/association_table_object.cpp ../src/knx/bau.cpp ../src/knx/bau07B0.cpp - ../src/knx/bau57B0.cpp + ../src/knx/bau27B0.cpp + ../src/knx/bau57B0.cpp ../src/knx/bau_systemB.cpp ../src/knx/bits.cpp ../src/knx/cemi_frame.cpp @@ -23,12 +24,50 @@ add_executable(knx-linux ../src/knx/memory.cpp ../src/knx/network_layer.cpp ../src/knx/npdu.cpp - ../src/knx/table_object.cpp + ../src/knx/rf_physical_layer.cpp + ../src/knx/rf_data_link_layer.cpp + ../src/knx/rf_medium_object.cpp + ../src/knx/table_object.cpp ../src/knx/tpdu.cpp ../src/knx/tpuart_data_link_layer.cpp ../src/knx/transport_layer.cpp ../src/knx/platform.cpp + ../src/knx/address_table_object.h + ../src/knx/apdu.h + ../src/knx/application_layer.h + ../src/knx/application_program_object.h + ../src/knx/association_table_object.h + ../src/knx/bau.h + ../src/knx/bau07B0.h + ../src/knx/bau27B0.h + ../src/knx/bau57B0.h + ../src/knx/bau_systemB.h + ../src/knx/bits.h + ../src/knx/cemi_frame.h + ../src/knx/data_link_layer.h + ../src/knx/device_object.h + ../src/knx/group_object.h + ../src/knx/group_object_table_object.h + ../src/knx/interface_object.h + ../src/knx/ip_data_link_layer.h + ../src/knx/ip_parameter_object.h + ../src/knx/memory.h + ../src/knx/network_layer.h + ../src/knx/npdu.h + ../src/knx/rf_physical_layer.h + ../src/knx/rf_data_link_layer.h + ../src/knx/rf_medium_object.h + ../src/knx/table_object.h + ../src/knx/tpdu.h + ../src/knx/tpuart_data_link_layer.h + ../src/knx/transport_layer.h + ../src/knx/platform.h main.cpp + ../src/linux_platform.h + ../src/knx_facade.h + ../src/knx/dptconvert.h + ../src/knx/knx_value.h + ../src/knx/dpt.h ../src/linux_platform.cpp ../src/knx_facade.cpp ../src/knx/dptconvert.cpp @@ -38,3 +77,5 @@ target_link_libraries(knx-linux "${LIBRARIES_FROM_REFERENCES}") include_directories(../src) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0") +set_property(TARGET knx-linux PROPERTY CXX_STANDARD 11) +install(TARGETS knx-linux RUNTIME DESTINATION /tmp) diff --git a/knx-linux/main.cpp b/knx-linux/main.cpp index 3a38d4a..21e7abc 100644 --- a/knx-linux/main.cpp +++ b/knx-linux/main.cpp @@ -1,5 +1,6 @@ #include "knx_facade.h" #include "knx/bau57B0.h" +//#include "knx/bau2705.h" #include "knx/group_object_table_object.h" #include "knx/bits.h" #include @@ -7,6 +8,7 @@ #include KnxFacade knx; +//KnxFacade knx; long lastsend = 0; @@ -94,6 +96,6 @@ int main(int argc, char **argv) knx.loop(); if(knx.configured()) appLoop(); - delay(100); + delayMicroseconds(1000); } } \ No newline at end of file diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp index 979745d..874f3b1 100644 --- a/src/arduino_platform.cpp +++ b/src/arduino_platform.cpp @@ -2,6 +2,7 @@ #include #include +#include Stream* ArduinoPlatform::SerialDebug = &Serial; @@ -60,6 +61,7 @@ void ArduinoPlatform::closeMultiCast() bool ArduinoPlatform::sendBytes(uint8_t * buffer, uint16_t len) { //not needed + return false; } int ArduinoPlatform::readBytes(uint8_t * buffer, uint16_t maxLen) @@ -137,6 +139,44 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length) return length; } +void ArduinoPlatform::setupSpi() +{ + SPI.begin(); + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); +} + +void ArduinoPlatform::closeSpi() +{ + SPI.endTransaction(); + SPI.end(); +} + +int ArduinoPlatform::readWriteSpi(uint8_t *data, size_t len) +{ + SPI.transfer(data, len); + return 0; +} + +void ArduinoPlatform::setupGpio(uint32_t dwPin, uint32_t dwMode) +{ + pinMode(dwPin, dwMode); +} + +void ArduinoPlatform::closeGpio(uint32_t dwPin) +{ + // not used +} + +void ArduinoPlatform::writeGpio(uint32_t dwPin, uint32_t dwVal) +{ + digitalWrite(dwPin, dwVal); +} + +uint32_t ArduinoPlatform::readGpio(uint32_t dwPin) +{ + return digitalRead(dwPin); +} + void print(const char* s) { ArduinoPlatform::SerialDebug->print(s); diff --git a/src/arduino_platform.h b/src/arduino_platform.h index 60acba8..53a32a8 100644 --- a/src/arduino_platform.h +++ b/src/arduino_platform.h @@ -35,6 +35,16 @@ class ArduinoPlatform : public Platform virtual int readUart(); virtual size_t readBytesUart(uint8_t* buffer, size_t length); + //spi + void setupSpi() override; + void closeSpi() override; + int readWriteSpi (uint8_t *data, size_t len) override; + + virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) override; + virtual void closeGpio(uint32_t dwPin) override; + virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) override; + virtual uint32_t readGpio(uint32_t dwPin) override; + static Stream* SerialDebug; protected: diff --git a/src/knx/application_layer.cpp b/src/knx/application_layer.cpp index 0252c0f..669bb44 100644 --- a/src/knx/application_layer.cpp +++ b/src/knx/application_layer.cpp @@ -90,8 +90,11 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr _bau.individualAddressReadAppLayerConfirm(hopType, apdu.frame().sourceAddress()); break; case IndividualAddressSerialNumberRead: - _bau.individualAddressSerialNumberReadIndication(hopType, data + 1); + { + uint8_t* knxSerialNumber = &data[1]; + _bau.individualAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber); break; + } case IndividualAddressSerialNumberResponse: { uint16_t domainAddress; @@ -102,9 +105,10 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr } case IndividualAddressSerialNumberWrite: { - uint16_t newAddress; - popWord(newAddress, data + 7); - _bau.individualAddressSerialNumberWriteIndication(hopType, data + 1, newAddress); + uint8_t* knxSerialNumber = &data[1]; + uint16_t newIndividualAddress; + popWord(newIndividualAddress, &data[7]); + _bau.individualAddressSerialNumberWriteIndication(priority, hopType, newIndividualAddress, knxSerialNumber); break; } } @@ -150,7 +154,40 @@ void ApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, P void ApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) { - + uint8_t* data = apdu.data(); + switch (apdu.type()) + { + // TODO: testInfo could be of any length + case SystemNetworkParameterRead: + { + uint16_t objectType; + uint16_t propertyId; + uint8_t testInfo[2]; + popWord(objectType, data + 1); + popWord(propertyId, data + 3); + popByte(testInfo[0], data + 4); + popByte(testInfo[1], data + 5); + propertyId = (propertyId >> 4) & 0x0FFF;; + testInfo[0] &= 0x0F; + _bau.systemNetworkParameterReadIndication(priority, hopType, objectType, propertyId, testInfo, sizeof(testInfo)); + break; + } + case DomainAddressSerialNumberWrite: + { + uint8_t* knxSerialNumber = &data[1]; + uint8_t* domainAddress = &data[7]; + _bau.domainAddressSerialNumberWriteIndication(priority, hopType, domainAddress, knxSerialNumber); + break; + } + case DomainAddressSerialNumberRead: + { + uint8_t* knxSerialNumber = &data[1]; + _bau.domainAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber); + break; + } + default: + break; + } } void ApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status) @@ -356,6 +393,65 @@ void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountTy individualSend(ack, hopType, priority, _connectedTsap, apdu); } +//TODO: ApplicationLayer::systemNetworkParameterReadRequest() +void ApplicationLayer::systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, + uint16_t objectType, uint16_t propertyId, + uint8_t* testInfo, uint16_t testInfoLength, + uint8_t* testResult, uint16_t testResultLength) +{ + CemiFrame frame(testInfoLength + testResultLength + 3 + 1); // PID and testInfo share an octet (+3) and +1 for APCI byte(?) + APDU& apdu = frame.apdu(); + apdu.type(SystemNetworkParameterResponse); + uint8_t* data = apdu.data() + 1; + + pushWord(objectType, data); + pushWord((propertyId << 4) & 0xFFF0, data + 2); // Reserved bits for test_info are always 0 + uint8_t* pData = pushByteArray(&testInfo[1], testInfoLength - 1, data + 4); // TODO: upper reserved bits (testInfo + 0) have to put into the lower bits of data + 3 + memcpy(pData, testResult, testResultLength); + + //apdu.printPDU(); + + _transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu); +} + +//TODO: ApplicationLayer::domainAddressSerialNumberWriteRequest() +//TODO: ApplicationLayer::domainAddressSerialNumberReadRequest() +void ApplicationLayer::domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber) +{ + CemiFrame frame(13); + APDU& apdu = frame.apdu(); + apdu.type(DomainAddressSerialNumberResponse); + + uint8_t* data = apdu.data() + 1; + + memcpy(data, knxSerialNumber, 6); + memcpy(data + 6, rfDoA, 6); + + //apdu.printPDU(); + + _transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu); +} + +//TODO: ApplicationLayer::IndividualAddressSerialNumberWriteRequest() +//TODO: ApplicationLayer::IndividualAddressSerialNumberReadRequest() +void ApplicationLayer::IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber) +{ + CemiFrame frame(13); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressSerialNumberResponse); + + uint8_t* data = apdu.data() + 1; + + memcpy(data, knxSerialNumber, 6); + memcpy(data + 6, rfDoA, 6); + + //apdu.printPDU(); + + _transportLayer->dataBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu); +} + void ApplicationLayer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) { diff --git a/src/knx/application_layer.h b/src/knx/application_layer.h index 81a80b6..9d7223f 100644 --- a/src/knx/application_layer.h +++ b/src/knx/application_layer.h @@ -129,6 +129,14 @@ class ApplicationLayer void authorizeResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level); void keyWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level, uint32_t key); void keyWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level); + + void systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, + uint8_t* testResult, uint16_t testResultLength); + void domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber); + void IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber); #pragma endregion private: diff --git a/src/knx/bau.cpp b/src/knx/bau.cpp index 4bed0e5..b60d60c 100644 --- a/src/knx/bau.cpp +++ b/src/knx/bau.cpp @@ -52,7 +52,7 @@ void BusAccessUnit::individualAddressSerialNumberReadLocalConfirm(AckType ack, H { } -void BusAccessUnit::individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t * serialNumber) +void BusAccessUnit::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber) { } @@ -68,7 +68,8 @@ void BusAccessUnit::individualAddressSerialNumberWriteLocalConfirm(AckType ack, { } -void BusAccessUnit::individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t * serialNumber, uint16_t newaddress) +void BusAccessUnit::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber) { } @@ -239,4 +240,22 @@ void BusAccessUnit::keyWriteAppLayerConfirm(Priority priority, HopCountType hopT void BusAccessUnit::connectConfirm(uint16_t destination) { -} \ No newline at end of file +} + +void BusAccessUnit::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength) +{ +} + +void BusAccessUnit::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber) +{ +} + +void BusAccessUnit::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber) +{ +} + + + + diff --git a/src/knx/bau.h b/src/knx/bau.h index b979185..e13af25 100644 --- a/src/knx/bau.h +++ b/src/knx/bau.h @@ -25,14 +25,15 @@ class BusAccessUnit virtual void individualAddressReadAppLayerConfirm(HopCountType hopType, uint16_t individualAddress); virtual void individualAddressSerialNumberReadLocalConfirm(AckType ack, HopCountType hopType, uint8_t* serialNumber, bool status); - virtual void individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t* serialNumber); + virtual void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber); virtual void individualAddressSerialNumberReadResponseConfirm(AckType ack, HopCountType hopType, uint8_t* serialNumber, uint16_t domainAddress, bool status); virtual void individualAddressSerialNumberReadAppLayerConfirm(HopCountType hopType, uint8_t* serialNumber, uint16_t individualAddress, uint16_t domainAddress); virtual void individualAddressSerialNumberWriteLocalConfirm(AckType ack, HopCountType hopType, uint8_t* serialNumber, uint16_t newaddress, bool status); - virtual void individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t* serialNumber, uint16_t newaddress); + virtual void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber); virtual void deviceDescriptorReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t descriptorType, bool status); virtual void deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, uint8_t descriptorType); @@ -108,5 +109,12 @@ class BusAccessUnit virtual void keyWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level, bool status); virtual void keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level); - virtual void connectConfirm(uint16_t destination); + virtual bool connectConfirm(uint16_t destination); + virtual void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength); + + virtual void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber); + + virtual void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber); }; diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp index 435ba5e..87db8ce 100644 --- a/src/knx/bau07B0.cpp +++ b/src/knx/bau07B0.cpp @@ -1,4 +1,5 @@ #include "bau07B0.h" +#include "bits.h" #include #include @@ -9,6 +10,11 @@ Bau07B0::Bau07B0(Platform& platform) _dlLayer(_deviceObj, _addrTable, _netLayer, _platform) { _netLayer.dataLinkLayer(_dlLayer); + + // Set Mask Version in Device Object depending on the BAU + uint16_t maskVersion; + popWord(maskVersion, _descriptor); + _deviceObj.maskVersion(maskVersion); } InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx) diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp new file mode 100644 index 0000000..2b5365d --- /dev/null +++ b/src/knx/bau27B0.cpp @@ -0,0 +1,103 @@ +#include "bau27B0.h" +#include "bits.h" +#include +#include + +using namespace std; + +Bau27B0::Bau27B0(Platform& platform) + : BauSystemB(platform), + _dlLayer(_deviceObj, _rfMediumObj, _addrTable, _netLayer, _platform) +{ + _netLayer.dataLinkLayer(_dlLayer); + _memory.addSaveRestore(&_rfMediumObj); + + // Set Mask Version in Device Object depending on the BAU + uint16_t maskVersion; + popWord(maskVersion, _descriptor); + _deviceObj.maskVersion(maskVersion); +} + +// see KNX AN160 p.74 for mask 27B0 +InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + case 1: + return &_addrTable; + case 2: + return &_assocTable; + case 3: + return &_groupObjTable; + case 4: + return &_appProgram; + case 5: // would be app_program 2 + return nullptr; + case 6: + return &_rfMediumObj; + default: + return nullptr; + } +} + +uint8_t* Bau27B0::descriptor() +{ + return _descriptor; +} + +DataLinkLayer& Bau27B0::dataLinkLayer() +{ + return _dlLayer; +} + +void Bau27B0::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber) +{ + uint8_t curSerialNumber[6]; + pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]); + pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]); + + // If the received serial number matches our serial number + // then store the received RF domain address in the RF medium object + if (!memcmp(knxSerialNumber, curSerialNumber, 6)) + _rfMediumObj.rfDomainAddress(rfDoA); +} + +void Bau27B0::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber) +{ + uint8_t curSerialNumber[6]; + pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]); + pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]); + + // If the received serial number matches our serial number + // then send a response with the current RF domain address stored in the RF medium object + if (!memcmp(knxSerialNumber, curSerialNumber, 6)) + _appLayer.domainAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber); +} + +void Bau27B0::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber) +{ + uint8_t curSerialNumber[6]; + pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]); + pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]); + + // If the received serial number matches our serial number + // then store the received new individual address in the device object + if (!memcmp(knxSerialNumber, curSerialNumber, 6)) + _deviceObj.induvidualAddress(newIndividualAddress); +} + +void Bau27B0::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber) +{ + uint8_t curSerialNumber[6]; + pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]); + pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]); + + // If the received serial number matches our serial number + // then send a response with the current RF domain address stored in the RF medium object and the serial number + if (!memcmp(knxSerialNumber, curSerialNumber, 6)) + _appLayer.IndividualAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber); +} diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h new file mode 100644 index 0000000..f1b30e6 --- /dev/null +++ b/src/knx/bau27B0.h @@ -0,0 +1,30 @@ +#pragma once + +#include "bau_systemB.h" +#include "rf_medium_object.h" +#include "rf_physical_layer.h" +#include "rf_data_link_layer.h" + +class Bau27B0 : public BauSystemB +{ + public: + Bau27B0(Platform& platform); + + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + uint8_t* descriptor(); + DataLinkLayer& dataLinkLayer(); + + private: + RfDataLinkLayer _dlLayer; + RfMediumObject _rfMediumObj; + + uint8_t _descriptor[2] = {0x27, 0xB0}; + + void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA, + uint8_t* knxSerialNumber); + void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber); + void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber); + void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber); +}; \ No newline at end of file diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp index c255934..74373c9 100644 --- a/src/knx/bau57B0.cpp +++ b/src/knx/bau57B0.cpp @@ -1,4 +1,5 @@ #include "bau57B0.h" +#include "bits.h" #include #include @@ -11,6 +12,11 @@ Bau57B0::Bau57B0(Platform& platform) { _netLayer.dataLinkLayer(_dlLayer); _memory.addSaveRestore(&_ipParameters); + + // Set Mask Version in Device Object depending on the BAU + uint16_t maskVersion; + popWord(maskVersion, _descriptor); + _deviceObj.maskVersion(maskVersion); } InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx) diff --git a/src/knx/bau_systemB.cpp b/src/knx/bau_systemB.cpp index 53548e6..807b5ef 100644 --- a/src/knx/bau_systemB.cpp +++ b/src/knx/bau_systemB.cpp @@ -350,3 +350,36 @@ void BauSystemB::nextRestartState() } } +void BauSystemB::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength) +{ + uint8_t knxSerialNumber[6]; + uint8_t operand; + + popByte(operand, testInfo + 1); // First byte (+ 0) contains only 4 reserved bits (0) + + // See KNX spec. 3.5.2 p.33 (Management Procedures: Procedures with A_SystemNetworkParameter_Read) + switch(operand) + { + case 0x01: // NM_Read_SerialNumber_By_ProgrammingMode + // Only send a reply if programming mode is on + if (_deviceObj.progMode() && (objectType == OT_DEVICE) && (propertyId == PID_SERIAL_NUMBER)) + { + // Send reply. testResult data is KNX serial number + pushWord(_deviceObj.manufacturerId(), &knxSerialNumber[0]); + pushInt(_deviceObj.bauNumber(), &knxSerialNumber[2]); + _appLayer.systemNetworkParameterReadResponse(priority, hopType, objectType, propertyId, + testInfo, testInfoLength, knxSerialNumber, sizeof(knxSerialNumber)); + } + break; + + case 0x02: // NM_Read_SerialNumber_By_ExFactoryState + break; + + case 0x03: // NM_Read_SerialNumber_By_PowerReset + break; + + case 0xFE: // Manufacturer specific use of A_SystemNetworkParameter_Read + break; + } +} diff --git a/src/knx/bau_systemB.h b/src/knx/bau_systemB.h index 6ad0300..5ca0ff5 100644 --- a/src/knx/bau_systemB.h +++ b/src/knx/bau_systemB.h @@ -58,6 +58,8 @@ class BauSystemB : protected BusAccessUnit uint8_t* data, uint8_t dataLength) override; void groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType, uint8_t* data, uint8_t dataLength) override; + void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testinfoLength); void connectConfirm(uint16_t tsap) override; virtual InterfaceObject* getInterfaceObject(uint8_t idx) = 0; diff --git a/src/knx/cemi_frame.cpp b/src/knx/cemi_frame.cpp index 005cd2e..d7be5d4 100644 --- a/src/knx/cemi_frame.cpp +++ b/src/knx/cemi_frame.cpp @@ -3,18 +3,88 @@ #include "string.h" #include -CemiFrame::CemiFrame(uint8_t* data, uint16_t length): _npdu(data + NPDU_LPDU_DIFF, *this), - _tpdu(data + TPDU_LPDU_DIFF, *this), _apdu(data + APDU_LPDU_DIFF, *this) +/* +cEMI Frame Format + ++---------+--------+--------+--------+--------+---------+---------+--------+---------+ + | Header | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source | Dest. | Data | APDU | + | | Code | Length | | | Address | Address | Length | | + +---------+--------+--------+--------+--------+---------+---------+--------+---------+ + 6 bytes 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte 2 bytes + + Header = See below the structure of a cEMI header + Message Code = See below. On Appendix A is the list of all existing EMI and cEMI codes + Add.Info Length = 0x00 - no additional info + Control Field 1 = + Control Field 2 = + Source Address = 0x0000 - filled in by router/gateway with its source address which is + part of the KNX subnet + Dest. Address = KNX group or individual address (2 byte) + Data Length = Number of bytes of data in the APDU excluding the TPCI/APCI bits + APDU = Application Protocol Data Unit - the actual payload including transport + protocol control information (TPCI), application protocol control + information (APCI) and data passed as an argument from higher layers of + the KNX communication stack + +Control Field 1 + + Bit | + ------+--------------------------------------------------------------- + 7 | Frame Type - 0x0 for extended frame + | 0x1 for standard frame + ------+--------------------------------------------------------------- + 6 | Reserved + | + ------+--------------------------------------------------------------- + 5 | Repeat Flag - 0x0 repeat frame on medium in case of an error + | 0x1 do not repeat + ------+--------------------------------------------------------------- + 4 | System Broadcast - 0x0 system broadcast + | 0x1 broadcast + ------+--------------------------------------------------------------- + 3 | Priority - 0x0 system + | 0x1 normal + ------+ 0x2 urgent + 2 | 0x3 low + | + ------+--------------------------------------------------------------- + 1 | Acknowledge Request - 0x0 no ACK requested + | (L_Data.req) 0x1 ACK requested + ------+--------------------------------------------------------------- + 0 | Confirm - 0x0 no error + | (L_Data.con) - 0x1 error + ------+--------------------------------------------------------------- + + Control Field 2 + + Bit | + ------+--------------------------------------------------------------- + 7 | Destination Address Type - 0x0 individual address + | - 0x1 group address + ------+--------------------------------------------------------------- + 6-4 | Hop Count (0-7) + ------+--------------------------------------------------------------- + 3-0 | Extended Frame Format - 0x0 standard frame + ------+--------------------------------------------------------------- +*/ + +CemiFrame::CemiFrame(uint8_t* data, uint16_t length) + : _npdu(data + NPDU_LPDU_DIFF, *this), + _tpdu(data + TPDU_LPDU_DIFF, *this), + _apdu(data + APDU_LPDU_DIFF, *this) { _data = data; - _ctrl1 = data + data[1] + 2; + _ctrl1 = data + data[1] + CEMI_HEADER_SIZE; _length = length; } -CemiFrame::CemiFrame(uint8_t apduLength): _data(buffer), - _npdu(_data + NPDU_LPDU_DIFF, *this), _tpdu(_data + TPDU_LPDU_DIFF, *this), _apdu(_data + APDU_LPDU_DIFF, *this) +CemiFrame::CemiFrame(uint8_t apduLength) + : _data(buffer), + _npdu(_data + NPDU_LPDU_DIFF, *this), + _tpdu(_data + TPDU_LPDU_DIFF, *this), + _apdu(_data + APDU_LPDU_DIFF, *this) { - _ctrl1 = _data + 2; + _ctrl1 = _data + CEMI_HEADER_SIZE; _length = 0; memset(_data, 0, apduLength + APDU_LPDU_DIFF); @@ -22,10 +92,13 @@ CemiFrame::CemiFrame(uint8_t apduLength): _data(buffer), _npdu.octetCount(apduLength); } -CemiFrame::CemiFrame(const CemiFrame & other): _data(buffer), - _npdu(_data + NPDU_LPDU_DIFF, *this), _tpdu(_data + TPDU_LPDU_DIFF, *this), _apdu(_data + APDU_LPDU_DIFF, *this) +CemiFrame::CemiFrame(const CemiFrame & other) + : _data(buffer), + _npdu(_data + NPDU_LPDU_DIFF, *this), + _tpdu(_data + TPDU_LPDU_DIFF, *this), + _apdu(_data + APDU_LPDU_DIFF, *this) { - _ctrl1 = _data + 2; + _ctrl1 = _data + CEMI_HEADER_SIZE; _length = other._length; memcpy(_data, other._data, other.totalLenght()); @@ -35,7 +108,7 @@ CemiFrame& CemiFrame::operator=(CemiFrame other) { _length = other._length; _data = buffer; - _ctrl1 = _data + 2; + _ctrl1 = _data + CEMI_HEADER_SIZE; memcpy(_data, other._data, other.totalLenght()); _npdu._data = _data + NPDU_LPDU_DIFF; _tpdu._data = _data + TPDU_LPDU_DIFF; @@ -84,10 +157,34 @@ void CemiFrame::fillTelegramTP(uint8_t* data) { memcpy(data, _ctrl1, len - 1); } - data[len - 1] = calcCRC(data, len - 1); + data[len - 1] = calcCrcTP(data, len - 1); } -uint8_t CemiFrame::calcCRC(uint8_t * buffer, uint16_t len) +uint16_t CemiFrame::telegramLengthtRF() const +{ + return totalLenght() - 3; +} + +void CemiFrame::fillTelegramRF(uint8_t* data) +{ + uint16_t len = telegramLengthtRF(); + + // We prepare the actual KNX telegram for RF here only. + // The packaging into blocks with CRC16 (Format based on FT3 Data Link Layer (IEC 870-5)) + // is done in the RF Data Link Layer code. + // RF always uses the Extended Frame Format. However, the length field is missing (right before the APDU) + // as there is already a length field at the beginning of the raw RF frame which is also used by the + // physical layer to control the HW packet engine of the transceiver. + + data[0] = _ctrl1[1] & 0x0F; // KNX CTRL field for RF (bits 3..0 EFF only), bits 7..4 are set to 0 for asynchronous RF frames + memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA + data[5] = (_ctrl1[1] & 0xF0) | ((_rfLfn & 0x7) << 1) | ((_ctrl1[0] & 0x10) >> 4); // L/NPCI field: AT, Hopcount, LFN, AET + memcpy(data + 6, _ctrl1 + 7, len - 6); // APDU + + //printHex("cEMI_fill: ", &data[0], len); +} + +uint8_t CemiFrame::calcCrcTP(uint8_t * buffer, uint16_t len) { uint8_t crc = 0xFF; @@ -198,6 +295,36 @@ void CemiFrame::destinationAddress(uint16_t value) pushWord(value, _ctrl1 + 4); } +uint8_t* CemiFrame::rfSerialOrDoA() const +{ + return _rfSerialOrDoA; +} + +void CemiFrame::rfSerialOrDoA(uint8_t* rfSerialOrDoA) +{ + _rfSerialOrDoA = rfSerialOrDoA; +} + +uint8_t CemiFrame::rfInfo() const +{ + return _rfInfo; +} + +void CemiFrame::rfInfo(uint8_t rfInfo) +{ + _rfInfo = rfInfo; +} + +uint8_t CemiFrame::rfLfn() const +{ + return _rfLfn; +} + +void CemiFrame::rfLfn(uint8_t rfLfn) +{ + _rfLfn = rfLfn; +} + NPDU& CemiFrame::npdu() { return _npdu; diff --git a/src/knx/cemi_frame.h b/src/knx/cemi_frame.h index f8f69e6..d363bb3 100644 --- a/src/knx/cemi_frame.h +++ b/src/knx/cemi_frame.h @@ -12,6 +12,9 @@ #define TPDU_LPDU_DIFF (TPDU_NPDU_DIFF + NPDU_LPDU_DIFF) #define APDU_LPDU_DIFF (APDU_TPDU_DIFF + TPDU_NPDU_DIFF + NPDU_LPDU_DIFF) +// Mesg Code and additional info length +#define CEMI_HEADER_SIZE 2 + class CemiFrame { friend class DataLinkLayer; @@ -27,6 +30,8 @@ class CemiFrame uint16_t totalLenght() const; uint16_t telegramLengthtTP() const; void fillTelegramTP(uint8_t* data); + uint16_t telegramLengthtRF() const; + void fillTelegramRF(uint8_t* data); FrameFormat frameType() const; void frameType(FrameFormat value); @@ -47,11 +52,19 @@ class CemiFrame uint16_t destinationAddress() const; void destinationAddress(uint16_t value); + // only for RF medium + uint8_t* rfSerialOrDoA() const; + void rfSerialOrDoA(uint8_t* rfSerialOrDoA); + uint8_t rfInfo() const; + void rfInfo(uint8_t rfInfo); + uint8_t rfLfn() const; + void rfLfn(uint8_t rfInfo); + NPDU& npdu(); TPDU& tpdu(); APDU& apdu(); - uint8_t calcCRC(uint8_t* buffer, uint16_t len); + uint8_t calcCrcTP(uint8_t* buffer, uint16_t len); bool valid() const; private: @@ -62,4 +75,9 @@ class CemiFrame TPDU _tpdu; APDU _apdu; uint16_t _length = 0; // only set if created from byte array + + // nly for RF medium + uint8_t* _rfSerialOrDoA = 0; + uint8_t _rfInfo = 0; + uint8_t _rfLfn = 0; // RF Data Link layer frame number }; \ No newline at end of file diff --git a/src/knx/data_link_layer.cpp b/src/knx/data_link_layer.cpp index 1690eb4..ca3a107 100644 --- a/src/knx/data_link_layer.cpp +++ b/src/knx/data_link_layer.cpp @@ -14,15 +14,20 @@ DataLinkLayer::DataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab, void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, FrameFormat format, Priority priority, NPDU& npdu) { - sendTelegram(npdu, ack, destinationAddr, addrType, format, priority); + // Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium) + // The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL) + // See 3.2.5 p.22 + sendTelegram(npdu, ack, destinationAddr, addrType, format, priority, Broadcast); } void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu) { - sendTelegram(npdu, ack, 0, GroupAddress, format, priority); + // System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium) + // See 3.2.5 p.22 + sendTelegram(npdu, ack, 0, GroupAddress, format, priority, SysBroadcast); } -void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success) +void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success) { AckType ack = frame.ack(); AddressType addrType = frame.addressType(); @@ -49,12 +54,18 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame) Priority priority = frame.priority(); NPDU& npdu = frame.npdu(); uint16_t ownAddr = _deviceObject.induvidualAddress(); + SystemBroadcast systemBroadcast = frame.systemBroadcast(); if (source == ownAddr) _deviceObject.induvidualAddressDuplication(true); if (addrType == GroupAddress && destination == 0) - _networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source); + { + if (systemBroadcast == SysBroadcast) + _networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source); + else + _networkLayer.dataIndication(ack, addrType, destination, type, npdu, priority, source); + } else { if (addrType == InduvidualAddress && destination != _deviceObject.induvidualAddress()) @@ -73,7 +84,7 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame) } } -bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority) +bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast) { CemiFrame& frame = npdu.frame(); frame.messageCode(L_data_ind); @@ -82,6 +93,7 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA frame.addressType(addrType); frame.priority(priority); frame.repetition(RepititionAllowed); + frame.systemBroadcast(systemBroadcast); if (npdu.octetCount() <= 15) frame.frameType(StandardFrame); diff --git a/src/knx/data_link_layer.h b/src/knx/data_link_layer.h index c1bd908..a08e524 100644 --- a/src/knx/data_link_layer.h +++ b/src/knx/data_link_layer.h @@ -23,7 +23,7 @@ class DataLinkLayer protected: void frameRecieved(CemiFrame& frame); void dataConReceived(CemiFrame& frame, bool success); - bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority); + bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast); virtual bool sendFrame(CemiFrame& frame) = 0; uint8_t* frameData(CemiFrame& frame); DeviceObject& _deviceObject; diff --git a/src/knx/device_object.cpp b/src/knx/device_object.cpp index 1784f62..f7a5f85 100644 --- a/src/knx/device_object.cpp +++ b/src/knx/device_object.cpp @@ -11,7 +11,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& break; case PID_SERIAL_NUMBER: pushWord(_manufacturerId, data); - pushInt(_bauNumber, data); + pushInt(_bauNumber, data + 2); break; case PID_MANUFACTURER_ID: pushWord(_manufacturerId, data); @@ -55,8 +55,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& break; } case PID_DEVICE_DESCRIPTOR: - data[0] = 0x57; - data[1] = 0xB0; + pushWord(_maskVersion, data); break; default: count = 0; @@ -254,6 +253,16 @@ void DeviceObject::version(uint16_t value) _version = value; } +uint16_t DeviceObject::maskVersion() +{ + return _maskVersion; +} + +void DeviceObject::maskVersion(uint16_t value) +{ + _maskVersion = value; +} + static PropertyDescription _propertyDescriptions[] = { { PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 }, diff --git a/src/knx/device_object.h b/src/knx/device_object.h index 6d66133..de86bb5 100644 --- a/src/knx/device_object.h +++ b/src/knx/device_object.h @@ -35,6 +35,8 @@ public: void hardwareType(const uint8_t* value); uint16_t version(); void version(uint16_t value); + uint16_t maskVersion(); + void maskVersion(uint16_t value); protected: uint8_t propertyCount(); PropertyDescription* propertyDescriptions(); @@ -44,8 +46,9 @@ private: uint8_t _prgMode = 0; uint16_t _ownAddress = 0; uint16_t _manufacturerId = 0xfa; //Default to KNXA - uint32_t _bauNumber = 0; + uint32_t _bauNumber = 0xaabbccdd; char _orderNumber[10] = ""; uint8_t _hardwareType[6] = { 0, 0, 0, 0, 0, 0}; uint16_t _version = 0; + uint16_t _maskVersion = 0x0000; }; \ No newline at end of file diff --git a/src/knx/interface_object.h b/src/knx/interface_object.h index 1452eca..2d2cfe8 100644 --- a/src/knx/interface_object.h +++ b/src/knx/interface_object.h @@ -47,7 +47,10 @@ enum ObjectType OT_RESERVED = 12, /** File Server Object */ - OT_FILE_SERVER = 13 + OT_FILE_SERVER = 13, + + /** RF Medium Object */ + OT_RF_MEDIUM = 19 }; /** diff --git a/src/knx/knx_types.h b/src/knx/knx_types.h index e4cd2c5..25afe8a 100644 --- a/src/knx/knx_types.h +++ b/src/knx/knx_types.h @@ -71,12 +71,30 @@ enum TpduType enum ApduType { + // Application Layer services on Multicast Communication Mode GroupValueRead = 0x000, GroupValueResponse = 0x040, GroupValueWrite = 0x080, + + // Application Layer services on Broadcast Communication Mode IndividualAddressWrite = 0x0c0, IndividualAddressRead = 0x100, IndividualAddressResponse = 0x140, + IndividualAddressSerialNumberRead = 0x3dc, + IndividualAddressSerialNumberResponse = 0x3dd, + IndividualAddressSerialNumberWrite = 0x3de, + + // Application Layer Services on System Broadcast communication mode + SystemNetworkParameterRead = 0x1c8, + SystemNetworkParameterResponse = 0x1c9, + SystemNetworkParameterWrite = 0x1ca, + // Open media specific Application Layer Services on System Broadcast communication mode + DomainAddressSerialNumberRead = 0x3ec, + DomainAddressSerialNumberResponse = 0x3ed, + DomainAddressSerialNumberWrite = 0x3ee, + + // Application Layer Services on Point-to-point Connection-Oriented Communication Mode (mandatory) + // Application Layer Services on Point-to-point Connectionless Communication Mode (either optional or mandatory) MemoryRead = 0x200, MemoryResponse = 0x240, MemoryWrite = 0x280, @@ -97,7 +115,14 @@ enum ApduType PropertyValueWrite = 0x3d7, PropertyDescriptionRead = 0x3d8, PropertyDescriptionResponse = 0x3d9, - IndividualAddressSerialNumberRead = 0x3dc, - IndividualAddressSerialNumberResponse = 0x3dd, - IndividualAddressSerialNumberWrite = 0x3de, +}; + +enum KnxMediumType +{ + KnxMediumType_TP1 = 0, + KnxMediumType_PL = 1, + KnxMediumType_RF = 2, + KnxMediumType_TP0 = 3, // not supported anymore + KnxMediumType_PL132 = 4, // not supported anymore + KnxMediumType_IP = 5, }; \ No newline at end of file diff --git a/src/knx/network_layer.cpp b/src/knx/network_layer.cpp index ceab6b6..4089c2e 100644 --- a/src/knx/network_layer.cpp +++ b/src/knx/network_layer.cpp @@ -69,7 +69,7 @@ void NetworkLayer::dataConfirm(AckType ack, AddressType addressType, uint16_t de void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) { HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; - _transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu()); + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); } void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) diff --git a/src/knx/platform.h b/src/knx/platform.h index 497d98e..903ad32 100644 --- a/src/knx/platform.h +++ b/src/knx/platform.h @@ -29,6 +29,15 @@ class Platform virtual int readUart() = 0; virtual size_t readBytesUart(uint8_t* buffer, size_t length) = 0; + virtual void setupSpi() = 0; + virtual void closeSpi() = 0; + virtual int readWriteSpi (uint8_t *data, size_t len) = 0; + + virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) = 0; + virtual void closeGpio(uint32_t dwPin) = 0; + virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) = 0; + virtual uint32_t readGpio(uint32_t dwPin) = 0; + virtual uint8_t* getEepromBuffer(uint16_t size) = 0; virtual void commitToEeprom() = 0; diff --git a/src/knx/property_types.h b/src/knx/property_types.h index 5f392e8..088c741 100644 --- a/src/knx/property_types.h +++ b/src/knx/property_types.h @@ -96,6 +96,17 @@ enum PropertyID PID_HARDWARE_TYPE = 78, PID_DEVICE_DESCRIPTOR = 83, + /** Properties in the RF Medium Object */ + PID_RF_MULTI_TYPE = 51, + PID_RF_DOMAIN_ADDRESS = 56, + PID_RF_RETRANSMITTER = 57, + PID_RF_FILTERING_MODE_SUPPORT = 58, + PID_RF_FILTERING_MODE_SELECT = 59, + PID_RF_BIDIR_TIMEOUT = 60, + PID_RF_DIAG_SA_FILTER_TABLE = 61, + PID_RF_DIAG_BUDGET_TABLE = 62, + PID_RF_DIAG_PROBE = 63, + /** KNXnet/IP Parameter Object */ PID_PROJECT_INSTALLATION_ID = 51, PID_KNX_INDIVIDUAL_ADDRESS = 52, diff --git a/src/knx/rf_data_link_layer.cpp b/src/knx/rf_data_link_layer.cpp new file mode 100644 index 0000000..253083e --- /dev/null +++ b/src/knx/rf_data_link_layer.cpp @@ -0,0 +1,365 @@ +#include "rf_physical_layer.h" +#include "rf_data_link_layer.h" + +#include "bits.h" +#include "platform.h" +#include "device_object.h" +#include "address_table_object.h" +#include "rf_medium_object.h" +#include "cemi_frame.h" + +#include +#include + +void RfDataLinkLayer::loop() +{ + if (!_enabled) + return; + + _rfPhy.loop(); +} + +bool RfDataLinkLayer::sendFrame(CemiFrame& frame) +{ + if (!_enabled) + return false; + + // Depending on this flag, use either KNX Serial Number + // or the RF domain address that was programmed by ETS + if (frame.systemBroadcast() == SysBroadcast) + { + uint8_t knxSerialNumber[6]; + pushWord(_deviceObject.manufacturerId(), &knxSerialNumber[0]); + pushInt(_deviceObject.bauNumber(), &knxSerialNumber[2]); + frame.rfSerialOrDoA(&knxSerialNumber[0]); + } + else + { + frame.rfSerialOrDoA(_rfMediumObj.rfDomainAddress()); + } + + // Set Data Link Layer Frame Number + frame.rfLfn(_frameNumber); + // Link Layer frame number counts 0..7 + _frameNumber = (_frameNumber + 1) & 0x7; + + // bidirectional device, battery is ok, signal strength indication is void (no measurement) + frame.rfInfo(0x02); + + // TODO: Is queueing really required? + // According to the spec. the upper layer may only send a new L_Data.req if it received + // the L_Data.con for the previous L_Data.req. + addFrameTxQueue(frame); + + // TODO: For now L_data.req is confirmed immediately (L_Data.con) + // see 3.6.3 p.80: L_Data.con shall be generated AFTER transmission of the corresponsing frame + // RF sender will never generate L_Data.con with C=1 (Error), but only if the TX buffer overflows + // The RF sender cannot detect if the RF frame was transmitted successfully or not according to the spec. + dataConReceived(frame, true); + + return true; +} + +RfDataLinkLayer::RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, AddressTableObject& addrTab, + NetworkLayer& layer, Platform& platform) + : DataLinkLayer(devObj, addrTab, layer, platform), + _rfMediumObj(rfMediumObj), + _rfPhy(*this, platform) +{ +} + +uint16_t RfDataLinkLayer::calcCrcRF(uint8_t* buffer, uint32_t offset, uint32_t len) +{ + // CRC-16-DNP + // generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0 + uint32_t pn = 0x13d65; // 1 0011 1101 0110 0101 + + // for much data, using a lookup table would be a way faster CRC calculation + uint32_t crc = 0; + for (uint32_t i = offset; i < offset + len; i++) { + uint8_t bite = buffer[i] & 0xff; + for (uint8_t b = 8; b --> 0;) { + bool bit = ((bite >> b) & 1) == 1; + bool one = (crc >> 15 & 1) == 1; + crc <<= 1; + if (one ^ bit) + crc ^= pn; + } + } + return (~crc) & 0xffff; +} + +void RfDataLinkLayer::frameBytesReceived(uint8_t* rfPacketBuf, uint16_t length) +{ + // RF data link layer frame format + // See 3.2.5 p.22 + + // First block + smallest KNX telegram will give a minimum size of 22 bytes with checksum bytes + if (length < 21) + { + print("Received packet is too small. length: "); + println(length); + return; + } + + // CRC16-DNP of first block is always located here + uint16_t block1Crc = rfPacketBuf[10] << 8 | rfPacketBuf[11]; + + // If the checksum was ok and the other + // two constant header bytes match the KNX-RF spec. (C-field: 0x44 and ESC-field: 0xFF)... + // then we seem to have a valid first block of an KNX RF frame. + // The first block basically contains the RF-info field and the KNX SN/Domain address. + if ((rfPacketBuf[1] == 0x44) && + (rfPacketBuf[2] == 0xFF) && + (calcCrcRF(rfPacketBuf, 0, 10) == block1Crc)) + { + // bytes left from the remaining block(s) + uint16_t bytesLeft = length - 12; + // we use two pointers to move over the two buffers + uint8_t* pRfPacketBuf = &rfPacketBuf[12]; // pointer to start of RF frame block 2 (with CTRL field) + // Reserve 1 byte (+1) for the second ctrl field + // cEMI frame has two CTRL fields, but RF frame has only one, but uses ALWAYS extended frames + // Information for BOTH cEMI CTRL fields is distributed in a RF frame (RF CTRL field and RF L/NPCI field) + // So we cannot just copy an RF frame with CTRL fields as is + // KNX RF frame will be placed starting at cEMI CTRL2 field (so RF CTRL field is CTRL2 field cEMI) + uint8_t* pBuffer = &_buffer[CEMI_HEADER_SIZE + 1]; + // New length of the packet with CRC bytes removed, add space for CEMI header and the second CTRL field + uint16_t newLength = CEMI_HEADER_SIZE + 1; + + // Now check each block checksum and copy the payload of the block + // into a new buffer without checksum + uint16_t blockCrc; + bool crcOk = true; + while (bytesLeft > 18) + { + // Get CRC16 from end of the block + blockCrc = pRfPacketBuf[16] << 8 | pRfPacketBuf[17]; + if (calcCrcRF(pRfPacketBuf, 0, 16) == blockCrc) + { + // Copy only the payload without the checksums + memcpy(pBuffer, pRfPacketBuf, 16); + } + else + { + crcOk = false; + break; + } + pBuffer += 16; + pRfPacketBuf += 18; + newLength += 16; + bytesLeft -= 18; + } + + // Now process the last block + blockCrc = pRfPacketBuf[bytesLeft - 2] << 8 | pRfPacketBuf[bytesLeft - 1]; + crcOk = crcOk && (calcCrcRF(&pRfPacketBuf[0], 0, bytesLeft -2) == blockCrc); + + // If all checksums were ok, then... + if (crcOk) + { + // Copy rest of the received packet without checksum + memcpy(pBuffer, pRfPacketBuf, bytesLeft -2); + newLength += bytesLeft -2; + + // Prepare CEMI by writing/overwriting certain fields in the buffer (contiguous frame without CRC checksums) + // See 3.6.3 p.79: L_Data services for KNX RF asynchronous frames + // For now we do not use additional info, but use normal method arguments for CEMI + _buffer[0] = 0x29; // L_data.ind + _buffer[1] = 0; // Additional info length (spec. says that local dev management is not required to use AddInfo internally) + _buffer[2] = 0; // CTRL1 field (will be set later, this is the field we reserved space for) + _buffer[3] &= 0x0F; // CTRL2 field (take only RFCtrl.b3..0, b7..4 shall always be 0 for asynchronous KNX RF) + + // Now get all control bits from the L/NPCI field of the RF frame + // so that we can overwrite it afterwards with the correct NPDU length + // Get data link layer frame number (LFN field) from L/NPCI.LFN (bit 3..1) + uint8_t lfn = (_buffer[8] & 0x0E) >> 1; + // Get address type from L/NPCI.LFN (bit 7) + AddressType addressType = (_buffer[8] & 0x80) ? GroupAddress:InduvidualAddress; + // Get routing counter from L/NPCI.LFN (bit 6..4) and map to hop count in Ctrl2.b6-4 + uint8_t hopCount = (_buffer[8] & 0x70) >> 4; + // Get AddrExtensionType from L/NPCI.LFN (bit 7) and map to system broadcast flag in Ctrl1.b4 + SystemBroadcast systemBroadcast = (_buffer[8] & 0x01) ? Broadcast:SysBroadcast; + + // Setup L field of the cEMI frame with the NPDU length + // newLength -8 bytes (NPDU_LPDU_DIFF, no AddInfo) -1 byte length field -1 byte TPCI/APCI bits + _buffer[8] = newLength - NPDU_LPDU_DIFF - 1 - 1; + + // If we have a broadcast message (within the domain), + // then we received the domain address and not the KNX serial number + if (systemBroadcast == Broadcast) + { + // Check if the received RF domain address matches the one stored in the RF medium object + // If it does not match then skip the remaining processing + if (memcmp(_rfMediumObj.rfDomainAddress(), &rfPacketBuf[4], 6)) + { + println("RX domain address does not match. Skipping..."); + return; + } + } + + // TODO + // Frame duplication prevention based on LFN (see KKNX RF spec. 3.2.5 p.28) + + // Prepare the cEMI frame + CemiFrame frame(_buffer, newLength); + frame.frameType(ExtendedFrame); // KNX RF uses only extended frame format + frame.priority(SystemPriority); // Not used in KNX RF + frame.ack(AckDontCare); // Not used in KNX RF + frame.systemBroadcast(systemBroadcast); // Mapped from flag AddrExtensionType (KNX serial(0) or Domain Address(1)) + frame.hopCount(hopCount); // Hop count from routing counter + frame.addressType(addressType); // Group address or individual address + frame.rfSerialOrDoA(&rfPacketBuf[4]); // Copy pointer to field Serial or Domain Address (check broadcast flag what it is exactly) + frame.rfInfo(rfPacketBuf[3]); // RF-info field (1 byte) + frame.rfLfn(lfn); // Data link layer frame number (LFN field) +/* + print("RX LFN: "); + print(lfn); + print(" len: "); + print(newLength); + + print(" data: "); + printHex(" data: ", _buffer, newLength); +*/ + frameRecieved(frame); + } + } +} + +void RfDataLinkLayer::enabled(bool value) +{ + if (value && !_enabled) + { + if (_rfPhy.InitChip()) + { + _enabled = true; + print("ownaddr "); + println(_deviceObject.induvidualAddress(), HEX); + } + else + { + _enabled = false; + println("ERROR, RF transceiver not responding"); + } + return; + } + + if (!value && _enabled) + { + _rfPhy.stopChip(); + _enabled = false; + return; + } +} + +bool RfDataLinkLayer::enabled() const +{ + return _enabled; +} + +void RfDataLinkLayer::fillRfFrame(CemiFrame& frame, uint8_t* data) +{ + uint16_t crc; + uint16_t length = frame.telegramLengthtRF(); + + data[0] = 9 + length; // Length block1 (always 9 bytes, without length itself) + Length of KNX telegram without CRCs + data[1] = 0x44; // C field: According to IEC870-5. KNX only uses SEND/NO REPLY (C = 44h) + data[2] = 0xFF; // ESC field: This field shall have the fixed value FFh. + data[3] = frame.rfInfo(); // RF-info field + + // Generate CRC16-DNP over the first block of data + pushByteArray(frame.rfSerialOrDoA(), 6, &data[4]); + crc = calcCrcRF(&data[0], 0, 10); + pushWord(crc, &data[10]); + + // Put the complete KNX telegram into a temporary buffer + // as we have to add CRC16 checksums after each block of 16 bytes + frame.fillTelegramRF(_buffer); + + // Create a checksum for each block of full 16 bytes + uint16_t bytesLeft = length; + uint8_t *pBuffer = &_buffer[0]; + uint8_t *pData = &data[12]; + while (bytesLeft > 16) + { + memcpy(pData, pBuffer, 16); + crc = calcCrcRF(pData, 0, 16); + pushWord(crc, &pData[16]); + + pBuffer += 16; + pData += 18; + bytesLeft -= 16; + } + + // Copy remaining bytes of last block. Could be less than 16 bytes + memcpy(pData, pBuffer, bytesLeft); + // And add last CRC + crc = calcCrcRF(pData, 0, bytesLeft); + pushWord(crc, &pData[bytesLeft]); +} + +void RfDataLinkLayer::addFrameTxQueue(CemiFrame& frame) +{ + _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; + + uint16_t length = frame.telegramLengthtRF(); // Just the pure KNX telegram from CTRL field until end of APDU + uint8_t nrFullBlocks = length / 16; // Number of full (16 bytes) RF blocks required + uint8_t bytesLeft = length % 16; // Remaining bytes of the last packet + + // Calculate total number of bytes required to store the complete raw RF frame + // Block1 always requires 12 bytes including Length and CRC + // Each full block has 16 bytes payload plus 2 bytes CRC + // Add remaining bytes of the last block and add 2 bytes for CRC + uint16_t totalLength = 12 + (nrFullBlocks * 18) + bytesLeft + 2; + + tx_frame->length = totalLength; + tx_frame->data = new uint8_t[tx_frame->length]; + tx_frame->next = NULL; + + // Prepare the raw RF frame + fillRfFrame(frame, tx_frame->data); +/* + print("TX LFN: "); + print(frame.rfLfn()); + + print(" len: "); + print(totalLength); + + printHex(" data:", tx_frame->data, totalLength); +*/ + 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; + } +} + +bool RfDataLinkLayer::isTxQueueEmpty() +{ + if (_tx_queue.front == NULL) + { + return true; + } + return false; +} + +void RfDataLinkLayer::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength) +{ + if (_tx_queue.front == NULL) + { + return; + } + _tx_queue_frame_t* tx_frame = _tx_queue.front; + *sendBuffer = tx_frame->data; + *sendBufferLength = tx_frame->length; + _tx_queue.front = tx_frame->next; + + if (_tx_queue.front == NULL) + { + _tx_queue.back = NULL; + } + delete tx_frame; +} diff --git a/src/knx/rf_data_link_layer.h b/src/knx/rf_data_link_layer.h new file mode 100644 index 0000000..10092ca --- /dev/null +++ b/src/knx/rf_data_link_layer.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include "data_link_layer.h" + +#define MAX_KNX_TELEGRAM_SIZE 263 + +class RfPhysicalLayer; +class RfMediumObject; + +class RfDataLinkLayer : public DataLinkLayer +{ + friend class RfPhysicalLayer; + + using DataLinkLayer::_deviceObject; + using DataLinkLayer::_groupAddressTable; + using DataLinkLayer::_platform; + + public: + RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, AddressTableObject& addrTab, NetworkLayer& layer, + Platform& platform); + + void loop(); + void enabled(bool value); + bool enabled() const; + + private: + bool _enabled = false; + uint8_t _loopState = 0; + + uint8_t _buffer[512]; + + uint8_t _frameNumber = 0; + + struct _tx_queue_frame_t + { + uint8_t* data; + uint16_t length; + _tx_queue_frame_t* next; + }; + + struct _tx_queue_t + { + _tx_queue_frame_t* front = NULL; + _tx_queue_frame_t* back = NULL; + } _tx_queue; + + RfMediumObject& _rfMediumObj; + RfPhysicalLayer _rfPhy; + + void fillRfFrame(CemiFrame& frame, uint8_t* data); + void addFrameTxQueue(CemiFrame& frame); + bool isTxQueueEmpty(); + void loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength); + bool sendFrame(CemiFrame& frame); + void frameBytesReceived(uint8_t* buffer, uint16_t length); + uint16_t calcCrcRF(uint8_t* buffer, uint32_t offset, uint32_t len); +}; diff --git a/src/knx/rf_medium_object.cpp b/src/knx/rf_medium_object.cpp new file mode 100644 index 0000000..2e96d9d --- /dev/null +++ b/src/knx/rf_medium_object.cpp @@ -0,0 +1,128 @@ +#include +#include "rf_medium_object.h" +#include "bits.h" + +void RfMediumObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& count, uint8_t* data) +{ + switch (propertyId) + { + case PID_OBJECT_TYPE: + pushWord(OT_RF_MEDIUM, data); + break; + case PID_RF_MULTI_TYPE: + data[0] = 0x00; // KNX RF ready only + break; + case PID_RF_DOMAIN_ADDRESS: + pushByteArray((uint8_t*)_rfDomainAddress, 6, data); + break; + case PID_RF_RETRANSMITTER: + data[0] = 0x00; // No KNX RF retransmitter + break; + case PID_RF_BIDIR_TIMEOUT: // PDT_FUNCTION + data[0] = 0x00; // success + data[1] = 0xFF; // permanent bidirectional device + data[2] = 0xFF; // permanent bidirectional device + break; + case PID_RF_DIAG_SA_FILTER_TABLE: // PDT_GENERIC_03[] + pushByteArray((uint8_t*)_rfDiagSourceAddressFilterTable, 24, data); + break; + case PID_RF_DIAG_BUDGET_TABLE: + pushByteArray((uint8_t*)_rfDiagLinkBudgetTable, 24, data); + break; + case PID_RF_DIAG_PROBE: // PDT_FUNCTION + // Not supported yet + break; + default: + count = 0; + } +} + +void RfMediumObject::writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count) +{ + switch (id) + { + case PID_RF_DOMAIN_ADDRESS: + for (uint8_t i = start; i < start + count; i++) + _rfDomainAddress[i-1] = data[i - start]; + break; + case PID_RF_BIDIR_TIMEOUT: // PDT_FUNCTION + // Not supported yet (permanent bidir device) + break; + case PID_RF_DIAG_SA_FILTER_TABLE: + for (uint8_t i = start; i < start + count; i++) + _rfDiagSourceAddressFilterTable[i-1] = data[i - start]; + break; + case PID_RF_DIAG_BUDGET_TABLE: + for (uint8_t i = start; i < start + count; i++) + _rfDiagLinkBudgetTable[i-1] = data[i - start]; + break; + case PID_RF_DIAG_PROBE: + // Not supported yet + break; + default: + break; + } +} + +uint8_t RfMediumObject::propertySize(PropertyID id) +{ + switch (id) + { + case PID_RF_MULTI_TYPE: + case PID_RF_RETRANSMITTER: + return 1; + case PID_OBJECT_TYPE: + return 2; + case PID_RF_DOMAIN_ADDRESS: + return 6; + case PID_RF_DIAG_SA_FILTER_TABLE: + case PID_RF_DIAG_BUDGET_TABLE: + return 24; + // case PID_RF_BIDIR_TIMEOUT: ? + // case PID_RF_DIAG_PROBE: ? + default: + break; + } + return 0; +} + +uint8_t* RfMediumObject::save(uint8_t* buffer) +{ + buffer = pushByteArray((uint8_t*)_rfDomainAddress, 6, buffer); + return buffer; +} + +uint8_t* RfMediumObject::restore(uint8_t* buffer) +{ + buffer = popByteArray((uint8_t*)_rfDomainAddress, 6, buffer); + return buffer; +} + +uint8_t* RfMediumObject::rfDomainAddress() +{ + return _rfDomainAddress; +} + +void RfMediumObject::rfDomainAddress(uint8_t* value) +{ + pushByteArray(value, 6, _rfDomainAddress); +} + +static PropertyDescription _propertyDescriptions[] = +{ + { PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 }, + { PID_RF_MULTI_TYPE, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0 }, + { PID_RF_RETRANSMITTER, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0 }, + { PID_RF_DOMAIN_ADDRESS, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0 } +}; +static uint8_t _propertyCount = sizeof(_propertyDescriptions) / sizeof(PropertyDescription); + +uint8_t RfMediumObject::propertyCount() +{ + return _propertyCount; +} + +PropertyDescription* RfMediumObject::propertyDescriptions() +{ + return _propertyDescriptions; +} diff --git a/src/knx/rf_medium_object.h b/src/knx/rf_medium_object.h new file mode 100644 index 0000000..57c0a2e --- /dev/null +++ b/src/knx/rf_medium_object.h @@ -0,0 +1,27 @@ +#pragma once + +#include "interface_object.h" + +class RfMediumObject: public InterfaceObject +{ +public: + void readProperty(PropertyID id, uint32_t start, uint32_t& count, uint8_t* data); + void writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count); + uint8_t propertySize(PropertyID id); + uint8_t* save(uint8_t* buffer); + uint8_t* restore(uint8_t* buffer); + void readPropertyDescription(uint8_t propertyId, uint8_t& propertyIndex, bool& writeEnable, uint8_t& type, uint16_t& numberOfElements, uint8_t& access); + + uint8_t* rfDomainAddress(); + void rfDomainAddress(uint8_t* value); + +protected: + uint8_t propertyCount(); + PropertyDescription* propertyDescriptions(); +private: + uint8_t _rfDomainAddress[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // see KNX RF S-Mode AN160 p.11 + uint8_t _rfDiagSourceAddressFilterTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + uint8_t _rfDiagLinkBudgetTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + + +}; \ No newline at end of file diff --git a/src/knx/rf_physical_layer.cpp b/src/knx/rf_physical_layer.cpp new file mode 100644 index 0000000..03f551a --- /dev/null +++ b/src/knx/rf_physical_layer.cpp @@ -0,0 +1,799 @@ +#include "rf_physical_layer.h" +#include "rf_data_link_layer.h" + +#include "bits.h" +#include "platform.h" + +#include +#include + +#define MIN(a, b) ((a < b) ? (a) : (b)) +#define MAX(a, b) ((a > b) ? (a) : (b)) +#define ABS(x) ((x > 0) ? (x) : (-x)) + +// Table for encoding 4-bit data into a 8-bit Manchester encoding. +const uint8_t RfPhysicalLayer::manchEncodeTab[16] = {0xAA, // 0x0 Manchester encoded + 0xA9, // 0x1 Manchester encoded + 0xA6, // 0x2 Manchester encoded + 0xA5, // 0x3 Manchester encoded + 0x9A, // 0x4 Manchester encoded + 0x99, // 0x5 Manchester encoded + 0x96, // 0x6 Manchester encoded + 0x95, // 0x7 Manchester encoded + 0x6A, // 0x8 Manchester encoded + 0x69, // 0x9 Manchester encoded + 0x66, // 0xA Manchester encoded + 0x65, // 0xB Manchester encoded + 0x5A, // 0xC Manchester encoded + 0x59, // 0xD Manchester encoded + 0x56, // 0xE Manchester encoded + 0x55}; // 0xF Manchester encoded + +// Table for decoding 4-bit Manchester encoded data into 2-bit +// data. 0xFF indicates invalid Manchester encoding +const uint8_t RfPhysicalLayer::manchDecodeTab[16] = {0xFF, // Manchester encoded 0x0 decoded + 0xFF, // Manchester encoded 0x1 decoded + 0xFF, // Manchester encoded 0x2 decoded + 0xFF, // Manchester encoded 0x3 decoded + 0xFF, // Manchester encoded 0x4 decoded + 0x03, // Manchester encoded 0x5 decoded + 0x02, // Manchester encoded 0x6 decoded + 0xFF, // Manchester encoded 0x7 decoded + 0xFF, // Manchester encoded 0x8 decoded + 0x01, // Manchester encoded 0x9 decoded + 0x00, // Manchester encoded 0xA decoded + 0xFF, // Manchester encoded 0xB decoded + 0xFF, // Manchester encoded 0xC decoded + 0xFF, // Manchester encoded 0xD decoded + 0xFF, // Manchester encoded 0xE decoded + 0xFF};// Manchester encoded 0xF decoded + +// Product = CC1101 +// Chip version = A (VERSION = 0x04) +// Crystal accuracy = 10 ppm +// X-tal frequency = 26 MHz +// RF output power = + 10 dBm +// RX filterbandwidth = 270 kHz +// Deviation = 47 kHz +// Datarate = 32.73 kBaud +// Modulation = (0) 2-FSK +// Manchester enable = (0) Manchester disabled +// RF Frequency = 868.299866 MHz +// Channel spacing = 199.951172 kHz +// Channel number = 0 +// Optimization = - +// Sync mode = (5) 15/16 + carrier-sense above threshold +// Format of RX/TX data = (0) Normal mode, use FIFOs for RX and TX +// CRC operation = (0) CRC disabled for TX and RX +// Forward Error Correction = (0) FEC disabled +// Length configuration = (0) Fixed length packets, length configured in PKTLEN register. +// Packetlength = 255 +// Preamble count = (2) 4 bytes +// Append status = 0 +// Address check = (0) No address check +// FIFO autoflush = 0 +// Device address = 0 +// GDO0 signal selection = ( 6) Asserts when sync word has been sent / received, and de-asserts at the end of the packet +// GDO2 signal selection = ( 0) Asserts when RX FiFO threshold +const uint8_t RfPhysicalLayer::cc1101_2FSK_32_7_kb[CFG_REGISTER] = { + 0x00, // IOCFG2 GDO2 Output Pin Configuration + 0x2E, // IOCFG1 GDO1 Output Pin Configuration + 0x06, // IOCFG0 GDO0 Output Pin Configuration + 0x40, // FIFOTHR RX FIFO and TX FIFO Thresholds // 4 bytes in RX FIFO (2 bytes manchester encoded) + 0x76, // SYNC1 Sync Word + 0x96, // SYNC0 Sync Word + 0xFF, // PKTLEN Packet Length + 0x00, // PKTCTRL1 Packet Automation Control + 0x00, // PKTCTRL0 Packet Automation Control + 0x00, // ADDR Device Address + 0x00, // CHANNR Channel Number + 0x08, // FSCTRL1 Frequency Synthesizer Control + 0x00, // FSCTRL0 Frequency Synthesizer Control + 0x21, // FREQ2 Frequency Control Word + 0x65, // FREQ1 Frequency Control Word + 0x6A, // FREQ0 Frequency Control Word + 0x6A, // MDMCFG4 Modem Configuration + 0x4A, // MDMCFG3 Modem Configuration + 0x05, // MDMCFG2 Modem Configuration + 0x22, // MDMCFG1 Modem Configuration + 0xF8, // MDMCFG0 Modem Configuration + 0x47, // DEVIATN Modem Deviation Setting + 0x07, // MCSM2 Main Radio Control State Machine Configuration + 0x30, // MCSM1 Main Radio Control State Machine Configuration (IDLE after TX and RX) + 0x18, // MCSM0 Main Radio Control State Machine Configuration + 0x2E, // FOCCFG Frequency Offset Compensation Configuration + 0x6D, // BSCFG Bit Synchronization Configuration + 0x43, // AGCCTRL2 AGC Control 0x04, // AGCCTRL2 magn target 33dB vs 36dB (max LNA+LNA2 gain vs. ) (highest gain cannot be used vs. all gain settings) + 0x40, // AGCCTRL1 AGC Control 0x09, // AGCCTRL1 carrier sense threshold disabled vs. 7dB below magn target (LNA prio strat. 1 vs 0) + 0x91, // AGCCTRL0 AGC Control 0xB2, // AGCCTRL0 channel filter samples 16 vs.32 + 0x87, // WOREVT1 High Byte Event0 Timeout + 0x6B, // WOREVT0 Low Byte Event0 Timeout + 0xFB, // WORCTRL Wake On Radio Control + 0xB6, // FREND1 Front End RX Configuration + 0x10, // FREND0 Front End TX Configuration + 0xE9, // FSCAL3 Frequency Synthesizer Calibration 0xEA, // FSCAL3 + 0x2A, // FSCAL2 Frequency Synthesizer Calibration + 0x00, // FSCAL1 Frequency Synthesizer Calibration + 0x1F, // FSCAL0 Frequency Synthesizer Calibration + 0x41, // RCCTRL1 RC Oscillator Configuration + 0x00, // RCCTRL0 RC Oscillator Configuration + 0x59, // FSTEST Frequency Synthesizer Calibration Control + 0x7F, // PTEST Production Test + 0x3F, // AGCTEST AGC Test + 0x81, // TEST2 Various Test Settings + 0x35, // TEST1 Various Test Settings + 0x09 // TEST0 Various Test Settings +}; + + //Patable index: -30 -20- -15 -10 0 5 7 10 dBm +const uint8_t RfPhysicalLayer::paTablePower868[8] = {0x03,0x17,0x1D,0x26,0x50,0x86,0xCD,0xC0}; + +RfPhysicalLayer::RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform) + : _rfDataLinkLayer(rfDataLinkLayer), + _platform(platform) +{ +} + +void RfPhysicalLayer::manchEncode(uint8_t *uncodedData, uint8_t *encodedData) +{ + uint8_t data0, data1; + + // - Shift to get 4-bit data values + data1 = (((*uncodedData) >> 4) & 0x0F); + data0 = ((*uncodedData) & 0x0F); + + // - Perform Manchester encoding - + *encodedData = (manchEncodeTab[data1]); + *(encodedData + 1) = manchEncodeTab[data0]; +} + +bool RfPhysicalLayer::manchDecode(uint8_t *encodedData, uint8_t *decodedData) +{ + uint8_t data0, data1, data2, data3; + + // - Shift to get 4 bit data and decode + data3 = ((*encodedData >> 4) & 0x0F); + data2 = ( *encodedData & 0x0F); + data1 = ((*(encodedData + 1) >> 4) & 0x0F); + data0 = ((*(encodedData + 1)) & 0x0F); + + // Check for invalid Manchester encoding + if ( (manchDecodeTab[data3] == 0xFF ) | (manchDecodeTab[data2] == 0xFF ) | + (manchDecodeTab[data1] == 0xFF ) | (manchDecodeTab[data0] == 0xFF ) ) + { + return false; + } + + // Shift result into a byte + *decodedData = (manchDecodeTab[data3] << 6) | (manchDecodeTab[data2] << 4) | + (manchDecodeTab[data1] << 2) | manchDecodeTab[data0]; + + return true; +} + +int RfPhysicalLayer::crc16(uint8_t* buffer, int offset, int length) +{ + // CRC-16-DNP + // generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0 + int pn = 0x13d65; // 1 0011 1101 0110 0101 + + // for much data, using a lookup table would be a way faster CRC calculation + int crc = 0; + for (int i = offset; i < offset + length; i++) { + int bite = buffer[i] & 0xff; + for (int b = 8; b --> 0;) { + bool bit = ((bite >> b) & 1) == 1; + bool one = (crc >> 15 & 1) == 1; + crc <<= 1; + if (one ^ bit) + crc ^= pn; + } + } + return (~crc) & 0xffff; +} + +uint8_t RfPhysicalLayer::sIdle() +{ + uint8_t marcState; + uint32_t timeStart; + + spiWriteStrobe(SIDLE); //sets to idle first. must be in + + marcState = 0xFF; //set unknown/dummy state value + timeStart = millis(); + while((marcState != MARCSTATE_IDLE) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x01 = sidle + { + marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + } + + //print("marcstate: 0x"); + //println(marcState, HEX); + + if(marcState != MARCSTATE_IDLE) + { + println("Timeout when trying to set idle state."); + return false; + } + + return true; +} + +uint8_t RfPhysicalLayer::sReceive() +{ + uint8_t marcState; + uint32_t timeStart; + + spiWriteStrobe(SRX); //writes receive strobe (receive mode) + + marcState = 0xFF; //set unknown/dummy state value + timeStart = millis(); + while((marcState != MARCSTATE_RX) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x0D = RX + { + marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + } + + //print("marcstate: 0x"); + //println(marcState, HEX); + + if(marcState != MARCSTATE_RX) + { + println("Timeout when trying to set receive state."); + return false; + } + + return true; +} + +void RfPhysicalLayer::spiWriteRegister(uint8_t spi_instr, uint8_t value) +{ + uint8_t tbuf[2] = {0}; + tbuf[0] = spi_instr | WRITE_SINGLE_BYTE; + tbuf[1] = value; + uint8_t len = 2; + _platform.writeGpio(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, len); + _platform.writeGpio(SPI_SS_PIN, HIGH); +} + +uint8_t RfPhysicalLayer::spiReadRegister(uint8_t spi_instr) +{ + uint8_t value; + uint8_t rbuf[2] = {0}; + rbuf[0] = spi_instr | READ_SINGLE_BYTE; + uint8_t len = 2; + _platform.writeGpio(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len); + _platform.writeGpio(SPI_SS_PIN, HIGH); + value = rbuf[1]; + //printf("SPI_arr_0: 0x%02X\n", rbuf[0]); + //printf("SPI_arr_1: 0x%02X\n", rbuf[1]); + return value; +} + +uint8_t RfPhysicalLayer::spiWriteStrobe(uint8_t spi_instr) +{ + uint8_t tbuf[1] = {0}; + tbuf[0] = spi_instr; + //printf("SPI_data: 0x%02X\n", tbuf[0]); + _platform.writeGpio(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, 1); + _platform.writeGpio(SPI_SS_PIN, HIGH); + return tbuf[0]; +} + +void RfPhysicalLayer::spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len) +{ + uint8_t rbuf[len + 1]; + rbuf[0] = spi_instr | READ_BURST; + _platform.writeGpio(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len + 1); + _platform.writeGpio(SPI_SS_PIN, HIGH); + for (uint8_t i=0; i abort + if(version == 0x00 || version == 0xFF) + { + println("No CC11xx found!"); + stopChip(); + return false; + } + + print("Partnumber: 0x"); + println(partnum, HEX); + print("Version : 0x"); + println(version, HEX); + + // Set modulation mode 2FSK, 32768kbit/s + spiWriteBurst(WRITE_BURST,cc1101_2FSK_32_7_kb,CFG_REGISTER); + + // Set PA table + spiWriteBurst(PATABLE_BURST, paTablePower868, 8); + + // Set ISM band to 868.3MHz + spiWriteRegister(FREQ2,0x21); + spiWriteRegister(FREQ1,0x65); + spiWriteRegister(FREQ0,0x6A); + + // Set channel 0 in ISM band + spiWriteRegister(CHANNR, 0); + + // Set PA to 0dBm as default + setOutputPowerLevel(0); + + return true; +} + +void RfPhysicalLayer::stopChip() +{ + powerDownCC1101(); + + _platform.closeGpio(GPIO_GDO0_PIN); + _platform.closeGpio(GPIO_GDO2_PIN); + _platform.closeGpio(SPI_SS_PIN); + + _platform.closeSpi(); +} + +void RfPhysicalLayer::showRegisterSettings() +{ + uint8_t config_reg_verify[CFG_REGISTER]; + uint8_t Patable_verify[CFG_REGISTER]; + + spiReadBurst(READ_BURST,config_reg_verify,CFG_REGISTER); //reads all 47 config register from cc1101 + spiReadBurst(PATABLE_BURST,Patable_verify,8); //reads output power settings from cc1101 + + println("Config Register:"); + printHex("", config_reg_verify, CFG_REGISTER); + + println("PaTable:"); + printHex("", Patable_verify, 8); +} + +uint16_t RfPhysicalLayer::packetSize (uint8_t lField) +{ + uint16_t nrBytes; + uint8_t nrBlocks; + + // The 2 first blocks contains 25 bytes when excluding CRC and the L-field + // The other blocks contains 16 bytes when excluding the CRC-fields + // Less than 26 (15 + 10) + if ( lField < 26 ) + nrBlocks = 2; + else + nrBlocks = (((lField - 26) / 16) + 3); + + // Add all extra fields, excluding the CRC fields + nrBytes = lField + 1; + + // Add the CRC fields, each block has 2 CRC bytes + nrBytes += (2 * nrBlocks); + + return nrBytes; +} + +void RfPhysicalLayer::loop() +{ + switch (_loopState) + { + case TX_START: + { + prevStatusGDO0 = 0; + prevStatusGDO2 = 0; + // Set sync word in TX mode + // The same sync word is used in RX mode, but we use it in different way here: + // Important: the TX FIFO must provide the last byte of the + // sync word + spiWriteRegister(SYNC1, 0x54); + spiWriteRegister(SYNC0, 0x76); + // Set TX FIFO threshold to 33 bytes + spiWriteRegister(FIFOTHR, 0x47); + // Set GDO2 to be TX FIFO threshold signal + spiWriteRegister(IOCFG2, 0x02); + // Set GDO0 to be packet transmitted signal + spiWriteRegister(IOCFG0, 0x06); + // Flush TX FIFO + spiWriteStrobe(SFTX); + + _rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength); + + // Calculate total number of bytes in the KNX RF packet from L-field + pktLen = packetSize(sendBuffer[0]); + // Check for valid length + if ((pktLen == 0) || (pktLen > 290)) + { + println("TX packet length error!"); + break; + } + + // Manchester encoded data takes twice the space plus + // 1 byte for postamble and 1 byte (LSB) of the synchronization word + bytesLeft = (2 * pktLen) + 2; + // Last byte of synchronization word + buffer[0] = 0x96; + // Manchester encode packet + for (int i = 0; i < pktLen; i++) + { + manchEncode(&sendBuffer[i], &buffer[1 + i*2]); + } + // Append the postamble sequence + buffer[1 + bytesLeft - 1] = 0x55; + + // Fill TX FIFO + pByteIndex = &buffer[0]; + // Set fixed packet length mode if less than 256 bytes to transmit + if (bytesLeft < 256) + { + spiWriteRegister(PKTLEN, bytesLeft); + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + else // Else set infinite length mode + { + uint8_t fixedLength = bytesLeft % 256; + spiWriteRegister(PKTLEN, fixedLength); + spiWriteRegister(PKTCTRL0, 0x02); + fixedLengthMode = false; + } + + uint8_t bytesToWrite = MIN(64, bytesLeft); + spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite); + pByteIndex += bytesToWrite; + bytesLeft -= bytesToWrite; + + // Enable transmission of packet + spiWriteStrobe(STX); + + _loopState = TX_ACTIVE; + } + // Fall through + + case TX_ACTIVE: + { + // Check if we have an incomplete packet transmission + if (syncStart && (millis() - packetStartTime > TX_PACKET_TIMEOUT)) + { + println("TX packet timeout!"); + // Set transceiver to IDLE (no RX or TX) + sIdle(); + _loopState = TX_END; + break; + } + + // Detect falling edge 1->0 on GDO2 + statusGDO2 = _platform.readGpio(GPIO_GDO2_PIN); + if(prevStatusGDO2 != statusGDO2) + { + prevStatusGDO2 = statusGDO2; + + // Check if signal GDO2 is de-asserted (TX FIFO is below threshold of 33 bytes, i.e. TX FIFO is half full) + if(statusGDO2 == 0) + { + // - TX FIFO half full detected (< 33 bytes) + // Write data fragment to TX FIFO + uint8_t bytesToWrite = MIN(64, bytesLeft); + spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite); + pByteIndex += bytesToWrite; + bytesLeft -= bytesToWrite; + + // Set fixed length mode if less than 256 left to transmit + if ( (bytesLeft < (256 - 64)) && !fixedLengthMode ) + { + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + } + } + + // Detect falling edge 1->0 on GDO0 + statusGDO0 = _platform.readGpio(GPIO_GDO0_PIN); + if(prevStatusGDO0 != statusGDO0) + { + prevStatusGDO0 = statusGDO0; + + // If GDO0 is de-asserted: TX packet complete or TX FIFO underflow + if (statusGDO0 == 0x00) + { + // There might be an TX FIFO underflow + uint8_t chipStatusBytes = spiWriteStrobe(SNOP); + if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_TX_UNDERFLOW) + { + println("TX FIFO underflow!"); + // Set transceiver to IDLE (no RX or TX) + sIdle(); + } + _loopState = TX_END; + } + else + { + // GDO0 asserted because sync word was transmitted + //println("TX Syncword!"); + // wait for TX_PACKET_TIMEOUT milliseconds + // Complete packet must have been transmitted within this time + packetStartTime = millis(); + syncStart = true; + } + } + } + break; + + case TX_END: + { + // free buffer + delete sendBuffer; + // Go back to RX after TX + _loopState = RX_START; + } + break; + + case RX_START: + { + prevStatusGDO2 = 0; + prevStatusGDO0 = 0; + syncStart = false; + packetStart = true; + fixedLengthMode = false; + pByteIndex = buffer; + bytesLeft = 0; + pktLen = 0; + // Set sync word in RX mode + // The same sync word is used in TX mode, but we use it in different way + spiWriteRegister(SYNC1, 0x76); + spiWriteRegister(SYNC0, 0x96); + // Set GDO2 to be RX FIFO threshold signal + spiWriteRegister(IOCFG2, 0x00); + // Set GDO0 to be packet received signal + spiWriteRegister(IOCFG0, 0x06); + // Set RX FIFO threshold to 4 bytes + spiWriteRegister(FIFOTHR, 0x40); + // Set infinite pktlen mode + spiWriteRegister(PKTCTRL0, 0x02); + // Flush RX FIFO + spiWriteStrobe(SFRX); + // Start RX + sReceive(); + _loopState = RX_ACTIVE; + } + break; + + case RX_ACTIVE: + { + if (!_rfDataLinkLayer.isTxQueueEmpty() && !syncStart) + { + sIdle(); + _loopState = TX_START; + break; + } + + // Check if we have an incomplete packet reception + // This is related to CC1101 errata "Radio stays in RX state instead of entering RXFIFO_OVERFLOW state" + if (syncStart && (millis() - packetStartTime > RX_PACKET_TIMEOUT)) + { + println("RX packet timeout!"); + //uint8_t marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + //print("marcstate: 0x"); + //println(marcState, HEX); + sIdle(); + _loopState = RX_START; + break; + } + + // Detect rising edge 0->1 on GDO2 + statusGDO2 = _platform.readGpio(GPIO_GDO2_PIN); + if(prevStatusGDO2 != statusGDO2) + { + prevStatusGDO2 = statusGDO2; + + // Check if signal GDO2 is asserted (RX FIFO is equal to or above threshold of 4 bytes) + if(statusGDO2 == 1) + { + if (packetStart) + { + // - RX FIFO 4 bytes detected - + // Calculate the total length of the packet, and set fixed mode if less + // than 255 bytes to receive + + // Read the 2 first bytes + spiReadBurst(RXFIFO_BURST, pByteIndex, 2); + + // Decode the L-field + if (!manchDecode(&buffer[0], &packet[0])) + { + //println("Could not decode L-field: manchester code violation"); + _loopState = RX_START; + break; + } + // Get bytes to receive from L-field, multiply by 2 because of manchester code + pktLen = 2 * packetSize(packet[0]); + + // - Length mode - + if (pktLen < 256) + { + // Set fixed packet length mode is less than 256 bytes + spiWriteRegister(PKTLEN, pktLen); + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + else + { + // Infinite packet length mode is more than 255 bytes + // Calculate the PKTLEN value + uint8_t fixedLength = pktLen % 256; + spiWriteRegister(PKTLEN, fixedLength); + } + + pByteIndex += 2; + bytesLeft = pktLen - 2; + + // Set RX FIFO threshold to 32 bytes + packetStart = false; + spiWriteRegister(FIFOTHR, 0x47); + } + else + { + // - RX FIFO Half Full detected - + // Read out the RX FIFO and set fixed mode if less + // than 255 bytes to receive + + // - Length mode - + // Set fixed packet length mode if less than 256 bytes + if ((bytesLeft < 256 ) && !fixedLengthMode) + { + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + + // Read out the RX FIFO + // Do not empty the FIFO (See the CC110x or 2500 Errata Note) + spiReadBurst(RXFIFO_BURST, pByteIndex, 32 - 1); + + bytesLeft -= (32 - 1); + pByteIndex += (32 - 1); + } + + } + } + + // Detect falling edge 1->0 on GDO0 + statusGDO0 = _platform.readGpio(GPIO_GDO0_PIN); + if(prevStatusGDO0 != statusGDO0) + { + prevStatusGDO0 = statusGDO0; + + // If GDO0 is de-asserted: RX packet complete or RX FIFO overflow + if (statusGDO0 == 0x00) + { + // There might be an RX FIFO overflow + uint8_t chipStatusBytes = spiWriteStrobe(SNOP); + if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_RX_OVERFLOW) + { + println("RX FIFO overflow!"); + _loopState = RX_START; + break; + } + + // Check if we are in the middle of the packet reception + if (!packetStart) + { + // Complete packet received + // Read out remaining bytes in the RX FIFO + spiReadBurst(RXFIFO_BURST, pByteIndex, bytesLeft); + _loopState = RX_END; + } + } + else + { + // GDO0 asserted because sync word was received and recognized + //println("RX Syncword!"); + // wait for RX_PACKET_TIMEOUT milliseconds + // Complete packet must have been received within this time + packetStartTime = millis(); + syncStart = true; + } + + } + } + break; + + case RX_END: + { + uint16_t pLen = packetSize(packet[0]); + // Decode the first block (always 10 bytes + 2 bytes CRC) + bool decodeOk = true; + for (uint16_t i = 1; i < pLen; i++) + { + // Check for manchester violation, abort if there is one + if(!manchDecode(&buffer[i*2], &packet[i])) + { + println("Could not decode packet: manchester code violation"); + decodeOk = false; + break; + } + } + + if (decodeOk) + { + _rfDataLinkLayer.frameBytesReceived(&packet[0], pLen); + } + + _loopState = RX_START; + } + break; + } +} diff --git a/src/knx/rf_physical_layer.h b/src/knx/rf_physical_layer.h new file mode 100644 index 0000000..931985e --- /dev/null +++ b/src/knx/rf_physical_layer.h @@ -0,0 +1,257 @@ + +#ifndef RF_PHYSICAL_LAYER_H +#define RF_PHYSICAL_LAYER_H + +#include + +#include "platform.h" + +/*----------------------------------[standard]--------------------------------*/ +#define CC1101_TIMEOUT 2000 // Time to wait for a response from CC1101 + +#define RX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete +#define TX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete + +//**************************** pins ******************************************// +#ifdef ARDUINO_ARCH_SAMD +#define SPI_SS_PIN 10 +#define GPIO_GDO2_PIN 9 +#define GPIO_GDO0_PIN 7 +#elif ARDUINO_ARCH_ESP8266 +#error KNX-RF not yet supported on ESP8266 +#elif ARDUINO_ARCH_ESP32 +#error KNX-RF not yet supported on ESP32 +#else // Linux Platform +extern void delayMicroseconds (unsigned int howLong); +#define SPI_SS_PIN 8 // GPIO 8 (SPI_CE0_N) -> WiringPi: 10 -> Pin number on header: 24 +#define GPIO_GDO2_PIN 25 // GPIO 25 (GPIO_GEN6) -> WiringPi: 6 -> Pin number on header: 22 +#define GPIO_GDO0_PIN 24 // GPIO 24 (GPIO_GEN5) -> WiringPi: 5 -> Pin number on header: 18 +#endif + +/*----------------------[CC1101 - misc]---------------------------------------*/ +#define CRYSTAL_FREQUENCY 26000000 +#define CFG_REGISTER 0x2F //47 registers +#define FIFOBUFFER 0x42 //size of Fifo Buffer +2 for rssi and lqi +#define RSSI_OFFSET_868MHZ 0x4E //dec = 74 +#define TX_RETRIES_MAX 0x05 //tx_retries_max +#define ACK_TIMEOUT 250 //ACK timeout in ms +#define CC1101_COMPARE_REGISTER 0x00 //register compare 0=no compare 1=compare +#define BROADCAST_ADDRESS 0x00 //broadcast address +#define CC1101_FREQ_315MHZ 0x01 +#define CC1101_FREQ_434MHZ 0x02 +#define CC1101_FREQ_868MHZ 0x03 +#define CC1101_FREQ_915MHZ 0x04 +#define CC1101_TEMP_ADC_MV 3.225 //3.3V/1023 . mV pro digit +#define CC1101_TEMP_CELS_CO 2.47 //Temperature coefficient 2.47mV per Grad Celsius + +/*---------------------------[CC1101 - R/W offsets]---------------------------*/ +#define WRITE_SINGLE_BYTE 0x00 +#define WRITE_BURST 0x40 +#define READ_SINGLE_BYTE 0x80 +#define READ_BURST 0xC0 +/*---------------------------[END R/W offsets]--------------------------------*/ + +/*------------------------[CC1101 - FIFO commands]----------------------------*/ +#define TXFIFO_BURST 0x7F //write burst only +#define TXFIFO_SINGLE_BYTE 0x3F //write single only +#define RXFIFO_BURST 0xFF //read burst only +#define RXFIFO_SINGLE_BYTE 0xBF //read single only +#define PATABLE_BURST 0x7E //power control read/write +#define PATABLE_SINGLE_BYTE 0xFE //power control read/write +/*---------------------------[END FIFO commands]------------------------------*/ + +/*----------------------[CC1101 - config register]----------------------------*/ +#define IOCFG2 0x00 // GDO2 output pin configuration +#define IOCFG1 0x01 // GDO1 output pin configuration +#define IOCFG0 0x02 // GDO0 output pin configuration +#define FIFOTHR 0x03 // RX FIFO and TX FIFO thresholds +#define SYNC1 0x04 // Sync word, high byte +#define SYNC0 0x05 // Sync word, low byte +#define PKTLEN 0x06 // Packet length +#define PKTCTRL1 0x07 // Packet automation control +#define PKTCTRL0 0x08 // Packet automation control +#define DADDR 0x09 // Device address +#define CHANNR 0x0A // Channel number +#define FSCTRL1 0x0B // Frequency synthesizer control +#define FSCTRL0 0x0C // Frequency synthesizer control +#define FREQ2 0x0D // Frequency control word, high byte +#define FREQ1 0x0E // Frequency control word, middle byte +#define FREQ0 0x0F // Frequency control word, low byte +#define MDMCFG4 0x10 // Modem configuration +#define MDMCFG3 0x11 // Modem configuration +#define MDMCFG2 0x12 // Modem configuration +#define MDMCFG1 0x13 // Modem configuration +#define MDMCFG0 0x14 // Modem configuration +#define DEVIATN 0x15 // Modem deviation setting +#define MCSM2 0x16 // Main Radio Cntrl State Machine config +#define MCSM1 0x17 // Main Radio Cntrl State Machine config +#define MCSM0 0x18 // Main Radio Cntrl State Machine config +#define FOCCFG 0x19 // Frequency Offset Compensation config +#define BSCFG 0x1A // Bit Synchronization configuration +#define AGCCTRL2 0x1B // AGC control +#define AGCCTRL1 0x1C // AGC control +#define AGCCTRL0 0x1D // AGC control +#define WOREVT1 0x1E // High byte Event 0 timeout +#define WOREVT0 0x1F // Low byte Event 0 timeout +#define WORCTRL 0x20 // Wake On Radio control +#define FREND1 0x21 // Front end RX configuration +#define FREND0 0x22 // Front end TX configuration +#define FSCAL3 0x23 // Frequency synthesizer calibration +#define FSCAL2 0x24 // Frequency synthesizer calibration +#define FSCAL1 0x25 // Frequency synthesizer calibration +#define FSCAL0 0x26 // Frequency synthesizer calibration +#define RCCTRL1 0x27 // RC oscillator configuration +#define RCCTRL0 0x28 // RC oscillator configuration +#define FSTEST 0x29 // Frequency synthesizer cal control +#define PTEST 0x2A // Production test +#define AGCTEST 0x2B // AGC test +#define TEST2 0x2C // Various test settings +#define TEST1 0x2D // Various test settings +#define TEST0 0x2E // Various test settings +/*-------------------------[END config register]------------------------------*/ + +/*------------------------[CC1101-command strobes]----------------------------*/ +#define SRES 0x30 // Reset chip +#define SFSTXON 0x31 // Enable/calibrate freq synthesizer +#define SXOFF 0x32 // Turn off crystal oscillator. +#define SCAL 0x33 // Calibrate freq synthesizer & disable +#define SRX 0x34 // Enable RX. +#define STX 0x35 // Enable TX. +#define SIDLE 0x36 // Exit RX / TX +#define SAFC 0x37 // AFC adjustment of freq synthesizer +#define SWOR 0x38 // Start automatic RX polling sequence +#define SPWD 0x39 // Enter pwr down mode when CSn goes hi +#define SFRX 0x3A // Flush the RX FIFO buffer. +#define SFTX 0x3B // Flush the TX FIFO buffer. +#define SWORRST 0x3C // Reset real time clock. +#define SNOP 0x3D // No operation. +/*-------------------------[END command strobes]------------------------------*/ + +/*----------------------[CC1101 - status register]----------------------------*/ +#define PARTNUM 0xF0 // Part number +#define VERSION 0xF1 // Current version number +#define FREQEST 0xF2 // Frequency offset estimate +#define LQI 0xF3 // Demodulator estimate for link quality +#define RSSI 0xF4 // Received signal strength indication +#define MARCSTATE 0xF5 // Control state machine state +#define WORTIME1 0xF6 // High byte of WOR timer +#define WORTIME0 0xF7 // Low byte of WOR timer +#define PKTSTATUS 0xF8 // Current GDOx status and packet status +#define VCO_VC_DAC 0xF9 // Current setting from PLL cal module +#define TXBYTES 0xFA // Underflow and # of bytes in TXFIFO +#define RXBYTES 0xFB // Overflow and # of bytes in RXFIFO +#define RCCTRL1_STATUS 0xFC //Last RC Oscillator Calibration Result +#define RCCTRL0_STATUS 0xFD //Last RC Oscillator Calibration Result +//--------------------------[END status register]------------------------------- + +/*----------------------[CC1101 - Main Radio Control State Machine states]-----*/ +#define MARCSTATE_BITMASK 0x1F +#define MARCSTATE_SLEEP 0x00 +#define MARCSTATE_IDLE 0x01 +#define MARCSTATE_XOFF 0x02 +#define MARCSTATE_VCOON_MC 0x03 +#define MARCSTATE_REGON_MC 0x04 +#define MARCSTATE_MANCAL 0x05 +#define MARCSTATE_VCOON 0x06 +#define MARCSTATE_REGON 0x07 +#define MARCSTATE_STARTCAL 0x08 +#define MARCSTATE_BWBOOST 0x09 +#define MARCSTATE_FS_LOCK 0x0A +#define MARCSTATE_IFADCON 0x0B +#define MARCSTATE_ENDCAL 0x0C +#define MARCSTATE_RX 0x0D +#define MARCSTATE_RX_END 0x0E +#define MARCSTATE_RX_RST 0x0F +#define MARCSTATE_TXRX_SWITCH 0x10 +#define MARCSTATE_RXFIFO_OVERFLOW 0x11 +#define MARCSTATE_FSTXON 0x12 +#define MARCSTATE_TX 0x13 +#define MARCSTATE_TX_END 0x14 +#define MARCSTATE_RXTX_SWITCH 0x15 +#define MARCSTATE_TXFIFO_UNDERFLOW 0x16 + +// Chip Status Byte +// Bit fields in the chip status byte +#define CHIPSTATUS_CHIP_RDYn_BITMASK 0x80 +#define CHIPSTATUS_STATE_BITMASK 0x70 +#define CHIPSTATUS_FIFO_BYTES_AVAILABLE_BITMASK 0x0F +// Chip states + #define CHIPSTATUS_STATE_IDLE 0x00 + #define CHIPSTATUS_STATE_RX 0x10 + #define CHIPSTATUS_STATE_TX 0x20 + #define CHIPSTATUS_STATE_FSTXON 0x30 + #define CHIPSTATUS_STATE_CALIBRATE 0x40 + #define CHIPSTATUS_STATE_SETTLING 0x50 + #define CHIPSTATUS_STATE_RX_OVERFLOW 0x60 + #define CHIPSTATUS_STATE_TX_UNDERFLOW 0x70 + +// loop states +#define RX_START 0 +#define RX_ACTIVE 1 +#define RX_END 2 +#define TX_START 3 +#define TX_ACTIVE 4 +#define TX_END 5 + +class RfDataLinkLayer; + +class RfPhysicalLayer +{ + public: + RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform); + + bool InitChip(); + void showRegisterSettings(); + void stopChip(); + void loop(); + + private: + // Table for encoding 4-bit data into a 8-bit Manchester encoding. + static const uint8_t manchEncodeTab[16]; + // Table for decoding 4-bit Manchester encoded data into 2-bit + static const uint8_t manchDecodeTab[16]; + + static const uint8_t cc1101_2FSK_32_7_kb[CFG_REGISTER]; + static const uint8_t paTablePower868[8]; + + void manchEncode(uint8_t *uncodedData, uint8_t *encodedData); + bool manchDecode(uint8_t *encodedData, uint8_t *decodedData); + + int crc16(uint8_t* buffer, int offset, int length); + void powerDownCC1101(); + void setOutputPowerLevel(int8_t dBm); + + uint16_t packetSize (uint8_t lField); + + uint8_t sIdle(); + uint8_t sReceive(); + + void spiWriteRegister(uint8_t spi_instr, uint8_t value); + uint8_t spiReadRegister(uint8_t spi_instr); + uint8_t spiWriteStrobe(uint8_t spi_instr); + void spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len); + void spiWriteBurst(uint8_t spi_instr, const uint8_t *pArr, uint8_t len); + + uint8_t _loopState = RX_START; + + bool syncStart = false; + bool packetStart = true; + bool fixedLengthMode = false; + uint8_t *sendBuffer {0}; + uint16_t sendBufferLength {0}; + uint8_t packet[512]; + uint8_t buffer[sizeof(packet)*2]; // We need twice the space due to manchester encoding + uint8_t* pByteIndex = &buffer[0]; + uint16_t pktLen {0}; + uint16_t bytesLeft = {0}; + uint8_t statusGDO0 {0}; + uint8_t statusGDO2 {0}; + uint8_t prevStatusGDO0 {0}; // for edge detection during polling + uint8_t prevStatusGDO2 {0}; // for edge detection during polling + uint32_t packetStartTime {0}; + + RfDataLinkLayer& _rfDataLinkLayer; + Platform& _platform; +}; + +#endif diff --git a/src/knx_facade.cpp b/src/knx_facade.cpp index 2b6d1cb..45cdbcc 100644 --- a/src/knx_facade.cpp +++ b/src/knx_facade.cpp @@ -3,15 +3,28 @@ #include "knx/bits.h" #ifdef ARDUINO_ARCH_SAMD -KnxFacade knx; -#define ICACHE_RAM_ATTR + // predefined global instance for TP or RF + #ifdef MEDIUM_TYPE + #if MEDIUM_TYPE == 0 + KnxFacade knx; + #elif MEDIUM_TYPE == 2 + KnxFacade knx; + #else + #error "Only TP and RF supported for Arduino SAMD platform!" + #endif + #else + #error "No medium type specified for platform Arduino_SAMD! Please set MEDIUM_TYPE! (TP:0, RF:2, IP:5)" + #endif + #define ICACHE_RAM_ATTR #elif ARDUINO_ARCH_ESP8266 -KnxFacade knx; + // predefined global instance for IP only + KnxFacade knx; #elif ARDUINO_ARCH_ESP32 -//KnxFacade knx; -KnxFacade knx; + // predefined global instance for IP only + KnxFacade knx; #elif __linux__ -#define ICACHE_RAM_ATTR + // no predefined global instance + #define ICACHE_RAM_ATTR #endif #ifndef __linux__ diff --git a/src/knx_facade.h b/src/knx_facade.h index 5328ac9..80c6976 100644 --- a/src/knx_facade.h +++ b/src/knx_facade.h @@ -3,19 +3,21 @@ #include "knx/bits.h" #ifdef ARDUINO_ARCH_SAMD -#include "samd_platform.h" -#include "knx/bau07B0.h" + #include "samd_platform.h" + #include "knx/bau07B0.h" + #include "knx/bau27B0.h" #elif ARDUINO_ARCH_ESP8266 -#include "esp_platform.h" -#include "knx/bau57B0.h" + #include "esp_platform.h" + #include "knx/bau57B0.h" #elif ARDUINO_ARCH_ESP32 -#define LED_BUILTIN 13 -#include "esp32_platform.h" -#include "knx/bau57B0.h" + #define LED_BUILTIN 13 + #include "esp32_platform.h" + #include "knx/bau57B0.h" #else -#include "linux_platform.h" -#include "knx/bau57B0.h" -#define LED_BUILTIN 0 + #define LED_BUILTIN 0 + #include "linux_platform.h" + #include "knx/bau57B0.h" + #include "knx/bau27B0.h" #endif void buttonUp(); @@ -297,11 +299,24 @@ template class KnxFacade : private SaveRestore }; #ifdef ARDUINO_ARCH_SAMD -extern KnxFacade knx; + // predefined global instance for TP or RF + #ifdef MEDIUM_TYPE + #if MEDIUM_TYPE == 0 + extern KnxFacade knx; + #elif MEDIUM_TYPE == 2 + extern KnxFacade knx; + #else + #error "Only TP and RF supported for Arduino SAMD platform!" + #endif + #else + #error "No medium type specified for Arduino_SAMD platform! Please set MEDIUM_TYPE! (TP:0, RF:2, IP:5)" + #endif #elif ARDUINO_ARCH_ESP8266 -extern KnxFacade knx; + // predefined global instance for IP only + extern KnxFacade knx; #elif ARDUINO_ARCH_ESP32 -extern KnxFacade knx; + // predefined global instance for IP only + extern KnxFacade knx; #elif __linux__ -// no predefined global instance -#endif \ No newline at end of file + // no predefined global instance +#endif diff --git a/src/linux_platform.cpp b/src/linux_platform.cpp index 8445381..8b67a39 100644 --- a/src/linux_platform.cpp +++ b/src/linux_platform.cpp @@ -19,6 +19,11 @@ #include #include +#include // Needed for SPI port +#include // Needed for SPI port +#include // Needed for GPIO edge detection +#include // Needed for delayMicroseconds() + #include "knx/device_object.h" #include "knx/address_table_object.h" #include "knx/association_table_object.h" @@ -299,6 +304,74 @@ void LinuxPlatform::setupUart() { } +void LinuxPlatform::closeSpi() +{ + close(_spiFd); + printf ("SPI device closed.\r\n"); +} + +int LinuxPlatform::readWriteSpi (uint8_t *data, size_t len) +{ + uint16_t spiDelay = 0 ; + uint32_t spiSpeed = 8000000; // 4 MHz SPI speed + uint8_t spiBPW = 8; // Bits per word + + struct spi_ioc_transfer spi ; + + // Mentioned in spidev.h but not used in the original kernel documentation + // test program )-: + + memset (&spi, 0, sizeof (spi)) ; + + spi.tx_buf = (uint64_t)data; + spi.rx_buf = (uint64_t)data; + spi.len = len; + spi.delay_usecs = spiDelay; + spi.speed_hz = spiSpeed; + spi.bits_per_word = spiBPW; + + return ioctl (_spiFd, SPI_IOC_MESSAGE(1), &spi) ; +} + +void LinuxPlatform::setupSpi() +{ + if ((_spiFd = open ("/dev/spidev0.0", O_RDWR)) < 0) + { + printf ("ERROR: SPI setup failed! Could not open SPI device!\r\n"); + return; + } + + // Set SPI parameters. + int mode = 0; // Mode 0 + uint8_t spiBPW = 8; // Bits per word + int speed = 8000000; // 4 MHz SPI speed + + if (ioctl (_spiFd, SPI_IOC_WR_MODE, &mode) < 0) + { + printf ("ERROR: SPI Mode Change failure: %s\n", strerror (errno)) ; + close(_spiFd); + return; + } + + if (ioctl (_spiFd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0) + { + printf ("ERROR: SPI BPW Change failure: %s\n", strerror (errno)) ; + close(_spiFd); + return; + } + + if (ioctl (_spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) + { + printf ("ERROR: SPI Speed Change failure: %s\n", strerror (errno)) ; + close(_spiFd); + return; + } + + printf ("SPI device setup ok.\r\n"); + + +} + /* * On linux the memory addresses from malloc may be to big for usermermory_write. * So we allocate some memory at the beginning and use it for address table, group object table etc. @@ -519,4 +592,326 @@ void LinuxPlatform::cmdLineArgs(int argc, char** argv) memcpy(_args, argv, argc * sizeof(char*)); _args[argc] = 0; } -#endif \ No newline at end of file + +void LinuxPlatform::setupGpio(uint32_t dwPin, uint32_t dwMode) +{ + gpio_export(dwPin); + gpio_direction(dwPin, dwMode); +} + +void LinuxPlatform::closeGpio(uint32_t dwPin) +{ + gpio_unexport(dwPin); + // Set direction to input always if we do not need the GPIO anymore? Unsure... + //gpio_direction(dwPin, INPUT); +} + +void LinuxPlatform::writeGpio(uint32_t dwPin, uint32_t dwVal) +{ + gpio_write(dwPin, dwVal); +} + +uint32_t LinuxPlatform::readGpio(uint32_t dwPin) +{ + return gpio_read(dwPin); +} + +/* Datenpuffer fuer die GPIO-Funktionen */ +#define MAXBUFFER 100 + +/* GPIO-Pin aktivieren + * Schreiben der Pinnummer nach /sys/class/gpio/export + * Ergebnis: 0 = O.K., -1 = Fehler + */ +int gpio_export(int pin) +{ + char buffer[MAXBUFFER]; /* Output Buffer */ + ssize_t bytes; /* Datensatzlaenge */ + int fd; /* Filedescriptor */ + int res; /* Ergebnis von write */ + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd < 0) + { + perror("Kann nicht auf export schreiben!\n"); + return(-1); + } + + bytes = snprintf(buffer, MAXBUFFER, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Kann Pin nicht aktivieren (write)!\n"); + return(-1); + } + + close(fd); + delay(100); + + return(0); +} + +/* GPIO-Pin deaktivieren + * Schreiben der Pinnummer nach /sys/class/gpio/unexport + * Ergebnis: 0 = O.K., -1 = Fehler + */ +int gpio_unexport(int pin) +{ + char buffer[MAXBUFFER]; /* Output Buffer */ + ssize_t bytes; /* Datensatzlaenge */ + int fd; /* Filedescriptor */ + int res; /* Ergebnis von write */ + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (fd < 0) + { + perror("Kann nicht auf unexport schreiben!\n"); + return(-1); + } + + bytes = snprintf(buffer, MAXBUFFER, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Kann Pin nicht deaktivieren (write)!\n"); + return(-1); + } + + close(fd); + return(0); +} + +/* Datenrichtung GPIO-Pin festlegen + * Schreiben Pinnummer nach /sys/class/gpioXX/direction + * Richtung dir: 0 = Lesen, 1 = Schreiben + * Ergebnis: 0 = O.K., -1 = Fehler + */ +int gpio_direction(int pin, int dir) +{ + char path[MAXBUFFER]; /* Buffer fuer Pfad */ + int fd; /* Filedescriptor */ + int res; /* Ergebnis von write */ + + snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/direction", pin); + fd = open(path, O_WRONLY); + if (fd < 0) + { + perror("Kann Datenrichtung nicht setzen (open)!\n"); + return(-1); + } + + switch (dir) + { + case INPUT : res = write(fd,"in",2); break; + case OUTPUT: res = write(fd,"out",3); break; + default: res = -1; break; + } + + if (res < 0) + { + perror("Kann Datenrichtung nicht setzen (write)!\n"); + return(-1); + } + + close(fd); + return(0); +} + +/* vom GPIO-Pin lesen + * Ergebnis: -1 = Fehler, 0/1 = Portstatus + */ +int gpio_read(int pin) +{ + char path[MAXBUFFER]; /* Buffer fuer Pfad */ + int fd; /* Filedescriptor */ + char result[MAXBUFFER] = {0}; /* Buffer fuer Ergebnis */ + + snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_RDONLY); + if (fd < 0) + { + perror("Kann vom GPIO nicht lesen (open)!\n"); + return(-1); + } + + if (read(fd, result, 3) < 0) + { + perror("Kann vom GPIO nicht lesen (read)!\n"); + return(-1); + } + + close(fd); + return(atoi(result)); +} + +/* auf GPIO schreiben + * Ergebnis: -1 = Fehler, 0 = O.K. + */ +int gpio_write(int pin, int value) +{ + char path[MAXBUFFER]; /* Buffer fuer Pfad */ + int fd; /* Filedescriptor */ + int res; /* Ergebnis von write */ + + snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_WRONLY); + + if (fd < 0) + { + perror("Kann auf GPIO nicht schreiben (open)!\n"); + return(-1); + } + + switch (value) + { + case LOW : res = write(fd,"0",1); break; + case HIGH: res = write(fd,"1",1); break; + default: res = -1; break; + } + + if (res < 0) + { + perror("Kann auf GPIO nicht schreiben (write)!\n"); + return(-1); + } + + close(fd); + return(0); +} + +/* GPIO-Pin auf Detektion einer Flanke setzen. + * Fuer die Flanke (edge) koennen folgende Parameter gesetzt werden: + * 'r' (rising) - steigende Flanke, + * 'f' (falling) - fallende Flanke, + * 'b' (both) - beide Flanken. + */ +int gpio_edge(unsigned int pin, char edge) +{ + char path[MAXBUFFER]; /* Buffer fuer Pfad */ + int fd; /* Filedescriptor */ + + snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/edge", pin); + + fd = open(path, O_WRONLY | O_NONBLOCK ); + if (fd < 0) + { + perror("gpio_edge: Kann auf GPIO nicht schreiben (open)!\n"); + return(-1); + } + + switch (edge) + { + case 'r': strncpy(path,"rising",8); break; + case 'f': strncpy(path,"falling",8); break; + case 'b': strncpy(path,"both",8); break; + case 'n': strncpy(path,"none",8); break; + default: close(fd);return(-2); + } + + write(fd, path, strlen(path) + 1); + + close(fd); + return 0; +} + +/* Warten auf Flanke am GPIO-Pin. + * Eingabewerte: pin: GPIO-Pin + * timeout: Wartezeit in Millisekunden + * Der Pin muss voher eingerichtet werden (export, + * direction, edge) + * Rueckgabewerte: <0: Fehler, 0: poll() Timeout, + * 1: Flanke erkannt, Pin lieferte "0" + * 2: Flanke erkannt, Pin lieferte "1" + */ +int gpio_wait(unsigned int pin, int timeout) +{ + char path[MAXBUFFER]; /* Buffer fuer Pfad */ + int fd; /* Filedescriptor */ + struct pollfd polldat[1]; /* Variable fuer poll() */ + char buf[MAXBUFFER]; /* Lesepuffer */ + int rc; /* Hilfsvariablen */ + + /* GPIO-Pin dauerhaft oeffnen */ + snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_RDONLY | O_NONBLOCK ); + if (fd < 0) + { + perror("gpio_wait: Kann von GPIO nicht lesen (open)!\n"); + return(-1); + } + + /* poll() vorbereiten */ + memset((void*)buf, 0, sizeof(buf)); + memset((void*)polldat, 0, sizeof(polldat)); + polldat[0].fd = fd; + polldat[0].events = POLLPRI; + + /* eventuell anstehende Interrupts loeschen */ + lseek(fd, 0, SEEK_SET); + rc = read(fd, buf, MAXBUFFER - 1); + + rc = poll(polldat, 1, timeout); + if (rc < 0) + { /* poll() failed! */ + perror("gpio_wait: Poll-Aufruf ging schief!\n"); + close(fd); + return(-1); + } + + if (rc == 0) + { /* poll() timeout! */ + close(fd); + return(0); + } + + if (polldat[0].revents & POLLPRI) + { + if (rc < 0) + { /* read() failed! */ + perror("gpio_wait: Kann von GPIO nicht lesen (read)!\n"); + close(fd); + return(-2); + } + /* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */ + close(fd); + return(1 + atoi(buf)); + } + + close(fd); + return(-1); +} + +void delayMicrosecondsHard (unsigned int howLong) +{ + struct timeval tNow, tLong, tEnd ; + + gettimeofday (&tNow, NULL) ; + tLong.tv_sec = howLong / 1000000 ; + tLong.tv_usec = howLong % 1000000 ; + timeradd (&tNow, &tLong, &tEnd) ; + + while (timercmp (&tNow, &tEnd, <)) + gettimeofday (&tNow, NULL) ; +} + +void delayMicroseconds (unsigned int howLong) +{ + struct timespec sleeper ; + unsigned int uSecs = howLong % 1000000 ; + unsigned int wSecs = howLong / 1000000 ; + + /**/ if (howLong == 0) + return ; + else if (howLong < 100) + delayMicrosecondsHard (howLong) ; + else + { + sleeper.tv_sec = wSecs ; + sleeper.tv_nsec = (long)(uSecs * 1000L) ; + nanosleep (&sleeper, NULL) ; + } +} + +#endif diff --git a/src/linux_platform.h b/src/linux_platform.h index 722c537..cda3af9 100644 --- a/src/linux_platform.h +++ b/src/linux_platform.h @@ -5,6 +5,13 @@ #include #include "knx/platform.h" +extern void delayMicroseconds (unsigned int howLong); +extern int gpio_direction(int pin, int dir); +extern int gpio_read(int pin); +extern int gpio_write(int pin, int value); +extern int gpio_export(int pin); +extern int gpio_unexport(int pin); + class LinuxPlatform: public Platform { using Platform::_memoryReference; @@ -43,6 +50,17 @@ public: int readUart() override; size_t readBytesUart(uint8_t *buffer, size_t length) override; + //spi + void setupSpi() override; + void closeSpi() override; + int readWriteSpi (uint8_t *data, size_t len) override; + + //gpio + virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) override; + virtual void closeGpio(uint32_t dwPin) override; + virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) override; + virtual uint32_t readGpio(uint32_t dwPin) override; + //memory uint8_t* getEepromBuffer(uint16_t size) override; void commitToEeprom() override; @@ -57,6 +75,7 @@ public: void doMemoryMapping(); uint8_t* _mappedFile = 0; int _fd = -1; + int _spiFd = -1; uint8_t* _currentMaxMem = 0; std::string _flashFilePath = "flash.bin"; char** _args = 0;