rpp0 / gr-lora

GNU Radio blocks for receiving LoRa modulated radio messages using SDR
GNU General Public License v3.0
525 stars 113 forks source link

Transmitting other packet than the fixed test packet #109

Open mustard123 opened 4 years ago

mustard123 commented 4 years ago

I can, with the encoder branch, send out the fixed test packet of 23 bytes and have it received on the gateway. But as soon as I vary the payload i.e add or remove one or more bytes and adjust the header accordingly, I get nothing on the gateway. Any idea why any packet not having 23 bytes of payload do not seems to work? Why did you decide to design the test packet to have exactly 23 bytes (plus CRC), any specific reasoning?

Anyway great work and kind regards

Backup-Toaster commented 4 years ago

Hi! This response is way overdue, but hopefully it will be a help to anyone who finds this in the future. I certainly had the same problem myself. And apologies in advance - I'm not a programmer (an engineer rather) so my coding style may offend.

What this boils down to is an incorrect sizing of the encoded array when it is initialised. In the latest branch it is of size packet_length*2 which is sufficient for the exact length of the example packet, but not good enough for other packet lengths. This causes the while loop while(interleave_offset+conf.tap.channel.sf <= num_bytes) to not cover the entire range of encoded symbols as needed.

To fix this I replaced the entirety of rows 250-253 of encoder_impl.cc with:

uint32_t header_length = sizeof(loraphy_header_t);
uint32_t payload_length = conf.phy.length + MAC_CRC_SIZE * conf.phy.has_mac_crc;
uint32_t packet_length = header_length + payload_length;

uint32_t bytes_needed_header_words = header_length * 2; // 2 = (4 + 4)/4, which is true since header coding always is (8,4).
uint32_t bytes_needed_payload_words = std::ceil(payload_length * (4 + conf.phy.cr) / 4) - (conf.tap.channel.sf - 7);
// Since SF - 2 codewords get packed into the header symbols, but only 5 header codewords are generated and used, SF-2-5 payload codewords are not included in the payload transmission.

// Small algorithm to find the next multiple of SF greater than or equal to the number of required bytes for payload words.
uint32_t N = bytes_needed_payload_words;
uint32_t K = conf.tap.channel.sf;
uint32_t bytes_needed_payload_words_padded =  (((N + K) % K) == 0) ? N : (N + K) - (N + K) % K;

uint32_t num_bytes = bytes_needed_header_words - 1 + bytes_needed_payload_words_padded + (conf.tap.channel.sf - 7); // Minus one as the last codeword of the header is overwritten.
// We add back in that (SF - 7) value to make sure we are counting all our bytes.

uint32_t num_symbols_header = 8; //Equal to cr + 4, the output length of the interleaving function, when header cr is always 4.
uint32_t num_symbols_payload = bytes_needed_payload_words_padded / conf.tap.channel.sf * (4 + conf.phy.cr); // Comes from fact that sf payload codewords are read in to produce 4 + cr symbols.

uint32_t num_symbols = num_symbols_header + num_symbols_payload;

uint8_t encoded[num_bytes];
memset(encoded, 0x00, num_bytes * sizeof(uint8_t)); // Set all symbols to zero so that zero symbols are transmitted if we don't use all the space.

There's a few things to understand here, which I believe are based in the actual LoRa PHY as I have tested this transmitter with actual hardware receivers.

First, is that in some cases header symbols are transmitted in the same block as payload symbols. The header is always encoded with H(8,4), so the three header bytes become six header codeword bytes; the last codeword however is not included (it is always zero anyway and is added back in at the receiver). So five header codeword bytes are used.

However, the interleaver, for explicit header mode, takes in SF - 2 codewords and produces 8 (= 4 + CR) symbols. Rather than treat the header and the payload in separate blocks, it actually adds some payload codewords to the header block. For H(8,4) (and I haven't done the same work for other code rates) SF-7 payload codewords are added to the header, which you'll see reference to above. So, when sizing the encoded array, this needs to be factored in.

Second relates to how the interleaver works, as alluded above. It takes in SF codeword bytes (SF-2 for reduced rate mode and for the header) and produces CR + 4 symbols for transmission. If the number of codewords that need to be interleaved (remembering to not count the first SF-7 for reasons above) does not divide SF, then the interleaver will start to pull in bytes from the addresses beyond what we have defined in uint8_t encoded[num_bytes] as it is originally defined.

Now, this is not really an issue as these extra bytes are removed at the receiver end, but I added that extra memset at the end there to make sure we are pulling 0x00, rather than garbage. (NB: I don't think that LoRa PHY actually uses 0x00, but like I said it works at the receiver). What is important however is that a) we do need to send these garbage symbols because they have real data interleaved in them, and b) this process has extended the number of bytes that we need to send. Hence, num_bytes has been more carefully defined above so that it accounts for the garbage bytes and hence is sure to send them.

I have these changes in my local repo and will create a pull request once cleaned up, so things might be more clear then.