diff --git a/.travis.yml b/.travis.yml index 0b8bcb6..852763e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,8 @@ before_install: - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.warning_level=all#compiler.warning_level=default#' ~/.arduino15/preferences.txt; fi # changes for bsec lib # samd - - if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{archive_file\}")( -Wl,--end-group)#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/arduino/hardware/samd/1.8.3/platform.txt; fi - - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/arduino/hardware/samd/1.8.3/platform.txt; fi + - if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{archive_file\}")( -Wl,--end-group)#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/arduino/hardware/samd/1.8.4/platform.txt; fi + - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/arduino/hardware/samd/1.8.4/platform.txt; fi # esp8266 - if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{compiler.c.elf.libs\})( -Wl,--end-group "-L\{build.path\}")#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/esp8266/hardware/esp8266/2.5.2/platform.txt; fi - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/esp8266/hardware/esp8266/2.5.2/platform.txt; fi diff --git a/doc/CC1101-868mhz-radio-module-pinout.jpg b/doc/CC1101-868mhz-radio-module-pinout.jpg new file mode 100644 index 0000000..1071fc9 Binary files /dev/null and b/doc/CC1101-868mhz-radio-module-pinout.jpg differ diff --git a/doc/knx_rf_notes.md b/doc/knx_rf_notes.md new file mode 100644 index 0000000..f20cbd0 --- /dev/null +++ b/doc/knx_rf_notes.md @@ -0,0 +1,79 @@ +KNX-RF S-Mode +============= + +Implementation Notes +-------------------- +* KNX-RF E-Mode (pushbutton method) is NOT supported! +* KNX-RF S-Mode is implemented as KNX-RF READY (KNX-RF 1.R) which means only one single channel and no fast-ack is used. + -> KNX RF Multi (KNX-RF 1.M) would be required for this. However, implementation is way more complex as frequency hopping (fast and slow channels) is used and fast-ack + is based on a TDMA-like access scheme which has very strict timing requirements for the time slots. + -> summary: KNX-RF 1.R does NOT acknowledge packets on the air (data link layer). So standard GROUP_VALUE_WRITE messages in multicast mode could get lost! + Connection-oriented communication for device management is handled by the transport layer and uses T_ACK and T_NACK. +* the driver (rf_physical_layer.cpp) uses BOTH signals (GDO0, GDO2) of the RF transceiver C1101 + -> GDO0 is asserted on RX/TX packet start and de-asserted on RX/TX packet end or RX/TX FIFO overflow/underflow + -> GDO2 is asserted is the FIFO needs to read out (RX) or refilled (TX) +* the driver (rf_physical_layer.cpp) uses both packet length modes of the CC1101: infinite length and fixed length are switched WHILE receiving or transmitting a packet. +* the edges of the signals GDO0 and GDO2 are detected by polling in the main loop and NOT by using interrupts + -> as a consequence the main loop must not be delayed too much, the transceiver receives/transmitts the data bytes itself into/from the FIFOs though (max. FIFO size 64 bytes each (TX/RX)!). + -> KNX-RF bitrate is 16384 bits/sec. -> so 40 bytes (Preamble, Syncword, Postamble) around 20ms for reception/transmission of a complete packet (to be verified!) + -> another implementation using interrupts could also be realized +* the driver does not use the wake-on-radio of the CC1101. The bidirectional battery powered devices are not useful at the moment. + -> wake-on-radio would also require the MCU to sleep and be woken up through interrupts. + -> BUT: UNIdirectional (battery powered) device could be realized: one the device has been configured in bidirectional mode. + The device (MCU) could sleep and only wake up if something needs to be done. Only useful for transmitters (e.g. sensors like push button, temperature sensor, etc.). + +ToDo +---- +* Packet duplication prevention based on the data link layer frame counter if KNX-RF retransmitters (range extension) are active (should be easy to add) +* KNX-RF 1.M (complex, may need an additional MCU, not planned for now) + -> maybe with a more capable transceiver: http://www.lapis-semi.com/en/semicon/telecom/telecom-product.php?PartNo=ML7345 + it could handle manchester code, CRC-16 and the whole Wireless MBUS frame structure in hardware. Also used by Tapko for KAIstack. +* KNX Data Secure with security proxy profile in line coupler (e.g. TP<>RF). See KNX AN158 (KNX Data Secure draft spec.) p.9 + -> KNX-RF very much benefits from having authenticated, encrypted data exchange. + -> Security Proxy terminates the secure applicationj layer (S-AL) in the line coupler. So the existing plain TP installation without data secure feature + can be kept as is. + +Development Setup +----------------- +Development is done on a cheap Wemos SAMD21 M0-Mini board with a standard CC1101 module (868MHz) from Ebay. Beware of defective and bad quality modules. +The SAMD21 MCU is connected via SWD (Segger J-Link) to PlatformIO (Visual Studio Code). Additionally the standard UART (Arduino) is used for serial debug messages. +The USB port of the SAMD21 is not used at all. + +Connection wiring: +------------------ + +Signal SAMD21 CC1101 +----------+-------------+--------------- +SPI_nCS | D10(PA18) | CSN +SPI_MOSI | D11(PA16) | SI +SPI_MISO | D12(PA19) | SO +SPI_SCK | D13(PA17) | SCLK +GDO0 | D7(PA21) | GDO0 +GDO2 | D9(PA07) | GDO2 + +Arduino MZEROUSB variant needs patching to enable SPI on SERCOM1 on D10-D13. + +variant.h +--------- +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (18u) +#define PIN_SPI_MOSI (21u) +#define PIN_SPI_SCK (20u) +#define PERIPH_SPI sercom1 +#define PAD_SPI_TX SPI_PAD_0_SCK_1 +#define PAD_SPI_RX SERCOM_RX_PAD_3 + +variant.cpp +----------- + // 18..23 - SPI pins (ICSP:MISO,SCK,MOSI) + // ---------------------- + { PORTA, 19, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_12 }, // MISO: SERCOM1/PAD[3] + { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // 5V0 + { PORTA, 17, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 }, // SCK: SERCOM1/PAD[1] + { PORTA, 16, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 }, // MOSI: SERCOM1/PAD[0] + { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // RESET + { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // GND diff --git a/examples/knx-demo/knx-demo-rf.knxprod b/examples/knx-demo/knx-demo-rf.knxprod new file mode 100644 index 0000000..ece15a9 Binary files /dev/null and b/examples/knx-demo/knx-demo-rf.knxprod differ diff --git a/examples/knx-demo/knx-demo-rf.xml b/examples/knx-demo/knx-demo-rf.xml new file mode 100644 index 0000000..38993f6 --- /dev/null +++ b/examples/knx-demo/knx-demo-rf.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/knx-linux/CMakeLists.txt b/knx-linux/CMakeLists.txt index 5a9ba65..6b2f1ad 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) +add_definitions(-DMEDIUM_TYPE=5) diff --git a/knx-linux/main.cpp b/knx-linux/main.cpp index 3a38d4a..4b64f64 100644 --- a/knx-linux/main.cpp +++ b/knx-linux/main.cpp @@ -1,12 +1,37 @@ #include "knx_facade.h" +#if MEDIUM_TYPE == 5 #include "knx/bau57B0.h" +#elif MEDIUM_TYPE == 2 +#include "knx/bau27B0.h" +#else +#error Only MEDIUM_TYPE IP and RF supported +#endif #include "knx/group_object_table_object.h" #include "knx/bits.h" #include #include #include +#include +#include +#include +#include +volatile sig_atomic_t loopActive = 1; +void signalHandler(int sig) +{ + (void)sig; + + // can be called asynchronously + loopActive = 0; +} + +#if MEDIUM_TYPE == 5 KnxFacade knx; +#elif MEDIUM_TYPE == 2 +KnxFacade knx; +#else +#error Only MEDIUM_TYPE IP and RF supported +#endif long lastsend = 0; @@ -85,15 +110,39 @@ void setup() int main(int argc, char **argv) { + printf("main() start.\n"); + + // Prevent swapping of this process + struct sched_param sp; + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &sp); + mlockall(MCL_CURRENT | MCL_FUTURE); + + // Register signals + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + knx.platform().cmdLineArgs(argc, argv); setup(); - while (1) + while (loopActive) { knx.loop(); if(knx.configured()) appLoop(); - delay(100); + delayMicroseconds(100); } -} \ No newline at end of file + + // pinMode() will automatically export GPIO pin in sysfs + // Read or writing the GPIO pin for the first time automatically + // opens the "value" sysfs file to read or write the GPIO pin value. + // The following calls will close the "value" sysfs fiel for the pin + // and unexport the GPIO pin. + gpio_unexport(SPI_SS_PIN); + gpio_unexport(GPIO_GDO2_PIN); + gpio_unexport(GPIO_GDO0_PIN); + + printf("main() exit.\n"); +} diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp index 979745d..58d9986 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,24 @@ 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 print(const char* s) { ArduinoPlatform::SerialDebug->print(s); diff --git a/src/arduino_platform.h b/src/arduino_platform.h index 60acba8..d7b6f69 100644 --- a/src/arduino_platform.h +++ b/src/arduino_platform.h @@ -35,6 +35,11 @@ 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; + static Stream* SerialDebug; protected: diff --git a/src/knx/apdu.cpp b/src/knx/apdu.cpp index 8809d10..9867215 100644 --- a/src/knx/apdu.cpp +++ b/src/knx/apdu.cpp @@ -12,7 +12,7 @@ ApduType APDU::type() apci = getWord(_data); popWord(apci, _data); apci &= 0x3ff; - if ((apci >> 6) < 11) //short apci + if ((apci >> 6) < 11 && (apci >> 6) != 7) //short apci apci &= 0x3c0; return (ApduType)apci; } diff --git a/src/knx/application_layer.cpp b/src/knx/application_layer.cpp index 321942a..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) @@ -176,7 +213,10 @@ void ApplicationLayer::connectIndication(uint16_t tsap) void ApplicationLayer::connectConfirm(uint16_t destination, uint16_t tsap, bool status) { if (status) + { _connectedTsap = tsap; + _bau.connectConfirm(tsap); + } else _connectedTsap = -1; } @@ -188,7 +228,7 @@ void ApplicationLayer::disconnectIndication(uint16_t tsap) void ApplicationLayer::disconnectConfirm(Priority priority, uint16_t tsap, bool status) { - + _connectedTsap = -1; } void ApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu) @@ -334,13 +374,82 @@ void ApplicationLayer::deviceDescriptorReadResponse(AckType ack, Priority priori individualSend(ack, hopType, priority, asap, apdu); } -void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap) +void ApplicationLayer::connectRequest(uint16_t destination, Priority priority) +{ + _transportLayer->connectRequest(destination, priority); +} + +void ApplicationLayer::disconnectRequest(Priority priority) +{ + _transportLayer->disconnectRequest(_connectedTsap, priority); +} + +void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountType hopType) { CemiFrame frame(1); APDU& apdu = frame.apdu(); apdu.type(Restart); - individualSend(ack, hopType, priority, asap, apdu); + 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, @@ -788,3 +897,8 @@ void ApplicationLayer::individualSend(AckType ack, HopCountType hopType, Priorit else _transportLayer->dataIndividualRequest(ack, hopType, priority, asap, apdu); } + +bool ApplicationLayer::isConnected() +{ + return (_connectedTsap >= 0); +} \ No newline at end of file diff --git a/src/knx/application_layer.h b/src/knx/application_layer.h index 7af7262..9d7223f 100644 --- a/src/knx/application_layer.h +++ b/src/knx/application_layer.h @@ -95,7 +95,10 @@ class ApplicationLayer uint8_t descriptorType); void deviceDescriptorReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t descriptorType, uint8_t* deviceDescriptor); - void restartRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap); + void connectRequest(uint16_t destination, Priority priority); + void disconnectRequest(Priority priority); + bool isConnected(); + void restartRequest(AckType ack, Priority priority, HopCountType hopType); void propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex); void propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex, @@ -126,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: @@ -147,5 +158,5 @@ class ApplicationLayer AssociationTableObject& _assocTable; BusAccessUnit& _bau; TransportLayer* _transportLayer = 0; - int32_t _connectedTsap; + int32_t _connectedTsap = -1; }; diff --git a/src/knx/bau.cpp b/src/knx/bau.cpp index 105b380..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) { } @@ -236,3 +237,25 @@ void BusAccessUnit::keyWriteResponseConfirm(AckType ack, Priority priority, HopC void BusAccessUnit::keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level) { } + +void BusAccessUnit::connectConfirm(uint16_t destination) +{ +} + +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 14ca5b5..d2d6ada 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,4 +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 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..6d5b464 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,16 @@ 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); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + _deviceObj.ifObj(_ifObjs); } InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx) diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h index 84d8aba..5fe3f67 100644 --- a/src/knx/bau07B0.h +++ b/src/knx/bau07B0.h @@ -16,4 +16,6 @@ class Bau07B0 : public BauSystemB private: TpUartDataLinkLayer _dlLayer; uint8_t _descriptor[2] = {0x07, 0xb0}; + const uint32_t _ifObjs[6] = { 5, // length + OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG}; }; \ No newline at end of file diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp new file mode 100644 index 0000000..3e1b548 --- /dev/null +++ b/src/knx/bau27B0.cpp @@ -0,0 +1,119 @@ +#if MEDIUM_TYPE == 2 + +#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); + + // Set the maximum APDU length + // ETS will consider this value while programming the device + // For KNX-RF we use a smallest allowed value for now, + // although long frame are also supported by the implementation. + // Needs some experimentation. + _deviceObj.maxApduLength(15); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + _deviceObj.ifObj(_ifObjs); +} + +// 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); +} + +#endif // #if MEDIUM_TYPE == 2 diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h new file mode 100644 index 0000000..b2c129e --- /dev/null +++ b/src/knx/bau27B0.h @@ -0,0 +1,32 @@ +#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}; + const uint32_t _ifObjs[7] = { 6, // length + OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM}; + + 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..648d777 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,16 @@ 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); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + _deviceObj.ifObj(_ifObjs); } InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx) diff --git a/src/knx/bau57B0.h b/src/knx/bau57B0.h index 297f4aa..8835150 100644 --- a/src/knx/bau57B0.h +++ b/src/knx/bau57B0.h @@ -18,4 +18,6 @@ class Bau57B0 : public BauSystemB IpParameterObject _ipParameters; IpDataLinkLayer _dlLayer; uint8_t _descriptor[2] = {0x57, 0xb0}; + const uint32_t _ifObjs[7] = { 6, // length + OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER}; }; \ No newline at end of file diff --git a/src/knx/bau_systemB.cpp b/src/knx/bau_systemB.cpp index 4e66daa..9840c0d 100644 --- a/src/knx/bau_systemB.cpp +++ b/src/knx/bau_systemB.cpp @@ -1,7 +1,16 @@ #include "bau_systemB.h" +#include "bits.h" #include #include +enum NmReadSerialNumberType +{ + NM_Read_SerialNumber_By_ProgrammingMode = 0x01, + NM_Read_SerialNumber_By_ExFactoryState = 0x02, + NM_Read_SerialNumber_By_PowerReset = 0x03, + NM_Read_SerialNumber_By_ManufacturerSpecific = 0xFE, +}; + BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platform), _assocTable(platform), _groupObjTable(platform), _appProgram(platform), _platform(platform), _appLayer(_assocTable, *this), @@ -21,6 +30,7 @@ void BauSystemB::loop() dataLinkLayer().loop(); _transLayer.loop(); sendNextGroupTelegram(); + nextRestartState(); } bool BauSystemB::enabled() @@ -293,8 +303,91 @@ void BauSystemB::addSaveRestore(SaveRestore* obj) _memory.addSaveRestore(obj); } - -void BauSystemB::restartRequest(uint16_t asap) +bool BauSystemB::restartRequest(uint16_t asap) { - _appLayer.restartRequest(AckRequested, LowPriority, NetworkLayerParameter, asap); + if (_appLayer.isConnected()) + return false; + _restartState = Connecting; // order important, has to be set BEFORE connectRequest + _appLayer.connectRequest(asap, SystemPriority); + _appLayer.deviceDescriptorReadRequest(AckRequested, SystemPriority, NetworkLayerParameter, asap, 0); + return true; +} + +void BauSystemB::connectConfirm(uint16_t tsap) +{ + if (_restartState == Connecting && tsap >= 0) + { + /* restart connection is confirmed, go to the next state */ + _restartState = Connected; + _restartDelay = millis(); + } + else + { + _restartState = Idle; + } +} + +void BauSystemB::nextRestartState() +{ + switch (_restartState) + { + case Idle: + /* inactive state, do nothing */ + break; + case Connecting: + /* wait for connection, we do nothing here */ + break; + case Connected: + /* connection confirmed, we send restartRequest, but we wait a moment (sending ACK etc)... */ + if (millis() - _restartDelay > 30) + { + _appLayer.restartRequest(AckRequested, SystemPriority, NetworkLayerParameter); + _restartState = Restarted; + _restartDelay = millis(); + } + break; + case Restarted: + /* restart is finished, we send a discommect */ + if (millis() - _restartDelay > 30) + { + _appLayer.disconnectRequest(SystemPriority); + _restartState = Idle; + } + default: + break; + } +} + +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((NmReadSerialNumberType)operand) + { + case NM_Read_SerialNumber_By_ProgrammingMode: // 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 NM_Read_SerialNumber_By_ExFactoryState: // NM_Read_SerialNumber_By_ExFactoryState + break; + + case NM_Read_SerialNumber_By_PowerReset: // NM_Read_SerialNumber_By_PowerReset + break; + + case NM_Read_SerialNumber_By_ManufacturerSpecific: // Manufacturer specific use of A_SystemNetworkParameter_Read + break; + } } diff --git a/src/knx/bau_systemB.h b/src/knx/bau_systemB.h index 91a0741..5ca0ff5 100644 --- a/src/knx/bau_systemB.h +++ b/src/knx/bau_systemB.h @@ -27,7 +27,7 @@ class BauSystemB : protected BusAccessUnit void readMemory(); void writeMemory(); void addSaveRestore(SaveRestore* obj); - void restartRequest(uint16_t asap); + bool restartRequest(uint16_t asap); protected: virtual DataLinkLayer& dataLinkLayer() = 0; @@ -58,10 +58,22 @@ 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; void sendNextGroupTelegram(); void updateGroupObject(GroupObject& go, uint8_t* data, uint8_t length); + void nextRestartState(); + + enum RestartState + { + Idle, + Connecting, + Connected, + Restarted + }; DeviceObject _deviceObj; Memory _memory; @@ -74,4 +86,6 @@ class BauSystemB : protected BusAccessUnit TransportLayer _transLayer; NetworkLayer _netLayer; bool _configured = true; + RestartState _restartState = Idle; + uint32_t _restartDelay = 0; }; \ No newline at end of file diff --git a/src/knx/bits.h b/src/knx/bits.h index de25682..3e6a95e 100644 --- a/src/knx/bits.h +++ b/src/knx/bits.h @@ -26,9 +26,11 @@ #define RISING 4 void delay(uint32_t millis); +void delayMicroseconds (unsigned int howLong); uint32_t millis(); void pinMode(uint32_t dwPin, uint32_t dwMode); void digitalWrite(uint32_t dwPin, uint32_t dwVal); +uint32_t digitalRead(uint32_t dwPin); typedef void (*voidFuncPtr)(void); void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode); 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..92d34a6 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(); @@ -31,9 +36,13 @@ void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success) FrameFormat type = frame.frameType(); Priority priority = frame.priority(); NPDU& npdu = frame.npdu(); + SystemBroadcast systemBroadcast = frame.systemBroadcast(); if (addrType == GroupAddress && destination == 0) - _networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success); + if (systemBroadcast == SysBroadcast) + _networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success); + else + _networkLayer.broadcastConfirm(ack, type, priority, source, npdu, success); else _networkLayer.dataConfirm(ack, addrType, destination, type, priority, source, npdu, success); @@ -49,12 +58,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.broadcastIndication(ack, type, npdu, priority, source); + } else { if (addrType == InduvidualAddress && destination != _deviceObject.induvidualAddress()) @@ -73,7 +88,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 +97,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..e9d0905 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); @@ -35,7 +35,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& *data = _prgMode; break; case PID_MAX_APDU_LENGTH: - pushWord(254, data); + pushWord(_maxApduLength, data); break; case PID_SUBNET_ADDR: *data = ((_ownAddress >> 8) & 0xff); @@ -45,18 +45,12 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& break; case PID_IO_LIST: { - uint32_t ifObjs[] = { - 6, // length - OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER}; - - for (uint32_t i = start; i < (ifObjs[0] + 1) && i < count; i++) - pushInt(ifObjs[i], data); - + for (uint32_t i = start; i < (_ifObjs[0] + 1) && i < count; i++) + pushInt(_ifObjs[i], data); break; } case PID_DEVICE_DESCRIPTOR: - data[0] = 0x57; - data[1] = 0xB0; + pushWord(_maskVersion, data); break; default: count = 0; @@ -254,6 +248,36 @@ void DeviceObject::version(uint16_t value) _version = value; } +uint16_t DeviceObject::maskVersion() +{ + return _maskVersion; +} + +void DeviceObject::maskVersion(uint16_t value) +{ + _maskVersion = value; +} + +void DeviceObject::maxApduLength(uint16_t value) +{ + _maxApduLength = value; +} + +uint16_t DeviceObject::maxApduLength() +{ + return _maxApduLength; +} + +const uint32_t* DeviceObject::ifObj() +{ + return _ifObjs; +} + +void DeviceObject::ifObj(const uint32_t* value) +{ + _ifObjs = 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..35704f1 100644 --- a/src/knx/device_object.h +++ b/src/knx/device_object.h @@ -35,6 +35,12 @@ public: void hardwareType(const uint8_t* value); uint16_t version(); void version(uint16_t value); + uint16_t maskVersion(); + void maskVersion(uint16_t value); + uint16_t maxApduLength(); + void maxApduLength(uint16_t value); + const uint32_t* ifObj(); + void ifObj(const uint32_t* value); protected: uint8_t propertyCount(); PropertyDescription* propertyDescriptions(); @@ -48,4 +54,7 @@ private: char _orderNumber[10] = ""; uint8_t _hardwareType[6] = { 0, 0, 0, 0, 0, 0}; uint16_t _version = 0; + uint16_t _maskVersion = 0x0000; + uint16_t _maxApduLength = 254; + const uint32_t* _ifObjs; }; \ 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..0421526 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,4 @@ enum ApduType PropertyValueWrite = 0x3d7, PropertyDescriptionRead = 0x3d8, PropertyDescriptionResponse = 0x3d9, - IndividualAddressSerialNumberRead = 0x3dc, - IndividualAddressSerialNumberResponse = 0x3dd, - IndividualAddressSerialNumberWrite = 0x3de, -}; \ No newline at end of file +}; diff --git a/src/knx/network_layer.cpp b/src/knx/network_layer.cpp index ceab6b6..ffea9d3 100644 --- a/src/knx/network_layer.cpp +++ b/src/knx/network_layer.cpp @@ -66,18 +66,30 @@ void NetworkLayer::dataConfirm(AckType ack, AddressType addressType, uint16_t de _transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status); } -void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) +void NetworkLayer::broadcastIndication(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()); } -void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) +void NetworkLayer::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) { HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; _transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status); } +void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); +} + +void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastConfirm(ack, hopType, npdu.tpdu(), priority, status); +} + void NetworkLayer::dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) { //if (tpdu.apdu().length() > 0) diff --git a/src/knx/network_layer.h b/src/knx/network_layer.h index 2386469..0b1f88e 100644 --- a/src/knx/network_layer.h +++ b/src/knx/network_layer.h @@ -20,6 +20,9 @@ class NetworkLayer Priority priority, uint16_t source); void dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status); + void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source); + void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status); void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source); void 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..5a082aa 100644 --- a/src/knx/platform.h +++ b/src/knx/platform.h @@ -29,6 +29,10 @@ 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 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..f4b6ff6 --- /dev/null +++ b/src/knx/rf_data_link_layer.cpp @@ -0,0 +1,369 @@ +#if MEDIUM_TYPE == 2 + +#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; +} + +#endif // #if MEDIUM_TYPE == 2 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..590667d --- /dev/null +++ b/src/knx/rf_physical_layer.cpp @@ -0,0 +1,799 @@ +#if MEDIUM_TYPE == 2 + +#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; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, len); + digitalWrite(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; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len); + digitalWrite(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]); + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, 1); + digitalWrite(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; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len + 1); + digitalWrite(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.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 = digitalRead(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 = digitalRead(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 = digitalRead(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 = digitalRead(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; + } +} + +#endif // #if MEDIUM_TYPE == 2 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..ab4c0a4 100644 --- a/src/knx_facade.h +++ b/src/knx_facade.h @@ -2,20 +2,27 @@ #include "knx/bits.h" +// Set default medium type to TP if no external definitions was given +#ifndef MEDIUM_TYPE +#define MEDIUM_TYPE 0 +#endif + #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 +304,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..e7f8b4a 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. @@ -499,10 +572,18 @@ void println(void) void pinMode(uint32_t dwPin, uint32_t dwMode) { + gpio_export(dwPin); + gpio_direction(dwPin, dwMode); } void digitalWrite(uint32_t dwPin, uint32_t dwVal) { + gpio_write(dwPin, dwVal); +} + +uint32_t digitalRead(uint32_t dwPin) +{ + return gpio_read(dwPin); } typedef void (*voidFuncPtr)(void); @@ -519,4 +600,315 @@ void LinuxPlatform::cmdLineArgs(int argc, char** argv) memcpy(_args, argv, argc * sizeof(char*)); _args[argc] = 0; } -#endif \ No newline at end of file + +/* Buffer size for string operations (e.g. snprintf())*/ +#define MAX_STRBUF_SIZE 100 +#define MAX_NUM_GPIO 64 + +static int gpioFds [MAX_NUM_GPIO] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +} ; + +/* Activate GPIO-Pin + * Write GPIO pin number to /sys/class/gpio/export + * Result: 0 = success, -1 = error + */ +int gpio_export(int pin) +{ + char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */ + ssize_t bytes; /* Used Buffer length */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Export GPIO pin %d\n", pin); + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd < 0) + { + perror("Could not export GPIO pin(open)!\n"); + return(-1); + } + + bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Could not export GPIO pin(write)!\n"); + return(-1); + } + + close(fd); + delay(100); + + return(0); +} + +/* Deactivate GPIO pin + * Write GPIO pin number to /sys/class/gpio/unexport + * Result: 0 = success, -1 = error + */ +int gpio_unexport(int pin) +{ + char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */ + ssize_t bytes; /* Used Buffer length */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Unexport GPIO pin %d\n", pin); + + close(gpioFds[pin]); + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (fd < 0) + { + perror("Could not unexport GPIO pin(open)!\n"); + return(-1); + } + + bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Could not unexport GPIO pin(write)!\n"); + return(-1); + } + + close(fd); + return(0); +} + +/* Set GPIO pin mode (input/output) + * Write GPIO pin number to /sys/class/gpioXX/direction + * Direction: 0 = input, 1 = output + * Result: 0 = success, -1 = error + */ +int gpio_direction(int pin, int dir) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Set GPIO direction for pin %d to %s\n", pin, (dir==INPUT) ? "INPUT":"OUTPUT"); + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/direction", pin); + fd = open(path, O_WRONLY); + if (fd < 0) + { + perror("Could not set mode for GPIO pin(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("Could not set mode for GPIO pin(write)!\n"); + return(-1); + } + + close(fd); + return(0); +} + +/* Read from GPIO pin + * Result: -1 = error, 0/1 = GPIO pin state + */ +int gpio_read(int pin) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + char c; + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + if (gpioFds[pin] < 0) + gpioFds[pin] = open(path, O_RDWR); + if (gpioFds[pin] < 0) + { + perror("Could not read from GPIO(open)!\n"); + return(-1); + } + + lseek(gpioFds [pin], 0L, SEEK_SET) ; + if (read(gpioFds[pin], &c, 1) < 0) + { + perror("Could not read from GPIO(read)!\n"); + return(-1); + } + + return (c == '0') ? LOW : HIGH; +} + +/* Write to GPIO pin + * Result: -1 = error, 0 = success + */ +int gpio_write(int pin, int value) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int res; /* Result from write()*/ + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + if (gpioFds[pin] < 0) + gpioFds[pin] = open(path, O_RDWR); + + if (gpioFds[pin] < 0) + { + perror("Could not write to GPIO(open)!\n"); + return(-1); + } + + switch (value) + { + case LOW : res = write(gpioFds[pin], "0\n", 2); break; + case HIGH: res = write(gpioFds[pin], "1\n", 2); break; + default: res = -1; break; + } + + if (res < 0) + { + perror("Could not write to GPIO(write)!\n"); + return(-1); + } + + return(0); +} + +/* Set GPIO pin edge detection + * 'r' (rising) + * 'f' (falling) + * 'b' (both) + */ +int gpio_edge(unsigned int pin, char edge) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/edge", pin); + + fd = open(path, O_WRONLY | O_NONBLOCK ); + if (fd < 0) + { + perror("Could not set GPIO edge detection(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; +} + +/* Wait for edge on GPIO pin + * timeout in milliseconds + * Result: <0: error, 0: poll() Timeout, + * 1: edge detected, GPIO pin reads "0" + * 2: edge detected, GPIO pin reads "1" + */ +int gpio_wait(unsigned int pin, int timeout) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + struct pollfd polldat[1]; /* Variable for poll() */ + char buf[MAX_STRBUF_SIZE]; /* Read buffer */ + int rc; /* Result */ + + /* Open GPIO pin */ + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_RDONLY | O_NONBLOCK ); + if (fd < 0) + { + perror("Could not wait for GPIO edge(open)!\n"); + return(-1); + } + + /* prepare poll() */ + memset((void*)buf, 0, sizeof(buf)); + memset((void*)polldat, 0, sizeof(polldat)); + polldat[0].fd = fd; + polldat[0].events = POLLPRI; + + /* clear any existing detected edges before */ + lseek(fd, 0, SEEK_SET); + rc = read(fd, buf, MAX_STRBUF_SIZE - 1); + + rc = poll(polldat, 1, timeout); + if (rc < 0) + { /* poll() failed! */ + perror("Could not wait for GPIO edge(poll)!\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("Could not wait for GPIO edge(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..a5605cd 100644 --- a/src/linux_platform.h +++ b/src/linux_platform.h @@ -5,6 +5,12 @@ #include #include "knx/platform.h" +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 +49,11 @@ 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; + //memory uint8_t* getEepromBuffer(uint16_t size) override; void commitToEeprom() override; @@ -57,6 +68,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;