Nano33 BLE Performance

Hi,

I am new to the forum and I'd like to make an application that transfer a file from iOS to a microcontroller using the nano33 as a BLE-UART(AT) converter.

Now it works as a basic concept. I do the following.
For Swift:

private func sendNextFileChunkToPeripheral()
{
    if ftpIsDownstreaming == false
    {
        return
    }
    
    guard let peripheral = getConnectedPeripheral()?.peripheral else {
        return
    }
    
    /* done */
    if ftpDownstreamChunks.count == 0
    {
        sendFtpCtlToPeripheral(what: .FileEnd)
        ftpIsDownstreaming = false
        return
    }
    
    // Find the target characteristic by its UUID
    if let characteristic = discoveredServices[targetFtpServiceUUID]?[CBUUID(string: "F47AC10B-58CC-4372-A568-000000000002")],
       let chunk = ftpDownstreamChunks.first
    {
        // Send the data to the peripheral
        if chunk.count >= 4 {
            let value = chunk.withUnsafeBytes { $0.load(as: UInt32.self) }
            print("Send chunk offset: \(value)")
        }
        peripheral.writeValue(Data(chunk), for: characteristic, type: .withoutResponse)
    }
}


func sendFileToPeripheral(offset: Int, data: Data)
{
    ftpIsDownstreaming = false
    
    guard let peripheral = getConnectedPeripheral()?.peripheral else
    {
        return
    }
    let chunkMtu = min(Int(peripheral.maximumWriteValueLength(for: .withoutResponse)), 244) - 4    // Ensure MTU ≤ 244 which should be optimal
    
    /* pack chunks */
    var chunks: [[UInt8]] = []
    var done: Int = 0
    while done < data.count
    {
        /* getting chunk */
        let chunkOffset: Int = offset + done
        let remaining = data.count - done
        let chunkSize = min(chunkMtu, remaining)
        let chunk = data.subdata(in: done ..< (done + chunkSize))
        
        /* format chunk */
        var offsetLE = UInt32(chunkOffset).littleEndian
        let offsetBytes = withUnsafeBytes(of: &offsetLE) { Array($0) }  // [UInt8]
        chunks.append(offsetBytes + chunk)
        
        done += chunkSize
    }
    
    DispatchQueue.main.async {
        self.ftpDownstreamChunks.removeAll();
        self.ftpDownstreamChunks.append(contentsOf: chunks)
        self.ftpIsDownstreaming = true
        self.sendNextFileChunkToPeripheral()
    }
}


func handleFtpCharacteristic(_ charUUID: CBUUID, data: Data, peripheral: CBPeripheral) {
    if charUUID == CBUUID(string: "F47AC10B-58CC-4372-A568-000000000001") {
        /* controll */
        parseFtpControl(data)
    }
    else if charUUID == CBUUID(string: "F47AC10B-58CC-4372-A568-000000000003")
    {
        let ackOffset = data.withUnsafeBytes { $0.load(as: UInt32.self) }
        
        print("ftp acknowledged chunk, todo check number \(ackOffset)")
        
        //ftpDownstreamChunks.removeFirst()
        //sendNextFileChunkToPeripheral()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
            self.ftpDownstreamChunks.removeFirst()
            self.sendNextFileChunkToPeripheral()
        }
    }
    else
    {
        print("Unhandled charUUID(mqtt): \(charUUID.uuidString)")
    }
}

In words: I send out a chunk of the file and wait until I receive an ACK on a different characteristic and then send the next chunk. Please note the 50ms delay! Yes I know I could use .withResponse here, but bare with me.

On the Nano33 I do:

/* ftp channel handler */
void BLEwrapper::ftpChannelCharacteristicWritten(BLECharacteristic &characteristic)
{
if(serial == nullptr)
return;

if(characteristic.uuid() == ftpRetrCharacteristic.uuid())
{
    const uint8_t *data = characteristic.value();
    uint32_t length = characteristic.valueLength();
    if(length < 4)
        return;

    /* separate header from payload */
    const uint32_t *offset = (uint32_t *) data;
    data += 4;
    length -= 4;
    
    //FTPR=offset,len,crc\n<DATA>
    char hdr[32];
    const uint16_t hdrLen = snprintf(hdr, sizeof(hdr), "FTPR=%lX,%lX,%X\n<", *offset, length, CRC::crc16_ccitt(data, length));
    uint16_t msgSize = hdrLen + length + 1;        //hdr + data + '>'
    uint8_t *msg = new uint8_t[msgSize];
    memcpy(msg, hdr, hdrLen);
    memcpy(msg+hdrLen, data, length);
    memcpy(msg+hdrLen+length, ">", 1);

    if(dataMsg_enqueue(msg, msgSize) == false)
    {
        delete [] msg;
        serial->print("+DMSG=full\n");
    }
    else
    {
        /* Ack back */
        ftpRetrAckCharacteristic.writeValue(offset, 4, false);

        serial->print("+DMSG=new\n");
    }

    return;
}

As soon as I receive something. I store it so that AT serial can collect it later on...

This works so far and I achieve around 2kb/s so far.
BUT: there is the 50ms delay which leaves a lot of questions! Why do I need it. If I omit it, the Nano33 will get stuck after 4 chunks and does not react for 30sec (even for AT commands). Thou' it does not crash, because after 30sec it will report the next chunk.

So my question is: I want to get rid of the magic 50ms delay. Does there exist a hook or something that indicated we have some space left to receive new data? Or in general: how to boost the throughput of the Nano33??

Many thanks!
Juergen