I am writing an application in C using SiliconLabs Simplicity IDE for an EFR32MG21 board that implements Bluetooth 5.0. My application sends data between devices using L2CAP connections over channels. One of the devices uses a CSR8675 Bluetooth chip that only supports Bluetooth spec 4.1.
I am trying to understand what I should use as the parameters to the LE CREDIT BASED CONNECTION REQUEST, LE CREDIT BASED CONNECTION RESPONSE, and LE FLOW CONTROL CREDIT signaling commands to get the fastest data rate.
The way the 4.1 & 5.0 specs read to me is that the credits are the number of LE-frames a device can send to another device. Got it. For 4.1, the LE-Frames can hold up to 23 bytes (27 - 4 byte header). So, since there's one credit for each LE-frame, I would think the maximum value of the MTU (maximum transferrable unit, aka SDU) parameter would be the combination of the lengths of the data in these LE-frames and that there'd be one LE-frame per MPS (Maximum PDU Size). However, the spec says that the MPS value in the LE CREDIT BASED CONNECTION REQUEST command can range from 23 to 65533 bytes. Can an MPS be bigger than the payload size of an LE-frame?
I understand that an MTU (maximum transferrable unit, aka SDU) parameter can be greater than 23. That makes sense since the MTU is the total length of all the LE-frames' payloads. But I don't understand why the 4.1 spec says that MPS can be greater than 23.
I inspected my data over the air and saw that, when I set my MPS to something greater than 23, the link layer broke that up into packets of 23 (27 - 4). It appeared as if it was breaking up the MPS packets that were the fragments of my MTU/SDU.
Help!
Thanks in advance!
I have tried different values for my MPS, MTU, and credits. I once tried to set my MPS to the size of the BT 5.0 device I'm using, 247 bytes. This partially worked but eventually an error was returned to me when I tried to transmit data and my connection was dropped. The error was L2CAP_WRONG_STATE.
With an MPS of 247 bytes, every time I send one I use 11 credits (my LE-frame has 23 bytes of payload). I think I am subtracting my credits and sending (waiting on) new ones accordingly.
I am surprised this even partially worked because, again, I don't understand why MPS can be set to something greater than the payload of an LE-frame.
Bluetooth 4.2 introduced LE Data Length extension, which is a feature at the Link Layer. Previously, the Link Layer was limited to only be able to send 27 bytes per over-the-air packet. With this new extension, this limit can be increased up to 251 bytes. Note that this is purely a performance feature and does not at all affect any code, features or limitations running on the host side. L2CAP PDUs up to 65535 bytes have always been, and are still, supported, in BLE (as long as the MTU or MPS is big enough).
With L2CAP CoC, the minimum unit at the host side is called a K-frame (called LE-frame in earlier versions). A K-frame is limited by the MPS (Maximum PDU payload Size). The application however sends and receives data in SDUs (Service Data Units). These are limited by the MTU (Maximum Transmission Unit). The L2CAP layer on the host side splits up an SDU into one or more K-frames, with the idea that these smaller frames can then be interleaved with other data, in case the SDU is very big.
As you say, MPS may be up to 65533 bytes (which is 2 bytes less than 65535 - this is to make space for the 2-byte SDU length header, since the PDU Length in the Basic L2CAP Header (which is present in all L2CAP PDUs) covers both the 2-byte SDU length header field and the information payload). In order to support such big K-frames, there is a feature in L2CAP called Fragmentation and Recombination. The L2CAP chapter, section 1.1 describes it as follows:
Section 1.4 describes Fragmentation as follows:
So yes, K-frames can be split up into multiple Link Layer PDUs. This is exactly how normal GATT works as well when the ATT_MTU is large.
For the best actual performance, you should take the following into account:
So, if you just have a big stream of bytes that you need to transfer and don't care about response time when interleaving with other streams, then use the maximum possible MPS, maximum possible MTU, and send as few SDUs as possible. If you send an SDU containing 10 kB that fits in one K-frame, that will just cost one credit. Let the receiver send back L2CAP_FLOW_CONTROL_CREDIT_IND as infrequent as possible, but before the sender runs out, to avoid stalling the sender. There is no need to set a "small" MPS such as 23 or 247 just to try to match your Link Layer maximum PDU size, if you have a lot of data to send. Rather the opposite, if you send data that uses 4 Link Layer PDUs and these fit in one K-frame, only one 4 byte Basic L2CAP Header is needed instead of 4*4=16 bytes.
Just note that in case of a e.g. a GATT, SMP or L2CAP Connection parameter update request is sent and a very big K-frame is being sent, the response cannot be sent before the K-frame has completed. But the response must be received within 30 seconds per the standard, otherwise the link may be terminated.
Also, make sure that whenever you offer credits, make sure you can actually handle receiving as much data you have given credits for, without blowing up the memory.
In general all guidelines for reaching high throughput over GATT also applies for L2CAP CoC, since the same underlying technology is used. This means you should utilize LE Data Length extension if available, use tuned connection parameters, and use as big packets as possible to avoid the relatively large overhead of sending small packets.
You should debug this. Probably some bug in your code.
If you have MPS set to 247, then you should also be able to send K-frames of that size, and hence need only 1 credit for such a frame.