From f1e659bfd00b90617b9eadaa10e799718f585d4d Mon Sep 17 00:00:00 2001 From: Pawel Spychalski Date: Tue, 10 Apr 2018 15:01:27 +0200 Subject: [PATCH 1/4] Happy path frequency hopping --- .vscode/c_cpp_properties.json | 14 ++++++++++- crossbow/config.h | 4 ++-- crossbow/crossbow.ino | 54 +++++++++++++++++++++++++++++++++++++++++-- crossbow/qsp.cpp | 5 ++-- crossbow/variables.h | 13 ++++++++++- 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index cfa69c7..5a0e42f 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -11,6 +11,12 @@ "${workspaceRoot}" ] }, + "defines": [ + "SPI_HAS_NOTUSINGINTERRUPT", + "FEATURE_TX_INPUT_SBUS", + "DEVICE_MODE_TX", + "DEVICE_MODE_RX" + ], "intelliSenseMode": "msvc-x64", "cStandard": "c11", "cppStandard": "c++17", @@ -29,7 +35,13 @@ "~/Documents/Arduino/libraries/" ] }, - "intelliSenseMode": "clang-x64" + "intelliSenseMode": "clang-x64", + "defines": [ + "SPI_HAS_NOTUSINGINTERRUPT", + "FEATURE_TX_INPUT_SBUS", + "DEVICE_MODE_TX", + "DEVICE_MODE_RX" + ] } ], "version": 3 diff --git a/crossbow/config.h b/crossbow/config.h index 2ca699a..0b540aa 100644 --- a/crossbow/config.h +++ b/crossbow/config.h @@ -13,8 +13,8 @@ * DEVICE_MODE_TX * DEVICE_MODE_RX */ -// #define DEVICE_MODE_TX -#define DEVICE_MODE_RX +#define DEVICE_MODE_TX +// #define DEVICE_MODE_RX // #define FEATURE_TX_OLED // #define FORCE_TX_WITHOUT_INPUT diff --git a/crossbow/crossbow.ino b/crossbow/crossbow.ino index a880816..f9a6d10 100644 --- a/crossbow/crossbow.ino +++ b/crossbow/crossbow.ino @@ -92,8 +92,37 @@ uint8_t getRadioSnr(void) return (uint8_t) constrain(LoRa.packetSnr(), 0, 255); } +uint32_t getFrequencyForChannel(uint8_t channel) { + return RADIO_FREQUENCY_MIN + (RADIO_CHANNEL_WIDTH * channel); +} + +uint8_t getNextChannel(uint8_t channel) { + return (channel + RADIO_HOP_OFFSET) % RADIO_CHANNEL_COUNT; +} + +uint8_t getPrevChannel(uint8_t channel) { + return (RADIO_CHANNEL_COUNT + channel - RADIO_HOP_OFFSET) % RADIO_CHANNEL_COUNT; +} + +void hopFrequency(RadioState_t *radioState, bool forward, uint8_t fromChannel) { + radioState->channelEntryMillis = millis(); + + if (forward) { + radioState->channel = getNextChannel(fromChannel); + } else { + radioState->channel = getPrevChannel(fromChannel); + } + + // And set hardware + LoRa.sleep(); + LoRa.setFrequency( + getFrequencyForChannel(radioState->channel) + ); + LoRa.idle(); +} + void onQspSuccess(QspConfiguration_t *qsp, TxDeviceState_t *txDeviceState, RxDeviceState_t *rxDeviceState, volatile RadioState_t *radioState) { - //If devide received a valid frame, that means it can start to talk + //If recide received a valid frame, that means it can start to talk qsp->canTransmit = true; //Store the last timestamp when frame was received @@ -129,6 +158,14 @@ void onQspSuccess(QspConfiguration_t *qsp, TxDeviceState_t *txDeviceState, RxDev break; } + /* + * RX module hops to next channel after frame has been received + */ +#ifdef DEVICE_MODE_RX + hopFrequency(radioState, true, radioState->lastReceivedChannel); + LoRa.receive(); //Put radio back into receive mode +#endif + qsp->transmitWindowOpen = true; } @@ -160,7 +197,7 @@ void setup(void) LORA_DI0_PIN ); - if (!LoRa.begin(radioState.frequency)) + if (!LoRa.begin(getFrequencyForChannel(radioState.channel))) { #ifdef DEBUG_SERIAL Serial.println("LoRa init failed. Check your connections."); @@ -298,11 +335,22 @@ void loop(void) radioState.deviceState == RADIO_STATE_TX && !LoRa.isTransmitting() ) { + + /* + * In case of TX module, hop right now + */ +#ifdef DEVICE_MODE_TX + hopFrequency(&radioState, true, radioState.channel); +#endif + LoRa.receive(); radioState.deviceState = RADIO_STATE_RX; radioState.nextTxCheckMillis = currentMillis + 1; //We check of TX done every 1ms } + /* + * There is data to be read from radio! + */ if (radioState.bytesToRead != NO_DATA_TO_READ) { LoRa.read(tmpBuffer, radioState.bytesToRead); @@ -380,6 +428,8 @@ void loop(void) #ifdef DEVICE_MODE_RX + //FIXME here we are missing the whole procedure for jumping to next channel when frame was not recived + /* * This routine updates RX device state and updates one of radio channels with RSSI value */ diff --git a/crossbow/qsp.cpp b/crossbow/qsp.cpp index 159eec9..2fe7ef0 100644 --- a/crossbow/qsp.cpp +++ b/crossbow/qsp.cpp @@ -161,6 +161,7 @@ void qspDecodeIncomingFrame( static uint8_t frameId; static uint8_t payloadLength; static uint8_t receivedPayload; + static uint8_t receivedChannel; if (qsp->protocolState == QSP_STATE_IDLE) { @@ -187,8 +188,7 @@ void qspDecodeIncomingFrame( qsp->frameId = (incomingByte >> 4) & 0x0f; payloadLength = qspFrameLengths[qsp->frameId]; - //4 bytes are now free to use for something else - // payloadLength = incomingByte & 0x0f; + receivedChannel = incomingByte & 0x0f; qsp->protocolState = QSP_STATE_FRAME_TYPE_RECEIVED; } @@ -214,6 +214,7 @@ void qspDecodeIncomingFrame( { if (qsp->crc == incomingByte) { //CRC is correct + radioState->lastReceivedChannel = receivedChannel; qsp->onSuccessCallback(qsp, txDeviceState, rxDeviceState, radioState); } else { qsp->onFailureCallback(qsp, txDeviceState, rxDeviceState, radioState); diff --git a/crossbow/variables.h b/crossbow/variables.h index 4e9d624..d8bab73 100644 --- a/crossbow/variables.h +++ b/crossbow/variables.h @@ -86,8 +86,14 @@ enum debugConfigFlags { #define RADIO_STATE_TX 1 #define RADIO_STATE_RX 2 +#define RADIO_FREQUENCY_MIN 868000000 +#define RADIO_FREQUENCY_MAX 870000000 +#define RADIO_FREQUENCY_RANGE (RADIO_FREQUENCY_MAX-RADIO_FREQUENCY_MIN) +#define RADIO_CHANNEL_WIDTH 250000 +#define RADIO_CHANNEL_COUNT (RADIO_FREQUENCY_RANGE/RADIO_CHANNEL_WIDTH) + 1 // 9 channels in 2MHz range +#define RADIO_HOP_OFFSET 5 + struct RadioState_t { - uint32_t frequency = 867000000; uint32_t loraBandwidth = 250000; uint8_t loraSpreadingFactor = 7; uint8_t loraCodingRate = 6; @@ -97,6 +103,11 @@ struct RadioState_t { uint8_t snr = 0; uint8_t deviceState = RADIO_STATE_RX; uint32_t nextTxCheckMillis = 0; + + const uint32_t dwellTime = TX_TRANSMIT_SLOT_RATE * 2; + uint8_t channel = 0; + uint8_t lastReceivedChannel = 0; + uint32_t channelEntryMillis = 0; }; struct TxDeviceState_t { From 12e2bca44104795c3a16d98d13808d2efdb97400 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 19 Apr 2018 12:49:40 +0200 Subject: [PATCH 2/4] Fixed current channel indication and next channel computation --- .vscode/c_cpp_properties.json | 6 ------ crossbow/config.h | 6 +++--- crossbow/crossbow.ino | 4 ++-- crossbow/qsp.cpp | 5 ++--- crossbow/qsp.h | 2 +- crossbow/variables.h | 2 +- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index fc1a11f..c6842ff 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -11,12 +11,6 @@ "${workspaceRoot}" ] }, - "defines": [ - "SPI_HAS_NOTUSINGINTERRUPT", - "FEATURE_TX_INPUT_SBUS", - "DEVICE_MODE_TX", - "DEVICE_MODE_RX" - ], "intelliSenseMode": "msvc-x64", "cStandard": "c11", "cppStandard": "c++17", diff --git a/crossbow/config.h b/crossbow/config.h index 0b540aa..9706f06 100644 --- a/crossbow/config.h +++ b/crossbow/config.h @@ -13,8 +13,8 @@ * DEVICE_MODE_TX * DEVICE_MODE_RX */ -#define DEVICE_MODE_TX -// #define DEVICE_MODE_RX +// #define DEVICE_MODE_TX +#define DEVICE_MODE_RX // #define FEATURE_TX_OLED // #define FORCE_TX_WITHOUT_INPUT @@ -27,7 +27,7 @@ */ #define FEATURE_TX_INPUT_SBUS -// #define DEBUG_SERIAL +#define DEBUG_SERIAL // #define DEBUG_PING_PONG // #define DEBUG_LED // #define DEBUG_TX_INPUT_ON_OLED diff --git a/crossbow/crossbow.ino b/crossbow/crossbow.ino index 0a6ba41..f3d8fa2 100644 --- a/crossbow/crossbow.ino +++ b/crossbow/crossbow.ino @@ -103,7 +103,7 @@ uint8_t getPrevChannel(uint8_t channel) { return (RADIO_CHANNEL_COUNT + channel - RADIO_HOP_OFFSET) % RADIO_CHANNEL_COUNT; } -void hopFrequency(RadioState_t *radioState, bool forward, uint8_t fromChannel) { +void hopFrequency(volatile RadioState_t *radioState, bool forward, uint8_t fromChannel) { radioState->channelEntryMillis = millis(); if (forward) { @@ -495,7 +495,7 @@ void loop(void) uint8_t size; LoRa.beginPacket(); //Prepare packet - qspEncodeFrame(&qsp, tmpBuffer, &size); + qspEncodeFrame(&qsp, &radioState, tmpBuffer, &size); //Sent it to radio in one SPI transaction LoRa.write(tmpBuffer, size); LoRa.endPacketAsync(); diff --git a/crossbow/qsp.cpp b/crossbow/qsp.cpp index 2fe7ef0..5106778 100644 --- a/crossbow/qsp.cpp +++ b/crossbow/qsp.cpp @@ -189,7 +189,6 @@ void qspDecodeIncomingFrame( qsp->frameId = (incomingByte >> 4) & 0x0f; payloadLength = qspFrameLengths[qsp->frameId]; receivedChannel = incomingByte & 0x0f; - qsp->protocolState = QSP_STATE_FRAME_TYPE_RECEIVED; } else if (qsp->protocolState == QSP_STATE_FRAME_TYPE_RECEIVED) @@ -228,7 +227,7 @@ void qspDecodeIncomingFrame( /** * Encode frame is corrent format and write to hardware */ -void qspEncodeFrame(QspConfiguration_t *qsp, uint8_t buffer[], uint8_t *size) { +void qspEncodeFrame(QspConfiguration_t *qsp, volatile RadioState_t *radioState, uint8_t buffer[], uint8_t *size) { //Zero CRC qsp->crc = 0; @@ -238,7 +237,7 @@ void qspEncodeFrame(QspConfiguration_t *qsp, uint8_t buffer[], uint8_t *size) { //Write frame type and length // We are no longer sending payload length, so 4 bits are now free for other usages // uint8_t data = qsp->payloadLength & 0x0f; - uint8_t data = 0; + uint8_t data = radioState->channel; data |= (qsp->frameToSend << 4) & 0xf0; qspComputeCrc(qsp, data); buffer[1] = data; diff --git a/crossbow/qsp.h b/crossbow/qsp.h index d9ee307..60eff2a 100644 --- a/crossbow/qsp.h +++ b/crossbow/qsp.h @@ -16,6 +16,6 @@ void qspDecodeIncomingFrame( volatile RadioState_t *radioState ); void qspClearPayload(QspConfiguration_t *qsp); -void qspEncodeFrame(QspConfiguration_t *qsp, uint8_t buffer[], uint8_t *size); +void qspEncodeFrame(QspConfiguration_t *qsp, volatile RadioState_t *radioState, uint8_t buffer[], uint8_t *size); void encodePingPayload(QspConfiguration_t *qsp, uint32_t currentMicros); \ No newline at end of file diff --git a/crossbow/variables.h b/crossbow/variables.h index d8bab73..6481cfa 100644 --- a/crossbow/variables.h +++ b/crossbow/variables.h @@ -90,7 +90,7 @@ enum debugConfigFlags { #define RADIO_FREQUENCY_MAX 870000000 #define RADIO_FREQUENCY_RANGE (RADIO_FREQUENCY_MAX-RADIO_FREQUENCY_MIN) #define RADIO_CHANNEL_WIDTH 250000 -#define RADIO_CHANNEL_COUNT (RADIO_FREQUENCY_RANGE/RADIO_CHANNEL_WIDTH) + 1 // 9 channels in 2MHz range +#define RADIO_CHANNEL_COUNT 9 // 9 channels in 2MHz range (RADIO_FREQUENCY_RANGE/RADIO_CHANNEL_WIDTH) + 1 #define RADIO_HOP_OFFSET 5 struct RadioState_t { From feeeb78105601595de67c3302d90bd9f545584f2 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 19 Apr 2018 15:40:36 +0200 Subject: [PATCH 3/4] Sync RX and TX --- crossbow/crossbow.ino | 37 +++++++++++++++++++++++++++++-------- crossbow/variables.h | 2 ++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/crossbow/crossbow.ino b/crossbow/crossbow.ino index f3d8fa2..9204c6e 100644 --- a/crossbow/crossbow.ino +++ b/crossbow/crossbow.ino @@ -124,6 +124,15 @@ void onQspSuccess(QspConfiguration_t *qsp, TxDeviceState_t *txDeviceState, RxDev //If recide received a valid frame, that means it can start to talk qsp->canTransmit = true; + /* + * RX module hops to next channel after frame has been received + */ +#ifdef DEVICE_MODE_RX + hopFrequency(radioState, true, radioState->lastReceivedChannel); + radioState->failedDwellsCount = 0; // We received a frame, so we can just reset this counter + LoRa.receive(); //Put radio back into receive mode +#endif + //Store the last timestamp when frame was received if (qsp->frameId < QSP_FRAME_COUNT) { qsp->lastFrameReceivedAt[qsp->frameId] = millis(); @@ -157,14 +166,6 @@ void onQspSuccess(QspConfiguration_t *qsp, TxDeviceState_t *txDeviceState, RxDev break; } - /* - * RX module hops to next channel after frame has been received - */ -#ifdef DEVICE_MODE_RX - hopFrequency(radioState, true, radioState->lastReceivedChannel); - LoRa.receive(); //Put radio back into receive mode -#endif - qsp->transmitWindowOpen = true; } @@ -326,6 +327,26 @@ void loop(void) uint32_t currentMillis = millis(); /* + * This routine handles resync of TX/RX while hoppping frequencies + */ +#ifdef DEVICE_MODE_RX + + //In the beginning just keep jumping forward and try to resync over lost single frames + if (radioState.failedDwellsCount < 6 && radioState.channelEntryMillis + RX_CHANNEL_DWELL_TIME < currentMillis) { + hopFrequency(&radioState, true, radioState.channel); + LoRa.receive(); + radioState.failedDwellsCount++; + } + + // If we are loosing more frames, start jumping in the opposite direction since probably we are completely out of sync now + if (radioState.failedDwellsCount >= 6 && radioState.channelEntryMillis + (RX_CHANNEL_DWELL_TIME * 5) < currentMillis) { + hopFrequency(&radioState, false, radioState.channel); //Start jumping in opposite direction to resync + LoRa.receive(); + } + +#endif + + /* * Detect the moment when radio module stopped transmittig and put it * back in to receive state */ diff --git a/crossbow/variables.h b/crossbow/variables.h index 6481cfa..fa13dd6 100644 --- a/crossbow/variables.h +++ b/crossbow/variables.h @@ -14,6 +14,7 @@ #define RSSI_CHANNEL 11 #define TX_TRANSMIT_SLOT_RATE 67 //ms +#define RX_CHANNEL_DWELL_TIME (TX_TRANSMIT_SLOT_RATE + 10) //Dwell on a channel slightly longer #define RX_FAILSAFE_DELAY (TX_TRANSMIT_SLOT_RATE * 8) #define TX_FAILSAFE_DELAY (RX_FAILSAFE_DELAY * 4) @@ -108,6 +109,7 @@ struct RadioState_t { uint8_t channel = 0; uint8_t lastReceivedChannel = 0; uint32_t channelEntryMillis = 0; + uint8_t failedDwellsCount = 0; }; struct TxDeviceState_t { From 956fe00ccbde80192824ec9639dfc778978e52e0 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 19 Apr 2018 16:06:54 +0200 Subject: [PATCH 4/4] Syncing improvements --- crossbow/crossbow.ino | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crossbow/crossbow.ino b/crossbow/crossbow.ino index 9204c6e..0678e3b 100644 --- a/crossbow/crossbow.ino +++ b/crossbow/crossbow.ino @@ -103,8 +103,8 @@ uint8_t getPrevChannel(uint8_t channel) { return (RADIO_CHANNEL_COUNT + channel - RADIO_HOP_OFFSET) % RADIO_CHANNEL_COUNT; } -void hopFrequency(volatile RadioState_t *radioState, bool forward, uint8_t fromChannel) { - radioState->channelEntryMillis = millis(); +void hopFrequency(volatile RadioState_t *radioState, bool forward, uint8_t fromChannel, uint32_t timestamp) { + radioState->channelEntryMillis = timestamp; if (forward) { radioState->channel = getNextChannel(fromChannel); @@ -128,7 +128,7 @@ void onQspSuccess(QspConfiguration_t *qsp, TxDeviceState_t *txDeviceState, RxDev * RX module hops to next channel after frame has been received */ #ifdef DEVICE_MODE_RX - hopFrequency(radioState, true, radioState->lastReceivedChannel); + hopFrequency(radioState, true, radioState->lastReceivedChannel, millis()); radioState->failedDwellsCount = 0; // We received a frame, so we can just reset this counter LoRa.receive(); //Put radio back into receive mode #endif @@ -333,15 +333,29 @@ void loop(void) //In the beginning just keep jumping forward and try to resync over lost single frames if (radioState.failedDwellsCount < 6 && radioState.channelEntryMillis + RX_CHANNEL_DWELL_TIME < currentMillis) { - hopFrequency(&radioState, true, radioState.channel); - LoRa.receive(); radioState.failedDwellsCount++; + +#ifdef DEBUG_SERIAL + Serial.print("Sync forward on ch "); + Serial.print(radioState.channel); + Serial.print(" number "); + Serial.println(radioState.failedDwellsCount); +#endif + + hopFrequency(&radioState, true, radioState.channel, radioState.channelEntryMillis + RX_CHANNEL_DWELL_TIME); + LoRa.receive(); + } // If we are loosing more frames, start jumping in the opposite direction since probably we are completely out of sync now if (radioState.failedDwellsCount >= 6 && radioState.channelEntryMillis + (RX_CHANNEL_DWELL_TIME * 5) < currentMillis) { - hopFrequency(&radioState, false, radioState.channel); //Start jumping in opposite direction to resync + hopFrequency(&radioState, false, radioState.channel, radioState.channelEntryMillis + RX_CHANNEL_DWELL_TIME); //Start jumping in opposite direction to resync LoRa.receive(); + +#ifdef DEBUG_SERIAL + Serial.println("Sync backward"); +#endif + } #endif @@ -360,7 +374,7 @@ void loop(void) * In case of TX module, hop right now */ #ifdef DEVICE_MODE_TX - hopFrequency(&radioState, true, radioState.channel); + hopFrequency(&radioState, true, radioState.channel, millis()); #endif LoRa.receive();