diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 8e3b549..c6842ff 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -32,7 +32,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..9706f06 100644 --- a/crossbow/config.h +++ b/crossbow/config.h @@ -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 3c2f32f..0678e3b 100644 --- a/crossbow/crossbow.ino +++ b/crossbow/crossbow.ino @@ -91,10 +91,48 @@ 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(volatile RadioState_t *radioState, bool forward, uint8_t fromChannel, uint32_t timestamp) { + radioState->channelEntryMillis = timestamp; + + 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; + /* + * RX module hops to next channel after frame has been received + */ +#ifdef DEVICE_MODE_RX + 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 + //Store the last timestamp when frame was received if (qsp->frameId < QSP_FRAME_COUNT) { qsp->lastFrameReceivedAt[qsp->frameId] = millis(); @@ -159,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."); @@ -289,6 +327,40 @@ 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) { + 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, radioState.channelEntryMillis + RX_CHANNEL_DWELL_TIME); //Start jumping in opposite direction to resync + LoRa.receive(); + +#ifdef DEBUG_SERIAL + Serial.println("Sync backward"); +#endif + + } + +#endif + + /* * Detect the moment when radio module stopped transmittig and put it * back in to receive state */ @@ -297,11 +369,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, millis()); +#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); @@ -379,6 +462,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 */ @@ -445,7 +530,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 159eec9..5106778 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,9 +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; } else if (qsp->protocolState == QSP_STATE_FRAME_TYPE_RECEIVED) @@ -214,6 +213,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); @@ -227,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; @@ -237,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 4e9d624..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) @@ -86,8 +87,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 9 // 9 channels in 2MHz range (RADIO_FREQUENCY_RANGE/RADIO_CHANNEL_WIDTH) + 1 +#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 +104,12 @@ 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; + uint8_t failedDwellsCount = 0; }; struct TxDeviceState_t {