The sx1276 radio (https://www.semtech.fr/products/wireless-rf/lora-connect/sx1276) is commonly used in LoRA mode but it also has an FSK mode, this Q is about the FSK mode used in packet mode. This question also applies to the sx1278, sx1275, hoperf rfm96, and other modules with an sx127x chip. I believe it also applies to the non-LoRA sx1231 / rfm69 as the FSK radios are virtually identical in most Semtech chips.

In the RX section, the sx1276 has a number of settings to perform AFC (automatic frequency correction) and AGC (automatic gain control), to measure RSSI (received signal strength indicator), and measure FEI (frequency error indication). The device also has a slew of interrupt options: on preamble detect, on rssi threshold, on sync/address match, on packet ready, and more. The result is a bewildering set of options and many users and libraries simply start RX and wait for (or poll for) the packet-ready interrupt. Why bother with the rest?

Meta: this question is a set-up for answers to the stuff I've been struggling with for the last decade when using the Semtech FSK radios. There are so many details that the datasheet does not mention. I want to write down what I've learned so I can reference it for the next decade... I'd love input/observations from others, though!


1 Answer 1


The primary reason to bother is that AFC and AGC are generally necessary because they improve reception but they can cause receiver lock-up. This lock-up has been a "feature" of Semtech's FSK radios going back at least to the sx1231. Managing it requires using more than just the packet-ready interrupt/flag.

First some background on AFC and AGC.

Frequency correction is needed to compensate for the fact that the transmitter and receiver's clocks often are not exactly on the same frequency and this increases the bit error rate. Gain control is needed when some devices come in strong and others weak: the gain of the LNA in the chip needs to be adjusted so weak signals are amplified and strong signals don't overdrive the mixer.

Both frequency correction and gain control can be done automatically by the device and it's best to let the device to these in most cases. Sounds easy, what's the catch?

Receiver lock-up

The RX chain starts out looking for a 0101010101 pattern: the so-called preamble. It uses this pattern to synchronize the bit clock so it knows when to sample each successive bit in the packet. This is also the best time to do AFC because measuring the frequency of the signal has to happen when there is an equal number of 1's and 0's since these are at different frequencies (that's what FSK means) and the receiver needs to be tuned to the middle. Similarly, the preamble is a good moment to correct the LNA gain so the subsequent steps have a higher chance to decode the data.

The problem here is that noise often has a 010101 pattern and so what looks like a preamble can well be noise and then the question is when to give up 'cause this ain't no packet and undo the AFC/AGC corrections. Turns out the sx1276 never does that on its own!

How to reproduce the problem:

  • Take three devices, two transmitting, one receiving. Use your favorite packet settings (bit rate, Fdev, BW, preamble length, sync word, etc.). Ensure you have AGC and AFC enabled in REG_RX_CONFIG and set to auto clear in REG_AFC_FEI.
  • Start the receiver such that it prints out each packet received using just the packet-ready interrupt.
  • Start a transmitter so it transmits once a second or so at a frequency higher than the receiver is tuned to, just within the bandwidth (something like Freq + 0.4*BW, assuming BW is the double-sided bandwidth as programmed into the device), and ensure that the receiver pretty reliably receives the packets despite the frequency offset.
  • Start the second transmitter to periodically (e.g. every 1.3 seconds) transmit at the opposite frequency offset, e.g. Freq - 0.4*BW. You should now see both series of packets being received. (This is a good moment to ensure that the RSSI at the receiver is "middle of the road", something around 70dBm, so don't use max power with a distance of 10cm...)
  • Now modify the sync word of the second transmitter so it does not match the receiver. You will see almost no packets received even though the first transmitter continues to work.
  • Why? What happens? Modify the receiver to print the current AGC value (REG_AGC) and the current LNA value (REG_LNA) every second and repeat the various tests. You will see that in the last scenario the AGC value shows the second transmitter's frequency offset and almost never changes from that. Basically the radio is now enough out of tune that it can no longer hear the first transmitter.
  • In more detail: When the receiver gets the preamble of the second transmitter it adjusts AFC to the frequency offset, i.e. tunes the radio to that. Because the sync word does not match a packet is never received. This means the receive chain is never restarted and is now stuck too far off-tune to hear the first transmitter. In contrast, when the sync word matches a packet is received, the receive chain is restarted with a zero offset and can hear both transmitters.

Is this a bug in the radio? IMHO yes. However, there is no clean solution so I can understand that Semtech doesn't commit something to silicon. The fundamental problem is that the radio doesn't know how long the preamble could be, so it doesn't know when to give up. Even knowing the preamble length there is no way to tell when it's totally safe to abort.

When is this a problem?

Before looking at fixes, the lock-up is a problem any time AFC or AGC are enabled (the same kind of lock-up as described above can happen with AGC). The two scenarios that come to mind where AFC and AGC are not needed are:

  • If just two nodes communicate with each other then one of them can measure the frequency offset, correct its frequency explicitly and be done, both can adjust their RX gain and be done. No need to auto correct on every packet.
  • In a star network the central node should use AGC/AFC but the peripheral ones can often just measure what they hear from the central node and make an explicit correction once or infrequently.

In many situations the lock-up is not a huge issue because there are enough nodes at various frequencies so one of them can be heard despite a large frequency offset and then the radio restarts. Packets are lost in the meantime but nobody knows why or most likely nobody really notices. However, I have experienced environments where the lock-up is a real persistent issue and a significant fraction of packets are lost systematically.

How to fix?

I don't know that there is a universal fix. The TL;DR; for the best fix I have come up with is to use the preamble interrupt to set a time-out by when the packet must have been received, cancel this time-out in the packet-ready interrupt handler, and if the timeout fires and no reception is in progress (and optionally if AGC or AFC corrections are non-zero) then restart the receiver (which should be configured to clear the AFC/AGC registers on restart).

How to verify: same set-up as above, in the scenario where the second transmitter uses a different sync word you should see the first transmitter's packets continue to come in reliably.

Unfortunately things are not quite as simple to configure correctly as the tl;dr; may suggest. Also, there are questions like: what about RSSI, specifically the RSSI threshold configuration and triggering AFC/AGC on RSSI, or getting an interrupt on RSSI? It's not even easy to correctly measure the frequency offset or the RSSI.

Measuring frequency error

The sx1276 can measure the frequency error, it does that continuously and the current value can be read in REG_FEI. But the value is only accurate during the preamble (because it requires alternating 0/1). In the datasheet this is captured in section by "The measurement must be launched during the reception of preamble".

Triggering this at the right time is not that easy so it's best to let the device do it, e.g. to enable AFC on preamble detect (see section 2.1.6). The value of REG_AFC is then the correction applied by the device, the value of REG_FEI is useless. REG_AFC can be read after the packet is received but must be read before the last byte is extracted from the FIFO if RX auto-restart is used.

Taming the preamble detector

AGC and AFC only get triggered once (well, they can be triggered a second time on RSSI threshold, see below) so it's important that they get triggered on the actual packet preamble and not on some random noise that preceded it. (The AGC/AFC values are fixed for the entire duration of the packet.) The default settings allow for 2.5 bit errors in the preamble, which may be too much. I had it configured for 1 byte of preamble and the default 2.5 bits, the result was that the preamble detector fired almost immediately every time the radio was restarted. This resulted in a non-stop stream of interrupts and in AFC/AGC being set by noise. Reducing the allowed number of errors fixed the problem (I couldn't use a 2 byte detector). I don't know whether some dynamic adjustment is required here, I'd rather avoid that complexity...

What happens if the preamble detector fires on noise? It essentially locks in the AFC/AGC on that noise. The radio then shifts bits into the sync word detector until that matches. So a packet coming much later may well match and be received, but AGC/AFC are then likely suboptimal.

Restarting the receiver

The dilemma here is how to restart the receiver with the least impact, i.e., creating the smallest window where the radio might miss a packet. There are two main methods (sec. 2.1.7 in the datasheet):

  • setting the RestartRxWithoutPllLock bit in REG_RX_CONFIG
  • setting the RestartRxWithPllLock bit in the same register

The difference is that the first one just restarts while the second one waits for the frequency change to undo the AFC to take effect. The datasheet recommends the second when using AGC and I believe the main reason is that it avoids that the preamble detector re-trigger immediately before the frequency has changed.

Some things I've tried that do not work to restart RX:

  • Clearing REG_AFC: this does have immediate effect and re-tunes the radio, but doesn't restart the preamble detector so a fresh AFC will never be triggered
  • Setting REG_LNA to max gain: this also takes effect immediately but also doesn't restart the preamble detector
  • Clearing the PreambleDetect bit in REG_IRQ_FLAGS1: this just hides the interrupt and also doesn't restart the preamble detector

What about RSSI and the RSSI threshold?

This has had me confused for years. The RSSI threshold has virtually no impact on reception! Basically, the radio first runs the preamble detector, when that fires it starts shifting bits into the sync word / address matcher, when that matches it shifts bits into the fifo 'til the end of the packet. RX done! (Well, it can check the CRC at the end.) RSSI doesn't figure in here. This is easy to test: set the RSSI threshold to an impossible value, like -20dBm and watch packets coming in just like before.

Where RSSI can come in is with AFC/AGC: you can configure the device to trigger AGC/AFC on RSSI interrupt, i.e., when the RSSI threshold is crossed. Where this is interesting is that this provides an opportunity to fix a poor AFC/AGC that happened due to noise. E.g., the preamble detector fires on some noise, AFC/AGC gets triggered, and shortly thereafter the real packet comes in: this causes the RSSI threshold to be crossed and a second AFC/AGC is performed now locking in good values for the real packet.

Measuring RSSI and setting RSSI threshold

In order for the RSSI threshold to be useful in triggering AGC/AFC it needs to be set to a good value, which is typically a few dB above the noise floor so it triggers on real packets.

The best way to do this that I know is to periodically (e.g. every second) measure the RSSI (by reading REG_RSSI_VALUE) when there is hopefully no packet in the air and calculate a relatively slow moving average. This works because the RSSI value is updated continuously independent of receiver state.

Related to this is how to measure the RSSI of a received packet. Because the RSSI value is updated continuously, in order to measure the signal strength of the packet the register must be read while the packet is being received! Specifically, reading it in the packet-ready interrupt handler is too late: the value is not latched and you'd be reading the noise level after the packet.

The best I've found is to read the RSSI in the preamble interrupt. This can cause some false readings when the preamble is triggered by noise and then a packet comes in right after. I don't know of a simple fix without adding another interrupt, e.g. reading RSSI triggered by a SyncAddrMatch interrupt.


The main insights for me have been:

  • The RX chain only has two states: before sync/addr match and after sync/addr match. Before, it shifts bits into the matcher (I believe it does this even without preamble and can match). After it shifts bits into the FIFO and a packet will eventually result (modulo CRC check).
  • The RSSI threshold is completely irrelevant to the RX chain's functioning, except as a second opportunity to trigger AFC/AGC.
  • There are only two opportunities to trigger AGC/AFC (preamble and RSSI) so it's important to make them count, e.g. not have the radio sit in preamble-detected mode forever due to some noise.

Open questions

  • Can RSSI threshold trigger a packet? In section 3.5.1 of the datasheet it says:

In the receiver operating mode two states of functionality are defined. Upon initial transition to receiver operating mode the receiver is in the ‘receiver-enabled’ state. In this state the receiver awaits for either the user defined valid preamble or RSSI detection criterion to be fulfilled. Once met the receiver enters ‘receiver-active’ state. In this second state the received signal is processed by the packet engine and top level sequencer.

This seems to suggest that RSSI can trigger packet reception, but I have not been able to see that in operation. I don't know how the bit synchronizer is going to get a proper lock without preamble other than by luck.

  • I don't know how the preamble detector works, but since the radio can detect a preamble (with a specified length and max number of error samples) why can't the radio detect that there is no preamble anymore and reset AFC/AGC?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.