From ad21e4e088bec93e34cb46c180b78d918ee9194b Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Wed, 4 Jan 2023 14:38:01 -1000 Subject: [PATCH 01/25] Start hopping channels with TX/RX of SYNC_CLOCKS Set the initial clock synchronization at less than a millisecond. The separation of the TX and RX transactionComplete interrupts is around 650 - 700 uSec. By letting the transactionCompleteISR routine call startChannelTimer, the first expire time of the two timers is within 1 millisecond. The transmit completion routine for SYNC_CLOCKS and the receive routine for SYNC_CLOCKS both call hopChannel to change to the next frequency. This ensures that the two radios start hopping as soon as they are synchronized. --- Firmware/LoRaSerial_Firmware/Begin.ino | 12 ++- .../LoRaSerial_Firmware.ino | 2 + Firmware/LoRaSerial_Firmware/Radio.ino | 95 +++++++++---------- Firmware/LoRaSerial_Firmware/States.ino | 39 +++++--- 4 files changed, 80 insertions(+), 68 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Begin.ino b/Firmware/LoRaSerial_Firmware/Begin.ino index 961d93fc..0cf06f9a 100644 --- a/Firmware/LoRaSerial_Firmware/Begin.ino +++ b/Firmware/LoRaSerial_Firmware/Begin.ino @@ -100,13 +100,17 @@ void channelTimerHandler() channelTimerStart = millis(); //Record when this ISR happened. Used for calculating clock sync. radioCallHistory[RADIO_CALL_channelTimerHandler] = channelTimerStart; - //Restore the full timer interval - channelTimer.setInterval_MS(settings.maxDwellTime, channelTimerHandler); - channelTimerMsec = settings.maxDwellTime; //ISR update - digitalWrite(pin_hop_timer, channelNumber & 1); + //If the last timer was used to sync clocks, restore full timer interval + if (reloadChannelTimer == true) + { + reloadChannelTimer = false; + channelTimer.setInterval_MS(settings.maxDwellTime, channelTimerHandler); + channelTimerMsec = settings.maxDwellTime; //ISR update + } if (settings.frequencyHop) { + digitalWrite(pin_hop_timer, ((channelNumber + 1) % settings.numberOfChannels) & 1); triggerEvent(TRIGGER_CHANNEL_TIMER_ISR); timeToHop = true; } diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index be1d3708..3ebadf15 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -170,6 +170,7 @@ SAMDTimer channelTimer(TIMER_TCC); //Available: TC3, TC4, TC5, TCC, TCC1 or TCC2 volatile uint16_t channelTimerMsec; //Last value programmed into the channel timer volatile unsigned long channelTimerStart = 0; //Tracks how long our timer has been running since last hop volatile bool timeToHop = false; //Set by channelTimerHandler to indicate that hopChannel needs to be called +volatile bool reloadChannelTimer = false; //When set channel timer interval needs to be reloaded with settings.maxDwellTime uint16_t petTimeout = 0; //A reduced amount of time before WDT triggers. Helps reduce amount of time spent petting. unsigned long lastPet = 0; //Remebers time of last WDT pet. @@ -585,6 +586,7 @@ char platformPrefix[25]; //Used for printing platform specific device name, ie " //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- bool clockSyncReceiver; //Receives and processes the clock synchronization +bool startChannelTimerPending; //When true, call startChannelTimer in transactionCompleteISR //RX and TX time measurements uint32_t rxTimeUsec; diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 890e8262..86267ae4 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -702,9 +702,17 @@ void printSX1276Registers() void transactionCompleteISR(void) { transactionCompleteMicros = micros(); - triggerEvent(TRIGGER_TRANSACTION_COMPLETE); radioCallHistory[RADIO_CALL_transactionCompleteISR] = millis(); + triggerEvent(TRIGGER_TRANSACTION_COMPLETE); + + //Start the channel timer if requested + if (startChannelTimerPending) + { + startChannelTimer(); //transactionCompleteISR, upon TX or RX of SYNC_CLOCKS + startChannelTimerPending = false; //transactionCompleteISR, upon TX or RX of SYNC_CLOCKS + } + //Signal the state machine that a receive or transmit has completed transactionComplete = true; } @@ -729,51 +737,6 @@ void updateHopISR() } } -//As we complete linkup, different airspeeds exit at different rates -//We adjust the initial clock setup as needed -int16_t getLinkupOffset() -{ - int linkupOffset = 0; - - switch (settings.airSpeed) - { - default: - break; - case (40): - linkupOffset = 0; - break; - case (150): - linkupOffset = 0; - break; - case (400): - linkupOffset = 0; - break; - case (1200): - linkupOffset = 0; - break; - case (2400): - linkupOffset = 0; - break; - case (4800): - linkupOffset = 0; - break; - case (9600): - linkupOffset = 0; - break; - case (19200): - linkupOffset = 0; - break; - case (28800): - linkupOffset = 0; - break; - case (38400): - linkupOffset = 0; - break; - } - - return (settings.maxDwellTime - getReceiveCompletionOffset() - linkupOffset); //Reduce the default window by the offset -} - //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Table for use in calculating the software CRC-16 //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -2529,10 +2492,10 @@ bool transmitDatagram() //Hake sure that the transmitted msToNextHop is in the range 0 - maxDwellTime if (timeToHop) hopChannel(); - uint16_t msToNextHop = settings.maxDwellTime - (millis() - channelTimerStart); //TX channel timer value - memcpy(header, &msToNextHop, sizeof(msToNextHop)); - header += sizeof(msToNextHop); //aka CHANNEL_TIMER_BYTES + unsigned long currentMillis = millis(); + uint16_t msToNextHop = channelTimerMsec - (currentMillis - channelTimerStart); //TX channel timer value + //Validate this value if (ENABLE_DEVELOPER && (!clockSyncReceiver)) { if ((msToNextHop < 0) || (msToNextHop > settings.maxDwellTime)) @@ -2540,7 +2503,8 @@ bool transmitDatagram() int16_t channelTimer; uint8_t * data; - systemPrintln("TX Frame"); + systemPrint("TX Frame "); + systemPrintln(radioDatagramType[txControl.datagramType]); data = outgoingPacket; if ((settings.operatingMode == MODE_POINT_TO_POINT) || settings.verifyRxNetID) { @@ -2558,10 +2522,34 @@ bool transmitDatagram() systemPrint("ERROR: Invalid msToNextHop value, "); systemPrintln(msToNextHop); - waitForever(); + + //Set a valid value + msToNextHop = settings.maxDwellTime; + } + else if (settings.debugSync) + { + switch (txControl.datagramType) + { + case DATAGRAM_DATA_ACK: + case DATAGRAM_SYNC_CLOCKS: + systemPrint("TX: "); + systemPrint(channelTimerMsec); + systemPrint(" channelTimerMsec + ("); + systemPrint(currentMillis); + systemPrint(" millis() - "); + systemPrint(channelTimerStart); + systemPrint(" channelTimerStart) = "); + systemPrint(msToNextHop); + systemPrintln(" mSec"); + break; + } } } //ENABLE_DEVELOPER + //Set the time in the frame + memcpy(header, &msToNextHop, sizeof(msToNextHop)); + header += sizeof(msToNextHop); //aka CHANNEL_TIMER_BYTES + if (settings.debugTransmit) { systemPrintTimestamp(); @@ -2998,6 +2986,7 @@ void startChannelTimer(int16_t startAmount) channelTimer.disableTimer(); channelTimer.setInterval_MS(startAmount, channelTimerHandler); digitalWrite(pin_hop_timer, channelNumber & 1); + reloadChannelTimer = (startAmount != settings.maxDwellTime); timeToHop = false; channelTimerStart = millis(); //startChannelTimer - ISR updates value channelTimerMsec = startAmount; //startChannelTimer - ISR updates value @@ -3013,6 +3002,7 @@ void stopChannelTimer() //Turn off the channel timer channelTimer.disableTimer(); digitalWrite(pin_hop_timer, channelNumber & 1); + channelTimerMsec = 0; //Indicate that the timer is off triggerEvent(TRIGGER_HOP_TIMER_STOP); timeToHop = false; @@ -3239,6 +3229,9 @@ void syncChannelTimer() //Compute the next hop time msToNextHop = rmtHopTimeMsec + adjustment; + //When the ISR fires, reload the channel timer with settings.maxDwellTime + reloadChannelTimer = true; + //Restart the channel timer channelTimer.setInterval_MS(msToNextHop, channelTimerHandler); //Adjust our hardware timer to match our mate's digitalWrite(pin_hop_timer, channelNumber & 1); diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 927a7b79..6251c474 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -243,18 +243,23 @@ void updateRadioState() | P2P_WAIT_TX_FIND_PARTNER_DONE | = true | | | | | | | | | Tx Complete - - - - - > | Rx FIND_PARTNER | | - | | Start Rx | Start HOP Timer | | - | | MAX_PACKET_SIZE | clockSyncReceiver | | - | V | = false | | + | | Start Rx | clockSyncReceiver | | + | | MAX_PACKET_SIZE | = false | | + | V | | | `---- P2P_WAIT_SYNC_CLOCKS | | | | | Tx SYNC_CLOCKS | | | V | | | P2P_WAIT_TX_SYNC_CLOCKS_DONE | | | | | | - Rx SYNC_CLOCKS | < - - - - - - - - - - - | Tx Complete | | + Rx Complete ISR | < - - - - - - - - - - - | Tx Complete ISR | | + | | Start HOP Timer | | + | Start HOP Timer | | | + | V | | + V | In state machine | | + Rx SYNC_CLOCKS | In state machine | Tx Complete | | | | Start Rx | | - | Start HOP Timer | MAX_PACKET_SIZE | | - | Sync HOP Timer | | | + | | MAX_PACKET_SIZE | | + | | | | | V Timeout | | | P2P_WAIT_ZERO_ACKS ----------------' | | | | @@ -321,9 +326,6 @@ void updateRadioState() //Compute the receive time COMPUTE_RX_TIME(rxData, 1); - //Start the channel timer - startChannelTimer(); //Start hopping - P2P clock source - //This system is the source of clock synchronization clockSyncReceiver = false; //P2P clock source @@ -338,6 +340,7 @@ void updateRadioState() triggerEvent(TRIGGER_TX_SYNC_CLOCKS); if (xmitDatagramP2PSyncClocks() == true) { + startChannelTimerPending = true; //Starts at SYNC_CLOCKS TX done sf6ExpectedSize = headerBytes + P2P_ZERO_ACKS_BYTES + trailerBytes; //Tell SF6 we expect ZERO_ACKS to contain millis info changeState(RADIO_P2P_WAIT_TX_SYNC_CLOCKS_DONE); } @@ -367,6 +370,7 @@ void updateRadioState() COMPUTE_TX_TIME(); triggerEvent(TRIGGER_TX_DONE); transactionComplete = false; //Reset ISR flag + startChannelTimerPending = true; //Starts at RX of SYNC_CLOCKS frame returnToReceiving(); changeState(RADIO_P2P_WAIT_SYNC_CLOCKS); } @@ -378,6 +382,13 @@ void updateRadioState() //Decode the received packet PacketType packetType = rcvDatagram(); + //Restart the channel timer when the SYNC_CLOCKS frame is received + if (packetType != DATAGRAM_SYNC_CLOCKS) + { + stopChannelTimer(); //Restart channel timer if SYNC_CLOCKS not RX + startChannelTimerPending = true; //Restart channel timer if SYNC_CLOCKS not RX + } + //Process the received datagram switch (packetType) { @@ -416,10 +427,6 @@ void updateRadioState() case DATAGRAM_SYNC_CLOCKS: //Received SYNC_CLOCKS - //Start the channel timer - startChannelTimer(); //Start hopping - P2P clock receiver - syncChannelTimer(); - //Display the channelTimer sink if (settings.debugSync) { @@ -430,6 +437,9 @@ void updateRadioState() //Compute the receive time COMPUTE_RX_TIME(rxData + 1, 1); + //Hop to the next channel + hopChannel(); + //Acknowledge the SYNC_CLOCKS triggerEvent(TRIGGER_TX_ZERO_ACKS); if (xmitDatagramP2PZeroAcks() == true) @@ -477,6 +487,9 @@ void updateRadioState() COMPUTE_TX_TIME(); triggerEvent(TRIGGER_TX_DONE); transactionComplete = false; //Reset ISR flag + + //Hop to the next channel + hopChannel(); returnToReceiving(); changeState(RADIO_P2P_WAIT_ZERO_ACKS); } From ab5b53774f0fa4474f519c8390d9d0e3323fedf4 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Wed, 4 Jan 2023 14:42:56 -1000 Subject: [PATCH 02/25] Compute the retransmit timeout (backoff) in retransmitDatagram --- Firmware/LoRaSerial_Firmware/Radio.ino | 6 +- Firmware/LoRaSerial_Firmware/States.ino | 106 ++++++++++++------------ 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 86267ae4..e11f40a2 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -2961,7 +2961,11 @@ bool retransmitDatagram(VIRTUAL_CIRCUIT * vc) frameAirTime += responseDelay; datagramTimer = millis(); //Move timestamp even if error - retransmitTimeout = random(ackAirTime, frameAirTime + ackAirTime); //Wait this number of ms between retransmits. Increases with each re-transmit. + + //Compute the interval before a retransmission occurs in milliseconds, + //this value increases with each transmission + retransmitTimeout = random(ackAirTime, frameAirTime + ackAirTime) + * (frameSentCount + 1) * 3 / 2; //BLink the RX LED if (settings.selectLedUse == LEDS_RADIO_USE) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 6251c474..88f6d8c8 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -836,73 +836,71 @@ void updateRadioState() //An ACK is expected when the ACK timer running else if (ackTimer) { - //Check for ACK timeout - if ((millis() - ackTimer) >= (frameAirTime + ackAirTime + settings.overheadTime + getReceiveCompletionOffset())) + //Throttle back retransmits based on the number of retransmits we've attempted + //retransmitTimeout is a random number set in retransmitDatagram + if (millis() - datagramTimer > retransmitTimeout) { //Determine if another retransmission is allowed if ((!settings.maxResends) || (frameSentCount < settings.maxResends)) { - //Throttle back retransmits based on the number of retransmits we've attempted - //retransmitTimeout is a random number, set when the first datagram is sent - if (millis() - datagramTimer > (frameSentCount * retransmitTimeout)) + lostFrames++; + + //Display the retransmit + if (settings.debugDatagrams) { - lostFrames++; + systemPrintTimestamp(); + systemPrintln("RX: ACK Timeout"); + outputSerialData(true); + } - //Display the retransmit - if (settings.debugDatagrams) + if (settings.debugDatagrams) + { + systemPrintTimestamp(); + systemPrint("TX: Retransmit "); + systemPrint(frameSentCount); + systemPrint(", "); + systemPrint(radioDatagramType[txControl.datagramType]); + switch (txControl.datagramType) { - systemPrintTimestamp(); - systemPrintln("RX: ACK Timeout"); - outputSerialData(true); - } + default: + systemPrintln(); + break; - triggerEvent(TRIGGER_RETRANSMIT); - if (settings.debugDatagrams) - { - systemPrintTimestamp(); - systemPrint("TX: Retransmit "); - systemPrint(frameSentCount); - systemPrint(", "); - systemPrint(radioDatagramType[txControl.datagramType]); - switch (txControl.datagramType) - { - default: - systemPrintln(); - break; - - case DATAGRAM_DATA: - case DATAGRAM_DATA_ACK: - case DATAGRAM_REMOTE_COMMAND: - case DATAGRAM_REMOTE_COMMAND_RESPONSE: - case DATAGRAM_HEARTBEAT: - systemPrint(" (ACK #"); - systemPrint(txControl.ackNumber); - systemPrint(")"); - systemPrintln(); - break; - } - outputSerialData(true); + case DATAGRAM_DATA: + case DATAGRAM_DATA_ACK: + case DATAGRAM_REMOTE_COMMAND: + case DATAGRAM_REMOTE_COMMAND_RESPONSE: + case DATAGRAM_HEARTBEAT: + systemPrint(" (ACK #"); + systemPrint(txControl.ackNumber); + systemPrint(")"); + systemPrintln(); + break; } + outputSerialData(true); + } - //Attempt the retransmission - RESTORE_TX_BUFFER(); - if (rexmtControl.datagramType == DATAGRAM_HEARTBEAT) + //Attempt the retransmission + RESTORE_TX_BUFFER(); + if (rexmtControl.datagramType == DATAGRAM_HEARTBEAT) + { + //Never retransmit the heartbeat, always send a new version to + //send the updated time value + if (xmitDatagramP2PHeartbeat() == true) { - //Never retransmit the heartbeat, always send a new version to - //send the updated time value - if (xmitDatagramP2PHeartbeat() == true) - { - changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); - triggerEvent(TRIGGER_TX_HEARTBEAT); - } - } - else if (retransmitDatagram(NULL) == true) + triggerEvent(TRIGGER_TX_HEARTBEAT); changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); - - //We're re-sending data, so don't be the first to send next heartbeat - setHeartbeatLong(); - START_ACK_TIMER(); + } } + else if (retransmitDatagram(NULL) == true) + { + triggerEvent(TRIGGER_RETRANSMIT); + changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); + } + + //We're re-sending data, so don't be the first to send next heartbeat + setHeartbeatLong(); + START_ACK_TIMER(); } else { From 798d3096cde48e319d37647df03b9366890266c1 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Fri, 6 Jan 2023 12:57:51 -1000 Subject: [PATCH 03/25] Clear timeToHop when delaying the hop --- Firmware/LoRaSerial_Firmware/Radio.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index e11f40a2..0cd3df6b 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3076,6 +3076,7 @@ void syncChannelTimer() //the channelTimerStart value indicated that it was done. The channel //timer update will add only microseconds to when the hop is done. delayedHopCount = timeToHop ? 1 : 0; + timeToHop = false; //The radios are using the same frequencies since the frame was successfully //received. The goal is to adjust the channel timer to fire in close proximity From ab58f447fd0be3a5c2453a84af002d8936c61a99 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Fri, 6 Jan 2023 12:59:12 -1000 Subject: [PATCH 04/25] Don't spin forever when a channel timer value error is detected on RX --- Firmware/LoRaSerial_Firmware/Radio.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 0cd3df6b..6c1eefc7 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3059,7 +3059,7 @@ void syncChannelTimer() systemPrint("ERROR: Invalid msToNextHopRemote value, "); systemPrintln(msToNextHopRemote); - waitForever(); + return; } } //ENABLE_DEVELOPER From e9519a760767ce60f01c1de24a62823973348fc7 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Fri, 6 Jan 2023 13:18:07 -1000 Subject: [PATCH 05/25] Fix the case when both the remote and local values are negative --- Firmware/LoRaSerial_Firmware/Radio.ino | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 6c1eefc7..f3a59320 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3105,8 +3105,7 @@ void syncChannelTimer() adjustment = 0; #define REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC 2 -#define CHANNEL_TIMER_SYNC_PLUS_MINUS_MSEC 3 -#define CHANNEL_TIMER_SYNC_MSEC (2 * CHANNEL_TIMER_SYNC_PLUS_MINUS_MSEC) +#define CHANNEL_TIMER_SYNC_MSEC (txRxTimeMsec + REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) //Determine if the remote has hopped or is likely to hop very soon if (rmtHopTimeMsec <= REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) @@ -3115,7 +3114,7 @@ void syncChannelTimer() adjustment += settings.maxDwellTime; //Determine if the local system has just hopped - if (lclHopTimeMsec <= CHANNEL_TIMER_SYNC_MSEC) + if (((unsigned long)lclHopTimeMsec) <= CHANNEL_TIMER_SYNC_MSEC) { //Case 1 above, both systems using the same channel // @@ -3176,7 +3175,7 @@ void syncChannelTimer() { //The remote system has not hopped //Determine if the local system has just hopped - if (lclHopTimeMsec <= CHANNEL_TIMER_SYNC_MSEC) + if (((unsigned long)lclHopTimeMsec) <= CHANNEL_TIMER_SYNC_MSEC) { //Case 3 above, extend the channel timer value // From fc83ce5e3503004905a60d3f5d71af08d1ecc74b Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sat, 7 Jan 2023 12:34:07 -1000 Subject: [PATCH 06/25] Transmit a valid value of channelTimer when the timer is not running --- Firmware/LoRaSerial_Firmware/Radio.ino | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index f3a59320..a9595f23 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -2493,7 +2493,11 @@ bool transmitDatagram() if (timeToHop) hopChannel(); unsigned long currentMillis = millis(); - uint16_t msToNextHop = channelTimerMsec - (currentMillis - channelTimerStart); //TX channel timer value + uint16_t msToNextHop; //TX channel timer value + if (channelTimerMsec) + msToNextHop = channelTimerMsec - (currentMillis - channelTimerStart); + else + msToNextHop = settings.maxDwellTime; //Validate this value if (ENABLE_DEVELOPER && (!clockSyncReceiver)) From 6542a09ea32d356dd992dc677e70dc67a736d241 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sat, 7 Jan 2023 12:34:55 -1000 Subject: [PATCH 07/25] Stop the channel timer when the P2P link is down --- Firmware/LoRaSerial_Firmware/States.ino | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 88f6d8c8..b9ce991b 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -293,6 +293,10 @@ void updateRadioState() setRadioFrequency(false); } + //Stop the channel timer if it is running + if (channelTimerMsec) + stopChannelTimer(); + //Determine if a FIND_PARTNER was received if (transactionComplete) { From 2be3dabe2409fbcb812546fdf813c9bcc45c27a2 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sat, 7 Jan 2023 12:49:52 -1000 Subject: [PATCH 08/25] Better handle the LEDs Perform the LED operations conditionally depending upon the state of settings.selectLedUse. This commit: * Adds a routine to blink the RSSI LED or LEDs * Renames the routine to blink the serial TX LED * Renames the routine to blink the serial RX LED * Adds a routine to blink the radio RX LED * Adds a routine to blink the radio TX LED * Adds a routine to blink the heartbeat LED * Adds a routine to blink the hop LED --- .../LoRaSerial_Firmware.ino | 23 +- Firmware/LoRaSerial_Firmware/Radio.ino | 13 +- Firmware/LoRaSerial_Firmware/Serial.ino | 8 +- Firmware/LoRaSerial_Firmware/States.ino | 7 +- Firmware/LoRaSerial_Firmware/System.ino | 301 ++++++++++++------ 5 files changed, 214 insertions(+), 138 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index 3ebadf15..62a31a3b 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -125,14 +125,16 @@ uint8_t pin_hop_timer = PIN_UNDEFINED; #define RADIO_USE_BAD_FRAMES_LED BLUE_LED //Blue #define RADIO_USE_BAD_CRC_LED YELLOW_LED //Yellow -#define LED_MP_HOP_CHANNEL BLUE_LED -#define LED_MP_HEARTBEAT YELLOW_LED +#define LED_MP_HEARTBEAT BLUE_LED +#define LED_MP_HOP_CHANNEL YELLOW_LED #define CYLON_TX_DATA_LED BLUE_LED #define CYLON_RX_DATA_LED YELLOW_LED #define LED_ON HIGH #define LED_OFF LOW + +#define LED_MAX_PULSE_WIDTH 24 //mSec //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Radio Library @@ -364,10 +366,8 @@ bool writeOnCommandExit = false; //Goes true if user specifies ATW command //Global variables - LEDs //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -int trainCylonNumber = 0b0001; -int trainCylonDirection = -1; - -unsigned long lastTrainBlink = 0; //Controls LED during training +uint8_t cylonLedPattern = 0b0001; +bool cylonPatternGoingLeft; //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Global variables - Radio (General) @@ -390,19 +390,8 @@ float frequencyCorrection = 0; //Adjust receive freq based on the last packet re volatile bool hop = true; //Clear the DIO1 hop ISR when possible -//RSSI must be above these negative numbers for LED to illuminate -const int rssiLevelLow = -150; -const int rssiLevelMed = -120; -const int rssiLevelHigh = -100; -const int rssiLevelMax = -70; int rssi; //Average signal level, measured during reception of a packet -//LED control values -uint32_t ledPreviousRssiMillis; -uint32_t ledHeartbeatMillis; -int8_t ledRssiCount; //Pulse width count for LED display -int8_t ledRssiValue; //Target pulse width count for LED display - //Link quality metrics uint32_t datagramsSent; //Total number of datagrams sent uint32_t datagramsReceived; //Total number of datagrams received diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index a9595f23..fe1929a1 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -558,6 +558,7 @@ void hopChannel(bool moveForwardThroughTable, uint8_t channelCount) //Select the new frequency setRadioFrequency(radioStateTable[radioState].rxState); + blinkChannelHopLed(true); } //Determine the time in milliseconds when channel zero is reached again @@ -2190,10 +2191,7 @@ PacketType rcvDatagram() linkDownTimer = millis(); //Blink the RX LED - if (settings.selectLedUse == LEDS_RADIO_USE) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_ON); - else if (settings.selectLedUse == LEDS_CYLON) - digitalWrite(CYLON_RX_DATA_LED, LED_ON); + blinkRadioRxLed(true); return datagramType; } @@ -2971,11 +2969,8 @@ bool retransmitDatagram(VIRTUAL_CIRCUIT * vc) retransmitTimeout = random(ackAirTime, frameAirTime + ackAirTime) * (frameSentCount + 1) * 3 / 2; - //BLink the RX LED - if (settings.selectLedUse == LEDS_RADIO_USE) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_ON); - else if (settings.selectLedUse == LEDS_CYLON) - digitalWrite(CYLON_TX_DATA_LED, LED_ON); + //BLink the TX LED + blinkRadioTxLed(true); return (true); //Transmission has started } diff --git a/Firmware/LoRaSerial_Firmware/Serial.ino b/Firmware/LoRaSerial_Firmware/Serial.ino index dc31408e..7a13f2d4 100644 --- a/Firmware/LoRaSerial_Firmware/Serial.ino +++ b/Firmware/LoRaSerial_Firmware/Serial.ino @@ -247,7 +247,7 @@ void updateSerial() previousHead = rxHead; while (rtsAsserted && arch.serialAvailable() && (transactionComplete == false)) { - rxLED(true); //Turn on LED during serial reception + blinkSerialRxLed(true); //Turn on LED during serial reception //Take a break if there are ISRs to attend to petWDT(); @@ -262,7 +262,7 @@ void updateSerial() serialReceiveBuffer[rxHead++] = incoming; //Push char to holding buffer rxHead %= sizeof(serialReceiveBuffer); } //End Serial.available() - rxLED(false); //Turn off LED + blinkSerialRxLed(false); //Turn off LED //Print the number of bytes received via serial if (settings.debugSerial && (rxHead - previousHead)) @@ -440,7 +440,7 @@ void outputSerialData(bool ignoreISR) dataBytes = availableTXBytes(); while (dataBytes-- && isCTS() && (ignoreISR || (!transactionComplete))) { - txLED(true); //Turn on LED during serial transmissions + blinkSerialTxLed(true); //Turn on LED during serial transmissions //Take a break if there are ISRs to attend to petWDT(); @@ -452,7 +452,7 @@ void outputSerialData(bool ignoreISR) txTail++; txTail %= sizeof(serialTransmitBuffer); } - txLED(false); //Turn off LED + blinkSerialTxLed(false); //Turn off LED } //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index b9ce991b..057add9b 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -741,6 +741,7 @@ void updateRadioState() //Adjust the timestamp offset COMPUTE_TIMESTAMP_OFFSET(rxData, 1); triggerEvent(TRIGGER_RX_HEARTBEAT); + blinkHeartbeatLed(true); //Transmit ACK P2P_SEND_ACK(TRIGGER_TX_ACK); @@ -1288,7 +1289,7 @@ void updateRadioState() lastPacketReceived = millis(); //Update timestamp for Link LED - ledMpHeartbeatOn(); + blinkHeartbeatLed(true); changeState(RADIO_MP_STANDBY); break; @@ -1330,7 +1331,6 @@ void updateRadioState() setHeartbeatMultipoint(); //We're sending something with clock data so reset heartbeat timer changeState(RADIO_MP_WAIT_TX_DONE); //Wait for heartbeat to transmit } - ledMpHeartbeatOn(); } } @@ -2903,6 +2903,9 @@ void vcReceiveHeartbeat(uint32_t rxMillis) //Update the timestamp offset if (rxSrcVc == VC_SERVER) { + //Blink the heartbeat LED + blinkHeartbeatLed(true); + //Assume client and server are running the same level of debugging, //then the delay from reading the millisecond value on the server should //get offset by the transmit setup time and the receive overhead time. diff --git a/Firmware/LoRaSerial_Firmware/System.ino b/Firmware/LoRaSerial_Firmware/System.ino index 0d17a326..9e2de995 100644 --- a/Firmware/LoRaSerial_Firmware/System.ino +++ b/Firmware/LoRaSerial_Firmware/System.ino @@ -592,31 +592,166 @@ void setRSSI(uint8_t ledBits) digitalWrite(pin_rssi4LED, LOW); } +//Start the cylon LEDs +void startCylonLEDs() +{ + cylonLedPattern = 0b0001; + cylonPatternGoingLeft = true; +} + +//Update the RSSI LED or LEDs +void blinkRadioRssiLed() +{ + unsigned long currentMillis; + static uint32_t ledPreviousRssiMillis; + static unsigned long lastCylonBlink; //Controls LED during training + static int8_t ledRssiPulseWidth; //Target pulse width for LED display + + currentMillis = millis(); + switch (settings.selectLedUse) + { + //Update the pattern on the 4 green LEDs + case LEDS_CYLON: + //Determine if it is time to update the cylon pattern + if ((currentMillis - lastCylonBlink) > 75) + { + lastCylonBlink = currentMillis; + + //The following shows the cylon pattern in four LEDs + // + // LED3 LED2 LED1 LED0 + // X + // X + // X + // X + // X + // X + // X + // X + // ... + // + //Cylon the RSSI LEDs + setRSSI(cylonLedPattern); + + //Determine if a change in direction is necessary + if ((cylonLedPattern == 0b1000) || (cylonLedPattern == 0b0001)) + cylonPatternGoingLeft = cylonPatternGoingLeft ? false : true; + + if (cylonPatternGoingLeft) + cylonLedPattern <<= 1; + else + cylonLedPattern >>= 1; + } + break; + + //Update the single RSSI LED + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + //Check for the start of a new pulse + if ((currentMillis - ledPreviousRssiMillis) >= LED_MAX_PULSE_WIDTH) + { + ledPreviousRssiMillis = currentMillis; + + //Determine the RSSI LED pulse width + //The RSSI value ranges from -164 to 30 dB + ledRssiPulseWidth = (150 + rssi) / 10; + if (ledRssiPulseWidth > 0) + digitalWrite(RADIO_USE_RSSI_LED, LED_ON); + } + + //Check for time to turn off the LED + else if ((currentMillis - ledPreviousRssiMillis) >= ledRssiPulseWidth) + digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); + break; + + //Update the 4 green LEDs based upon the RSSI value + case LEDS_RSSI: + if (rssi > -70) + setRSSI(0b1111); + else if (rssi > -100) + setRSSI(0b0111); + else if (rssi > -120) + setRSSI(0b0011); + else if (rssi > -150) + setRSSI(0b0001); + else + setRSSI(0); + break; + } +} + //Set the serial TX (blue) LED value -void txLED(bool illuminate) +void blinkSerialTxLed(bool illuminate) { - if (settings.selectLedUse != LEDS_RSSI) - return; - if (pin_txLED != PIN_UNDEFINED) + switch (settings.selectLedUse) { - if (illuminate == true) - digitalWrite(pin_txLED, HIGH); - else - digitalWrite(pin_txLED, LOW); + case LEDS_RSSI: + if (pin_txLED != PIN_UNDEFINED) + { + if (illuminate == true) + digitalWrite(pin_txLED, HIGH); + else + digitalWrite(pin_txLED, LOW); + } + break; } } //Set the serial RX (yellow) LED value -void rxLED(bool illuminate) +void blinkSerialRxLed(bool illuminate) { - if (settings.selectLedUse != LEDS_RSSI) - return; - if (pin_rxLED != PIN_UNDEFINED) + switch (settings.selectLedUse) + { + case LEDS_RSSI: + if (illuminate == true) + digitalWrite(pin_rxLED, HIGH); + else + digitalWrite(pin_rxLED, LOW); + break; + } +} + +//Blink the RX LED +void blinkRadioRxLed(bool on) +{ + switch (settings.selectLedUse) + { + case LEDS_CYLON: + if (on) + digitalWrite(CYLON_RX_DATA_LED, LED_ON); + else if ((millis() - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(CYLON_RX_DATA_LED, LED_OFF); + break; + + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + if (on) + digitalWrite(RADIO_USE_RX_DATA_LED, LED_ON); + else if ((millis() - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + break; + } +} + +//BLink the TX LED +void blinkRadioTxLed(bool on) +{ + switch (settings.selectLedUse) { - if (illuminate == true) - digitalWrite(pin_rxLED, HIGH); - else - digitalWrite(pin_rxLED, LOW); + case LEDS_CYLON: + if (on) + digitalWrite(CYLON_TX_DATA_LED, LED_ON); + else if ((millis() - datagramTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(CYLON_TX_DATA_LED, LED_OFF); + break; + + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + if (on) + digitalWrite(RADIO_USE_TX_DATA_LED, LED_ON); + else if ((millis() - datagramTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + break; } } @@ -636,16 +771,19 @@ void radioLeds() static uint32_t badCrcMillis; //Turn off the RX LED to end the blink - currentMillis = millis(); - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - currentMillis = millis(); - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); + + //Update the link LED + digitalWrite(RADIO_USE_LINK_LED, isLinked() ? LED_ON : LED_OFF); + + //Update the RSSI LED + blinkRadioRssiLed(); //Blink the bad frames LED + currentMillis = millis(); if (badFrames != previousBadFrames) { previousBadFrames = badFrames; @@ -676,35 +814,39 @@ void radioLeds() badCrcMillis = 0; digitalWrite(RADIO_USE_BAD_CRC_LED, LED_OFF); } +} - //Update the link LED - digitalWrite(RADIO_USE_LINK_LED, isLinked() ? LED_ON : LED_OFF); +//Blink the heartbeat LED +void blinkHeartbeatLed(bool on) +{ + static unsigned long ledHeartbeatMillis; - //Update the RSSI LED - if (currentMillis != ledPreviousRssiMillis) + switch (settings.selectLedUse) { - if (ledRssiCount >= 16) - { - ledRssiValue = (150 + rssi) / 10; - ledRssiCount = 0; - if (ledRssiValue >= ledRssiCount) - digitalWrite(RADIO_USE_RSSI_LED, LED_ON); - } - - //Turn off the RSSI LED - else if (ledRssiValue < ledRssiCount++) - digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); + case LEDS_MULTIPOINT: + if (on) + { + digitalWrite(LED_MP_HEARTBEAT, LED_ON); + ledHeartbeatMillis = millis(); + } + else if ((millis() - ledHeartbeatMillis) > RADIO_USE_BLINK_MILLIS) + digitalWrite(LED_MP_HEARTBEAT, LED_OFF); + break; } - - //Save the last millis value - ledPreviousRssiMillis = currentMillis; } -//Turn on the heartbeat LED -void ledMpHeartbeatOn() +//Blink the channel hop LED +void blinkChannelHopLed(bool on) { - digitalWrite(LED_MP_HEARTBEAT, LED_OFF); - ledHeartbeatMillis = millis(); + switch (settings.selectLedUse) + { + case LEDS_MULTIPOINT: + if (on) + digitalWrite(LED_MP_HOP_CHANNEL, LED_ON); + else if ((millis() - radioCallHistory[RADIO_CALL_hopChannel]) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(LED_MP_HOP_CHANNEL, LED_OFF); + break; + } } //Display the multi-point LED pattern @@ -713,83 +855,37 @@ void multiPointLeds() uint32_t currentMillis; //Turn off the RX LED to end the blink - currentMillis = millis(); - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - currentMillis = millis(); - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); //Update the sync LED digitalWrite(RADIO_USE_LINK_LED, isMultiPointSync() ? LED_ON : LED_OFF); //Update the RSSI LED - if (currentMillis != ledPreviousRssiMillis) - { - if (ledRssiCount >= 16) - { - ledRssiValue = (150 + rssi) / 10; - ledRssiCount = 0; - if (ledRssiValue >= ledRssiCount) - digitalWrite(RADIO_USE_RSSI_LED, LED_ON); - } - - //Turn off the RSSI LED - else if (ledRssiValue < ledRssiCount++) - digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); - } + blinkRadioRssiLed(); //Update the hop LED if ((millis() - radioCallHistory[RADIO_CALL_hopChannel]) >= RADIO_USE_BLINK_MILLIS) digitalWrite(LED_MP_HOP_CHANNEL, LED_OFF); //Update the HEARTBEAT LED - if ((millis() - ledHeartbeatMillis) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(LED_MP_HEARTBEAT, LED_OFF); - - //Save the last millis value - ledPreviousRssiMillis = currentMillis; -} - -//Start the cylon LEDs -void startCylonLEDs() -{ - trainCylonNumber = 0b0001; - trainCylonDirection = -1; + blinkHeartbeatLed(false); } //Update the cylon LEDs void updateCylonLEDs() { - unsigned long currentMillis; - - currentMillis = millis(); - if ((currentMillis - lastTrainBlink) > 75) //Blink while unit waits in training state - { - lastTrainBlink = millis(); - - //Cylon the RSSI LEDs - setRSSI(trainCylonNumber); - - if (trainCylonNumber == 0b1000 || trainCylonNumber == 0b0001) - trainCylonDirection *= -1; //Change direction - - if (trainCylonDirection > 0) - trainCylonNumber <<= 1; - else - trainCylonNumber >>= 1; - } + //Display the cylon pattern on the RSSI LEDs + blinkRadioRssiLed(); //Use the blue and yellow LEDS to indicate radio activity during training //Turn off the RX LED to end the blink - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(CYLON_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(CYLON_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); } //Update the LED values depending upon the selected display @@ -816,14 +912,7 @@ void updateLeds() //Set LEDs according to RSSI level default: case LEDS_RSSI: - if (rssi > rssiLevelLow) - setRSSI(0b0001); - if (rssi > rssiLevelMed) - setRSSI(0b0011); - if (rssi > rssiLevelHigh) - setRSSI(0b0111); - if (rssi > rssiLevelMax) - setRSSI(0b1111); + blinkRadioRssiLed(); break; case LEDS_RADIO_USE: From 91aca0aadfb3c38f66fad1929c18544541f6e13e Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 14:17:49 -1000 Subject: [PATCH 09/25] Add the timestamp to link up and link down display --- Firmware/LoRaSerial_Firmware/States.ino | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 057add9b..3d3e72f6 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -2618,10 +2618,25 @@ void displayRadioStateHistory() //Break a point-to-point link void breakLink() { + unsigned long linkDownTime; + //Break the link + if (settings.printTimestamp) + { + linkDownTime = millis(); + + //Offset the value for display + if (!settings.displayRealMillis) + linkDownTime += timestampOffset; + } linkFailures++; if (settings.printLinkUpDown) { + if (settings.printTimestamp) + { + systemPrintTimestamp(linkDownTime); + systemPrint(": "); + } systemPrintln("--------- Link DOWN ---------"); outputSerialData(true); } @@ -2663,6 +2678,7 @@ void enterLinkUp() changeState(RADIO_P2P_LINK_UP); if (settings.printLinkUpDown) { + systemPrintTimestamp(); systemPrintln("========== Link UP =========="); outputSerialData(true); } From 3f2898dc034e0bf92d429635d05ac8c5aba07e28 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 14:28:23 -1000 Subject: [PATCH 10/25] Add frameAirTimeMsec parameter to syncChannelTime --- Firmware/LoRaSerial_Firmware/Radio.ino | 8 ++++---- Firmware/LoRaSerial_Firmware/States.ino | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index fe1929a1..9d7d4dcb 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3013,7 +3013,7 @@ void stopChannelTimer() //Given the remote unit's number of ms before its next hop, //adjust our own channelTimer interrupt to be synchronized with the remote unit -void syncChannelTimer() +void syncChannelTimer(uint16_t frameAirTimeMsec) { int16_t adjustment; unsigned long currentMillis; @@ -3097,14 +3097,14 @@ void syncChannelTimer() //Compute the remote system's channel timer firing time offset in milliseconds //using the channel timer value and the adjustments for transmit and receive //time (time of flight) - rmtHopTimeMsec = msToNextHopRemote - txRxTimeMsec; + rmtHopTimeMsec = msToNextHopRemote - frameAirTimeMsec; //Compute the when the local system last hopped lclHopTimeMsec = currentMillis - channelTimerStart; adjustment = 0; #define REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC 2 -#define CHANNEL_TIMER_SYNC_MSEC (txRxTimeMsec + REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) +#define CHANNEL_TIMER_SYNC_MSEC (frameAirTimeMsec + REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) //Determine if the remote has hopped or is likely to hop very soon if (rmtHopTimeMsec <= REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) @@ -3260,7 +3260,7 @@ void syncChannelTimer() systemPrint(" Hops, "); systemPrint(msToNextHopRemote); systemPrint(" Nxt Hop - "); - systemPrint(txRxTimeMsec); + systemPrint(frameAirTimeMsec); systemPrint(" (TX + RX)"); if (adjustment) { diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 3d3e72f6..a217f9e1 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -766,7 +766,7 @@ void updateRadioState() COMPUTE_RX_TIME(rxData, 1); //The datagram we are expecting - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock triggerEvent(TRIGGER_RX_ACK); @@ -1122,7 +1122,7 @@ void updateRadioState() COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0); //Server has responded with ACK - syncChannelTimer(); //Start and adjust freq hop ISR based on remote's remaining clock + syncChannelTimer(txRxTimeMsec); //Start and adjust freq hop ISR based on remote's remaining clock //Change to the server's channel number channelNumber = rxVcData[0]; @@ -1283,7 +1283,7 @@ void updateRadioState() //Sync clock if server sent the heartbeat if (settings.server == false) - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock frequencyCorrection += radio.getFrequencyError() / 1000000.0; @@ -1299,7 +1299,7 @@ void updateRadioState() //Sync clock if server sent the datagram if (settings.server == false) - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock setHeartbeatMultipoint(); //We're sync'd so reset heartbeat timer @@ -2914,7 +2914,7 @@ void vcReceiveHeartbeat(uint32_t rxMillis) int vcSrc; if (rxSrcVc == VC_SERVER) - syncChannelTimer(); //Adjust freq hop ISR based on server's remaining clock + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on server's remaining clock //Update the timestamp offset if (rxSrcVc == VC_SERVER) From 72e44927074c2d1e8d9550277983597c7f24ebb1 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 14:52:33 -1000 Subject: [PATCH 11/25] Add channelTimer history --- .../LoRaSerial_Firmware.ino | 2 + Firmware/LoRaSerial_Firmware/Radio.ino | 18 +++++-- Firmware/LoRaSerial_Firmware/States.ino | 52 +++++++++++++++++-- Firmware/LoRaSerial_Firmware/settings.h | 11 ++++ 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index 62a31a3b..90c8e154 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -429,6 +429,8 @@ unsigned long radioStateHistory[RADIO_MAX_STATE]; uint8_t packetLength = 0; //Total bytes received, used for calculating clock sync times in multi-point mode int16_t msToNextHopRemote; //Can become negative +uint8_t clockSyncIndex; +CLOCK_SYNC_DATA clockSyncData[16]; bool requestYield = false; //Datagram sender can tell this radio to stop transmitting to enable two-way comm unsigned long yieldTimerStart = 0; diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 9d7d4dcb..d008d3ba 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3075,7 +3075,6 @@ void syncChannelTimer(uint16_t frameAirTimeMsec) //the channelTimerStart value indicated that it was done. The channel //timer update will add only microseconds to when the hop is done. delayedHopCount = timeToHop ? 1 : 0; - timeToHop = false; //The radios are using the same frequencies since the frame was successfully //received. The goal is to adjust the channel timer to fire in close proximity @@ -3235,20 +3234,31 @@ void syncChannelTimer(uint16_t frameAirTimeMsec) //When the ISR fires, reload the channel timer with settings.maxDwellTime reloadChannelTimer = true; + //Log the previous clock sync + clockSyncData[clockSyncIndex].msToNextHop = msToNextHop; + clockSyncData[clockSyncIndex].frameAirTimeMsec = frameAirTimeMsec; + clockSyncData[clockSyncIndex].msToNextHopRemote = msToNextHopRemote; + clockSyncData[clockSyncIndex].adjustment = adjustment; + clockSyncData[clockSyncIndex].delayedHopCount = delayedHopCount; + clockSyncData[clockSyncIndex].lclHopTimeMsec = lclHopTimeMsec; + clockSyncData[clockSyncIndex].timeToHop = timeToHop; + clockSyncIndex += 1; + //Restart the channel timer + timeToHop = false; channelTimer.setInterval_MS(msToNextHop, channelTimerHandler); //Adjust our hardware timer to match our mate's digitalWrite(pin_hop_timer, channelNumber & 1); channelTimerStart = currentMillis; channelTimerMsec = msToNextHop; //syncChannelTimer update channelTimer.enableTimer(); - //Trigger after adjustments to timer to avoid skew during debug - triggerEvent(TRIGGER_SYNC_CHANNEL_TIMER); - //---------------------------------------------------------------------- // Leave the critical section //---------------------------------------------------------------------- + //Trigger after adjustments to timer to avoid skew during debug + triggerEvent(TRIGGER_SYNC_CHANNEL_TIMER); + //Hop if the timer fired prior to disabling the timer, resetting the channelTimerStart value if (delayedHopCount) hopChannel(true, delayedHopCount); diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index a217f9e1..759b640e 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -1355,6 +1355,7 @@ void updateRadioState() systemPrintln("HEARTBEAT Timeout"); outputSerialData(true); } + dumpClockSynchronization(); changeState(RADIO_DISCOVER_BEGIN); } } @@ -2615,6 +2616,43 @@ void displayRadioStateHistory() petWDT(); } +//Dump the clock synchronization data +void dumpClockSynchronization() +{ + //Dump the clock sync data + petWDT(); + for (uint8_t x = 0; x < (sizeof(clockSyncData) / sizeof(clockSyncData[0])); x++) + { + uint8_t index = (x + clockSyncIndex) % clockSyncIndex; + if (clockSyncData[index].frameAirTimeMsec) + { + systemPrint("Lcl: "); + systemPrint(clockSyncData[index].lclHopTimeMsec); + systemPrint(", Rmt: "); + systemPrint(clockSyncData[index].msToNextHopRemote); + systemPrint(" - "); + systemPrint(clockSyncData[index].frameAirTimeMsec); + systemPrint(" = "); + systemPrint(clockSyncData[index].msToNextHopRemote - clockSyncData[index].frameAirTimeMsec); + systemPrint(" + "); + systemPrint(clockSyncData[index].adjustment); + systemPrint(" = "); + systemPrint(clockSyncData[index].msToNextHop); + systemPrint(" msToNextHop"); + if (clockSyncData[index].delayedHopCount) + { + systemPrint(", timeToHop: "); + systemPrint(clockSyncData[index].timeToHop); + systemPrint(", Hops: "); + systemPrint(clockSyncData[index].delayedHopCount); + } + systemPrintln(); + outputSerialData(true); + petWDT(); + } + } +} + //Break a point-to-point link void breakLink() { @@ -2630,6 +2668,13 @@ void breakLink() linkDownTime += timestampOffset; } linkFailures++; + + //Stop the ACK timer + STOP_ACK_TIMER(); + + //Dump the clock synchronization + dumpClockSynchronization(); + if (settings.printLinkUpDown) { if (settings.printTimestamp) @@ -2640,13 +2685,12 @@ void breakLink() systemPrintln("--------- Link DOWN ---------"); outputSerialData(true); } - triggerEvent(TRIGGER_RADIO_RESET); - - //Stop the ACK timer - STOP_ACK_TIMER(); //Flush the buffers resetSerial(); + + //Reset the radio and the link + triggerEvent(TRIGGER_RADIO_RESET); changeState(RADIO_RESET); } diff --git a/Firmware/LoRaSerial_Firmware/settings.h b/Firmware/LoRaSerial_Firmware/settings.h index cf1e1a40..694776e0 100644 --- a/Firmware/LoRaSerial_Firmware/settings.h +++ b/Firmware/LoRaSerial_Firmware/settings.h @@ -353,6 +353,17 @@ typedef enum //Add user LED types from 255 working down } LEDS_USE_TYPE; +typedef struct _CLOCK_SYNC_DATA +{ + int16_t msToNextHopRemote; + uint16_t frameAirTimeMsec; + uint16_t msToNextHop; + int16_t lclHopTimeMsec; + int16_t adjustment; + int8_t delayedHopCount; + bool timeToHop; +} CLOCK_SYNC_DATA; + //These are all the settings that can be set on Serial Terminal Radio. It's recorded to NVM. typedef struct struct_settings { uint16_t sizeOfSettings = 0; //sizeOfSettings **must** be the first entry and must be int From ca7dde8d9b9aff55cca8a2d1117982f7bc28f98b Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 15:19:12 -1000 Subject: [PATCH 12/25] Add a trigger to measure the transmit time --- Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino | 1 + Firmware/LoRaSerial_Firmware/Radio.ino | 12 +++++------- Firmware/LoRaSerial_Firmware/settings.h | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index 90c8e154..b24d4afe 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -584,6 +584,7 @@ uint32_t rxTimeUsec; uint32_t txTimeUsec; uint16_t txRxTimeMsec; +uint32_t txSetChannelTimerMicros; //Timestamp when millis is read in TX routine to set channel timer value uint32_t transactionCompleteMicros; //Timestamp at the beginning of the transactionCompleteIsr routine uint32_t txDatagramMicros; //Timestamp at the beginning of the transmitDatagram routine uint16_t maxFrameAirTime; //Air time of the maximum sized message diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index d008d3ba..e6df726b 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -2490,6 +2490,10 @@ bool transmitDatagram() //Hake sure that the transmitted msToNextHop is in the range 0 - maxDwellTime if (timeToHop) hopChannel(); + + //Measure the time to the next hop + triggerEvent(TRIGGER_TX_LOAD_CHANNE_TIMER_VALUE); + txSetChannelTimerMicros = micros(); unsigned long currentMillis = millis(); uint16_t msToNextHop; //TX channel timer value if (channelTimerMsec) @@ -2534,13 +2538,7 @@ bool transmitDatagram() { case DATAGRAM_DATA_ACK: case DATAGRAM_SYNC_CLOCKS: - systemPrint("TX: "); - systemPrint(channelTimerMsec); - systemPrint(" channelTimerMsec + ("); - systemPrint(currentMillis); - systemPrint(" millis() - "); - systemPrint(channelTimerStart); - systemPrint(" channelTimerStart) = "); + systemPrint("TX msToNextHop: "); systemPrint(msToNextHop); systemPrintln(" mSec"); break; diff --git a/Firmware/LoRaSerial_Firmware/settings.h b/Firmware/LoRaSerial_Firmware/settings.h index 694776e0..64f80453 100644 --- a/Firmware/LoRaSerial_Firmware/settings.h +++ b/Firmware/LoRaSerial_Firmware/settings.h @@ -286,6 +286,7 @@ enum TRIGGER_TX_DUPLICATE_ACK, TRIGGER_TX_FIND_PARTNER, TRIGGER_TX_HEARTBEAT, + TRIGGER_TX_LOAD_CHANNE_TIMER_VALUE, TRIGGER_TX_SPI_DONE, TRIGGER_TX_SYNC_CLOCKS, TRIGGER_TX_YIELD, From 1743f9daf8706f3301d56d9ef36c93667210060b Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 15:52:59 -1000 Subject: [PATCH 13/25] Compute the transmit times for SYNC_CLOCKS, HEARTBEAT and DATA_ACK --- .../LoRaSerial_Firmware.ino | 3 + Firmware/LoRaSerial_Firmware/States.ino | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index b24d4afe..e93d9d6b 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -586,6 +586,9 @@ uint16_t txRxTimeMsec; uint32_t txSetChannelTimerMicros; //Timestamp when millis is read in TX routine to set channel timer value uint32_t transactionCompleteMicros; //Timestamp at the beginning of the transactionCompleteIsr routine +uint32_t txDataAckUsec; //Time in microseconds to transmit the DATA_ACK frame +uint32_t txHeartbeatUsec; //Time in microseconds to transmit the HEARTBEAT frame +uint32_t txSyncClockUsec; //Time in microseconds to transmit the SYNC_CLOCKS frame uint32_t txDatagramMicros; //Timestamp at the beginning of the transmitDatagram routine uint16_t maxFrameAirTime; //Air time of the maximum sized message unsigned long remoteSystemMillis; //Millis value contained in the received message diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 759b640e..626262a3 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -488,6 +488,17 @@ void updateRadioState() //Determine if a SYNC_CLOCKS has completed transmission if (transactionComplete) { + //Compute the SYNC_CLOCKS frame transmit frame + if ((!txSyncClockUsec) && (txControl.datagramType == DATAGRAM_SYNC_CLOCKS)) + { + txSyncClockUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } + COMPUTE_TX_TIME(); triggerEvent(TRIGGER_TX_DONE); transactionComplete = false; //Reset ISR flag @@ -661,6 +672,28 @@ void updateRadioState() { transactionComplete = false; //Reset ISR flag + //Compute the ACK frame transmit frame + if ((!txDataAckUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txDataAckUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txDataAckUsec: "); + systemPrintln(txDataAckUsec); + } + } + + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + //Determine if an ACK was transmitted if (txControl.datagramType = DATAGRAM_DATA_ACK) { @@ -1374,6 +1407,29 @@ void updateRadioState() if (transactionComplete == true) { transactionComplete = false; //Reset ISR flag + + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + + //Compute the SYNC_CLOCKS frame transmit frame + else if ((!txSyncClockUsec) && (txControl.datagramType == DATAGRAM_SYNC_CLOCKS)) + { + txSyncClockUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } + returnToReceiving(); changeState(RADIO_MP_STANDBY); } From d962c67dcb555417a68368d155e187ba6c3ebb46 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 17:58:27 -1000 Subject: [PATCH 14/25] Pass in the frame air time when computing the timestamp offset --- .../LoRaSerial_Firmware.ino | 2 ++ Firmware/LoRaSerial_Firmware/States.ino | 31 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index e93d9d6b..f1e2d81f 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -72,6 +72,8 @@ const int FIRMWARE_VERSION_MINOR = 0; #define P2P_ACK_BYTES sizeof(unsigned long) //Number of data bytes in the ACK frame #define VC_HEARTBEAT_BYTES 0 //Number of data bytes in the VC_HEARTBEAT frame +#define TX_TO_RX_USEC 680 //Time from TX to RX transactionComplete in microseconds + //Bit 0: Signal Detected indicates that a valid LoRa preamble has been detected //Bit 1: Signal Synchronized indicates that the end of Preamble has been detected, the modem is in lock //Bit 3: Header Info Valid toggles high when a valid Header (with correct CRC) is detected diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 626262a3..d425c548 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -48,15 +48,15 @@ ackTimer = 0; \ } -#define COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift) \ - { \ - unsigned long deltaUsec = txTimeUsec + rxTimeUsec; \ - memcpy(&remoteSystemMillis, millisBuffer, sizeof(currentMillis)); \ - timestampOffset = (remoteSystemMillis + (deltaUsec / 1000) - currentMillis);\ - timestampOffset >>= rShift; \ +#define COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift, frameAirTimeUsec) \ + { \ + unsigned long deltaUsec = frameAirTimeUsec + micros() - transactionCompleteMicros; \ + memcpy(&remoteSystemMillis, millisBuffer, sizeof(currentMillis)); \ + timestampOffset = (remoteSystemMillis + (deltaUsec / 1000) - currentMillis); \ + timestampOffset >>= rShift; \ } -#define COMPUTE_RX_TIME(millisBuffer, rShift) \ +#define COMPUTE_RX_TIME(millisBuffer, rShift, frameAirTimeUsec) \ { \ currentMillis = millis(); \ long deltaUsec = micros() - transactionCompleteMicros; \ @@ -80,7 +80,7 @@ } \ \ /*Adjust the timestamp offset*/ \ - COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift); \ + COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift, frameAirTimeUsec); \ } #define COMPUTE_TX_TIME() \ @@ -328,7 +328,7 @@ void updateRadioState() case DATAGRAM_FIND_PARTNER: //Received FIND_PARTNER //Compute the receive time - COMPUTE_RX_TIME(rxData, 1); + COMPUTE_RX_TIME(rxData, 1, calcAirTimeUsec(headerBytes + P2P_FIND_PARTNER_BYTES + trailerBytes)); //This system is the source of clock synchronization clockSyncReceiver = false; //P2P clock source @@ -418,7 +418,7 @@ void updateRadioState() case DATAGRAM_FIND_PARTNER: //Received FIND_PARTNER //Compute the receive time - COMPUTE_RX_TIME(rxData + 1, 1); + COMPUTE_RX_TIME(rxData + 1, 1, calcAirTimeUsec(headerBytes + P2P_FIND_PARTNER_BYTES + trailerBytes)); //Acknowledge the FIND_PARTNER triggerEvent(TRIGGER_TX_SYNC_CLOCKS); @@ -439,7 +439,7 @@ void updateRadioState() } //Compute the receive time - COMPUTE_RX_TIME(rxData + 1, 1); + COMPUTE_RX_TIME(rxData + 1, 1, txSyncClockUsec); //Hop to the next channel hopChannel(); @@ -541,7 +541,7 @@ void updateRadioState() case DATAGRAM_ZERO_ACKS: //Received ACK 2 //Compute the receive time - COMPUTE_RX_TIME(rxData, 1); + COMPUTE_RX_TIME(rxData, 1, calcAirTimeUsec(headerBytes + P2P_ZERO_ACKS_BYTES + trailerBytes)); setHeartbeatLong(); //We sent SYNC_CLOCKS and they sent ZERO_ACKS, so don't be the first to send heartbeat @@ -772,7 +772,7 @@ void updateRadioState() case DATAGRAM_HEARTBEAT: //Received heartbeat while link was idle. Send ack to sync clocks. //Adjust the timestamp offset - COMPUTE_TIMESTAMP_OFFSET(rxData, 1); + COMPUTE_TIMESTAMP_OFFSET(rxData, 1, txHeartbeatUsec); triggerEvent(TRIGGER_RX_HEARTBEAT); blinkHeartbeatLed(true); @@ -796,7 +796,7 @@ void updateRadioState() case DATAGRAM_DATA_ACK: //Adjust the timestamp offset - COMPUTE_RX_TIME(rxData, 1); + COMPUTE_RX_TIME(rxData, 1, txDataAckUsec); //The datagram we are expecting syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock @@ -1152,7 +1152,7 @@ void updateRadioState() //Compute the common clock currentMillis = millis(); - COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0); + COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0, txTimeUsec + rxTimeUsec); //Server has responded with ACK syncChannelTimer(txRxTimeMsec); //Start and adjust freq hop ISR based on remote's remaining clock @@ -1172,7 +1172,6 @@ void updateRadioState() frequencyCorrection += radio.getFrequencyError() / 1000000.0; lastPacketReceived = millis(); //Reset - changeState(RADIO_MP_STANDBY); break; } From c3843dc3354acc121dc4a677dfa997ad78ec5250 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 06:05:36 -1000 Subject: [PATCH 15/25] Rename FrameTimeout to SerialDelay and move to serial settings --- Firmware/LoRaSerial_Firmware/Commands.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/Commands.ino b/Firmware/LoRaSerial_Firmware/Commands.ino index ad2b3528..4ac6950f 100644 --- a/Firmware/LoRaSerial_Firmware/Commands.ino +++ b/Firmware/LoRaSerial_Firmware/Commands.ino @@ -1102,7 +1102,6 @@ const COMMAND_ENTRY commands[] = {'R', 0, 1, 0, 1, 0, TYPE_BOOL, valInt, "EncryptData", &tempSettings.encryptData}, {'R', 0, 1, 0, 0, 0, TYPE_KEY, valKey, "EncryptionKey", &tempSettings.encryptionKey}, {'R', 0, 0, 0, 255, 0, TYPE_U8, valInt, "FramesToYield", &tempSettings.framesToYield}, - {'R', 0, 0, 10, 2000, 0, TYPE_U16, valInt, "FrameTimeout", &tempSettings.serialTimeoutBeforeSendingFrame_ms}, {'R', 0, 0, 250, 65535, 0, TYPE_U16, valInt, "HeartBeatTimeout", &tempSettings.heartbeatTimeout}, {'R', 0, 0, 0, 255, 0, TYPE_U8, valInt, "MaxResends", &tempSettings.maxResends}, {'R', 0, 1, 0, 255, 0, TYPE_U8, valInt, "NetID", &tempSettings.netID}, @@ -1118,6 +1117,7 @@ const COMMAND_ENTRY commands[] = {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "FlowControl", &tempSettings.flowControl}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "InvertCts", &tempSettings.invertCts}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "InvertRts", &tempSettings.invertRts}, + {'S', 0, 0, 10, 2000, 0, TYPE_U16, valInt, "SerialDelay", &tempSettings.serialTimeoutBeforeSendingFrame_ms}, {'S', 0, 0, 0, 0, 0, TYPE_SPEED_SERIAL, valSpeedSerial, "SerialSpeed", &tempSettings.serialSpeed}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "UsbSerialWait", &tempSettings.usbSerialWait}, From cf5f7bf6f590f2e119a5f37f03e73ee1069382b6 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 06:10:54 -1000 Subject: [PATCH 16/25] Move overheadTime from being a radio parameter to being a debug param --- Firmware/LoRaSerial_Firmware/Commands.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/Commands.ino b/Firmware/LoRaSerial_Firmware/Commands.ino index 4ac6950f..eeeb144b 100644 --- a/Firmware/LoRaSerial_Firmware/Commands.ino +++ b/Firmware/LoRaSerial_Firmware/Commands.ino @@ -1068,6 +1068,7 @@ const COMMAND_ENTRY commands[] = {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DebugTransmit", &tempSettings.debugTransmit}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DebugSerial", &tempSettings.debugSerial}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DisplayRealMillis", &tempSettings.displayRealMillis}, + {'D', 0, 1, 0, 1000, 0, TYPE_U16, valInt, "OverHeadtime", &tempSettings.overheadTime}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintAckNumbers", &tempSettings.printAckNumbers}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintChannel", &tempSettings.printChannel}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintFrequency", &tempSettings.printFrequency}, @@ -1106,7 +1107,6 @@ const COMMAND_ENTRY commands[] = {'R', 0, 0, 0, 255, 0, TYPE_U8, valInt, "MaxResends", &tempSettings.maxResends}, {'R', 0, 1, 0, 255, 0, TYPE_U8, valInt, "NetID", &tempSettings.netID}, {'R', 0, 1, 0, 2, 0, TYPE_U8, valInt, "OperatingMode", &tempSettings.operatingMode}, - {'R', 0, 1, 0, 1000, 0, TYPE_U16, valInt, "OverHeadtime", &tempSettings.overheadTime}, {'R', 0, 0, 0, 1, 0, TYPE_BOOL, valServer, "Server", &tempSettings.server}, {'R', 0, 1, 0, 1, 0, TYPE_BOOL, valInt, "VerifyRxNetID", &tempSettings.verifyRxNetID}, From 390c62bc9463e8c1fedb3ac4a61b51c3c03a046c Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 06:03:32 -1000 Subject: [PATCH 17/25] Describe the steps to prevent attacks on the LoRaSerial network --- Firmware/LoRaSerial_Firmware/Radio.ino | 104 +++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index e6df726b..543528fc 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -1536,6 +1536,110 @@ bool xmitVcZeroAcks(int8_t destVc) //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Datagram reception +// +//Security or lack there of: +// +//The comments below outline the radio parameters and how they can be determined by +//listening to the radio traffic. The required parameters are followed by one or +//more step numbers indicating the step which determines the parameter's value. The +//comments further describe the steps that can be taken to reduce the attack surface +//for the LoRaSerial network. +// +//The following settings define the LoRaSerial radio network: +// +// AirSpeed - 3, 4, 5 +// AutoTune - This is receive only +// Bandwidth - 3 +// CodingRate - 5 +// FrequencyHop - 1 +// FrequencyMax - 1 +// FrequencyMin - 1 +// MaxDwellTime - 1 +// NumberOfChannels - 1 +// PreambleLength - 4 +// SpreadFactor - 4 +// SyncWord - 4 +// TxPower +// +// Protocol settings: +// +// DataScrambling - 6 +// EnableCRC16 - 8 +// EncryptData - 7 +// EncryptionKey - 7 +// FramesToYield +// HeartBeatTimeout - 12 +// MaxResends - 14 +// NetID - 9 +// OperatingMode - 10, 11 +// Server - 10, 11 +// VerifyRxNetID - 9 +// +//Attempting to attack a network of LoRaSerial radios would be done with the following +//steps: +// +// 1. Locate the frequencies that the network is using +// a. Listen for traffic on a specific channel to determine if the network is using +// that channel +// b. Repeat across all of the channels in the available frequency band +// 2. Once the frequencies are identified, attempt to determine the channel sequence +// by monitoring when traffic occurs on each of the identified frequencies, this +// allows the attacker to construct the hop table for the network +// 3. Look at the bandwidth utilized by the radio network. The signal for each +// symbol is a ramp that spans the bandwidth selected for use +// 4. Using a receiver that uses the opposite ramp to generate the sub frequencies +// within the bandwidth to decode the symbols. By monitoring the network traffic +// it is possible to determine the spreadfactor since there are 2^SF sub frequencies +// that will be used within the bandwidth +// 5. Now that the spread factor is known, the next step is to determine the coding +// rate used for forward error correction. Here 4 data bits are converted into +// between 5 to 8 bits in the transmitted frame +// 6. Look at the signal for multiple transmissions, does this signal have a DC offset? +// The data scrambling setting is false if a DC offset is present, or is true when +// no DC offset is present +// 7. Next step is breaking the encryption key +// 8. After the encryption key is obtained, it is possible determine if the link is +// using software CRC by computing the CRC-16 value on the first n-2 bytes and +// comparing that with the last 2 bytes +// 9. Determine if the link is using a network ID value, by checking the first byte +// in each of the transmitted frames +// 10. Determine if the virtual circuit header is contained in the transmitted frames +// 11. Determine which set of data frames are being transmitted +// 12. Determine the maximum interval between HEARTBEAT frames to roughly determine +// the HeartbeatTimeout value +// 13. Look for HEARTBEAT frames and FIND_PARTNER frames. A FIND_PARTNER frame +// is sent following a link failure which occurs after three HEARTBEAT timeout +// periods. +// 14. The MaxResends value can be estimated by the maximum number of data +// retransmissions done prior to the link failure. A large number of link +// failures will need to be collected from a very radio near the network. +// +//How do you prevent the attacks on a LoRaSerial radio network: +// +// 1. Don't advertize the network. Reduce the TxPower value to the minimum needed +// to successfully operate the network +// +// 2. Encrypt the data on the network. Don't use the factory default encryption key. +// Select a new encryption key and and train all of the radios in the network with +// this key. This will prevent attacks from any groups that are not able to break +// the encryption. However the radio network is vulnerable to well funded or +// dedicated groups that are able to break the encryption. +// +// 3. Change the encryption key. When another network is using the same encryption key +// and same network ID traffic from the other network is likely to be received. +// Selecting another encryption key will avoid this behavior. Also the encryption +// key may be tested by setting a radio to MODE_MULTIPOINT, disabling VerifyRxNetID +// and disabling EnableCRC16. Both settings of DataScrambling will need to be tested. +// +// 4. Use a network ID. This won't prevent attacks, but it prevents the reception +// of frames from other networks use the same encryption key. +// +// 5. Use software CRC-16. This won't prevent attacks but will eliminate most +// frames from networks using the same encryption key that don't use a network +// ID. Occasionally, when they get lucky, our network ID matches the first byte +// of their transmitted frame. The software CRC-16 will most likely cause this +// frame to be discarded. +// //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Determine the type of datagram received From 267b183304a61ff07bd52047fca525ad1a0f2bbd Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 09:55:40 -1000 Subject: [PATCH 18/25] Output the correct channelNumber LSB on port A1 in syncChannelTimer --- Firmware/LoRaSerial_Firmware/Radio.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 543528fc..d2b8da17 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -3349,7 +3349,7 @@ void syncChannelTimer(uint16_t frameAirTimeMsec) //Restart the channel timer timeToHop = false; channelTimer.setInterval_MS(msToNextHop, channelTimerHandler); //Adjust our hardware timer to match our mate's - digitalWrite(pin_hop_timer, channelNumber & 1); + digitalWrite(pin_hop_timer, ((channelNumber + delayedHopCount) % settings.numberOfChannels) & 1); channelTimerStart = currentMillis; channelTimerMsec = msToNextHop; //syncChannelTimer update channelTimer.enableTimer(); From 3ba72d3306d0b1bf4f9e6d056641097161477673 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 10:00:25 -1000 Subject: [PATCH 19/25] Display the ACK, HEARTBEAT and SYNC_CLOCKS transmit times with ATI10 --- Firmware/LoRaSerial_Firmware/Commands.ino | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Commands.ino b/Firmware/LoRaSerial_Firmware/Commands.ino index eeeb144b..2bd08c72 100644 --- a/Firmware/LoRaSerial_Firmware/Commands.ino +++ b/Firmware/LoRaSerial_Firmware/Commands.ino @@ -411,15 +411,14 @@ bool commandAT(const char * commandString) //Clock synchronization systemPrintln(" Clock Synchronization"); - systemPrint(" TX Time: "); - systemPrint(txTimeUsec / 1000., 4); - systemPrintln(" mSec"); - systemPrint(" RX Time: "); - systemPrint(rxTimeUsec / 1000., 4); - systemPrintln(" mSec"); - systemPrint(" Total Time: "); - systemPrint((txTimeUsec + rxTimeUsec) / 1000., 5); - systemPrintln(" mSec"); + systemPrint(" ACK Time: "); + systemPrint(txDataAckUsec); + systemPrint(" HEARTBEAT Time: "); + systemPrint(txHeartbeatUsec); + systemPrintln(" uSec"); + systemPrint(" SYNC_CLOCKS Time: "); + systemPrint(txSyncClockUsec); + systemPrintln(" uSec"); systemPrint(" Uptime: "); deltaMillis = millis(); systemPrintTimestamp(deltaMillis); From 4969ce6aad4f7d11950f3b97bda192153472b4bc Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 10:04:26 -1000 Subject: [PATCH 20/25] Update the VC HEARTBEAT diagram --- Firmware/LoRaSerial_Firmware/Radio.ino | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index d2b8da17..5819129d 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -1384,14 +1384,14 @@ bool xmitVcHeartbeat(int8_t addr, uint8_t * id) *txData = (uint8_t)(endOfTxData - txData); /* - endOfTxData ---. - | - V - +----------+---------+----------+------------+----------+---------+----------+---------+----------+ - | Optional | | Optional | Optional | | | | | Optional | - | NET ID | Control | C-Timer | SF6 Length | DestAddr | SrcAddr | Src ID | millis | Trailer | - | 8 bits | 8 bits | 2 bytes | 8 bits | 8 bits | 8 bits | 16 Bytes | 4 Bytes | n Bytes | - +----------+---------+----------+------------+----------+---------+----------+---------+----------+ + endOfTxData ---. + | + V + +----------+---------+----------+------------+----------+----------+---------+----------+---------+----------+ + | Optional | | Optional | Optional | | | | | | Optional | + | NET ID | Control | C-Timer | SF6 Length | Length | DestAddr | SrcAddr | Src ID | millis | Trailer | + | 8 bits | 8 bits | 2 bytes | 8 bits | 8 bits | 8 bits | 8 bits | 16 Bytes | 4 Bytes | n Bytes | + +----------+---------+----------+------------+----------+----------+---------+----------+---------+----------+ */ //Verify the data length From 436d504d3cfee251990d2d66259e262db1904f9e Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 10:12:22 -1000 Subject: [PATCH 21/25] Properly set the VC_HEARTBEAT_BYTES value --- Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index f1e2d81f..a7d2cda9 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -70,7 +70,7 @@ const int FIRMWARE_VERSION_MINOR = 0; #define P2P_ZERO_ACKS_BYTES sizeof(unsigned long) //Number of data bytes in the ZERO_ACKS frame #define P2P_HEARTBEAT_BYTES sizeof(unsigned long) //Number of data bytes in the HEARTBEAT frame #define P2P_ACK_BYTES sizeof(unsigned long) //Number of data bytes in the ACK frame -#define VC_HEARTBEAT_BYTES 0 //Number of data bytes in the VC_HEARTBEAT frame +#define VC_HEARTBEAT_BYTES 1 + 1 + 1 + 16 + 4 //Number of data bytes in the VC_HEARTBEAT frame #define TX_TO_RX_USEC 680 //Time from TX to RX transactionComplete in microseconds From fb6f8fcd9d4c547eaedd0e6bc811b04db96df34b Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 10:18:52 -1000 Subject: [PATCH 22/25] Add virtual-circuit triggers for TX and RX --- Firmware/LoRaSerial_Firmware/States.ino | 31 +++++++++++++++++++++---- Firmware/LoRaSerial_Firmware/settings.h | 10 ++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index d425c548..cc663da3 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -1867,6 +1867,8 @@ void updateRadioState() break; case DATAGRAM_DATA: + triggerEvent(TRIGGER_RX_DATA); + //Move the data into the serial output buffer if (settings.debugSerial) { @@ -1884,6 +1886,8 @@ void updateRadioState() break; case DATAGRAM_DATAGRAM: + triggerEvent(TRIGGER_RX_DATAGRAM); + //Move the data into the serial output buffer if (settings.debugSerial) { @@ -1899,6 +1903,7 @@ void updateRadioState() break; case DATAGRAM_DATA_ACK: + triggerEvent(TRIGGER_RX_ACK); vcSendPcAckNack(rexmtTxDestVc, true); STOP_ACK_TIMER(); break; @@ -1916,6 +1921,7 @@ void updateRadioState() //Second step in the 3-way handshake, received UNKNOWN_ACKS, respond //with SYNC_ACKS case DATAGRAM_VC_UNKNOWN_ACKS: + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); if (xmitVcSyncAcks(rxSrcVc)) changeState(RADIO_VC_WAIT_TX_DONE); vcChangeState(rxSrcVc, VC_STATE_WAIT_ZERO_ACKS); @@ -1924,6 +1930,7 @@ void updateRadioState() //Third step in the 3-way handshake, received SYNC_ACKS, respond with ZERO_ACKS case DATAGRAM_VC_SYNC_ACKS: + triggerEvent(TRIGGER_RX_VC_SYNC_ACKS); if (xmitVcZeroAcks(rxSrcVc)) { changeState(RADIO_VC_WAIT_TX_DONE); @@ -1934,11 +1941,13 @@ void updateRadioState() //Last step in the 3-way handshake, received ZERO_ACKS, done case DATAGRAM_VC_ZERO_ACKS: + triggerEvent(TRIGGER_RX_VC_ZERO_ACKS); vcZeroAcks(rxSrcVc); vcChangeState(rxSrcVc, VC_STATE_CONNECTED); break; case DATAGRAM_REMOTE_COMMAND: + triggerEvent(TRIGGER_RX_COMMAND); rmtCmdVc = rxSrcVc; //Copy the command into the command buffer @@ -1984,6 +1993,8 @@ void updateRadioState() break; case DATAGRAM_REMOTE_COMMAND_RESPONSE: + triggerEvent(TRIGGER_RX_COMMAND_RESPONSE); + //Debug the serial path if (settings.debugSerial) { @@ -2022,7 +2033,7 @@ void updateRadioState() //Send another heartbeat if (xmitVcHeartbeat(myVc, myUniqueId)) { - triggerEvent(TRIGGER_TX_HEARTBEAT); + triggerEvent(TRIGGER_TX_VC_HEARTBEAT); if (((uint8_t)myVc) < MAX_VC) virtualCircuitList[myVc].lastTrafficMillis = currentMillis; changeState(RADIO_VC_WAIT_TX_DONE); @@ -2147,6 +2158,7 @@ void updateRadioState() //Send the UNKNOWN_ACKS datagram, first part of the 3-way handshake if (xmitVcUnknownAcks(index)) { + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); vcChangeState(index, VC_STATE_WAIT_SYNC_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); @@ -2163,6 +2175,7 @@ void updateRadioState() //Retransmit the UNKNOWN_ACKS if (xmitVcUnknownAcks(index)) { + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); } @@ -2179,6 +2192,7 @@ void updateRadioState() //Retransmit the SYNC_CLOCKS if (xmitVcSyncAcks(index)) { + triggerEvent(TRIGGER_RX_VC_SYNC_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); } @@ -2210,15 +2224,20 @@ void updateRadioState() if (vcHeader->destVc == VC_BROADCAST) { //Broadcast this data to all VCs, no ACKs will be received - triggerEvent(TRIGGER_TX_DATA); - xmitVcDatagram(); + if (xmitVcDatagram()) + { + triggerEvent(TRIGGER_TX_DATA); + changeState(RADIO_VC_WAIT_TX_DONE); + } break; } //Transmit the packet - triggerEvent(TRIGGER_TX_DATA); if (xmitDatagramP2PData() == true) + { + triggerEvent(TRIGGER_TX_DATA); changeState(RADIO_VC_WAIT_TX_DONE); + } START_ACK_TIMER(); @@ -2289,7 +2308,10 @@ void updateRadioState() //Send the remote command vcHeader->destVc &= VCAB_NUMBER_MASK; if (xmitDatagramP2PCommand() == true) + { + triggerEvent(TRIGGER_TX_COMMAND); changeState(RADIO_VC_WAIT_TX_DONE); + } START_ACK_TIMER(); @@ -3027,6 +3049,7 @@ void vcReceiveHeartbeat(uint32_t rxMillis) memcpy(×tampOffset, &rxVcData[UNIQUE_ID_BYTES], sizeof(timestampOffset)); timestampOffset += vcTxHeartbeatMillis + rxMillis - millis(); } + triggerEvent(TRIGGER_RX_VC_HEARTBEAT); //Save our address if ((myVc == VC_UNASSIGNED) && (memcmp(myUniqueId, rxVcData, UNIQUE_ID_BYTES) == 0)) diff --git a/Firmware/LoRaSerial_Firmware/settings.h b/Firmware/LoRaSerial_Firmware/settings.h index 64f80453..33e7257a 100644 --- a/Firmware/LoRaSerial_Firmware/settings.h +++ b/Firmware/LoRaSerial_Firmware/settings.h @@ -263,10 +263,15 @@ enum TRIGGER_RX_COMMAND, TRIGGER_RX_COMMAND_RESPONSE, TRIGGER_RX_DATA, + TRIGGER_RX_DATAGRAM, TRIGGER_RX_FIND_PARTNER, TRIGGER_RX_HEARTBEAT, TRIGGER_RX_SPI_DONE, TRIGGER_RX_SYNC_CLOCKS, + TRIGGER_RX_VC_HEARTBEAT, + TRIGGER_RX_VC_SYNC_ACKS, + TRIGGER_RX_VC_UNKNOWN_ACKS, + TRIGGER_RX_VC_ZERO_ACKS, TRIGGER_RX_YIELD, TRIGGER_RX_ZERO_ACKS, TRIGGER_SYNC_CHANNEL_TIMER, @@ -282,6 +287,7 @@ enum TRIGGER_TX_COMMAND, TRIGGER_TX_COMMAND_RESPONSE, TRIGGER_TX_DATA, + TRIGGER_TX_DATAGRAM, TRIGGER_TX_DONE, TRIGGER_TX_DUPLICATE_ACK, TRIGGER_TX_FIND_PARTNER, @@ -289,6 +295,10 @@ enum TRIGGER_TX_LOAD_CHANNE_TIMER_VALUE, TRIGGER_TX_SPI_DONE, TRIGGER_TX_SYNC_CLOCKS, + TRIGGER_TX_VC_HEARTBEAT, + TRIGGER_TX_VC_SYNC_ACKS, + TRIGGER_TX_VC_UNKNOWN_ACKS, + TRIGGER_TX_VC_ZERO_ACKS, TRIGGER_TX_YIELD, TRIGGER_TX_ZERO_ACKS, TRIGGER_UNKNOWN_PACKET, From 79da1cc80db3f5d8054ea90fde2c30618924b123 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 10:22:11 -1000 Subject: [PATCH 23/25] Virtual Circuit: Synchronize channel timer using the VC_HEARTBEAT frame --- Firmware/LoRaSerial_Firmware/States.ino | 70 ++++++++++++++++++------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index cc663da3..c3334279 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -187,36 +187,45 @@ void updateRadioState() //Start the link between the radios if (settings.operatingMode == MODE_POINT_TO_POINT) + { changeState(RADIO_P2P_LINK_DOWN); - else if (settings.operatingMode == MODE_VIRTUAL_CIRCUIT) + break; + } + + //Virtual circuit mode + if (settings.operatingMode == MODE_VIRTUAL_CIRCUIT) { if (settings.server) { //Reserve the server's address (0) myVc = vcIdToAddressByte(VC_SERVER, myUniqueId); clockSyncReceiver = false; //VC server is clock source + if (settings.frequencyHop) + startChannelTimer(); + + //Start sending heartbeats + xmitVcHeartbeat(myVc, myUniqueId); + changeState(RADIO_VC_WAIT_TX_DONE); + break; } - else - //Unknown client address - myVc = VC_UNASSIGNED; + + //Unknown client address + myVc = VC_UNASSIGNED; //Start sending heartbeats - xmitVcHeartbeat(myVc, myUniqueId); - changeState(RADIO_VC_WAIT_TX_DONE); + changeState(RADIO_VC_WAIT_SERVER); + break; } - else + + //Multipoint mode + if (settings.server == true) { - if (settings.server == true) - { - clockSyncReceiver = false; //Multipoint server is clock source - startChannelTimer(); //Start hopping - multipoint clock source - changeState(RADIO_MP_STANDBY); - } - else - { - changeState(RADIO_DISCOVER_BEGIN); - } + clockSyncReceiver = false; //Multipoint server is clock source + startChannelTimer(); //Start hopping - multipoint clock source + changeState(RADIO_MP_STANDBY); } + else + changeState(RADIO_DISCOVER_BEGIN); break; //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -1780,12 +1789,21 @@ void updateRadioState() //Wait for a HEARTBEAT from the server //==================== case RADIO_VC_WAIT_SERVER: + //The server should never be in this state if (myVc == VC_SERVER) { changeState(RADIO_VC_WAIT_RECEIVE); break; } + //Stop the frequency hopping + stopChannelTimer(); + if (channelNumber != 0) + { + channelNumber = 0; + setRadioFrequency(false); + } + //If dio0ISR has fired, a packet has arrived currentMillis = millis(); if (transactionComplete == true) @@ -1796,6 +1814,11 @@ void updateRadioState() //Process the received datagram if ((packetType == DATAGRAM_VC_HEARTBEAT) && (rxSrcVc == VC_SERVER)) { + //Start the channel timer + startChannelTimer(); + channelTimerStart -= settings.maxDwellTime; + + //Synchronize the channel timer with the server vcReceiveHeartbeat(millis() - currentMillis); changeState(RADIO_VC_WAIT_RECEIVE); } @@ -1818,6 +1841,17 @@ void updateRadioState() { transactionComplete = false; + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + //Indicate that the transmission is complete triggerEvent(TRIGGER_TX_DONE); @@ -3034,7 +3068,7 @@ void vcReceiveHeartbeat(uint32_t rxMillis) uint32_t deltaMillis; int vcSrc; - if (rxSrcVc == VC_SERVER) + if ((rxSrcVc == VC_SERVER) || (memcmp(rxVcData, myUniqueId, sizeof(myUniqueId)) == 0)) syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on server's remaining clock //Update the timestamp offset From eb033a5eee89f4258a2b617f05de8726ddbde6c6 Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Sun, 8 Jan 2023 17:17:30 -1000 Subject: [PATCH 24/25] Add DATA_ACK, HEARTBEAT and SYNC_CLOCKS air time in mSec to SYNC_CLOCKS --- .../LoRaSerial_Firmware.ino | 2 +- Firmware/LoRaSerial_Firmware/Radio.ino | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index a7d2cda9..b3ab6c2e 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -66,7 +66,7 @@ const int FIRMWARE_VERSION_MINOR = 0; //Frame lengths #define MP_HEARTBEAT_BYTES 0 //Number of data bytes in the MP_HEARTBEAT frame #define P2P_FIND_PARTNER_BYTES sizeof(unsigned long) //Number of data bytes in the FIND_PARTNER frame -#define P2P_SYNC_CLOCKS_BYTES (sizeof(uint8_t) + sizeof(unsigned long)) //Number of data bytes in the SYNC_CLOCKS frame +#define P2P_SYNC_CLOCKS_BYTES (sizeof(uint8_t) + sizeof(unsigned long) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t)) //Number of data bytes in the SYNC_CLOCKS frame #define P2P_ZERO_ACKS_BYTES sizeof(unsigned long) //Number of data bytes in the ZERO_ACKS frame #define P2P_HEARTBEAT_BYTES sizeof(unsigned long) //Number of data bytes in the HEARTBEAT frame #define P2P_ACK_BYTES sizeof(unsigned long) //Number of data bytes in the ACK frame diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 5819129d..541e4cce 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -889,15 +889,24 @@ bool xmitDatagramP2PSyncClocks() memcpy(endOfTxData, ¤tMillis, sizeof(currentMillis)); endOfTxData += sizeof(unsigned long); + memcpy(endOfTxData, &txHeartbeatUsec, sizeof(txHeartbeatUsec)); + endOfTxData += sizeof(txHeartbeatUsec); + + memcpy(endOfTxData, &txSyncClockUsec, sizeof(txSyncClockUsec)); + endOfTxData += sizeof(txSyncClockUsec); + + memcpy(endOfTxData, &txDataAckUsec, sizeof(txDataAckUsec)); + endOfTxData += sizeof(txDataAckUsec); + /* - endOfTxData ---. - | - V - +----------+---------+----------+------------+---------+---------+----------+ - | Optional | | Optional | Optional | Channel | | | - | NET ID | Control | C-Timer | SF6 Length | Number | Millis | Trailer | - | 8 bits | 8 bits | 2 bytes | 8 bits | 1 byte | 4 bytes | n Bytes | - +----------+---------+----------+------------+---------+---------+----------+ + endOfTxData ---. + | + V + +----------+---------+----------+------------+---------+---------+-------------+-------------+-----------+----------+ + | Optional | | Optional | Optional | Channel | | HEARTBEAT | SYNC_CLOCKS | DATA_ACK | | + | NET ID | Control | C-Timer | SF6 Length | Number | Millis | Micros | Micros | Micros | Trailer | + | 8 bits | 8 bits | 2 bytes | 8 bits | 1 byte | 4 bytes | 4 Bytes | 4 Bytes | 4 Bytes | n Bytes | + +----------+---------+----------+------------+---------+---------+-------------+-------------+-----------+----------+ */ //Verify the data length From 2309b2bdb1a4e26f3873fbb24c1993765631f8ed Mon Sep 17 00:00:00 2001 From: Lee Leahy Date: Mon, 9 Jan 2023 11:19:37 -1000 Subject: [PATCH 25/25] Multipoint: Start the channelTimer synchronization --- Firmware/LoRaSerial_Firmware/States.ino | 109 ++++++++++++++++-------- 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index c3334279..8a160faf 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -1095,8 +1095,6 @@ void updateRadioState() //Start searching for other radios //==================== case RADIO_DISCOVER_BEGIN: - stopChannelTimer(); //Stop hopping - multipoint discovery - if (settings.debugSync) { systemPrintln("Start scanning"); @@ -1114,6 +1112,8 @@ void updateRadioState() //Walk through channel table backwards, transmitting a FIND_PARTNER and looking for an SYNC_CLOCKS //==================== case RADIO_DISCOVER_SCANNING: + stopChannelTimer(); //Stop hopping - multipoint discovery + if (transactionComplete) { //Decode the received datagram @@ -1157,31 +1157,65 @@ void updateRadioState() break; case DATAGRAM_SYNC_CLOCKS: - triggerEvent(TRIGGER_RX_SYNC_CLOCKS); + if (!settings.server) + { + //Change to the server's channel number + channelNumber = rxVcData[0]; - //Compute the common clock - currentMillis = millis(); - COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0, txTimeUsec + rxTimeUsec); + //Get the HEARTBEAT TX time + if (!txHeartbeatUsec) + { + memcpy(&txHeartbeatUsec, rxData + 1 + 4, sizeof(txHeartbeatUsec)); + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } - //Server has responded with ACK - syncChannelTimer(txRxTimeMsec); //Start and adjust freq hop ISR based on remote's remaining clock + //Get the SYNC_CLOCKS TX time + if (!txSyncClockUsec) + { + memcpy(&txSyncClockUsec, rxData + 1 + 4 + 4, sizeof(txSyncClockUsec)); + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } - //Change to the server's channel number - channelNumber = rxVcData[0]; + //Ignore this frame when the times are not filled in + if ((!txHeartbeatUsec) || (!txSyncClockUsec)) + triggerEvent(TRIGGER_RX_SYNC_CLOCKS); + else + { + COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0, txSyncClockUsec); - if (settings.debugSync) - { - systemPrint(" Channel Number: "); - systemPrintln(channelNumber); - outputSerialData(true); - if (timeToHop == true) //If the channelTimer has expired, move to next frequency - hopChannel(); - } + //Server has responded to FIND_PARTNER with SYNC_CLOCKS + //Start and adjust freq hop ISR based on remote's remaining clock + startChannelTimer(); + channelTimerStart -= settings.maxDwellTime; + syncChannelTimer((txSyncClockUsec + TX_TO_RX_USEC + micros() - transactionCompleteMicros) / 1000); + triggerEvent(TRIGGER_RX_SYNC_CLOCKS); - frequencyCorrection += radio.getFrequencyError() / 1000000.0; + //Switch to the proper frequency for the channel + setRadioFrequency(false); - lastPacketReceived = millis(); //Reset - changeState(RADIO_MP_STANDBY); + if (settings.debugSync) + { + systemPrint(" Channel Number: "); + systemPrintln(channelNumber); + outputSerialData(true); + if (timeToHop == true) //If the channelTimer has expired, move to next frequency + hopChannel(); + } + + frequencyCorrection += radio.getFrequencyError() / 1000000.0; + + lastPacketReceived = millis(); //Reset + changeState(RADIO_MP_STANDBY); + } + } break; } } @@ -1319,12 +1353,19 @@ void updateRadioState() break; case DATAGRAM_HEARTBEAT: - //Received heartbeat - do not ack. - triggerEvent(TRIGGER_RX_HEARTBEAT); - //Sync clock if server sent the heartbeat if (settings.server == false) - syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock + { + uint16_t frameAirTimeMsec; + + //Adjust freq hop ISR based on server's remaining clock + syncChannelTimer((txHeartbeatUsec + TX_TO_RX_USEC + micros() - transactionCompleteMicros) / 1000); + systemPrint("HEARTBEAT TX mSec: "); + systemPrintln(frameAirTime); + } + + //Received heartbeat - do not ack. + triggerEvent(TRIGGER_RX_HEARTBEAT); frequencyCorrection += radio.getFrequencyError() / 1000000.0; @@ -1338,10 +1379,6 @@ void updateRadioState() //Received data - do not ack. triggerEvent(TRIGGER_RX_DATA); - //Sync clock if server sent the datagram - if (settings.server == false) - syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock - setHeartbeatMultipoint(); //We're sync'd so reset heartbeat timer //Place any available data in the serial output buffer @@ -1362,16 +1399,14 @@ void updateRadioState() heartbeatTimeout = ((millis() - heartbeatTimer) > heartbeatRandomTime); //Only the server transmits heartbeats - if (settings.server == true) + if ((settings.server == true) && (heartbeatTimeout)) { - if (heartbeatTimeout) + if (xmitDatagramMpHeartbeat() == true) { - if (xmitDatagramMpHeartbeat() == true) - { - triggerEvent(TRIGGER_TX_HEARTBEAT); - setHeartbeatMultipoint(); //We're sending something with clock data so reset heartbeat timer - changeState(RADIO_MP_WAIT_TX_DONE); //Wait for heartbeat to transmit - } + triggerEvent(TRIGGER_TX_HEARTBEAT); + blinkHeartbeatLed(true); + setHeartbeatMultipoint(); //We're sending something with clock data so reset heartbeat timer + changeState(RADIO_MP_WAIT_TX_DONE); //Wait for heartbeat to transmit } }