Merge pull request #42 from nanosonde/rf_smode_cleanup

Add new KNX medium type RF for SAMD and Linux platform
This commit is contained in:
thelsing 2019-10-28 19:03:04 +01:00 committed by GitHub
commit 4ef513410a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3021 additions and 74 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

79
doc/knx_rf_notes.md Normal file
View File

@ -0,0 +1,79 @@
KNX-RF S-Mode
=============
Implementation Notes
--------------------
* KNX-RF E-Mode (pushbutton method) is NOT supported!
* KNX-RF S-Mode is implemented as KNX-RF READY (KNX-RF 1.R) which means only one single channel and no fast-ack is used.
-> KNX RF Multi (KNX-RF 1.M) would be required for this. However, implementation is way more complex as frequency hopping (fast and slow channels) is used and fast-ack
is based on a TDMA-like access scheme which has very strict timing requirements for the time slots.
-> summary: KNX-RF 1.R does NOT acknowledge packets on the air (data link layer). So standard GROUP_VALUE_WRITE messages in multicast mode could get lost!
Connection-oriented communication for device management is handled by the transport layer and uses T_ACK and T_NACK.
* the driver (rf_physical_layer.cpp) uses BOTH signals (GDO0, GDO2) of the RF transceiver C1101
-> GDO0 is asserted on RX/TX packet start and de-asserted on RX/TX packet end or RX/TX FIFO overflow/underflow
-> GDO2 is asserted is the FIFO needs to read out (RX) or refilled (TX)
* the driver (rf_physical_layer.cpp) uses both packet length modes of the CC1101: infinite length and fixed length are switched WHILE receiving or transmitting a packet.
* the edges of the signals GDO0 and GDO2 are detected by polling in the main loop and NOT by using interrupts
-> as a consequence the main loop must not be delayed too much, the transceiver receives/transmitts the data bytes itself into/from the FIFOs though (max. FIFO size 64 bytes each (TX/RX)!).
-> KNX-RF bitrate is 16384 bits/sec. -> so 40 bytes (Preamble, Syncword, Postamble) around 20ms for reception/transmission of a complete packet (to be verified!)
-> another implementation using interrupts could also be realized
* the driver does not use the wake-on-radio of the CC1101. The bidirectional battery powered devices are not useful at the moment.
-> wake-on-radio would also require the MCU to sleep and be woken up through interrupts.
-> BUT: UNIdirectional (battery powered) device could be realized: one the device has been configured in bidirectional mode.
The device (MCU) could sleep and only wake up if something needs to be done. Only useful for transmitters (e.g. sensors like push button, temperature sensor, etc.).
ToDo
----
* Packet duplication prevention based on the data link layer frame counter if KNX-RF retransmitters (range extension) are active (should be easy to add)
* KNX-RF 1.M (complex, may need an additional MCU, not planned for now)
-> maybe with a more capable transceiver: http://www.lapis-semi.com/en/semicon/telecom/telecom-product.php?PartNo=ML7345
it could handle manchester code, CRC-16 and the whole Wireless MBUS frame structure in hardware. Also used by Tapko for KAIstack.
* KNX Data Secure with security proxy profile in line coupler (e.g. TP<>RF). See KNX AN158 (KNX Data Secure draft spec.) p.9
-> KNX-RF very much benefits from having authenticated, encrypted data exchange.
-> Security Proxy terminates the secure applicationj layer (S-AL) in the line coupler. So the existing plain TP installation without data secure feature
can be kept as is.
Development Setup
-----------------
Development is done on a cheap Wemos SAMD21 M0-Mini board with a standard CC1101 module (868MHz) from Ebay. Beware of defective and bad quality modules.
The SAMD21 MCU is connected via SWD (Segger J-Link) to PlatformIO (Visual Studio Code). Additionally the standard UART (Arduino) is used for serial debug messages.
The USB port of the SAMD21 is not used at all.
Connection wiring:
------------------
Signal SAMD21 CC1101
----------+-------------+---------------
SPI_nCS | D10(PA18) | CSN
SPI_MOSI | D11(PA16) | SI
SPI_MISO | D12(PA19) | SO
SPI_SCK | D13(PA17) | SCLK
GDO0 | D7(PA21) | GDO0
GDO2 | D9(PA07) | GDO2
Arduino MZEROUSB variant needs patching to enable SPI on SERCOM1 on D10-D13.
variant.h
---------
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (18u)
#define PIN_SPI_MOSI (21u)
#define PIN_SPI_SCK (20u)
#define PERIPH_SPI sercom1
#define PAD_SPI_TX SPI_PAD_0_SCK_1
#define PAD_SPI_RX SERCOM_RX_PAD_3
variant.cpp
-----------
// 18..23 - SPI pins (ICSP:MISO,SCK,MOSI)
// ----------------------
{ PORTA, 19, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_12 }, // MISO: SERCOM1/PAD[3]
{ NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // 5V0
{ PORTA, 17, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 }, // SCK: SERCOM1/PAD[1]
{ PORTA, 16, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 }, // MOSI: SERCOM1/PAD[0]
{ NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // RESET
{ NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // GND

Binary file not shown.

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<KNX xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" CreatedBy="KNX MT" ToolVersion="5.6.407.26745" xmlns="http://knx.org/xml/project/11">
<ManufacturerData>
<Manufacturer RefId="M-00FA">
<Catalog>
<CatalogSection Id="M-00FA_CS-1" Name="Geräte" Number="1" DefaultLanguage="en-US">
<CatalogItem Id="M-00FA_H-0124-0_HP-ABCE-79-0000_CI-IA4313-1" Name="Temperatursensor RF" Number="1" ProductRefId="M-00FA_H-0124-0_P-IA4313" Hardware2ProgramRefId="M-00FA_H-0124-0_HP-ABCE-79-0000" DefaultLanguage="en-US" />
</CatalogSection>
</Catalog>
<ApplicationPrograms>
<ApplicationProgram Id="M-00FA_A-ABCE-79-0000" ApplicationNumber="43982" ApplicationVersion="121" ProgramType="ApplicationProgram" MaskVersion="MV-27B0" Name="TK TEMP RF" LoadProcedureStyle="MergedProcedure" PeiType="0" DefaultLanguage="en-US" DynamicTableManagement="false" Linkable="false" MinEtsVersion="4.0">
<Static>
<Code>
<RelativeSegment Id="M-00FA_A-ABCE-79-0000_RS-04-00000" Name="Parameters" Offset="0" Size="8" LoadStateMachine="4" />
</Code>
<ParameterTypes>
<ParameterType Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout" Name="startupTimeout">
<TypeRestriction Base="Value" SizeInBit="8">
<Enumeration Text="0 s" Value="0" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-0" />
<Enumeration Text="1 s" Value="1" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-1" />
<Enumeration Text="2 s" Value="2" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-2" />
<Enumeration Text="3 s" Value="3" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-3" />
<Enumeration Text="4 s" Value="4" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-4" />
<Enumeration Text="5 s" Value="5" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-5" />
<Enumeration Text="6 s" Value="6" Id="M-00FA_A-ABCE-79-0000_PT-startupTimeout_EN-6" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="M-00FA_A-ABCE-79-0000_PT-TempChange" Name="TempChange">
<TypeRestriction Base="Value" SizeInBit="8">
<Enumeration Text="Disabled" Value="0" Id="M-00FA_A-ABCE-79-0000_PT-TempChange_EN-0" />
<Enumeration Text="0.1 K" Value="1" Id="M-00FA_A-ABCE-79-0000_PT-TempChange_EN-1" />
<Enumeration Text="0.2 K" Value="2" Id="M-00FA_A-ABCE-79-0000_PT-TempChange_EN-2" />
<Enumeration Text="0.3 K" Value="3" Id="M-00FA_A-ABCE-79-0000_PT-TempChange_EN-3" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="M-00FA_A-ABCE-79-0000_PT-TempCycle" Name="TempCycle">
<TypeRestriction Base="Value" SizeInBit="8">
<Enumeration Text="Disabled" Value="0" Id="M-00FA_A-ABCE-79-0000_PT-TempCycle_EN-0" />
<Enumeration Text="1 min" Value="1" Id="M-00FA_A-ABCE-79-0000_PT-TempCycle_EN-1" />
<Enumeration Text="2 min" Value="2" Id="M-00FA_A-ABCE-79-0000_PT-TempCycle_EN-2" />
<Enumeration Text="3 min" Value="3" Id="M-00FA_A-ABCE-79-0000_PT-TempCycle_EN-3" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="M-00FA_A-ABCE-79-0000_PT-TempAbgleich" Name="TempAbgleich">
<TypeNumber SizeInBit="32" Type="signedInt" minInclusive="-50" maxInclusive="50" />
</ParameterType>
<ParameterType Id="M-00FA_A-ABCE-79-0000_PT-TempSendMinMax" Name="TempSendMinMax">
<TypeRestriction Base="Value" SizeInBit="8">
<Enumeration Text="Disabled" Value="0" Id="M-00FA_A-ABCE-79-0000_PT-TempSendMinMax_EN-0" />
<Enumeration Text="Send Enable" Value="1" Id="M-00FA_A-ABCE-79-0000_PT-TempSendMinMax_EN-1" />
</TypeRestriction>
</ParameterType>
</ParameterTypes>
<Parameters>
<Parameter Id="M-00FA_A-ABCE-79-0000_P-1" Name="startupTimeout" ParameterType="M-00FA_A-ABCE-79-0000_PT-startupTimeout" Text="Startup delaytime" Value="0">
<Memory CodeSegment="M-00FA_A-ABCE-79-0000_RS-04-00000" Offset="0" BitOffset="0" />
</Parameter>
<Parameter Id="M-00FA_A-ABCE-79-0000_P-2" Name="Aender Senden" ParameterType="M-00FA_A-ABCE-79-0000_PT-TempChange" Text="Send actual value after change of" Value="0">
<Memory CodeSegment="M-00FA_A-ABCE-79-0000_RS-04-00000" Offset="1" BitOffset="0" />
</Parameter>
<Parameter Id="M-00FA_A-ABCE-79-0000_P-3" Name="ZyklSenden" ParameterType="M-00FA_A-ABCE-79-0000_PT-TempCycle" Text="Send actual temperature cyclically" Value="0">
<Memory CodeSegment="M-00FA_A-ABCE-79-0000_RS-04-00000" Offset="2" BitOffset="0" />
</Parameter>
<Parameter Id="M-00FA_A-ABCE-79-0000_P-4" Name="MinMaxSenden" ParameterType="M-00FA_A-ABCE-79-0000_PT-TempSendMinMax" Text="Send min/max value" Value="0">
<Memory CodeSegment="M-00FA_A-ABCE-79-0000_RS-04-00000" Offset="3" BitOffset="0" />
</Parameter>
<Parameter Id="M-00FA_A-ABCE-79-0000_P-5" Name="Abgleich" ParameterType="M-00FA_A-ABCE-79-0000_PT-TempAbgleich" Text="Internal sensor correction value (value * 0.1 K)" Value="0">
<Memory CodeSegment="M-00FA_A-ABCE-79-0000_RS-04-00000" Offset="4" BitOffset="0" />
</Parameter>
</Parameters>
<ParameterRefs>
<ParameterRef Id="M-00FA_A-ABCE-79-0000_P-1_R-1" RefId="M-00FA_A-ABCE-79-0000_P-1" />
<ParameterRef Id="M-00FA_A-ABCE-79-0000_P-2_R-2" RefId="M-00FA_A-ABCE-79-0000_P-2" />
<ParameterRef Id="M-00FA_A-ABCE-79-0000_P-3_R-3" RefId="M-00FA_A-ABCE-79-0000_P-3" />
<ParameterRef Id="M-00FA_A-ABCE-79-0000_P-4_R-4" RefId="M-00FA_A-ABCE-79-0000_P-4" />
<ParameterRef Id="M-00FA_A-ABCE-79-0000_P-5_R-5" RefId="M-00FA_A-ABCE-79-0000_P-5" />
</ParameterRefs>
<ComObjectTable>
<ComObject Id="M-00FA_A-ABCE-79-0000_O-1" Name="Current temperature value" Text="Current temperature value" Number="1" FunctionText="Transmit temperature value" ObjectSize="2 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="" />
<ComObject Id="M-00FA_A-ABCE-79-0000_O-2" Name="Max memory value" Text="Max memory value" Number="2" FunctionText="Read memory" ObjectSize="2 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="" />
<ComObject Id="M-00FA_A-ABCE-79-0000_O-3" Name="Min memory value" Text="Min memory value" Number="3" FunctionText="Read memory" ObjectSize="2 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="" />
<ComObject Id="M-00FA_A-ABCE-79-0000_O-4" Name="Min/max memory reseet" Text="Min/max memory reseet" Number="4" FunctionText="Reset memory" ObjectSize="1 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="" />
</ComObjectTable>
<ComObjectRefs>
<ComObjectRef Id="M-00FA_A-ABCE-79-0000_O-1_R-1" RefId="M-00FA_A-ABCE-79-0000_O-1" />
<ComObjectRef Id="M-00FA_A-ABCE-79-0000_O-2_R-2" RefId="M-00FA_A-ABCE-79-0000_O-2" />
<ComObjectRef Id="M-00FA_A-ABCE-79-0000_O-3_R-3" RefId="M-00FA_A-ABCE-79-0000_O-3" />
<ComObjectRef Id="M-00FA_A-ABCE-79-0000_O-4_R-4" RefId="M-00FA_A-ABCE-79-0000_O-4" />
</ComObjectRefs>
<AddressTable MaxEntries="65000" />
<AssociationTable MaxEntries="65000" />
<LoadProcedures>
<LoadProcedure MergeId="2">
<LdCtrlRelSegment LsmIdx="4" Size="8" Mode="0" Fill="0" AppliesTo="full" />
</LoadProcedure>
<LoadProcedure MergeId="4">
<LdCtrlWriteRelMem ObjIdx="4" Offset="0" Size="8" Verify="true" />
</LoadProcedure>
</LoadProcedures>
<Options />
</Static>
<Dynamic>
<ChannelIndependentBlock>
<ParameterBlock Id="M-00FA_A-ABCE-79-0000_PB-1" Name="ParameterPage" Text="Allgemeine Parameter">
<ParameterRefRef RefId="M-00FA_A-ABCE-79-0000_P-1_R-1" />
<ParameterRefRef RefId="M-00FA_A-ABCE-79-0000_P-2_R-2" />
<ParameterRefRef RefId="M-00FA_A-ABCE-79-0000_P-3_R-3" />
<ParameterRefRef RefId="M-00FA_A-ABCE-79-0000_P-4_R-4" />
<ParameterRefRef RefId="M-00FA_A-ABCE-79-0000_P-5_R-5" />
<ComObjectRefRef RefId="M-00FA_A-ABCE-79-0000_O-1_R-1" />
<ComObjectRefRef RefId="M-00FA_A-ABCE-79-0000_O-2_R-2" />
<ComObjectRefRef RefId="M-00FA_A-ABCE-79-0000_O-3_R-3" />
<ComObjectRefRef RefId="M-00FA_A-ABCE-79-0000_O-4_R-4" />
</ParameterBlock>
</ChannelIndependentBlock>
</Dynamic>
</ApplicationProgram>
</ApplicationPrograms>
<Hardware>
<Hardware Id="M-00FA_H-0124-0" Name="SAMD Random" SerialNumber="0124" VersionNumber="0" HasIndividualAddress="true" HasApplicationProgram="true">
<Products>
<Product Id="M-00FA_H-0124-0_P-IA4313" Text="Temperatursensor RF" OrderNumber="IA4313" IsRailMounted="false" DefaultLanguage="en-US">
<RegistrationInfo RegistrationStatus="Registered" />
</Product>
</Products>
<Hardware2Programs>
<Hardware2Program Id="M-00FA_H-0124-0_HP-ABCE-79-0000" MediumTypes="MT-2">
<ApplicationProgramRef RefId="M-00FA_A-ABCE-79-0000" />
<RegistrationInfo RegistrationStatus="Registered" RegistrationNumber="0001/0121" />
</Hardware2Program>
</Hardware2Programs>
</Hardware>
</Hardware>
</Manufacturer>
</ManufacturerData>
</KNX>

View File

@ -9,7 +9,8 @@ add_executable(knx-linux
../src/knx/association_table_object.cpp
../src/knx/bau.cpp
../src/knx/bau07B0.cpp
../src/knx/bau57B0.cpp
../src/knx/bau27B0.cpp
../src/knx/bau57B0.cpp
../src/knx/bau_systemB.cpp
../src/knx/bits.cpp
../src/knx/cemi_frame.cpp
@ -23,12 +24,50 @@ add_executable(knx-linux
../src/knx/memory.cpp
../src/knx/network_layer.cpp
../src/knx/npdu.cpp
../src/knx/table_object.cpp
../src/knx/rf_physical_layer.cpp
../src/knx/rf_data_link_layer.cpp
../src/knx/rf_medium_object.cpp
../src/knx/table_object.cpp
../src/knx/tpdu.cpp
../src/knx/tpuart_data_link_layer.cpp
../src/knx/transport_layer.cpp
../src/knx/platform.cpp
../src/knx/address_table_object.h
../src/knx/apdu.h
../src/knx/application_layer.h
../src/knx/application_program_object.h
../src/knx/association_table_object.h
../src/knx/bau.h
../src/knx/bau07B0.h
../src/knx/bau27B0.h
../src/knx/bau57B0.h
../src/knx/bau_systemB.h
../src/knx/bits.h
../src/knx/cemi_frame.h
../src/knx/data_link_layer.h
../src/knx/device_object.h
../src/knx/group_object.h
../src/knx/group_object_table_object.h
../src/knx/interface_object.h
../src/knx/ip_data_link_layer.h
../src/knx/ip_parameter_object.h
../src/knx/memory.h
../src/knx/network_layer.h
../src/knx/npdu.h
../src/knx/rf_physical_layer.h
../src/knx/rf_data_link_layer.h
../src/knx/rf_medium_object.h
../src/knx/table_object.h
../src/knx/tpdu.h
../src/knx/tpuart_data_link_layer.h
../src/knx/transport_layer.h
../src/knx/platform.h
main.cpp
../src/linux_platform.h
../src/knx_facade.h
../src/knx/dptconvert.h
../src/knx/knx_value.h
../src/knx/dpt.h
../src/linux_platform.cpp
../src/knx_facade.cpp
../src/knx/dptconvert.cpp
@ -38,3 +77,5 @@ target_link_libraries(knx-linux "${LIBRARIES_FROM_REFERENCES}")
include_directories(../src)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
set_property(TARGET knx-linux PROPERTY CXX_STANDARD 11)
add_definitions(-DMEDIUM_TYPE=5)

View File

@ -1,12 +1,24 @@
#include "knx_facade.h"
#if MEDIUM_TYPE == 5
#include "knx/bau57B0.h"
#elif MEDIUM_TYPE == 2
#include "knx/bau27B0.h"
#else
#error Only MEDIUM_TYPE IP and RF supported
#endif
#include "knx/group_object_table_object.h"
#include "knx/bits.h"
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#if MEDIUM_TYPE == 5
KnxFacade<LinuxPlatform, Bau57B0> knx;
#elif MEDIUM_TYPE == 2
KnxFacade<LinuxPlatform, Bau27B0> knx;
#else
#error Only MEDIUM_TYPE IP and RF supported
#endif
long lastsend = 0;
@ -94,6 +106,6 @@ int main(int argc, char **argv)
knx.loop();
if(knx.configured())
appLoop();
delay(100);
delayMicroseconds(100);
}
}
}

View File

@ -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,24 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length)
return length;
}
void ArduinoPlatform::setupSpi()
{
SPI.begin();
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
}
void ArduinoPlatform::closeSpi()
{
SPI.endTransaction();
SPI.end();
}
int ArduinoPlatform::readWriteSpi(uint8_t *data, size_t len)
{
SPI.transfer(data, len);
return 0;
}
void print(const char* s)
{
ArduinoPlatform::SerialDebug->print(s);

View File

@ -35,6 +35,11 @@ class ArduinoPlatform : public Platform
virtual int readUart();
virtual size_t readBytesUart(uint8_t* buffer, size_t length);
//spi
void setupSpi() override;
void closeSpi() override;
int readWriteSpi (uint8_t *data, size_t len) override;
static Stream* SerialDebug;
protected:

View File

@ -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)
{

View File

@ -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:

View File

@ -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)
{
}

View File

@ -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);
@ -109,4 +110,11 @@ class BusAccessUnit
bool status);
virtual void keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
virtual void connectConfirm(uint16_t destination);
virtual void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength);
virtual void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
uint8_t* knxSerialNumber);
virtual void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
};

View File

@ -1,4 +1,5 @@
#include "bau07B0.h"
#include "bits.h"
#include <string.h>
#include <stdio.h>
@ -9,6 +10,16 @@ Bau07B0::Bau07B0(Platform& platform)
_dlLayer(_deviceObj, _addrTable, _netLayer, _platform)
{
_netLayer.dataLinkLayer(_dlLayer);
// Set Mask Version in Device Object depending on the BAU
uint16_t maskVersion;
popWord(maskVersion, _descriptor);
_deviceObj.maskVersion(maskVersion);
// Set which interface objects are available in the device object
// This differs from BAU to BAU with different medium types.
// See PID_IO_LIST
_deviceObj.ifObj(_ifObjs);
}
InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx)

View File

@ -16,4 +16,6 @@ class Bau07B0 : public BauSystemB
private:
TpUartDataLinkLayer _dlLayer;
uint8_t _descriptor[2] = {0x07, 0xb0};
const uint32_t _ifObjs[6] = { 5, // length
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG};
};

119
src/knx/bau27B0.cpp Normal file
View File

@ -0,0 +1,119 @@
#if MEDIUM_TYPE == 2
#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);
// Set the maximum APDU length
// ETS will consider this value while programming the device
// For KNX-RF we use a smallest allowed value for now,
// although long frame are also supported by the implementation.
// Needs some experimentation.
_deviceObj.maxApduLength(15);
// Set which interface objects are available in the device object
// This differs from BAU to BAU with different medium types.
// See PID_IO_LIST
_deviceObj.ifObj(_ifObjs);
}
// see KNX AN160 p.74 for mask 27B0
InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx)
{
switch (idx)
{
case 0:
return &_deviceObj;
case 1:
return &_addrTable;
case 2:
return &_assocTable;
case 3:
return &_groupObjTable;
case 4:
return &_appProgram;
case 5: // would be app_program 2
return nullptr;
case 6:
return &_rfMediumObj;
default:
return nullptr;
}
}
uint8_t* Bau27B0::descriptor()
{
return _descriptor;
}
DataLinkLayer& Bau27B0::dataLinkLayer()
{
return _dlLayer;
}
void Bau27B0::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
uint8_t* knxSerialNumber)
{
uint8_t curSerialNumber[6];
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
// If the received serial number matches our serial number
// then store the received RF domain address in the RF medium object
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
_rfMediumObj.rfDomainAddress(rfDoA);
}
void Bau27B0::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
{
uint8_t curSerialNumber[6];
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
// If the received serial number matches our serial number
// then send a response with the current RF domain address stored in the RF medium object
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
_appLayer.domainAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
}
void Bau27B0::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
uint8_t* knxSerialNumber)
{
uint8_t curSerialNumber[6];
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
// If the received serial number matches our serial number
// then store the received new individual address in the device object
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
_deviceObj.induvidualAddress(newIndividualAddress);
}
void Bau27B0::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
{
uint8_t curSerialNumber[6];
pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
// If the received serial number matches our serial number
// then send a response with the current RF domain address stored in the RF medium object and the serial number
if (!memcmp(knxSerialNumber, curSerialNumber, 6))
_appLayer.IndividualAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
}
#endif // #if MEDIUM_TYPE == 2

32
src/knx/bau27B0.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "bau_systemB.h"
#include "rf_medium_object.h"
#include "rf_physical_layer.h"
#include "rf_data_link_layer.h"
class Bau27B0 : public BauSystemB
{
public:
Bau27B0(Platform& platform);
protected:
InterfaceObject* getInterfaceObject(uint8_t idx);
uint8_t* descriptor();
DataLinkLayer& dataLinkLayer();
private:
RfDataLinkLayer _dlLayer;
RfMediumObject _rfMediumObj;
uint8_t _descriptor[2] = {0x27, 0xB0};
const uint32_t _ifObjs[7] = { 6, // length
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM};
void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
uint8_t* knxSerialNumber);
void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
uint8_t* knxSerialNumber);
void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
};

View File

@ -1,4 +1,5 @@
#include "bau57B0.h"
#include "bits.h"
#include <string.h>
#include <stdio.h>
@ -11,6 +12,16 @@ Bau57B0::Bau57B0(Platform& platform)
{
_netLayer.dataLinkLayer(_dlLayer);
_memory.addSaveRestore(&_ipParameters);
// Set Mask Version in Device Object depending on the BAU
uint16_t maskVersion;
popWord(maskVersion, _descriptor);
_deviceObj.maskVersion(maskVersion);
// Set which interface objects are available in the device object
// This differs from BAU to BAU with different medium types.
// See PID_IO_LIST
_deviceObj.ifObj(_ifObjs);
}
InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx)

View File

@ -18,4 +18,6 @@ class Bau57B0 : public BauSystemB
IpParameterObject _ipParameters;
IpDataLinkLayer _dlLayer;
uint8_t _descriptor[2] = {0x57, 0xb0};
const uint32_t _ifObjs[7] = { 6, // length
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER};
};

View File

@ -3,6 +3,14 @@
#include <string.h>
#include <stdio.h>
enum NmReadSerialNumberType
{
NM_Read_SerialNumber_By_ProgrammingMode = 0x01,
NM_Read_SerialNumber_By_ExFactoryState = 0x02,
NM_Read_SerialNumber_By_PowerReset = 0x03,
NM_Read_SerialNumber_By_ManufacturerSpecific = 0xFE,
};
BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platform),
_assocTable(platform), _groupObjTable(platform), _appProgram(platform),
_platform(platform), _appLayer(_assocTable, *this),
@ -350,3 +358,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((NmReadSerialNumberType)operand)
{
case NM_Read_SerialNumber_By_ProgrammingMode: // NM_Read_SerialNumber_By_ProgrammingMode
// Only send a reply if programming mode is on
if (_deviceObj.progMode() && (objectType == OT_DEVICE) && (propertyId == PID_SERIAL_NUMBER))
{
// Send reply. testResult data is KNX serial number
pushWord(_deviceObj.manufacturerId(), &knxSerialNumber[0]);
pushInt(_deviceObj.bauNumber(), &knxSerialNumber[2]);
_appLayer.systemNetworkParameterReadResponse(priority, hopType, objectType, propertyId,
testInfo, testInfoLength, knxSerialNumber, sizeof(knxSerialNumber));
}
break;
case NM_Read_SerialNumber_By_ExFactoryState: // NM_Read_SerialNumber_By_ExFactoryState
break;
case NM_Read_SerialNumber_By_PowerReset: // NM_Read_SerialNumber_By_PowerReset
break;
case NM_Read_SerialNumber_By_ManufacturerSpecific: // Manufacturer specific use of A_SystemNetworkParameter_Read
break;
}
}

View File

@ -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;

View File

@ -26,9 +26,11 @@
#define RISING 4
void delay(uint32_t millis);
void delayMicroseconds (unsigned int howLong);
uint32_t millis();
void pinMode(uint32_t dwPin, uint32_t dwMode);
void digitalWrite(uint32_t dwPin, uint32_t dwVal);
uint32_t digitalRead(uint32_t dwPin);
typedef void (*voidFuncPtr)(void);
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode);

View File

@ -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;

View File

@ -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
};

View File

@ -14,15 +14,20 @@ DataLinkLayer::DataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab,
void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, FrameFormat format, Priority priority, NPDU& npdu)
{
sendTelegram(npdu, ack, destinationAddr, addrType, format, priority);
// Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium)
// The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL)
// See 3.2.5 p.22
sendTelegram(npdu, ack, destinationAddr, addrType, format, priority, Broadcast);
}
void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu)
{
sendTelegram(npdu, ack, 0, GroupAddress, format, priority);
// System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium)
// See 3.2.5 p.22
sendTelegram(npdu, ack, 0, GroupAddress, format, priority, SysBroadcast);
}
void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success)
void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success)
{
AckType ack = frame.ack();
AddressType addrType = frame.addressType();
@ -31,9 +36,13 @@ void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success)
FrameFormat type = frame.frameType();
Priority priority = frame.priority();
NPDU& npdu = frame.npdu();
SystemBroadcast systemBroadcast = frame.systemBroadcast();
if (addrType == GroupAddress && destination == 0)
_networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success);
if (systemBroadcast == SysBroadcast)
_networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success);
else
_networkLayer.broadcastConfirm(ack, type, priority, source, npdu, success);
else
_networkLayer.dataConfirm(ack, addrType, destination, type, priority, source, npdu, success);
@ -49,12 +58,18 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
Priority priority = frame.priority();
NPDU& npdu = frame.npdu();
uint16_t ownAddr = _deviceObject.induvidualAddress();
SystemBroadcast systemBroadcast = frame.systemBroadcast();
if (source == ownAddr)
_deviceObject.induvidualAddressDuplication(true);
if (addrType == GroupAddress && destination == 0)
_networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
{
if (systemBroadcast == SysBroadcast)
_networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
else
_networkLayer.broadcastIndication(ack, type, npdu, priority, source);
}
else
{
if (addrType == InduvidualAddress && destination != _deviceObject.induvidualAddress())
@ -73,7 +88,7 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
}
}
bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority)
bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast)
{
CemiFrame& frame = npdu.frame();
frame.messageCode(L_data_ind);
@ -82,6 +97,7 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA
frame.addressType(addrType);
frame.priority(priority);
frame.repetition(RepititionAllowed);
frame.systemBroadcast(systemBroadcast);
if (npdu.octetCount() <= 15)
frame.frameType(StandardFrame);

View File

@ -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;

View File

@ -11,7 +11,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
break;
case PID_SERIAL_NUMBER:
pushWord(_manufacturerId, data);
pushInt(_bauNumber, data);
pushInt(_bauNumber, data + 2);
break;
case PID_MANUFACTURER_ID:
pushWord(_manufacturerId, data);
@ -35,7 +35,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
*data = _prgMode;
break;
case PID_MAX_APDU_LENGTH:
pushWord(254, data);
pushWord(_maxApduLength, data);
break;
case PID_SUBNET_ADDR:
*data = ((_ownAddress >> 8) & 0xff);
@ -45,18 +45,12 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
break;
case PID_IO_LIST:
{
uint32_t ifObjs[] = {
6, // length
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER};
for (uint32_t i = start; i < (ifObjs[0] + 1) && i < count; i++)
pushInt(ifObjs[i], data);
for (uint32_t i = start; i < (_ifObjs[0] + 1) && i < count; i++)
pushInt(_ifObjs[i], data);
break;
}
case PID_DEVICE_DESCRIPTOR:
data[0] = 0x57;
data[1] = 0xB0;
pushWord(_maskVersion, data);
break;
default:
count = 0;
@ -254,6 +248,36 @@ void DeviceObject::version(uint16_t value)
_version = value;
}
uint16_t DeviceObject::maskVersion()
{
return _maskVersion;
}
void DeviceObject::maskVersion(uint16_t value)
{
_maskVersion = value;
}
void DeviceObject::maxApduLength(uint16_t value)
{
_maxApduLength = value;
}
uint16_t DeviceObject::maxApduLength()
{
return _maxApduLength;
}
const uint32_t* DeviceObject::ifObj()
{
return _ifObjs;
}
void DeviceObject::ifObj(const uint32_t* value)
{
_ifObjs = value;
}
static PropertyDescription _propertyDescriptions[] =
{
{ PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 },

View File

@ -35,6 +35,12 @@ public:
void hardwareType(const uint8_t* value);
uint16_t version();
void version(uint16_t value);
uint16_t maskVersion();
void maskVersion(uint16_t value);
uint16_t maxApduLength();
void maxApduLength(uint16_t value);
const uint32_t* ifObj();
void ifObj(const uint32_t* value);
protected:
uint8_t propertyCount();
PropertyDescription* propertyDescriptions();
@ -48,4 +54,7 @@ private:
char _orderNumber[10] = "";
uint8_t _hardwareType[6] = { 0, 0, 0, 0, 0, 0};
uint16_t _version = 0;
uint16_t _maskVersion = 0x0000;
uint16_t _maxApduLength = 254;
const uint32_t* _ifObjs;
};

View File

@ -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
};
/**

View File

@ -71,12 +71,30 @@ enum TpduType
enum ApduType
{
// Application Layer services on Multicast Communication Mode
GroupValueRead = 0x000,
GroupValueResponse = 0x040,
GroupValueWrite = 0x080,
// Application Layer services on Broadcast Communication Mode
IndividualAddressWrite = 0x0c0,
IndividualAddressRead = 0x100,
IndividualAddressResponse = 0x140,
IndividualAddressSerialNumberRead = 0x3dc,
IndividualAddressSerialNumberResponse = 0x3dd,
IndividualAddressSerialNumberWrite = 0x3de,
// Application Layer Services on System Broadcast communication mode
SystemNetworkParameterRead = 0x1c8,
SystemNetworkParameterResponse = 0x1c9,
SystemNetworkParameterWrite = 0x1ca,
// Open media specific Application Layer Services on System Broadcast communication mode
DomainAddressSerialNumberRead = 0x3ec,
DomainAddressSerialNumberResponse = 0x3ed,
DomainAddressSerialNumberWrite = 0x3ee,
// Application Layer Services on Point-to-point Connection-Oriented Communication Mode (mandatory)
// Application Layer Services on Point-to-point Connectionless Communication Mode (either optional or mandatory)
MemoryRead = 0x200,
MemoryResponse = 0x240,
MemoryWrite = 0x280,
@ -97,7 +115,4 @@ enum ApduType
PropertyValueWrite = 0x3d7,
PropertyDescriptionRead = 0x3d8,
PropertyDescriptionResponse = 0x3d9,
IndividualAddressSerialNumberRead = 0x3dc,
IndividualAddressSerialNumberResponse = 0x3dd,
IndividualAddressSerialNumberWrite = 0x3de,
};
};

View File

@ -66,18 +66,30 @@ void NetworkLayer::dataConfirm(AckType ack, AddressType addressType, uint16_t de
_transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status);
}
void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
void NetworkLayer::broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu());
}
void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
void NetworkLayer::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status);
}
void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu());
}
void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataSystemBroadcastConfirm(ack, hopType, npdu.tpdu(), priority, status);
}
void NetworkLayer::dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu)
{
//if (tpdu.apdu().length() > 0)

View File

@ -20,6 +20,9 @@ class NetworkLayer
Priority priority, uint16_t source);
void dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority,
uint16_t source, NPDU& npdu, bool status);
void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu,
Priority priority, uint16_t source);
void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status);
void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu,
Priority priority, uint16_t source);
void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status);

View File

@ -29,6 +29,10 @@ class Platform
virtual int readUart() = 0;
virtual size_t readBytesUart(uint8_t* buffer, size_t length) = 0;
virtual void setupSpi() = 0;
virtual void closeSpi() = 0;
virtual int readWriteSpi (uint8_t *data, size_t len) = 0;
virtual uint8_t* getEepromBuffer(uint16_t size) = 0;
virtual void commitToEeprom() = 0;

View File

@ -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,

View File

@ -0,0 +1,369 @@
#if MEDIUM_TYPE == 2
#include "rf_physical_layer.h"
#include "rf_data_link_layer.h"
#include "bits.h"
#include "platform.h"
#include "device_object.h"
#include "address_table_object.h"
#include "rf_medium_object.h"
#include "cemi_frame.h"
#include <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;
}
#endif // #if MEDIUM_TYPE == 2

View 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);
};

View 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;
}

View 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,};
};

View File

@ -0,0 +1,799 @@
#if MEDIUM_TYPE == 2
#include "rf_physical_layer.h"
#include "rf_data_link_layer.h"
#include "bits.h"
#include "platform.h"
#include <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;
digitalWrite(SPI_SS_PIN, LOW);
_platform.readWriteSpi(tbuf, len);
digitalWrite(SPI_SS_PIN, HIGH);
}
uint8_t RfPhysicalLayer::spiReadRegister(uint8_t spi_instr)
{
uint8_t value;
uint8_t rbuf[2] = {0};
rbuf[0] = spi_instr | READ_SINGLE_BYTE;
uint8_t len = 2;
digitalWrite(SPI_SS_PIN, LOW);
_platform.readWriteSpi(rbuf, len);
digitalWrite(SPI_SS_PIN, HIGH);
value = rbuf[1];
//printf("SPI_arr_0: 0x%02X\n", rbuf[0]);
//printf("SPI_arr_1: 0x%02X\n", rbuf[1]);
return value;
}
uint8_t RfPhysicalLayer::spiWriteStrobe(uint8_t spi_instr)
{
uint8_t tbuf[1] = {0};
tbuf[0] = spi_instr;
//printf("SPI_data: 0x%02X\n", tbuf[0]);
digitalWrite(SPI_SS_PIN, LOW);
_platform.readWriteSpi(tbuf, 1);
digitalWrite(SPI_SS_PIN, HIGH);
return tbuf[0];
}
void RfPhysicalLayer::spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len)
{
uint8_t rbuf[len + 1];
rbuf[0] = spi_instr | READ_BURST;
digitalWrite(SPI_SS_PIN, LOW);
_platform.readWriteSpi(rbuf, len + 1);
digitalWrite(SPI_SS_PIN, HIGH);
for (uint8_t i=0; i<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]);
}
digitalWrite(SPI_SS_PIN, LOW);
_platform.readWriteSpi(tbuf, len + 1);
digitalWrite(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();
pinMode(GPIO_GDO2_PIN, INPUT);
pinMode(GPIO_GDO0_PIN, INPUT);
pinMode(SPI_SS_PIN, OUTPUT);
// Toggle chip select signal as described in CC11xx manual
digitalWrite(SPI_SS_PIN, HIGH);
delayMicroseconds(30);
digitalWrite(SPI_SS_PIN, LOW);
delayMicroseconds(30);
digitalWrite(SPI_SS_PIN, HIGH);
delayMicroseconds(45);
// Send SRES command
digitalWrite(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));
digitalWrite(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.closeSpi();
}
void RfPhysicalLayer::showRegisterSettings()
{
uint8_t config_reg_verify[CFG_REGISTER];
uint8_t Patable_verify[CFG_REGISTER];
spiReadBurst(READ_BURST,config_reg_verify,CFG_REGISTER); //reads all 47 config register from cc1101
spiReadBurst(PATABLE_BURST,Patable_verify,8); //reads output power settings from cc1101
println("Config Register:");
printHex("", config_reg_verify, CFG_REGISTER);
println("PaTable:");
printHex("", Patable_verify, 8);
}
uint16_t RfPhysicalLayer::packetSize (uint8_t lField)
{
uint16_t nrBytes;
uint8_t nrBlocks;
// The 2 first blocks contains 25 bytes when excluding CRC and the L-field
// The other blocks contains 16 bytes when excluding the CRC-fields
// Less than 26 (15 + 10)
if ( lField < 26 )
nrBlocks = 2;
else
nrBlocks = (((lField - 26) / 16) + 3);
// Add all extra fields, excluding the CRC fields
nrBytes = lField + 1;
// Add the CRC fields, each block has 2 CRC bytes
nrBytes += (2 * nrBlocks);
return nrBytes;
}
void RfPhysicalLayer::loop()
{
switch (_loopState)
{
case TX_START:
{
prevStatusGDO0 = 0;
prevStatusGDO2 = 0;
// Set sync word in TX mode
// The same sync word is used in RX mode, but we use it in different way here:
// Important: the TX FIFO must provide the last byte of the
// sync word
spiWriteRegister(SYNC1, 0x54);
spiWriteRegister(SYNC0, 0x76);
// Set TX FIFO threshold to 33 bytes
spiWriteRegister(FIFOTHR, 0x47);
// Set GDO2 to be TX FIFO threshold signal
spiWriteRegister(IOCFG2, 0x02);
// Set GDO0 to be packet transmitted signal
spiWriteRegister(IOCFG0, 0x06);
// Flush TX FIFO
spiWriteStrobe(SFTX);
_rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength);
// Calculate total number of bytes in the KNX RF packet from L-field
pktLen = packetSize(sendBuffer[0]);
// Check for valid length
if ((pktLen == 0) || (pktLen > 290))
{
println("TX packet length error!");
break;
}
// Manchester encoded data takes twice the space plus
// 1 byte for postamble and 1 byte (LSB) of the synchronization word
bytesLeft = (2 * pktLen) + 2;
// Last byte of synchronization word
buffer[0] = 0x96;
// Manchester encode packet
for (int i = 0; i < pktLen; i++)
{
manchEncode(&sendBuffer[i], &buffer[1 + i*2]);
}
// Append the postamble sequence
buffer[1 + bytesLeft - 1] = 0x55;
// Fill TX FIFO
pByteIndex = &buffer[0];
// Set fixed packet length mode if less than 256 bytes to transmit
if (bytesLeft < 256)
{
spiWriteRegister(PKTLEN, bytesLeft);
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
fixedLengthMode = true;
}
else // Else set infinite length mode
{
uint8_t fixedLength = bytesLeft % 256;
spiWriteRegister(PKTLEN, fixedLength);
spiWriteRegister(PKTCTRL0, 0x02);
fixedLengthMode = false;
}
uint8_t bytesToWrite = MIN(64, bytesLeft);
spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
pByteIndex += bytesToWrite;
bytesLeft -= bytesToWrite;
// Enable transmission of packet
spiWriteStrobe(STX);
_loopState = TX_ACTIVE;
}
// Fall through
case TX_ACTIVE:
{
// Check if we have an incomplete packet transmission
if (syncStart && (millis() - packetStartTime > TX_PACKET_TIMEOUT))
{
println("TX packet timeout!");
// Set transceiver to IDLE (no RX or TX)
sIdle();
_loopState = TX_END;
break;
}
// Detect falling edge 1->0 on GDO2
statusGDO2 = digitalRead(GPIO_GDO2_PIN);
if(prevStatusGDO2 != statusGDO2)
{
prevStatusGDO2 = statusGDO2;
// Check if signal GDO2 is de-asserted (TX FIFO is below threshold of 33 bytes, i.e. TX FIFO is half full)
if(statusGDO2 == 0)
{
// - TX FIFO half full detected (< 33 bytes)
// Write data fragment to TX FIFO
uint8_t bytesToWrite = MIN(64, bytesLeft);
spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
pByteIndex += bytesToWrite;
bytesLeft -= bytesToWrite;
// Set fixed length mode if less than 256 left to transmit
if ( (bytesLeft < (256 - 64)) && !fixedLengthMode )
{
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
fixedLengthMode = true;
}
}
}
// Detect falling edge 1->0 on GDO0
statusGDO0 = digitalRead(GPIO_GDO0_PIN);
if(prevStatusGDO0 != statusGDO0)
{
prevStatusGDO0 = statusGDO0;
// If GDO0 is de-asserted: TX packet complete or TX FIFO underflow
if (statusGDO0 == 0x00)
{
// There might be an TX FIFO underflow
uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_TX_UNDERFLOW)
{
println("TX FIFO underflow!");
// Set transceiver to IDLE (no RX or TX)
sIdle();
}
_loopState = TX_END;
}
else
{
// GDO0 asserted because sync word was transmitted
//println("TX Syncword!");
// wait for TX_PACKET_TIMEOUT milliseconds
// Complete packet must have been transmitted within this time
packetStartTime = millis();
syncStart = true;
}
}
}
break;
case TX_END:
{
// free buffer
delete sendBuffer;
// Go back to RX after TX
_loopState = RX_START;
}
break;
case RX_START:
{
prevStatusGDO2 = 0;
prevStatusGDO0 = 0;
syncStart = false;
packetStart = true;
fixedLengthMode = false;
pByteIndex = buffer;
bytesLeft = 0;
pktLen = 0;
// Set sync word in RX mode
// The same sync word is used in TX mode, but we use it in different way
spiWriteRegister(SYNC1, 0x76);
spiWriteRegister(SYNC0, 0x96);
// Set GDO2 to be RX FIFO threshold signal
spiWriteRegister(IOCFG2, 0x00);
// Set GDO0 to be packet received signal
spiWriteRegister(IOCFG0, 0x06);
// Set RX FIFO threshold to 4 bytes
spiWriteRegister(FIFOTHR, 0x40);
// Set infinite pktlen mode
spiWriteRegister(PKTCTRL0, 0x02);
// Flush RX FIFO
spiWriteStrobe(SFRX);
// Start RX
sReceive();
_loopState = RX_ACTIVE;
}
break;
case RX_ACTIVE:
{
if (!_rfDataLinkLayer.isTxQueueEmpty() && !syncStart)
{
sIdle();
_loopState = TX_START;
break;
}
// Check if we have an incomplete packet reception
// This is related to CC1101 errata "Radio stays in RX state instead of entering RXFIFO_OVERFLOW state"
if (syncStart && (millis() - packetStartTime > RX_PACKET_TIMEOUT))
{
println("RX packet timeout!");
//uint8_t marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
//print("marcstate: 0x");
//println(marcState, HEX);
sIdle();
_loopState = RX_START;
break;
}
// Detect rising edge 0->1 on GDO2
statusGDO2 = digitalRead(GPIO_GDO2_PIN);
if(prevStatusGDO2 != statusGDO2)
{
prevStatusGDO2 = statusGDO2;
// Check if signal GDO2 is asserted (RX FIFO is equal to or above threshold of 4 bytes)
if(statusGDO2 == 1)
{
if (packetStart)
{
// - RX FIFO 4 bytes detected -
// Calculate the total length of the packet, and set fixed mode if less
// than 255 bytes to receive
// Read the 2 first bytes
spiReadBurst(RXFIFO_BURST, pByteIndex, 2);
// Decode the L-field
if (!manchDecode(&buffer[0], &packet[0]))
{
//println("Could not decode L-field: manchester code violation");
_loopState = RX_START;
break;
}
// Get bytes to receive from L-field, multiply by 2 because of manchester code
pktLen = 2 * packetSize(packet[0]);
// - Length mode -
if (pktLen < 256)
{
// Set fixed packet length mode is less than 256 bytes
spiWriteRegister(PKTLEN, pktLen);
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
fixedLengthMode = true;
}
else
{
// Infinite packet length mode is more than 255 bytes
// Calculate the PKTLEN value
uint8_t fixedLength = pktLen % 256;
spiWriteRegister(PKTLEN, fixedLength);
}
pByteIndex += 2;
bytesLeft = pktLen - 2;
// Set RX FIFO threshold to 32 bytes
packetStart = false;
spiWriteRegister(FIFOTHR, 0x47);
}
else
{
// - RX FIFO Half Full detected -
// Read out the RX FIFO and set fixed mode if less
// than 255 bytes to receive
// - Length mode -
// Set fixed packet length mode if less than 256 bytes
if ((bytesLeft < 256 ) && !fixedLengthMode)
{
spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
fixedLengthMode = true;
}
// Read out the RX FIFO
// Do not empty the FIFO (See the CC110x or 2500 Errata Note)
spiReadBurst(RXFIFO_BURST, pByteIndex, 32 - 1);
bytesLeft -= (32 - 1);
pByteIndex += (32 - 1);
}
}
}
// Detect falling edge 1->0 on GDO0
statusGDO0 = digitalRead(GPIO_GDO0_PIN);
if(prevStatusGDO0 != statusGDO0)
{
prevStatusGDO0 = statusGDO0;
// If GDO0 is de-asserted: RX packet complete or RX FIFO overflow
if (statusGDO0 == 0x00)
{
// There might be an RX FIFO overflow
uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_RX_OVERFLOW)
{
println("RX FIFO overflow!");
_loopState = RX_START;
break;
}
// Check if we are in the middle of the packet reception
if (!packetStart)
{
// Complete packet received
// Read out remaining bytes in the RX FIFO
spiReadBurst(RXFIFO_BURST, pByteIndex, bytesLeft);
_loopState = RX_END;
}
}
else
{
// GDO0 asserted because sync word was received and recognized
//println("RX Syncword!");
// wait for RX_PACKET_TIMEOUT milliseconds
// Complete packet must have been received within this time
packetStartTime = millis();
syncStart = true;
}
}
}
break;
case RX_END:
{
uint16_t pLen = packetSize(packet[0]);
// Decode the first block (always 10 bytes + 2 bytes CRC)
bool decodeOk = true;
for (uint16_t i = 1; i < pLen; i++)
{
// Check for manchester violation, abort if there is one
if(!manchDecode(&buffer[i*2], &packet[i]))
{
println("Could not decode packet: manchester code violation");
decodeOk = false;
break;
}
}
if (decodeOk)
{
_rfDataLinkLayer.frameBytesReceived(&packet[0], pLen);
}
_loopState = RX_START;
}
break;
}
}
#endif // #if MEDIUM_TYPE == 2

257
src/knx/rf_physical_layer.h Normal file
View 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

View File

@ -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__

View File

@ -2,20 +2,27 @@
#include "knx/bits.h"
// Set default medium type to TP if no external definitions was given
#ifndef MEDIUM_TYPE
#define MEDIUM_TYPE 0
#endif
#ifdef ARDUINO_ARCH_SAMD
#include "samd_platform.h"
#include "knx/bau07B0.h"
#include "samd_platform.h"
#include "knx/bau07B0.h"
#include "knx/bau27B0.h"
#elif ARDUINO_ARCH_ESP8266
#include "esp_platform.h"
#include "knx/bau57B0.h"
#include "esp_platform.h"
#include "knx/bau57B0.h"
#elif ARDUINO_ARCH_ESP32
#define LED_BUILTIN 13
#include "esp32_platform.h"
#include "knx/bau57B0.h"
#define LED_BUILTIN 13
#include "esp32_platform.h"
#include "knx/bau57B0.h"
#else
#include "linux_platform.h"
#include "knx/bau57B0.h"
#define LED_BUILTIN 0
#define LED_BUILTIN 0
#include "linux_platform.h"
#include "knx/bau57B0.h"
#include "knx/bau27B0.h"
#endif
void buttonUp();
@ -297,11 +304,24 @@ template <class 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

View File

@ -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.
@ -499,10 +572,18 @@ void println(void)
void pinMode(uint32_t dwPin, uint32_t dwMode)
{
gpio_export(dwPin);
gpio_direction(dwPin, dwMode);
}
void digitalWrite(uint32_t dwPin, uint32_t dwVal)
{
gpio_write(dwPin, dwVal);
}
uint32_t digitalRead(uint32_t dwPin)
{
return gpio_read(dwPin);
}
typedef void (*voidFuncPtr)(void);
@ -519,4 +600,299 @@ void LinuxPlatform::cmdLineArgs(int argc, char** argv)
memcpy(_args, argv, argc * sizeof(char*));
_args[argc] = 0;
}
#endif
/* Buffer size for string operations (e.g. snprintf())*/
#define MAXBUFFER 100
/* Activate GPIO-Pin
* Write GPIO pin number to /sys/class/gpio/export
* Result: 0 = success, -1 = error
*/
int gpio_export(int pin)
{
char buffer[MAXBUFFER]; /* Output Buffer */
ssize_t bytes; /* Used Buffer length */
int fd; /* Filedescriptor */
int res; /* Result from write() */
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0)
{
perror("Could not export GPIO pin(open)!\n");
return(-1);
}
bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
res = write(fd, buffer, bytes);
if (res < 0)
{
perror("Could not export GPIO pin(write)!\n");
return(-1);
}
close(fd);
delay(100);
return(0);
}
/* Deactivate GPIO pin
* Write GPIO pin number to /sys/class/gpio/unexport
* Result: 0 = success, -1 = error
*/
int gpio_unexport(int pin)
{
char buffer[MAXBUFFER]; /* Output Buffer */
ssize_t bytes; /* Used Buffer length */
int fd; /* Filedescriptor */
int res; /* Result from write() */
fd = open("/sys/class/gpio/unexport", O_WRONLY);
if (fd < 0)
{
perror("Could not unexport GPIO pin(open)!\n");
return(-1);
}
bytes = snprintf(buffer, MAXBUFFER, "%d", pin);
res = write(fd, buffer, bytes);
if (res < 0)
{
perror("Could not unexport GPIO pin(write)!\n");
return(-1);
}
close(fd);
return(0);
}
/* Set GPIO pin mode (input/output)
* Write GPIO pin number to /sys/class/gpioXX/direction
* Direction: 0 = input, 1 = output
* Result: 0 = success, -1 = error
*/
int gpio_direction(int pin, int dir)
{
char path[MAXBUFFER]; /* Buffer for path */
int fd; /* Filedescriptor */
int res; /* Result from write() */
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/direction", pin);
fd = open(path, O_WRONLY);
if (fd < 0)
{
perror("Could not set mode for GPIO pin(open)!\n");
return(-1);
}
switch (dir)
{
case INPUT : res = write(fd,"in",2); break;
case OUTPUT: res = write(fd,"out",3); break;
default: res = -1; break;
}
if (res < 0)
{
perror("Could not set mode for GPIO pin(write)!\n");
return(-1);
}
close(fd);
return(0);
}
/* Read from GPIO pin
* Result: -1 = error, 0/1 = GPIO pin state
*/
int gpio_read(int pin)
{
char path[MAXBUFFER]; /* Buffer for path */
int fd; /* Filedescriptor */
char result[MAXBUFFER] = {0}; /* Buffer for result */
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
fd = open(path, O_RDONLY);
if (fd < 0)
{
perror("Could not read from GPIO(open)!\n");
return(-1);
}
if (read(fd, result, 3) < 0)
{
perror("Could not read from GPIO(read)!\n");
return(-1);
}
close(fd);
return(atoi(result));
}
/* Write to GPIO pin
* Result: -1 = error, 0 = success
*/
int gpio_write(int pin, int value)
{
char path[MAXBUFFER]; /* Buffer for path */
int fd; /* Filedescriptor */
int res; /* Result from write()*/
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
fd = open(path, O_WRONLY);
if (fd < 0)
{
perror("Could not write to GPIO(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("Could not write to GPIO(write)!\n");
return(-1);
}
close(fd);
return(0);
}
/* Set GPIO pin edge detection
* 'r' (rising)
* 'f' (falling)
* 'b' (both)
*/
int gpio_edge(unsigned int pin, char edge)
{
char path[MAXBUFFER]; /* Buffer for path */
int fd; /* Filedescriptor */
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/edge", pin);
fd = open(path, O_WRONLY | O_NONBLOCK );
if (fd < 0)
{
perror("Could not set GPIO edge detection(open)!\n");
return(-1);
}
switch (edge)
{
case 'r': strncpy(path,"rising",8); break;
case 'f': strncpy(path,"falling",8); break;
case 'b': strncpy(path,"both",8); break;
case 'n': strncpy(path,"none",8); break;
default: close(fd);return(-2);
}
write(fd, path, strlen(path) + 1);
close(fd);
return 0;
}
/* Wait for edge on GPIO pin
* timeout in milliseconds
* Result: <0: error, 0: poll() Timeout,
* 1: edge detected, GPIO pin reads "0"
* 2: edge detected, GPIO pin reads "1"
*/
int gpio_wait(unsigned int pin, int timeout)
{
char path[MAXBUFFER]; /* Buffer for path */
int fd; /* Filedescriptor */
struct pollfd polldat[1]; /* Variable for poll() */
char buf[MAXBUFFER]; /* Read buffer */
int rc; /* Result */
/* Open GPIO pin */
snprintf(path, MAXBUFFER, "/sys/class/gpio/gpio%d/value", pin);
fd = open(path, O_RDONLY | O_NONBLOCK );
if (fd < 0)
{
perror("Could not wait for GPIO edge(open)!\n");
return(-1);
}
/* prepare poll() */
memset((void*)buf, 0, sizeof(buf));
memset((void*)polldat, 0, sizeof(polldat));
polldat[0].fd = fd;
polldat[0].events = POLLPRI;
/* clear any existing detected edges before */
lseek(fd, 0, SEEK_SET);
rc = read(fd, buf, MAXBUFFER - 1);
rc = poll(polldat, 1, timeout);
if (rc < 0)
{ /* poll() failed! */
perror("Could not wait for GPIO edge(poll)!\n");
close(fd);
return(-1);
}
if (rc == 0)
{ /* poll() timeout! */
close(fd);
return(0);
}
if (polldat[0].revents & POLLPRI)
{
if (rc < 0)
{ /* read() failed! */
perror("Could not wait for GPIO edge(read)!\n");
close(fd);
return(-2);
}
/* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */
close(fd);
return(1 + atoi(buf));
}
close(fd);
return(-1);
}
void delayMicrosecondsHard (unsigned int howLong)
{
struct timeval tNow, tLong, tEnd ;
gettimeofday (&tNow, NULL) ;
tLong.tv_sec = howLong / 1000000 ;
tLong.tv_usec = howLong % 1000000 ;
timeradd (&tNow, &tLong, &tEnd) ;
while (timercmp (&tNow, &tEnd, <))
gettimeofday (&tNow, NULL) ;
}
void delayMicroseconds (unsigned int howLong)
{
struct timespec sleeper ;
unsigned int uSecs = howLong % 1000000 ;
unsigned int wSecs = howLong / 1000000 ;
/**/ if (howLong == 0)
return ;
else if (howLong < 100)
delayMicrosecondsHard (howLong) ;
else
{
sleeper.tv_sec = wSecs ;
sleeper.tv_nsec = (long)(uSecs * 1000L) ;
nanosleep (&sleeper, NULL) ;
}
}
#endif

View File

@ -5,6 +5,12 @@
#include <string>
#include "knx/platform.h"
extern int gpio_direction(int pin, int dir);
extern int gpio_read(int pin);
extern int gpio_write(int pin, int value);
extern int gpio_export(int pin);
extern int gpio_unexport(int pin);
class LinuxPlatform: public Platform
{
using Platform::_memoryReference;
@ -43,6 +49,11 @@ public:
int readUart() override;
size_t readBytesUart(uint8_t *buffer, size_t length) override;
//spi
void setupSpi() override;
void closeSpi() override;
int readWriteSpi (uint8_t *data, size_t len) override;
//memory
uint8_t* getEepromBuffer(uint16_t size) override;
void commitToEeprom() override;
@ -57,6 +68,7 @@ public:
void doMemoryMapping();
uint8_t* _mappedFile = 0;
int _fd = -1;
int _spiFd = -1;
uint8_t* _currentMaxMem = 0;
std::string _flashFilePath = "flash.bin";
char** _args = 0;