mirror of
https://github.com/thelsing/knx.git
synced 2025-03-31 01:17:31 +02:00
initial KNX RF S-Mode support
This commit is contained in:
parent
eaab7d7548
commit
fc0153e52a
knx-linux
src
arduino_platform.cpparduino_platform.h
knx
application_layer.cppapplication_layer.hbau.cppbau.hbau07B0.cppbau27B0.cppbau27B0.hbau57B0.cppbau_systemB.cppbau_systemB.hcemi_frame.cppcemi_frame.hdata_link_layer.cppdata_link_layer.hdevice_object.cppdevice_object.hinterface_object.hknx_types.hnetwork_layer.cppplatform.hproperty_types.hrf_data_link_layer.cpprf_data_link_layer.hrf_medium_object.cpprf_medium_object.hrf_physical_layer.cpprf_physical_layer.h
knx_facade.cppknx_facade.hlinux_platform.cpplinux_platform.h@ -9,7 +9,8 @@ add_executable(knx-linux
|
||||
../src/knx/association_table_object.cpp
|
||||
../src/knx/bau.cpp
|
||||
../src/knx/bau07B0.cpp
|
||||
../src/knx/bau57B0.cpp
|
||||
../src/knx/bau27B0.cpp
|
||||
../src/knx/bau57B0.cpp
|
||||
../src/knx/bau_systemB.cpp
|
||||
../src/knx/bits.cpp
|
||||
../src/knx/cemi_frame.cpp
|
||||
@ -23,12 +24,50 @@ add_executable(knx-linux
|
||||
../src/knx/memory.cpp
|
||||
../src/knx/network_layer.cpp
|
||||
../src/knx/npdu.cpp
|
||||
../src/knx/table_object.cpp
|
||||
../src/knx/rf_physical_layer.cpp
|
||||
../src/knx/rf_data_link_layer.cpp
|
||||
../src/knx/rf_medium_object.cpp
|
||||
../src/knx/table_object.cpp
|
||||
../src/knx/tpdu.cpp
|
||||
../src/knx/tpuart_data_link_layer.cpp
|
||||
../src/knx/transport_layer.cpp
|
||||
../src/knx/platform.cpp
|
||||
../src/knx/address_table_object.h
|
||||
../src/knx/apdu.h
|
||||
../src/knx/application_layer.h
|
||||
../src/knx/application_program_object.h
|
||||
../src/knx/association_table_object.h
|
||||
../src/knx/bau.h
|
||||
../src/knx/bau07B0.h
|
||||
../src/knx/bau27B0.h
|
||||
../src/knx/bau57B0.h
|
||||
../src/knx/bau_systemB.h
|
||||
../src/knx/bits.h
|
||||
../src/knx/cemi_frame.h
|
||||
../src/knx/data_link_layer.h
|
||||
../src/knx/device_object.h
|
||||
../src/knx/group_object.h
|
||||
../src/knx/group_object_table_object.h
|
||||
../src/knx/interface_object.h
|
||||
../src/knx/ip_data_link_layer.h
|
||||
../src/knx/ip_parameter_object.h
|
||||
../src/knx/memory.h
|
||||
../src/knx/network_layer.h
|
||||
../src/knx/npdu.h
|
||||
../src/knx/rf_physical_layer.h
|
||||
../src/knx/rf_data_link_layer.h
|
||||
../src/knx/rf_medium_object.h
|
||||
../src/knx/table_object.h
|
||||
../src/knx/tpdu.h
|
||||
../src/knx/tpuart_data_link_layer.h
|
||||
../src/knx/transport_layer.h
|
||||
../src/knx/platform.h
|
||||
main.cpp
|
||||
../src/linux_platform.h
|
||||
../src/knx_facade.h
|
||||
../src/knx/dptconvert.h
|
||||
../src/knx/knx_value.h
|
||||
../src/knx/dpt.h
|
||||
../src/linux_platform.cpp
|
||||
../src/knx_facade.cpp
|
||||
../src/knx/dptconvert.cpp
|
||||
@ -38,3 +77,5 @@ target_link_libraries(knx-linux "${LIBRARIES_FROM_REFERENCES}")
|
||||
include_directories(../src)
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
|
||||
set_property(TARGET knx-linux PROPERTY CXX_STANDARD 11)
|
||||
install(TARGETS knx-linux RUNTIME DESTINATION /tmp)
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "knx_facade.h"
|
||||
#include "knx/bau57B0.h"
|
||||
//#include "knx/bau2705.h"
|
||||
#include "knx/group_object_table_object.h"
|
||||
#include "knx/bits.h"
|
||||
#include <time.h>
|
||||
@ -7,6 +8,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
KnxFacade<LinuxPlatform, Bau57B0> knx;
|
||||
//KnxFacade<LinuxPlatform, Bau2705> knx;
|
||||
|
||||
long lastsend = 0;
|
||||
|
||||
@ -94,6 +96,6 @@ int main(int argc, char **argv)
|
||||
knx.loop();
|
||||
if(knx.configured())
|
||||
appLoop();
|
||||
delay(100);
|
||||
delayMicroseconds(1000);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include <knx/bits.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
Stream* ArduinoPlatform::SerialDebug = &Serial;
|
||||
|
||||
@ -60,6 +61,7 @@ void ArduinoPlatform::closeMultiCast()
|
||||
bool ArduinoPlatform::sendBytes(uint8_t * buffer, uint16_t len)
|
||||
{
|
||||
//not needed
|
||||
return false;
|
||||
}
|
||||
|
||||
int ArduinoPlatform::readBytes(uint8_t * buffer, uint16_t maxLen)
|
||||
@ -137,6 +139,44 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length)
|
||||
return length;
|
||||
}
|
||||
|
||||
void ArduinoPlatform::setupSpi()
|
||||
{
|
||||
SPI.begin();
|
||||
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
}
|
||||
|
||||
void ArduinoPlatform::closeSpi()
|
||||
{
|
||||
SPI.endTransaction();
|
||||
SPI.end();
|
||||
}
|
||||
|
||||
int ArduinoPlatform::readWriteSpi(uint8_t *data, size_t len)
|
||||
{
|
||||
SPI.transfer(data, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ArduinoPlatform::setupGpio(uint32_t dwPin, uint32_t dwMode)
|
||||
{
|
||||
pinMode(dwPin, dwMode);
|
||||
}
|
||||
|
||||
void ArduinoPlatform::closeGpio(uint32_t dwPin)
|
||||
{
|
||||
// not used
|
||||
}
|
||||
|
||||
void ArduinoPlatform::writeGpio(uint32_t dwPin, uint32_t dwVal)
|
||||
{
|
||||
digitalWrite(dwPin, dwVal);
|
||||
}
|
||||
|
||||
uint32_t ArduinoPlatform::readGpio(uint32_t dwPin)
|
||||
{
|
||||
return digitalRead(dwPin);
|
||||
}
|
||||
|
||||
void print(const char* s)
|
||||
{
|
||||
ArduinoPlatform::SerialDebug->print(s);
|
||||
|
@ -35,6 +35,16 @@ class ArduinoPlatform : public Platform
|
||||
virtual int readUart();
|
||||
virtual size_t readBytesUart(uint8_t* buffer, size_t length);
|
||||
|
||||
//spi
|
||||
void setupSpi() override;
|
||||
void closeSpi() override;
|
||||
int readWriteSpi (uint8_t *data, size_t len) override;
|
||||
|
||||
virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) override;
|
||||
virtual void closeGpio(uint32_t dwPin) override;
|
||||
virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) override;
|
||||
virtual uint32_t readGpio(uint32_t dwPin) override;
|
||||
|
||||
static Stream* SerialDebug;
|
||||
|
||||
protected:
|
||||
|
@ -90,8 +90,11 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr
|
||||
_bau.individualAddressReadAppLayerConfirm(hopType, apdu.frame().sourceAddress());
|
||||
break;
|
||||
case IndividualAddressSerialNumberRead:
|
||||
_bau.individualAddressSerialNumberReadIndication(hopType, data + 1);
|
||||
{
|
||||
uint8_t* knxSerialNumber = &data[1];
|
||||
_bau.individualAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber);
|
||||
break;
|
||||
}
|
||||
case IndividualAddressSerialNumberResponse:
|
||||
{
|
||||
uint16_t domainAddress;
|
||||
@ -102,9 +105,10 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr
|
||||
}
|
||||
case IndividualAddressSerialNumberWrite:
|
||||
{
|
||||
uint16_t newAddress;
|
||||
popWord(newAddress, data + 7);
|
||||
_bau.individualAddressSerialNumberWriteIndication(hopType, data + 1, newAddress);
|
||||
uint8_t* knxSerialNumber = &data[1];
|
||||
uint16_t newIndividualAddress;
|
||||
popWord(newIndividualAddress, &data[7]);
|
||||
_bau.individualAddressSerialNumberWriteIndication(priority, hopType, newIndividualAddress, knxSerialNumber);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -150,7 +154,40 @@ void ApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, P
|
||||
|
||||
void ApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
|
||||
{
|
||||
|
||||
uint8_t* data = apdu.data();
|
||||
switch (apdu.type())
|
||||
{
|
||||
// TODO: testInfo could be of any length
|
||||
case SystemNetworkParameterRead:
|
||||
{
|
||||
uint16_t objectType;
|
||||
uint16_t propertyId;
|
||||
uint8_t testInfo[2];
|
||||
popWord(objectType, data + 1);
|
||||
popWord(propertyId, data + 3);
|
||||
popByte(testInfo[0], data + 4);
|
||||
popByte(testInfo[1], data + 5);
|
||||
propertyId = (propertyId >> 4) & 0x0FFF;;
|
||||
testInfo[0] &= 0x0F;
|
||||
_bau.systemNetworkParameterReadIndication(priority, hopType, objectType, propertyId, testInfo, sizeof(testInfo));
|
||||
break;
|
||||
}
|
||||
case DomainAddressSerialNumberWrite:
|
||||
{
|
||||
uint8_t* knxSerialNumber = &data[1];
|
||||
uint8_t* domainAddress = &data[7];
|
||||
_bau.domainAddressSerialNumberWriteIndication(priority, hopType, domainAddress, knxSerialNumber);
|
||||
break;
|
||||
}
|
||||
case DomainAddressSerialNumberRead:
|
||||
{
|
||||
uint8_t* knxSerialNumber = &data[1];
|
||||
_bau.domainAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status)
|
||||
@ -356,6 +393,65 @@ void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountTy
|
||||
individualSend(ack, hopType, priority, _connectedTsap, apdu);
|
||||
}
|
||||
|
||||
//TODO: ApplicationLayer::systemNetworkParameterReadRequest()
|
||||
void ApplicationLayer::systemNetworkParameterReadResponse(Priority priority, HopCountType hopType,
|
||||
uint16_t objectType, uint16_t propertyId,
|
||||
uint8_t* testInfo, uint16_t testInfoLength,
|
||||
uint8_t* testResult, uint16_t testResultLength)
|
||||
{
|
||||
CemiFrame frame(testInfoLength + testResultLength + 3 + 1); // PID and testInfo share an octet (+3) and +1 for APCI byte(?)
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(SystemNetworkParameterResponse);
|
||||
uint8_t* data = apdu.data() + 1;
|
||||
|
||||
pushWord(objectType, data);
|
||||
pushWord((propertyId << 4) & 0xFFF0, data + 2); // Reserved bits for test_info are always 0
|
||||
uint8_t* pData = pushByteArray(&testInfo[1], testInfoLength - 1, data + 4); // TODO: upper reserved bits (testInfo + 0) have to put into the lower bits of data + 3
|
||||
memcpy(pData, testResult, testResultLength);
|
||||
|
||||
//apdu.printPDU();
|
||||
|
||||
_transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
|
||||
}
|
||||
|
||||
//TODO: ApplicationLayer::domainAddressSerialNumberWriteRequest()
|
||||
//TODO: ApplicationLayer::domainAddressSerialNumberReadRequest()
|
||||
void ApplicationLayer::domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber)
|
||||
{
|
||||
CemiFrame frame(13);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(DomainAddressSerialNumberResponse);
|
||||
|
||||
uint8_t* data = apdu.data() + 1;
|
||||
|
||||
memcpy(data, knxSerialNumber, 6);
|
||||
memcpy(data + 6, rfDoA, 6);
|
||||
|
||||
//apdu.printPDU();
|
||||
|
||||
_transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
|
||||
}
|
||||
|
||||
//TODO: ApplicationLayer::IndividualAddressSerialNumberWriteRequest()
|
||||
//TODO: ApplicationLayer::IndividualAddressSerialNumberReadRequest()
|
||||
void ApplicationLayer::IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber)
|
||||
{
|
||||
CemiFrame frame(13);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(IndividualAddressSerialNumberResponse);
|
||||
|
||||
uint8_t* data = apdu.data() + 1;
|
||||
|
||||
memcpy(data, knxSerialNumber, 6);
|
||||
memcpy(data + 6, rfDoA, 6);
|
||||
|
||||
//apdu.printPDU();
|
||||
|
||||
_transportLayer->dataBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
|
||||
}
|
||||
|
||||
void ApplicationLayer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex)
|
||||
{
|
||||
|
@ -129,6 +129,14 @@ class ApplicationLayer
|
||||
void authorizeResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
|
||||
void keyWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level, uint32_t key);
|
||||
void keyWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
|
||||
|
||||
void systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, uint16_t objectType,
|
||||
uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength,
|
||||
uint8_t* testResult, uint16_t testResultLength);
|
||||
void domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber);
|
||||
void IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber);
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
|
@ -52,7 +52,7 @@ void BusAccessUnit::individualAddressSerialNumberReadLocalConfirm(AckType ack, H
|
||||
{
|
||||
}
|
||||
|
||||
void BusAccessUnit::individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t * serialNumber)
|
||||
void BusAccessUnit::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
|
||||
{
|
||||
}
|
||||
|
||||
@ -68,7 +68,8 @@ void BusAccessUnit::individualAddressSerialNumberWriteLocalConfirm(AckType ack,
|
||||
{
|
||||
}
|
||||
|
||||
void BusAccessUnit::individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t * serialNumber, uint16_t newaddress)
|
||||
void BusAccessUnit::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
|
||||
uint8_t* knxSerialNumber)
|
||||
{
|
||||
}
|
||||
|
||||
@ -239,4 +240,22 @@ void BusAccessUnit::keyWriteAppLayerConfirm(Priority priority, HopCountType hopT
|
||||
|
||||
void BusAccessUnit::connectConfirm(uint16_t destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -25,14 +25,15 @@ class BusAccessUnit
|
||||
virtual void individualAddressReadAppLayerConfirm(HopCountType hopType, uint16_t individualAddress);
|
||||
virtual void individualAddressSerialNumberReadLocalConfirm(AckType ack, HopCountType hopType,
|
||||
uint8_t* serialNumber, bool status);
|
||||
virtual void individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t* serialNumber);
|
||||
virtual void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
|
||||
virtual void individualAddressSerialNumberReadResponseConfirm(AckType ack, HopCountType hopType,
|
||||
uint8_t* serialNumber, uint16_t domainAddress, bool status);
|
||||
virtual void individualAddressSerialNumberReadAppLayerConfirm(HopCountType hopType, uint8_t* serialNumber,
|
||||
uint16_t individualAddress, uint16_t domainAddress);
|
||||
virtual void individualAddressSerialNumberWriteLocalConfirm(AckType ack, HopCountType hopType, uint8_t* serialNumber,
|
||||
uint16_t newaddress, bool status);
|
||||
virtual void individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t* serialNumber, uint16_t newaddress);
|
||||
virtual void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
|
||||
uint8_t* knxSerialNumber);
|
||||
virtual void deviceDescriptorReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t descriptorType, bool status);
|
||||
virtual void deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, uint8_t descriptorType);
|
||||
@ -108,5 +109,12 @@ class BusAccessUnit
|
||||
virtual void keyWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level,
|
||||
bool status);
|
||||
virtual void keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
|
||||
virtual void connectConfirm(uint16_t destination);
|
||||
virtual bool connectConfirm(uint16_t destination);
|
||||
virtual void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
|
||||
uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength);
|
||||
|
||||
virtual void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber);
|
||||
|
||||
virtual void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "bau07B0.h"
|
||||
#include "bits.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@ -9,6 +10,11 @@ Bau07B0::Bau07B0(Platform& platform)
|
||||
_dlLayer(_deviceObj, _addrTable, _netLayer, _platform)
|
||||
{
|
||||
_netLayer.dataLinkLayer(_dlLayer);
|
||||
|
||||
// Set Mask Version in Device Object depending on the BAU
|
||||
uint16_t maskVersion;
|
||||
popWord(maskVersion, _descriptor);
|
||||
_deviceObj.maskVersion(maskVersion);
|
||||
}
|
||||
|
||||
InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx)
|
||||
|
103
src/knx/bau27B0.cpp
Normal file
103
src/knx/bau27B0.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "bau27B0.h"
|
||||
#include "bits.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Bau27B0::Bau27B0(Platform& platform)
|
||||
: BauSystemB(platform),
|
||||
_dlLayer(_deviceObj, _rfMediumObj, _addrTable, _netLayer, _platform)
|
||||
{
|
||||
_netLayer.dataLinkLayer(_dlLayer);
|
||||
_memory.addSaveRestore(&_rfMediumObj);
|
||||
|
||||
// Set Mask Version in Device Object depending on the BAU
|
||||
uint16_t maskVersion;
|
||||
popWord(maskVersion, _descriptor);
|
||||
_deviceObj.maskVersion(maskVersion);
|
||||
}
|
||||
|
||||
// see KNX AN160 p.74 for mask 27B0
|
||||
InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0:
|
||||
return &_deviceObj;
|
||||
case 1:
|
||||
return &_addrTable;
|
||||
case 2:
|
||||
return &_assocTable;
|
||||
case 3:
|
||||
return &_groupObjTable;
|
||||
case 4:
|
||||
return &_appProgram;
|
||||
case 5: // would be app_program 2
|
||||
return nullptr;
|
||||
case 6:
|
||||
return &_rfMediumObj;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* Bau27B0::descriptor()
|
||||
{
|
||||
return _descriptor;
|
||||
}
|
||||
|
||||
DataLinkLayer& Bau27B0::dataLinkLayer()
|
||||
{
|
||||
return _dlLayer;
|
||||
}
|
||||
|
||||
void Bau27B0::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber)
|
||||
{
|
||||
uint8_t curSerialNumber[6];
|
||||
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
|
||||
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
|
||||
|
||||
// If the received serial number matches our serial number
|
||||
// then store the received RF domain address in the RF medium object
|
||||
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
|
||||
_rfMediumObj.rfDomainAddress(rfDoA);
|
||||
}
|
||||
|
||||
void Bau27B0::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
|
||||
{
|
||||
uint8_t curSerialNumber[6];
|
||||
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
|
||||
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
|
||||
|
||||
// If the received serial number matches our serial number
|
||||
// then send a response with the current RF domain address stored in the RF medium object
|
||||
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
|
||||
_appLayer.domainAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
|
||||
}
|
||||
|
||||
void Bau27B0::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
|
||||
uint8_t* knxSerialNumber)
|
||||
{
|
||||
uint8_t curSerialNumber[6];
|
||||
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
|
||||
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
|
||||
|
||||
// If the received serial number matches our serial number
|
||||
// then store the received new individual address in the device object
|
||||
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
|
||||
_deviceObj.induvidualAddress(newIndividualAddress);
|
||||
}
|
||||
|
||||
void Bau27B0::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
|
||||
{
|
||||
uint8_t curSerialNumber[6];
|
||||
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
|
||||
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
|
||||
|
||||
// If the received serial number matches our serial number
|
||||
// then send a response with the current RF domain address stored in the RF medium object and the serial number
|
||||
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
|
||||
_appLayer.IndividualAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
|
||||
}
|
30
src/knx/bau27B0.h
Normal file
30
src/knx/bau27B0.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "bau_systemB.h"
|
||||
#include "rf_medium_object.h"
|
||||
#include "rf_physical_layer.h"
|
||||
#include "rf_data_link_layer.h"
|
||||
|
||||
class Bau27B0 : public BauSystemB
|
||||
{
|
||||
public:
|
||||
Bau27B0(Platform& platform);
|
||||
|
||||
protected:
|
||||
InterfaceObject* getInterfaceObject(uint8_t idx);
|
||||
uint8_t* descriptor();
|
||||
DataLinkLayer& dataLinkLayer();
|
||||
|
||||
private:
|
||||
RfDataLinkLayer _dlLayer;
|
||||
RfMediumObject _rfMediumObj;
|
||||
|
||||
uint8_t _descriptor[2] = {0x27, 0xB0};
|
||||
|
||||
void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber);
|
||||
void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
|
||||
void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
|
||||
uint8_t* knxSerialNumber);
|
||||
void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
#include "bau57B0.h"
|
||||
#include "bits.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@ -11,6 +12,11 @@ Bau57B0::Bau57B0(Platform& platform)
|
||||
{
|
||||
_netLayer.dataLinkLayer(_dlLayer);
|
||||
_memory.addSaveRestore(&_ipParameters);
|
||||
|
||||
// Set Mask Version in Device Object depending on the BAU
|
||||
uint16_t maskVersion;
|
||||
popWord(maskVersion, _descriptor);
|
||||
_deviceObj.maskVersion(maskVersion);
|
||||
}
|
||||
|
||||
InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx)
|
||||
|
@ -350,3 +350,36 @@ void BauSystemB::nextRestartState()
|
||||
}
|
||||
}
|
||||
|
||||
void BauSystemB::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
|
||||
uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength)
|
||||
{
|
||||
uint8_t knxSerialNumber[6];
|
||||
uint8_t operand;
|
||||
|
||||
popByte(operand, testInfo + 1); // First byte (+ 0) contains only 4 reserved bits (0)
|
||||
|
||||
// See KNX spec. 3.5.2 p.33 (Management Procedures: Procedures with A_SystemNetworkParameter_Read)
|
||||
switch(operand)
|
||||
{
|
||||
case 0x01: // NM_Read_SerialNumber_By_ProgrammingMode
|
||||
// Only send a reply if programming mode is on
|
||||
if (_deviceObj.progMode() && (objectType == OT_DEVICE) && (propertyId == PID_SERIAL_NUMBER))
|
||||
{
|
||||
// Send reply. testResult data is KNX serial number
|
||||
pushWord(_deviceObj.manufacturerId(), &knxSerialNumber[0]);
|
||||
pushInt(_deviceObj.bauNumber(), &knxSerialNumber[2]);
|
||||
_appLayer.systemNetworkParameterReadResponse(priority, hopType, objectType, propertyId,
|
||||
testInfo, testInfoLength, knxSerialNumber, sizeof(knxSerialNumber));
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // NM_Read_SerialNumber_By_ExFactoryState
|
||||
break;
|
||||
|
||||
case 0x03: // NM_Read_SerialNumber_By_PowerReset
|
||||
break;
|
||||
|
||||
case 0xFE: // Manufacturer specific use of A_SystemNetworkParameter_Read
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,8 @@ class BauSystemB : protected BusAccessUnit
|
||||
uint8_t* data, uint8_t dataLength) override;
|
||||
void groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType,
|
||||
uint8_t* data, uint8_t dataLength) override;
|
||||
void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
|
||||
uint16_t propertyId, uint8_t* testInfo, uint16_t testinfoLength);
|
||||
void connectConfirm(uint16_t tsap) override;
|
||||
|
||||
virtual InterfaceObject* getInterfaceObject(uint8_t idx) = 0;
|
||||
|
@ -3,18 +3,88 @@
|
||||
#include "string.h"
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
|
@ -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
|
||||
};
|
@ -14,15 +14,20 @@ DataLinkLayer::DataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab,
|
||||
|
||||
void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, FrameFormat format, Priority priority, NPDU& npdu)
|
||||
{
|
||||
sendTelegram(npdu, ack, destinationAddr, addrType, format, priority);
|
||||
// Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium)
|
||||
// The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL)
|
||||
// See 3.2.5 p.22
|
||||
sendTelegram(npdu, ack, destinationAddr, addrType, format, priority, Broadcast);
|
||||
}
|
||||
|
||||
void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu)
|
||||
{
|
||||
sendTelegram(npdu, ack, 0, GroupAddress, format, priority);
|
||||
// System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium)
|
||||
// See 3.2.5 p.22
|
||||
sendTelegram(npdu, ack, 0, GroupAddress, format, priority, SysBroadcast);
|
||||
}
|
||||
|
||||
void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success)
|
||||
void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success)
|
||||
{
|
||||
AckType ack = frame.ack();
|
||||
AddressType addrType = frame.addressType();
|
||||
@ -49,12 +54,18 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
|
||||
Priority priority = frame.priority();
|
||||
NPDU& npdu = frame.npdu();
|
||||
uint16_t ownAddr = _deviceObject.induvidualAddress();
|
||||
SystemBroadcast systemBroadcast = frame.systemBroadcast();
|
||||
|
||||
if (source == ownAddr)
|
||||
_deviceObject.induvidualAddressDuplication(true);
|
||||
|
||||
if (addrType == GroupAddress && destination == 0)
|
||||
_networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
|
||||
{
|
||||
if (systemBroadcast == SysBroadcast)
|
||||
_networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
|
||||
else
|
||||
_networkLayer.dataIndication(ack, addrType, destination, type, npdu, priority, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (addrType == InduvidualAddress && destination != _deviceObject.induvidualAddress())
|
||||
@ -73,7 +84,7 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
|
||||
}
|
||||
}
|
||||
|
||||
bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority)
|
||||
bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast)
|
||||
{
|
||||
CemiFrame& frame = npdu.frame();
|
||||
frame.messageCode(L_data_ind);
|
||||
@ -82,6 +93,7 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA
|
||||
frame.addressType(addrType);
|
||||
frame.priority(priority);
|
||||
frame.repetition(RepititionAllowed);
|
||||
frame.systemBroadcast(systemBroadcast);
|
||||
|
||||
if (npdu.octetCount() <= 15)
|
||||
frame.frameType(StandardFrame);
|
||||
|
@ -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;
|
||||
|
@ -11,7 +11,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
|
||||
break;
|
||||
case PID_SERIAL_NUMBER:
|
||||
pushWord(_manufacturerId, data);
|
||||
pushInt(_bauNumber, data);
|
||||
pushInt(_bauNumber, data + 2);
|
||||
break;
|
||||
case PID_MANUFACTURER_ID:
|
||||
pushWord(_manufacturerId, data);
|
||||
@ -55,8 +55,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
|
||||
break;
|
||||
}
|
||||
case PID_DEVICE_DESCRIPTOR:
|
||||
data[0] = 0x57;
|
||||
data[1] = 0xB0;
|
||||
pushWord(_maskVersion, data);
|
||||
break;
|
||||
default:
|
||||
count = 0;
|
||||
@ -254,6 +253,16 @@ void DeviceObject::version(uint16_t value)
|
||||
_version = value;
|
||||
}
|
||||
|
||||
uint16_t DeviceObject::maskVersion()
|
||||
{
|
||||
return _maskVersion;
|
||||
}
|
||||
|
||||
void DeviceObject::maskVersion(uint16_t value)
|
||||
{
|
||||
_maskVersion = value;
|
||||
}
|
||||
|
||||
static PropertyDescription _propertyDescriptions[] =
|
||||
{
|
||||
{ PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 },
|
||||
|
@ -35,6 +35,8 @@ public:
|
||||
void hardwareType(const uint8_t* value);
|
||||
uint16_t version();
|
||||
void version(uint16_t value);
|
||||
uint16_t maskVersion();
|
||||
void maskVersion(uint16_t value);
|
||||
protected:
|
||||
uint8_t propertyCount();
|
||||
PropertyDescription* propertyDescriptions();
|
||||
@ -44,8 +46,9 @@ private:
|
||||
uint8_t _prgMode = 0;
|
||||
uint16_t _ownAddress = 0;
|
||||
uint16_t _manufacturerId = 0xfa; //Default to KNXA
|
||||
uint32_t _bauNumber = 0;
|
||||
uint32_t _bauNumber = 0xaabbccdd;
|
||||
char _orderNumber[10] = "";
|
||||
uint8_t _hardwareType[6] = { 0, 0, 0, 0, 0, 0};
|
||||
uint16_t _version = 0;
|
||||
uint16_t _maskVersion = 0x0000;
|
||||
};
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -71,12 +71,30 @@ enum TpduType
|
||||
|
||||
enum ApduType
|
||||
{
|
||||
// Application Layer services on Multicast Communication Mode
|
||||
GroupValueRead = 0x000,
|
||||
GroupValueResponse = 0x040,
|
||||
GroupValueWrite = 0x080,
|
||||
|
||||
// Application Layer services on Broadcast Communication Mode
|
||||
IndividualAddressWrite = 0x0c0,
|
||||
IndividualAddressRead = 0x100,
|
||||
IndividualAddressResponse = 0x140,
|
||||
IndividualAddressSerialNumberRead = 0x3dc,
|
||||
IndividualAddressSerialNumberResponse = 0x3dd,
|
||||
IndividualAddressSerialNumberWrite = 0x3de,
|
||||
|
||||
// Application Layer Services on System Broadcast communication mode
|
||||
SystemNetworkParameterRead = 0x1c8,
|
||||
SystemNetworkParameterResponse = 0x1c9,
|
||||
SystemNetworkParameterWrite = 0x1ca,
|
||||
// Open media specific Application Layer Services on System Broadcast communication mode
|
||||
DomainAddressSerialNumberRead = 0x3ec,
|
||||
DomainAddressSerialNumberResponse = 0x3ed,
|
||||
DomainAddressSerialNumberWrite = 0x3ee,
|
||||
|
||||
// Application Layer Services on Point-to-point Connection-Oriented Communication Mode (mandatory)
|
||||
// Application Layer Services on Point-to-point Connectionless Communication Mode (either optional or mandatory)
|
||||
MemoryRead = 0x200,
|
||||
MemoryResponse = 0x240,
|
||||
MemoryWrite = 0x280,
|
||||
@ -97,7 +115,14 @@ enum ApduType
|
||||
PropertyValueWrite = 0x3d7,
|
||||
PropertyDescriptionRead = 0x3d8,
|
||||
PropertyDescriptionResponse = 0x3d9,
|
||||
IndividualAddressSerialNumberRead = 0x3dc,
|
||||
IndividualAddressSerialNumberResponse = 0x3dd,
|
||||
IndividualAddressSerialNumberWrite = 0x3de,
|
||||
};
|
||||
|
||||
enum KnxMediumType
|
||||
{
|
||||
KnxMediumType_TP1 = 0,
|
||||
KnxMediumType_PL = 1,
|
||||
KnxMediumType_RF = 2,
|
||||
KnxMediumType_TP0 = 3, // not supported anymore
|
||||
KnxMediumType_PL132 = 4, // not supported anymore
|
||||
KnxMediumType_IP = 5,
|
||||
};
|
@ -69,7 +69,7 @@ void NetworkLayer::dataConfirm(AckType ack, AddressType addressType, uint16_t de
|
||||
void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
|
||||
{
|
||||
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
|
||||
_transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu());
|
||||
_transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu());
|
||||
}
|
||||
|
||||
void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
|
||||
|
@ -29,6 +29,15 @@ class Platform
|
||||
virtual int readUart() = 0;
|
||||
virtual size_t readBytesUart(uint8_t* buffer, size_t length) = 0;
|
||||
|
||||
virtual void setupSpi() = 0;
|
||||
virtual void closeSpi() = 0;
|
||||
virtual int readWriteSpi (uint8_t *data, size_t len) = 0;
|
||||
|
||||
virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) = 0;
|
||||
virtual void closeGpio(uint32_t dwPin) = 0;
|
||||
virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) = 0;
|
||||
virtual uint32_t readGpio(uint32_t dwPin) = 0;
|
||||
|
||||
virtual uint8_t* getEepromBuffer(uint16_t size) = 0;
|
||||
virtual void commitToEeprom() = 0;
|
||||
|
||||
|
@ -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,
|
||||
|
365
src/knx/rf_data_link_layer.cpp
Normal file
365
src/knx/rf_data_link_layer.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
#include "rf_physical_layer.h"
|
||||
#include "rf_data_link_layer.h"
|
||||
|
||||
#include "bits.h"
|
||||
#include "platform.h"
|
||||
#include "device_object.h"
|
||||
#include "address_table_object.h"
|
||||
#include "rf_medium_object.h"
|
||||
#include "cemi_frame.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
58
src/knx/rf_data_link_layer.h
Normal file
58
src/knx/rf_data_link_layer.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
};
|
128
src/knx/rf_medium_object.cpp
Normal file
128
src/knx/rf_medium_object.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include <cstring>
|
||||
#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;
|
||||
}
|
27
src/knx/rf_medium_object.h
Normal file
27
src/knx/rf_medium_object.h
Normal file
@ -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,};
|
||||
|
||||
|
||||
};
|
799
src/knx/rf_physical_layer.cpp
Normal file
799
src/knx/rf_physical_layer.cpp
Normal file
@ -0,0 +1,799 @@
|
||||
#include "rf_physical_layer.h"
|
||||
#include "rf_data_link_layer.h"
|
||||
|
||||
#include "bits.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MIN(a, b) ((a < b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a > b) ? (a) : (b))
|
||||
#define ABS(x) ((x > 0) ? (x) : (-x))
|
||||
|
||||
// Table for encoding 4-bit data into a 8-bit Manchester encoding.
|
||||
const uint8_t RfPhysicalLayer::manchEncodeTab[16] = {0xAA, // 0x0 Manchester encoded
|
||||
0xA9, // 0x1 Manchester encoded
|
||||
0xA6, // 0x2 Manchester encoded
|
||||
0xA5, // 0x3 Manchester encoded
|
||||
0x9A, // 0x4 Manchester encoded
|
||||
0x99, // 0x5 Manchester encoded
|
||||
0x96, // 0x6 Manchester encoded
|
||||
0x95, // 0x7 Manchester encoded
|
||||
0x6A, // 0x8 Manchester encoded
|
||||
0x69, // 0x9 Manchester encoded
|
||||
0x66, // 0xA Manchester encoded
|
||||
0x65, // 0xB Manchester encoded
|
||||
0x5A, // 0xC Manchester encoded
|
||||
0x59, // 0xD Manchester encoded
|
||||
0x56, // 0xE Manchester encoded
|
||||
0x55}; // 0xF Manchester encoded
|
||||
|
||||
// Table for decoding 4-bit Manchester encoded data into 2-bit
|
||||
// data. 0xFF indicates invalid Manchester encoding
|
||||
const uint8_t RfPhysicalLayer::manchDecodeTab[16] = {0xFF, // Manchester encoded 0x0 decoded
|
||||
0xFF, // Manchester encoded 0x1 decoded
|
||||
0xFF, // Manchester encoded 0x2 decoded
|
||||
0xFF, // Manchester encoded 0x3 decoded
|
||||
0xFF, // Manchester encoded 0x4 decoded
|
||||
0x03, // Manchester encoded 0x5 decoded
|
||||
0x02, // Manchester encoded 0x6 decoded
|
||||
0xFF, // Manchester encoded 0x7 decoded
|
||||
0xFF, // Manchester encoded 0x8 decoded
|
||||
0x01, // Manchester encoded 0x9 decoded
|
||||
0x00, // Manchester encoded 0xA decoded
|
||||
0xFF, // Manchester encoded 0xB decoded
|
||||
0xFF, // Manchester encoded 0xC decoded
|
||||
0xFF, // Manchester encoded 0xD decoded
|
||||
0xFF, // Manchester encoded 0xE decoded
|
||||
0xFF};// Manchester encoded 0xF decoded
|
||||
|
||||
// Product = CC1101
|
||||
// Chip version = A (VERSION = 0x04)
|
||||
// Crystal accuracy = 10 ppm
|
||||
// X-tal frequency = 26 MHz
|
||||
// RF output power = + 10 dBm
|
||||
// RX filterbandwidth = 270 kHz
|
||||
// Deviation = 47 kHz
|
||||
// Datarate = 32.73 kBaud
|
||||
// Modulation = (0) 2-FSK
|
||||
// Manchester enable = (0) Manchester disabled
|
||||
// RF Frequency = 868.299866 MHz
|
||||
// Channel spacing = 199.951172 kHz
|
||||
// Channel number = 0
|
||||
// Optimization = -
|
||||
// Sync mode = (5) 15/16 + carrier-sense above threshold
|
||||
// Format of RX/TX data = (0) Normal mode, use FIFOs for RX and TX
|
||||
// CRC operation = (0) CRC disabled for TX and RX
|
||||
// Forward Error Correction = (0) FEC disabled
|
||||
// Length configuration = (0) Fixed length packets, length configured in PKTLEN register.
|
||||
// Packetlength = 255
|
||||
// Preamble count = (2) 4 bytes
|
||||
// Append status = 0
|
||||
// Address check = (0) No address check
|
||||
// FIFO autoflush = 0
|
||||
// Device address = 0
|
||||
// GDO0 signal selection = ( 6) Asserts when sync word has been sent / received, and de-asserts at the end of the packet
|
||||
// GDO2 signal selection = ( 0) Asserts when RX FiFO threshold
|
||||
const uint8_t RfPhysicalLayer::cc1101_2FSK_32_7_kb[CFG_REGISTER] = {
|
||||
0x00, // IOCFG2 GDO2 Output Pin Configuration
|
||||
0x2E, // IOCFG1 GDO1 Output Pin Configuration
|
||||
0x06, // IOCFG0 GDO0 Output Pin Configuration
|
||||
0x40, // FIFOTHR RX FIFO and TX FIFO Thresholds // 4 bytes in RX FIFO (2 bytes manchester encoded)
|
||||
0x76, // SYNC1 Sync Word
|
||||
0x96, // SYNC0 Sync Word
|
||||
0xFF, // PKTLEN Packet Length
|
||||
0x00, // PKTCTRL1 Packet Automation Control
|
||||
0x00, // PKTCTRL0 Packet Automation Control
|
||||
0x00, // ADDR Device Address
|
||||
0x00, // CHANNR Channel Number
|
||||
0x08, // FSCTRL1 Frequency Synthesizer Control
|
||||
0x00, // FSCTRL0 Frequency Synthesizer Control
|
||||
0x21, // FREQ2 Frequency Control Word
|
||||
0x65, // FREQ1 Frequency Control Word
|
||||
0x6A, // FREQ0 Frequency Control Word
|
||||
0x6A, // MDMCFG4 Modem Configuration
|
||||
0x4A, // MDMCFG3 Modem Configuration
|
||||
0x05, // MDMCFG2 Modem Configuration
|
||||
0x22, // MDMCFG1 Modem Configuration
|
||||
0xF8, // MDMCFG0 Modem Configuration
|
||||
0x47, // DEVIATN Modem Deviation Setting
|
||||
0x07, // MCSM2 Main Radio Control State Machine Configuration
|
||||
0x30, // MCSM1 Main Radio Control State Machine Configuration (IDLE after TX and RX)
|
||||
0x18, // MCSM0 Main Radio Control State Machine Configuration
|
||||
0x2E, // FOCCFG Frequency Offset Compensation Configuration
|
||||
0x6D, // BSCFG Bit Synchronization Configuration
|
||||
0x43, // AGCCTRL2 AGC Control 0x04, // AGCCTRL2 magn target 33dB vs 36dB (max LNA+LNA2 gain vs. ) (highest gain cannot be used vs. all gain settings)
|
||||
0x40, // AGCCTRL1 AGC Control 0x09, // AGCCTRL1 carrier sense threshold disabled vs. 7dB below magn target (LNA prio strat. 1 vs 0)
|
||||
0x91, // AGCCTRL0 AGC Control 0xB2, // AGCCTRL0 channel filter samples 16 vs.32
|
||||
0x87, // WOREVT1 High Byte Event0 Timeout
|
||||
0x6B, // WOREVT0 Low Byte Event0 Timeout
|
||||
0xFB, // WORCTRL Wake On Radio Control
|
||||
0xB6, // FREND1 Front End RX Configuration
|
||||
0x10, // FREND0 Front End TX Configuration
|
||||
0xE9, // FSCAL3 Frequency Synthesizer Calibration 0xEA, // FSCAL3
|
||||
0x2A, // FSCAL2 Frequency Synthesizer Calibration
|
||||
0x00, // FSCAL1 Frequency Synthesizer Calibration
|
||||
0x1F, // FSCAL0 Frequency Synthesizer Calibration
|
||||
0x41, // RCCTRL1 RC Oscillator Configuration
|
||||
0x00, // RCCTRL0 RC Oscillator Configuration
|
||||
0x59, // FSTEST Frequency Synthesizer Calibration Control
|
||||
0x7F, // PTEST Production Test
|
||||
0x3F, // AGCTEST AGC Test
|
||||
0x81, // TEST2 Various Test Settings
|
||||
0x35, // TEST1 Various Test Settings
|
||||
0x09 // TEST0 Various Test Settings
|
||||
};
|
||||
|
||||
//Patable index: -30 -20- -15 -10 0 5 7 10 dBm
|
||||
const uint8_t RfPhysicalLayer::paTablePower868[8] = {0x03,0x17,0x1D,0x26,0x50,0x86,0xCD,0xC0};
|
||||
|
||||
RfPhysicalLayer::RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform)
|
||||
: _rfDataLinkLayer(rfDataLinkLayer),
|
||||
_platform(platform)
|
||||
{
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::manchEncode(uint8_t *uncodedData, uint8_t *encodedData)
|
||||
{
|
||||
uint8_t data0, data1;
|
||||
|
||||
// - Shift to get 4-bit data values
|
||||
data1 = (((*uncodedData) >> 4) & 0x0F);
|
||||
data0 = ((*uncodedData) & 0x0F);
|
||||
|
||||
// - Perform Manchester encoding -
|
||||
*encodedData = (manchEncodeTab[data1]);
|
||||
*(encodedData + 1) = manchEncodeTab[data0];
|
||||
}
|
||||
|
||||
bool RfPhysicalLayer::manchDecode(uint8_t *encodedData, uint8_t *decodedData)
|
||||
{
|
||||
uint8_t data0, data1, data2, data3;
|
||||
|
||||
// - Shift to get 4 bit data and decode
|
||||
data3 = ((*encodedData >> 4) & 0x0F);
|
||||
data2 = ( *encodedData & 0x0F);
|
||||
data1 = ((*(encodedData + 1) >> 4) & 0x0F);
|
||||
data0 = ((*(encodedData + 1)) & 0x0F);
|
||||
|
||||
// Check for invalid Manchester encoding
|
||||
if ( (manchDecodeTab[data3] == 0xFF ) | (manchDecodeTab[data2] == 0xFF ) |
|
||||
(manchDecodeTab[data1] == 0xFF ) | (manchDecodeTab[data0] == 0xFF ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shift result into a byte
|
||||
*decodedData = (manchDecodeTab[data3] << 6) | (manchDecodeTab[data2] << 4) |
|
||||
(manchDecodeTab[data1] << 2) | manchDecodeTab[data0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int RfPhysicalLayer::crc16(uint8_t* buffer, int offset, int length)
|
||||
{
|
||||
// CRC-16-DNP
|
||||
// generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0
|
||||
int pn = 0x13d65; // 1 0011 1101 0110 0101
|
||||
|
||||
// for much data, using a lookup table would be a way faster CRC calculation
|
||||
int crc = 0;
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
int bite = buffer[i] & 0xff;
|
||||
for (int b = 8; b --> 0;) {
|
||||
bool bit = ((bite >> b) & 1) == 1;
|
||||
bool one = (crc >> 15 & 1) == 1;
|
||||
crc <<= 1;
|
||||
if (one ^ bit)
|
||||
crc ^= pn;
|
||||
}
|
||||
}
|
||||
return (~crc) & 0xffff;
|
||||
}
|
||||
|
||||
uint8_t RfPhysicalLayer::sIdle()
|
||||
{
|
||||
uint8_t marcState;
|
||||
uint32_t timeStart;
|
||||
|
||||
spiWriteStrobe(SIDLE); //sets to idle first. must be in
|
||||
|
||||
marcState = 0xFF; //set unknown/dummy state value
|
||||
timeStart = millis();
|
||||
while((marcState != MARCSTATE_IDLE) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x01 = sidle
|
||||
{
|
||||
marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
|
||||
}
|
||||
|
||||
//print("marcstate: 0x");
|
||||
//println(marcState, HEX);
|
||||
|
||||
if(marcState != MARCSTATE_IDLE)
|
||||
{
|
||||
println("Timeout when trying to set idle state.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t RfPhysicalLayer::sReceive()
|
||||
{
|
||||
uint8_t marcState;
|
||||
uint32_t timeStart;
|
||||
|
||||
spiWriteStrobe(SRX); //writes receive strobe (receive mode)
|
||||
|
||||
marcState = 0xFF; //set unknown/dummy state value
|
||||
timeStart = millis();
|
||||
while((marcState != MARCSTATE_RX) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x0D = RX
|
||||
{
|
||||
marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
|
||||
}
|
||||
|
||||
//print("marcstate: 0x");
|
||||
//println(marcState, HEX);
|
||||
|
||||
if(marcState != MARCSTATE_RX)
|
||||
{
|
||||
println("Timeout when trying to set receive state.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::spiWriteRegister(uint8_t spi_instr, uint8_t value)
|
||||
{
|
||||
uint8_t tbuf[2] = {0};
|
||||
tbuf[0] = spi_instr | WRITE_SINGLE_BYTE;
|
||||
tbuf[1] = value;
|
||||
uint8_t len = 2;
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
_platform.readWriteSpi(tbuf, len);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
}
|
||||
|
||||
uint8_t RfPhysicalLayer::spiReadRegister(uint8_t spi_instr)
|
||||
{
|
||||
uint8_t value;
|
||||
uint8_t rbuf[2] = {0};
|
||||
rbuf[0] = spi_instr | READ_SINGLE_BYTE;
|
||||
uint8_t len = 2;
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
_platform.readWriteSpi(rbuf, len);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
value = rbuf[1];
|
||||
//printf("SPI_arr_0: 0x%02X\n", rbuf[0]);
|
||||
//printf("SPI_arr_1: 0x%02X\n", rbuf[1]);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t RfPhysicalLayer::spiWriteStrobe(uint8_t spi_instr)
|
||||
{
|
||||
uint8_t tbuf[1] = {0};
|
||||
tbuf[0] = spi_instr;
|
||||
//printf("SPI_data: 0x%02X\n", tbuf[0]);
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
_platform.readWriteSpi(tbuf, 1);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
return tbuf[0];
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len)
|
||||
{
|
||||
uint8_t rbuf[len + 1];
|
||||
rbuf[0] = spi_instr | READ_BURST;
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
_platform.readWriteSpi(rbuf, len + 1);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
for (uint8_t i=0; i<len ;i++ )
|
||||
{
|
||||
pArr[i] = rbuf[i+1];
|
||||
//printf("SPI_arr_read: 0x%02X\n", pArr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::spiWriteBurst(uint8_t spi_instr, const uint8_t *pArr, uint8_t len)
|
||||
{
|
||||
uint8_t tbuf[len + 1];
|
||||
tbuf[0] = spi_instr | WRITE_BURST;
|
||||
for (uint8_t i=0; i<len ;i++ )
|
||||
{
|
||||
tbuf[i+1] = pArr[i];
|
||||
//printf("SPI_arr_write: 0x%02X\n", tbuf[i+1]);
|
||||
}
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
_platform.readWriteSpi(tbuf, len + 1);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::powerDownCC1101()
|
||||
{
|
||||
// Set IDLE state first
|
||||
sIdle();
|
||||
delayMicroseconds(100);
|
||||
// CC1101 Power Down
|
||||
spiWriteStrobe(SPWD);
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::setOutputPowerLevel(int8_t dBm)
|
||||
{
|
||||
uint8_t pa = 0xC0;
|
||||
|
||||
if (dBm <= -30) pa = 0x00;
|
||||
else if (dBm <= -20) pa = 0x01;
|
||||
else if (dBm <= -15) pa = 0x02;
|
||||
else if (dBm <= -10) pa = 0x03;
|
||||
else if (dBm <= 0) pa = 0x04;
|
||||
else if (dBm <= 5) pa = 0x05;
|
||||
else if (dBm <= 7) pa = 0x06;
|
||||
else if (dBm <= 10) pa = 0x07;
|
||||
|
||||
spiWriteRegister(FREND0, pa);
|
||||
}
|
||||
|
||||
bool RfPhysicalLayer::InitChip()
|
||||
{
|
||||
// Setup SPI and GPIOs
|
||||
_platform.setupSpi();
|
||||
_platform.setupGpio(GPIO_GDO2_PIN, INPUT);
|
||||
_platform.setupGpio(GPIO_GDO0_PIN, INPUT);
|
||||
_platform.setupGpio(SPI_SS_PIN, OUTPUT);
|
||||
|
||||
// Toggle chip select signal as described in CC11xx manual
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
delayMicroseconds(30);
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
delayMicroseconds(30);
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
delayMicroseconds(45);
|
||||
|
||||
// Send SRES command
|
||||
_platform.writeGpio(SPI_SS_PIN, LOW);
|
||||
delay(10); // Normally we would have to poll MISO here: while(_platform.readGpio(SPI_MISO_PIN));
|
||||
spiWriteStrobe(SRES);
|
||||
// Wait for chip to finish internal reset
|
||||
delay(10); // Normally we would have to poll MISO here: while(_platform.readGpio(SPI_MISO_PIN));
|
||||
_platform.writeGpio(SPI_SS_PIN, HIGH);
|
||||
|
||||
// Flush the FIFOs
|
||||
spiWriteStrobe(SFTX);
|
||||
delayMicroseconds(100);
|
||||
spiWriteStrobe(SFRX);
|
||||
delayMicroseconds(100);
|
||||
|
||||
uint8_t partnum = spiReadRegister(PARTNUM); //reads CC1101 partnumber;
|
||||
uint8_t version = spiReadRegister(VERSION); //reads CC1101 version number;
|
||||
|
||||
// Checks if valid chip ID is found. Usually 0x03 or 0x14. if not -> abort
|
||||
if(version == 0x00 || version == 0xFF)
|
||||
{
|
||||
println("No CC11xx found!");
|
||||
stopChip();
|
||||
return false;
|
||||
}
|
||||
|
||||
print("Partnumber: 0x");
|
||||
println(partnum, HEX);
|
||||
print("Version : 0x");
|
||||
println(version, HEX);
|
||||
|
||||
// Set modulation mode 2FSK, 32768kbit/s
|
||||
spiWriteBurst(WRITE_BURST,cc1101_2FSK_32_7_kb,CFG_REGISTER);
|
||||
|
||||
// Set PA table
|
||||
spiWriteBurst(PATABLE_BURST, paTablePower868, 8);
|
||||
|
||||
// Set ISM band to 868.3MHz
|
||||
spiWriteRegister(FREQ2,0x21);
|
||||
spiWriteRegister(FREQ1,0x65);
|
||||
spiWriteRegister(FREQ0,0x6A);
|
||||
|
||||
// Set channel 0 in ISM band
|
||||
spiWriteRegister(CHANNR, 0);
|
||||
|
||||
// Set PA to 0dBm as default
|
||||
setOutputPowerLevel(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::stopChip()
|
||||
{
|
||||
powerDownCC1101();
|
||||
|
||||
_platform.closeGpio(GPIO_GDO0_PIN);
|
||||
_platform.closeGpio(GPIO_GDO2_PIN);
|
||||
_platform.closeGpio(SPI_SS_PIN);
|
||||
|
||||
_platform.closeSpi();
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::showRegisterSettings()
|
||||
{
|
||||
uint8_t config_reg_verify[CFG_REGISTER];
|
||||
uint8_t Patable_verify[CFG_REGISTER];
|
||||
|
||||
spiReadBurst(READ_BURST,config_reg_verify,CFG_REGISTER); //reads all 47 config register from cc1101
|
||||
spiReadBurst(PATABLE_BURST,Patable_verify,8); //reads output power settings from cc1101
|
||||
|
||||
println("Config Register:");
|
||||
printHex("", config_reg_verify, CFG_REGISTER);
|
||||
|
||||
println("PaTable:");
|
||||
printHex("", Patable_verify, 8);
|
||||
}
|
||||
|
||||
uint16_t RfPhysicalLayer::packetSize (uint8_t lField)
|
||||
{
|
||||
uint16_t nrBytes;
|
||||
uint8_t nrBlocks;
|
||||
|
||||
// The 2 first blocks contains 25 bytes when excluding CRC and the L-field
|
||||
// The other blocks contains 16 bytes when excluding the CRC-fields
|
||||
// Less than 26 (15 + 10)
|
||||
if ( lField < 26 )
|
||||
nrBlocks = 2;
|
||||
else
|
||||
nrBlocks = (((lField - 26) / 16) + 3);
|
||||
|
||||
// Add all extra fields, excluding the CRC fields
|
||||
nrBytes = lField + 1;
|
||||
|
||||
// Add the CRC fields, each block has 2 CRC bytes
|
||||
nrBytes += (2 * nrBlocks);
|
||||
|
||||
return nrBytes;
|
||||
}
|
||||
|
||||
void RfPhysicalLayer::loop()
|
||||
{
|
||||
switch (_loopState)
|
||||
{
|
||||
case TX_START:
|
||||
{
|
||||
prevStatusGDO0 = 0;
|
||||
prevStatusGDO2 = 0;
|
||||
// Set sync word in TX mode
|
||||
// The same sync word is used in RX mode, but we use it in different way here:
|
||||
// Important: the TX FIFO must provide the last byte of the
|
||||
// sync word
|
||||
spiWriteRegister(SYNC1, 0x54);
|
||||
spiWriteRegister(SYNC0, 0x76);
|
||||
// Set TX FIFO threshold to 33 bytes
|
||||
spiWriteRegister(FIFOTHR, 0x47);
|
||||
// Set GDO2 to be TX FIFO threshold signal
|
||||
spiWriteRegister(IOCFG2, 0x02);
|
||||
// Set GDO0 to be packet transmitted signal
|
||||
spiWriteRegister(IOCFG0, 0x06);
|
||||
// Flush TX FIFO
|
||||
spiWriteStrobe(SFTX);
|
||||
|
||||
_rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength);
|
||||
|
||||
// Calculate total number of bytes in the KNX RF packet from L-field
|
||||
pktLen = packetSize(sendBuffer[0]);
|
||||
// Check for valid length
|
||||
if ((pktLen == 0) || (pktLen > 290))
|
||||
{
|
||||
println("TX packet length error!");
|
||||
break;
|
||||
}
|
||||
|
||||
// Manchester encoded data takes twice the space plus
|
||||
// 1 byte for postamble and 1 byte (LSB) of the synchronization word
|
||||
bytesLeft = (2 * pktLen) + 2;
|
||||
// Last byte of synchronization word
|
||||
buffer[0] = 0x96;
|
||||
// Manchester encode packet
|
||||
for (int i = 0; i < pktLen; i++)
|
||||
{
|
||||
manchEncode(&sendBuffer[i], &buffer[1 + i*2]);
|
||||
}
|
||||
// Append the postamble sequence
|
||||
buffer[1 + bytesLeft - 1] = 0x55;
|
||||
|
||||
// Fill TX FIFO
|
||||
pByteIndex = &buffer[0];
|
||||
// Set fixed packet length mode if less than 256 bytes to transmit
|
||||
if (bytesLeft < 256)
|
||||
{
|
||||
spiWriteRegister(PKTLEN, bytesLeft);
|
||||
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
|
||||
fixedLengthMode = true;
|
||||
}
|
||||
else // Else set infinite length mode
|
||||
{
|
||||
uint8_t fixedLength = bytesLeft % 256;
|
||||
spiWriteRegister(PKTLEN, fixedLength);
|
||||
spiWriteRegister(PKTCTRL0, 0x02);
|
||||
fixedLengthMode = false;
|
||||
}
|
||||
|
||||
uint8_t bytesToWrite = MIN(64, bytesLeft);
|
||||
spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
|
||||
pByteIndex += bytesToWrite;
|
||||
bytesLeft -= bytesToWrite;
|
||||
|
||||
// Enable transmission of packet
|
||||
spiWriteStrobe(STX);
|
||||
|
||||
_loopState = TX_ACTIVE;
|
||||
}
|
||||
// Fall through
|
||||
|
||||
case TX_ACTIVE:
|
||||
{
|
||||
// Check if we have an incomplete packet transmission
|
||||
if (syncStart && (millis() - packetStartTime > TX_PACKET_TIMEOUT))
|
||||
{
|
||||
println("TX packet timeout!");
|
||||
// Set transceiver to IDLE (no RX or TX)
|
||||
sIdle();
|
||||
_loopState = TX_END;
|
||||
break;
|
||||
}
|
||||
|
||||
// Detect falling edge 1->0 on GDO2
|
||||
statusGDO2 = _platform.readGpio(GPIO_GDO2_PIN);
|
||||
if(prevStatusGDO2 != statusGDO2)
|
||||
{
|
||||
prevStatusGDO2 = statusGDO2;
|
||||
|
||||
// Check if signal GDO2 is de-asserted (TX FIFO is below threshold of 33 bytes, i.e. TX FIFO is half full)
|
||||
if(statusGDO2 == 0)
|
||||
{
|
||||
// - TX FIFO half full detected (< 33 bytes)
|
||||
// Write data fragment to TX FIFO
|
||||
uint8_t bytesToWrite = MIN(64, bytesLeft);
|
||||
spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
|
||||
pByteIndex += bytesToWrite;
|
||||
bytesLeft -= bytesToWrite;
|
||||
|
||||
// Set fixed length mode if less than 256 left to transmit
|
||||
if ( (bytesLeft < (256 - 64)) && !fixedLengthMode )
|
||||
{
|
||||
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
|
||||
fixedLengthMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect falling edge 1->0 on GDO0
|
||||
statusGDO0 = _platform.readGpio(GPIO_GDO0_PIN);
|
||||
if(prevStatusGDO0 != statusGDO0)
|
||||
{
|
||||
prevStatusGDO0 = statusGDO0;
|
||||
|
||||
// If GDO0 is de-asserted: TX packet complete or TX FIFO underflow
|
||||
if (statusGDO0 == 0x00)
|
||||
{
|
||||
// There might be an TX FIFO underflow
|
||||
uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
|
||||
if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_TX_UNDERFLOW)
|
||||
{
|
||||
println("TX FIFO underflow!");
|
||||
// Set transceiver to IDLE (no RX or TX)
|
||||
sIdle();
|
||||
}
|
||||
_loopState = TX_END;
|
||||
}
|
||||
else
|
||||
{
|
||||
// GDO0 asserted because sync word was transmitted
|
||||
//println("TX Syncword!");
|
||||
// wait for TX_PACKET_TIMEOUT milliseconds
|
||||
// Complete packet must have been transmitted within this time
|
||||
packetStartTime = millis();
|
||||
syncStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_END:
|
||||
{
|
||||
// free buffer
|
||||
delete sendBuffer;
|
||||
// Go back to RX after TX
|
||||
_loopState = RX_START;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_START:
|
||||
{
|
||||
prevStatusGDO2 = 0;
|
||||
prevStatusGDO0 = 0;
|
||||
syncStart = false;
|
||||
packetStart = true;
|
||||
fixedLengthMode = false;
|
||||
pByteIndex = buffer;
|
||||
bytesLeft = 0;
|
||||
pktLen = 0;
|
||||
// Set sync word in RX mode
|
||||
// The same sync word is used in TX mode, but we use it in different way
|
||||
spiWriteRegister(SYNC1, 0x76);
|
||||
spiWriteRegister(SYNC0, 0x96);
|
||||
// Set GDO2 to be RX FIFO threshold signal
|
||||
spiWriteRegister(IOCFG2, 0x00);
|
||||
// Set GDO0 to be packet received signal
|
||||
spiWriteRegister(IOCFG0, 0x06);
|
||||
// Set RX FIFO threshold to 4 bytes
|
||||
spiWriteRegister(FIFOTHR, 0x40);
|
||||
// Set infinite pktlen mode
|
||||
spiWriteRegister(PKTCTRL0, 0x02);
|
||||
// Flush RX FIFO
|
||||
spiWriteStrobe(SFRX);
|
||||
// Start RX
|
||||
sReceive();
|
||||
_loopState = RX_ACTIVE;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_ACTIVE:
|
||||
{
|
||||
if (!_rfDataLinkLayer.isTxQueueEmpty() && !syncStart)
|
||||
{
|
||||
sIdle();
|
||||
_loopState = TX_START;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if we have an incomplete packet reception
|
||||
// This is related to CC1101 errata "Radio stays in RX state instead of entering RXFIFO_OVERFLOW state"
|
||||
if (syncStart && (millis() - packetStartTime > RX_PACKET_TIMEOUT))
|
||||
{
|
||||
println("RX packet timeout!");
|
||||
//uint8_t marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
|
||||
//print("marcstate: 0x");
|
||||
//println(marcState, HEX);
|
||||
sIdle();
|
||||
_loopState = RX_START;
|
||||
break;
|
||||
}
|
||||
|
||||
// Detect rising edge 0->1 on GDO2
|
||||
statusGDO2 = _platform.readGpio(GPIO_GDO2_PIN);
|
||||
if(prevStatusGDO2 != statusGDO2)
|
||||
{
|
||||
prevStatusGDO2 = statusGDO2;
|
||||
|
||||
// Check if signal GDO2 is asserted (RX FIFO is equal to or above threshold of 4 bytes)
|
||||
if(statusGDO2 == 1)
|
||||
{
|
||||
if (packetStart)
|
||||
{
|
||||
// - RX FIFO 4 bytes detected -
|
||||
// Calculate the total length of the packet, and set fixed mode if less
|
||||
// than 255 bytes to receive
|
||||
|
||||
// Read the 2 first bytes
|
||||
spiReadBurst(RXFIFO_BURST, pByteIndex, 2);
|
||||
|
||||
// Decode the L-field
|
||||
if (!manchDecode(&buffer[0], &packet[0]))
|
||||
{
|
||||
//println("Could not decode L-field: manchester code violation");
|
||||
_loopState = RX_START;
|
||||
break;
|
||||
}
|
||||
// Get bytes to receive from L-field, multiply by 2 because of manchester code
|
||||
pktLen = 2 * packetSize(packet[0]);
|
||||
|
||||
// - Length mode -
|
||||
if (pktLen < 256)
|
||||
{
|
||||
// Set fixed packet length mode is less than 256 bytes
|
||||
spiWriteRegister(PKTLEN, pktLen);
|
||||
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
|
||||
fixedLengthMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Infinite packet length mode is more than 255 bytes
|
||||
// Calculate the PKTLEN value
|
||||
uint8_t fixedLength = pktLen % 256;
|
||||
spiWriteRegister(PKTLEN, fixedLength);
|
||||
}
|
||||
|
||||
pByteIndex += 2;
|
||||
bytesLeft = pktLen - 2;
|
||||
|
||||
// Set RX FIFO threshold to 32 bytes
|
||||
packetStart = false;
|
||||
spiWriteRegister(FIFOTHR, 0x47);
|
||||
}
|
||||
else
|
||||
{
|
||||
// - RX FIFO Half Full detected -
|
||||
// Read out the RX FIFO and set fixed mode if less
|
||||
// than 255 bytes to receive
|
||||
|
||||
// - Length mode -
|
||||
// Set fixed packet length mode if less than 256 bytes
|
||||
if ((bytesLeft < 256 ) && !fixedLengthMode)
|
||||
{
|
||||
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
|
||||
fixedLengthMode = true;
|
||||
}
|
||||
|
||||
// Read out the RX FIFO
|
||||
// Do not empty the FIFO (See the CC110x or 2500 Errata Note)
|
||||
spiReadBurst(RXFIFO_BURST, pByteIndex, 32 - 1);
|
||||
|
||||
bytesLeft -= (32 - 1);
|
||||
pByteIndex += (32 - 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Detect falling edge 1->0 on GDO0
|
||||
statusGDO0 = _platform.readGpio(GPIO_GDO0_PIN);
|
||||
if(prevStatusGDO0 != statusGDO0)
|
||||
{
|
||||
prevStatusGDO0 = statusGDO0;
|
||||
|
||||
// If GDO0 is de-asserted: RX packet complete or RX FIFO overflow
|
||||
if (statusGDO0 == 0x00)
|
||||
{
|
||||
// There might be an RX FIFO overflow
|
||||
uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
|
||||
if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_RX_OVERFLOW)
|
||||
{
|
||||
println("RX FIFO overflow!");
|
||||
_loopState = RX_START;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if we are in the middle of the packet reception
|
||||
if (!packetStart)
|
||||
{
|
||||
// Complete packet received
|
||||
// Read out remaining bytes in the RX FIFO
|
||||
spiReadBurst(RXFIFO_BURST, pByteIndex, bytesLeft);
|
||||
_loopState = RX_END;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// GDO0 asserted because sync word was received and recognized
|
||||
//println("RX Syncword!");
|
||||
// wait for RX_PACKET_TIMEOUT milliseconds
|
||||
// Complete packet must have been received within this time
|
||||
packetStartTime = millis();
|
||||
syncStart = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_END:
|
||||
{
|
||||
uint16_t pLen = packetSize(packet[0]);
|
||||
// Decode the first block (always 10 bytes + 2 bytes CRC)
|
||||
bool decodeOk = true;
|
||||
for (uint16_t i = 1; i < pLen; i++)
|
||||
{
|
||||
// Check for manchester violation, abort if there is one
|
||||
if(!manchDecode(&buffer[i*2], &packet[i]))
|
||||
{
|
||||
println("Could not decode packet: manchester code violation");
|
||||
decodeOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (decodeOk)
|
||||
{
|
||||
_rfDataLinkLayer.frameBytesReceived(&packet[0], pLen);
|
||||
}
|
||||
|
||||
_loopState = RX_START;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
257
src/knx/rf_physical_layer.h
Normal file
257
src/knx/rf_physical_layer.h
Normal file
@ -0,0 +1,257 @@
|
||||
|
||||
#ifndef RF_PHYSICAL_LAYER_H
|
||||
#define RF_PHYSICAL_LAYER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
@ -3,15 +3,28 @@
|
||||
#include "knx/bits.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
KnxFacade<SamdPlatform, Bau07B0> knx;
|
||||
#define ICACHE_RAM_ATTR
|
||||
// predefined global instance for TP or RF
|
||||
#ifdef MEDIUM_TYPE
|
||||
#if MEDIUM_TYPE == 0
|
||||
KnxFacade<SamdPlatform, Bau07B0> knx;
|
||||
#elif MEDIUM_TYPE == 2
|
||||
KnxFacade<SamdPlatform, Bau27B0> 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<EspPlatform, Bau57B0> knx;
|
||||
// predefined global instance for IP only
|
||||
KnxFacade<EspPlatform, Bau57B0> knx;
|
||||
#elif ARDUINO_ARCH_ESP32
|
||||
//KnxFacade<Esp32Platform, Bau57B0> knx;
|
||||
KnxFacade<Esp32Platform, Bau57B0> knx;
|
||||
// predefined global instance for IP only
|
||||
KnxFacade<Esp32Platform, Bau57B0> knx;
|
||||
#elif __linux__
|
||||
#define ICACHE_RAM_ATTR
|
||||
// no predefined global instance
|
||||
#define ICACHE_RAM_ATTR
|
||||
#endif
|
||||
|
||||
#ifndef __linux__
|
||||
|
@ -3,19 +3,21 @@
|
||||
#include "knx/bits.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
#include "samd_platform.h"
|
||||
#include "knx/bau07B0.h"
|
||||
#include "samd_platform.h"
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/bau27B0.h"
|
||||
#elif ARDUINO_ARCH_ESP8266
|
||||
#include "esp_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#include "esp_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#elif ARDUINO_ARCH_ESP32
|
||||
#define LED_BUILTIN 13
|
||||
#include "esp32_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#define LED_BUILTIN 13
|
||||
#include "esp32_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#else
|
||||
#include "linux_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#define LED_BUILTIN 0
|
||||
#define LED_BUILTIN 0
|
||||
#include "linux_platform.h"
|
||||
#include "knx/bau57B0.h"
|
||||
#include "knx/bau27B0.h"
|
||||
#endif
|
||||
|
||||
void buttonUp();
|
||||
@ -297,11 +299,24 @@ template <class P, class B> class KnxFacade : private SaveRestore
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
extern KnxFacade<SamdPlatform, Bau07B0> knx;
|
||||
// predefined global instance for TP or RF
|
||||
#ifdef MEDIUM_TYPE
|
||||
#if MEDIUM_TYPE == 0
|
||||
extern KnxFacade<SamdPlatform, Bau07B0> knx;
|
||||
#elif MEDIUM_TYPE == 2
|
||||
extern KnxFacade<SamdPlatform, Bau27B0> 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<EspPlatform, Bau57B0> knx;
|
||||
// predefined global instance for IP only
|
||||
extern KnxFacade<EspPlatform, Bau57B0> knx;
|
||||
#elif ARDUINO_ARCH_ESP32
|
||||
extern KnxFacade<Esp32Platform, Bau57B0> knx;
|
||||
// predefined global instance for IP only
|
||||
extern KnxFacade<Esp32Platform, Bau57B0> knx;
|
||||
#elif __linux__
|
||||
// no predefined global instance
|
||||
#endif
|
||||
// no predefined global instance
|
||||
#endif
|
||||
|
@ -19,6 +19,11 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/ioctl.h> // Needed for SPI port
|
||||
#include <linux/spi/spidev.h> // Needed for SPI port
|
||||
#include <poll.h> // Needed for GPIO edge detection
|
||||
#include <sys/time.h> // Needed for delayMicroseconds()
|
||||
|
||||
#include "knx/device_object.h"
|
||||
#include "knx/address_table_object.h"
|
||||
#include "knx/association_table_object.h"
|
||||
@ -299,6 +304,74 @@ void LinuxPlatform::setupUart()
|
||||
{
|
||||
}
|
||||
|
||||
void LinuxPlatform::closeSpi()
|
||||
{
|
||||
close(_spiFd);
|
||||
printf ("SPI device closed.\r\n");
|
||||
}
|
||||
|
||||
int LinuxPlatform::readWriteSpi (uint8_t *data, size_t len)
|
||||
{
|
||||
uint16_t spiDelay = 0 ;
|
||||
uint32_t spiSpeed = 8000000; // 4 MHz SPI speed
|
||||
uint8_t spiBPW = 8; // Bits per word
|
||||
|
||||
struct spi_ioc_transfer spi ;
|
||||
|
||||
// Mentioned in spidev.h but not used in the original kernel documentation
|
||||
// test program )-:
|
||||
|
||||
memset (&spi, 0, sizeof (spi)) ;
|
||||
|
||||
spi.tx_buf = (uint64_t)data;
|
||||
spi.rx_buf = (uint64_t)data;
|
||||
spi.len = len;
|
||||
spi.delay_usecs = spiDelay;
|
||||
spi.speed_hz = spiSpeed;
|
||||
spi.bits_per_word = spiBPW;
|
||||
|
||||
return ioctl (_spiFd, SPI_IOC_MESSAGE(1), &spi) ;
|
||||
}
|
||||
|
||||
void LinuxPlatform::setupSpi()
|
||||
{
|
||||
if ((_spiFd = open ("/dev/spidev0.0", O_RDWR)) < 0)
|
||||
{
|
||||
printf ("ERROR: SPI setup failed! Could not open SPI device!\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set SPI parameters.
|
||||
int mode = 0; // Mode 0
|
||||
uint8_t spiBPW = 8; // Bits per word
|
||||
int speed = 8000000; // 4 MHz SPI speed
|
||||
|
||||
if (ioctl (_spiFd, SPI_IOC_WR_MODE, &mode) < 0)
|
||||
{
|
||||
printf ("ERROR: SPI Mode Change failure: %s\n", strerror (errno)) ;
|
||||
close(_spiFd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ioctl (_spiFd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0)
|
||||
{
|
||||
printf ("ERROR: SPI BPW Change failure: %s\n", strerror (errno)) ;
|
||||
close(_spiFd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ioctl (_spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)
|
||||
{
|
||||
printf ("ERROR: SPI Speed Change failure: %s\n", strerror (errno)) ;
|
||||
close(_spiFd);
|
||||
return;
|
||||
}
|
||||
|
||||
printf ("SPI device setup ok.\r\n");
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* On linux the memory addresses from malloc may be to big for usermermory_write.
|
||||
* So we allocate some memory at the beginning and use it for address table, group object table etc.
|
||||
@ -519,4 +592,326 @@ void LinuxPlatform::cmdLineArgs(int argc, char** argv)
|
||||
memcpy(_args, argv, argc * sizeof(char*));
|
||||
_args[argc] = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LinuxPlatform::setupGpio(uint32_t dwPin, uint32_t dwMode)
|
||||
{
|
||||
gpio_export(dwPin);
|
||||
gpio_direction(dwPin, dwMode);
|
||||
}
|
||||
|
||||
void LinuxPlatform::closeGpio(uint32_t dwPin)
|
||||
{
|
||||
gpio_unexport(dwPin);
|
||||
// Set direction to input always if we do not need the GPIO anymore? Unsure...
|
||||
//gpio_direction(dwPin, INPUT);
|
||||
}
|
||||
|
||||
void LinuxPlatform::writeGpio(uint32_t dwPin, uint32_t dwVal)
|
||||
{
|
||||
gpio_write(dwPin, dwVal);
|
||||
}
|
||||
|
||||
uint32_t LinuxPlatform::readGpio(uint32_t dwPin)
|
||||
{
|
||||
return gpio_read(dwPin);
|
||||
}
|
||||
|
||||
/* Datenpuffer fuer die GPIO-Funktionen */
|
||||
#define MAXBUFFER 100
|
||||
|
||||
/* GPIO-Pin aktivieren
|
||||
* Schreiben der Pinnummer nach /sys/class/gpio/export
|
||||
* Ergebnis: 0 = O.K., -1 = Fehler
|
||||
*/
|
||||
int gpio_export(int pin)
|
||||
{
|
||||
char buffer[MAXBUFFER]; /* Output Buffer */
|
||||
ssize_t bytes; /* Datensatzlaenge */
|
||||
int fd; /* Filedescriptor */
|
||||
int res; /* Ergebnis von write */
|
||||
|
||||
fd = open("/sys/class/gpio/export", O_WRONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Kann nicht auf export schreiben!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
|
||||
res = write(fd, buffer, bytes);
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
perror("Kann Pin nicht aktivieren (write)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
delay(100);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* GPIO-Pin deaktivieren
|
||||
* Schreiben der Pinnummer nach /sys/class/gpio/unexport
|
||||
* Ergebnis: 0 = O.K., -1 = Fehler
|
||||
*/
|
||||
int gpio_unexport(int pin)
|
||||
{
|
||||
char buffer[MAXBUFFER]; /* Output Buffer */
|
||||
ssize_t bytes; /* Datensatzlaenge */
|
||||
int fd; /* Filedescriptor */
|
||||
int res; /* Ergebnis von write */
|
||||
|
||||
fd = open("/sys/class/gpio/unexport", O_WRONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Kann nicht auf unexport schreiben!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
|
||||
res = write(fd, buffer, bytes);
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
perror("Kann Pin nicht deaktivieren (write)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* Datenrichtung GPIO-Pin festlegen
|
||||
* Schreiben Pinnummer nach /sys/class/gpioXX/direction
|
||||
* Richtung dir: 0 = Lesen, 1 = Schreiben
|
||||
* Ergebnis: 0 = O.K., -1 = Fehler
|
||||
*/
|
||||
int gpio_direction(int pin, int dir)
|
||||
{
|
||||
char path[MAXBUFFER]; /* Buffer fuer Pfad */
|
||||
int fd; /* Filedescriptor */
|
||||
int res; /* Ergebnis von write */
|
||||
|
||||
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/direction", pin);
|
||||
fd = open(path, O_WRONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Kann Datenrichtung nicht setzen (open)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
switch (dir)
|
||||
{
|
||||
case INPUT : res = write(fd,"in",2); break;
|
||||
case OUTPUT: res = write(fd,"out",3); break;
|
||||
default: res = -1; break;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
perror("Kann Datenrichtung nicht setzen (write)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* vom GPIO-Pin lesen
|
||||
* Ergebnis: -1 = Fehler, 0/1 = Portstatus
|
||||
*/
|
||||
int gpio_read(int pin)
|
||||
{
|
||||
char path[MAXBUFFER]; /* Buffer fuer Pfad */
|
||||
int fd; /* Filedescriptor */
|
||||
char result[MAXBUFFER] = {0}; /* Buffer fuer Ergebnis */
|
||||
|
||||
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Kann vom GPIO nicht lesen (open)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
if (read(fd, result, 3) < 0)
|
||||
{
|
||||
perror("Kann vom GPIO nicht lesen (read)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return(atoi(result));
|
||||
}
|
||||
|
||||
/* auf GPIO schreiben
|
||||
* Ergebnis: -1 = Fehler, 0 = O.K.
|
||||
*/
|
||||
int gpio_write(int pin, int value)
|
||||
{
|
||||
char path[MAXBUFFER]; /* Buffer fuer Pfad */
|
||||
int fd; /* Filedescriptor */
|
||||
int res; /* Ergebnis von write */
|
||||
|
||||
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
|
||||
fd = open(path, O_WRONLY);
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Kann auf GPIO nicht schreiben (open)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case LOW : res = write(fd,"0",1); break;
|
||||
case HIGH: res = write(fd,"1",1); break;
|
||||
default: res = -1; break;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
perror("Kann auf GPIO nicht schreiben (write)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* GPIO-Pin auf Detektion einer Flanke setzen.
|
||||
* Fuer die Flanke (edge) koennen folgende Parameter gesetzt werden:
|
||||
* 'r' (rising) - steigende Flanke,
|
||||
* 'f' (falling) - fallende Flanke,
|
||||
* 'b' (both) - beide Flanken.
|
||||
*/
|
||||
int gpio_edge(unsigned int pin, char edge)
|
||||
{
|
||||
char path[MAXBUFFER]; /* Buffer fuer Pfad */
|
||||
int fd; /* Filedescriptor */
|
||||
|
||||
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/edge", pin);
|
||||
|
||||
fd = open(path, O_WRONLY | O_NONBLOCK );
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("gpio_edge: Kann auf GPIO nicht schreiben (open)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
switch (edge)
|
||||
{
|
||||
case 'r': strncpy(path,"rising",8); break;
|
||||
case 'f': strncpy(path,"falling",8); break;
|
||||
case 'b': strncpy(path,"both",8); break;
|
||||
case 'n': strncpy(path,"none",8); break;
|
||||
default: close(fd);return(-2);
|
||||
}
|
||||
|
||||
write(fd, path, strlen(path) + 1);
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Warten auf Flanke am GPIO-Pin.
|
||||
* Eingabewerte: pin: GPIO-Pin
|
||||
* timeout: Wartezeit in Millisekunden
|
||||
* Der Pin muss voher eingerichtet werden (export,
|
||||
* direction, edge)
|
||||
* Rueckgabewerte: <0: Fehler, 0: poll() Timeout,
|
||||
* 1: Flanke erkannt, Pin lieferte "0"
|
||||
* 2: Flanke erkannt, Pin lieferte "1"
|
||||
*/
|
||||
int gpio_wait(unsigned int pin, int timeout)
|
||||
{
|
||||
char path[MAXBUFFER]; /* Buffer fuer Pfad */
|
||||
int fd; /* Filedescriptor */
|
||||
struct pollfd polldat[1]; /* Variable fuer poll() */
|
||||
char buf[MAXBUFFER]; /* Lesepuffer */
|
||||
int rc; /* Hilfsvariablen */
|
||||
|
||||
/* GPIO-Pin dauerhaft oeffnen */
|
||||
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
|
||||
fd = open(path, O_RDONLY | O_NONBLOCK );
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("gpio_wait: Kann von GPIO nicht lesen (open)!\n");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
/* poll() vorbereiten */
|
||||
memset((void*)buf, 0, sizeof(buf));
|
||||
memset((void*)polldat, 0, sizeof(polldat));
|
||||
polldat[0].fd = fd;
|
||||
polldat[0].events = POLLPRI;
|
||||
|
||||
/* eventuell anstehende Interrupts loeschen */
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
rc = read(fd, buf, MAXBUFFER - 1);
|
||||
|
||||
rc = poll(polldat, 1, timeout);
|
||||
if (rc < 0)
|
||||
{ /* poll() failed! */
|
||||
perror("gpio_wait: Poll-Aufruf ging schief!\n");
|
||||
close(fd);
|
||||
return(-1);
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
{ /* poll() timeout! */
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
|
||||
if (polldat[0].revents & POLLPRI)
|
||||
{
|
||||
if (rc < 0)
|
||||
{ /* read() failed! */
|
||||
perror("gpio_wait: Kann von GPIO nicht lesen (read)!\n");
|
||||
close(fd);
|
||||
return(-2);
|
||||
}
|
||||
/* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */
|
||||
close(fd);
|
||||
return(1 + atoi(buf));
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return(-1);
|
||||
}
|
||||
|
||||
void delayMicrosecondsHard (unsigned int howLong)
|
||||
{
|
||||
struct timeval tNow, tLong, tEnd ;
|
||||
|
||||
gettimeofday (&tNow, NULL) ;
|
||||
tLong.tv_sec = howLong / 1000000 ;
|
||||
tLong.tv_usec = howLong % 1000000 ;
|
||||
timeradd (&tNow, &tLong, &tEnd) ;
|
||||
|
||||
while (timercmp (&tNow, &tEnd, <))
|
||||
gettimeofday (&tNow, NULL) ;
|
||||
}
|
||||
|
||||
void delayMicroseconds (unsigned int howLong)
|
||||
{
|
||||
struct timespec sleeper ;
|
||||
unsigned int uSecs = howLong % 1000000 ;
|
||||
unsigned int wSecs = howLong / 1000000 ;
|
||||
|
||||
/**/ if (howLong == 0)
|
||||
return ;
|
||||
else if (howLong < 100)
|
||||
delayMicrosecondsHard (howLong) ;
|
||||
else
|
||||
{
|
||||
sleeper.tv_sec = wSecs ;
|
||||
sleeper.tv_nsec = (long)(uSecs * 1000L) ;
|
||||
nanosleep (&sleeper, NULL) ;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -5,6 +5,13 @@
|
||||
#include <string>
|
||||
#include "knx/platform.h"
|
||||
|
||||
extern void delayMicroseconds (unsigned int howLong);
|
||||
extern int gpio_direction(int pin, int dir);
|
||||
extern int gpio_read(int pin);
|
||||
extern int gpio_write(int pin, int value);
|
||||
extern int gpio_export(int pin);
|
||||
extern int gpio_unexport(int pin);
|
||||
|
||||
class LinuxPlatform: public Platform
|
||||
{
|
||||
using Platform::_memoryReference;
|
||||
@ -43,6 +50,17 @@ public:
|
||||
int readUart() override;
|
||||
size_t readBytesUart(uint8_t *buffer, size_t length) override;
|
||||
|
||||
//spi
|
||||
void setupSpi() override;
|
||||
void closeSpi() override;
|
||||
int readWriteSpi (uint8_t *data, size_t len) override;
|
||||
|
||||
//gpio
|
||||
virtual void setupGpio(uint32_t dwPin, uint32_t dwMode) override;
|
||||
virtual void closeGpio(uint32_t dwPin) override;
|
||||
virtual void writeGpio(uint32_t dwPin, uint32_t dwVal) override;
|
||||
virtual uint32_t readGpio(uint32_t dwPin) override;
|
||||
|
||||
//memory
|
||||
uint8_t* getEepromBuffer(uint16_t size) override;
|
||||
void commitToEeprom() override;
|
||||
@ -57,6 +75,7 @@ public:
|
||||
void doMemoryMapping();
|
||||
uint8_t* _mappedFile = 0;
|
||||
int _fd = -1;
|
||||
int _spiFd = -1;
|
||||
uint8_t* _currentMaxMem = 0;
|
||||
std::string _flashFilePath = "flash.bin";
|
||||
char** _args = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user