ESP32 Arduino Uart takes long to transmit

I have a “standard” Serial code with data to transmit between the ESP and a device. The baudrate is 38400 and can’t be changed due to the hardware on the other side.

For example I send 12 bytes to the device and it answers with 10 bytes, but that whole transaction takes around 1 second. I tried to change the Serial.setTimeout and Serial.setRxTimeout to lower values but I ended up messing up the transmission and even getting it to just stop completely…

Does anyone know what I could change to get faster transmitting speeds?

What is the “standard” serial code you are referring to?

I suspect the “standard code” you mentioned will not fit your application (receiving exactly 10 bytes) but will probably receive a string terminated with “\n”.
Since this (again only assumed) does not apply, your code will run into a timeout of 1 second.

To be able to answer more precisely, please show your relevant code.

Hi thanks for your answer. Well yeah standard really isn’t a good definition, so here’s the “standard” code :slight_smile:

std::vector<uint8_t> Device::ReceivePayload()
{
    std::vector<uint8_t> buffer;
    uint8_t c[50];
    int rxBytesCount = 0;
    int rxIndex = 0;
    int rxCount = 0;

    std::stringstream ss;
    ss << "ReceivePayload";
    ss << std::hex;

    std::stringstream ss2;
    do
    {
        rxBytesCount = _uartPort->readBytes(c,50);
        rxIndex = 0;

        // Serial.print("Device. ReceivePayload RxBytesCount = ");
        // std::stringstream ss1;        
        ss2 << std::hex;
        // ss1 << std::hex << "0x" << std::to_string(rxBytesCount);
        // Serial.println(ss1.str().c_str());

        if(rxBytesCount == 0)
            break;
            

        while (rxBytesCount > 0)
        {
            // Ignore echo
            if(rxCount >= _lastSentBuffer.size())
            {
                ss << std::setw(2) << std::setfill('0') << (int32_t)c[rxIndex];
                ss2 << "0x" << std::setw(2) << std::setfill('0') << static_cast<int>(c[rxIndex]) << ", ";
                buffer.push_back(c[rxIndex]);
            }
            rxIndex++;
            rxCount++;
            --rxBytesCount; 
        }
    } while (true);

    info2 = ss.str();
    Serial.print("Device::ReceivePayload (UART)= ");
    Serial.println(ss2.str().c_str());
    return ScpFrame::GetPayloadFromData(buffer);
}

That’s just the receive function now…

So you’re telling it to read 50 bytes (which includes waiting for a maximum timeout, like the docs say), and the only way the loop is escaped is by the break when the timeout has occurred (rxByteCount == 0)? But… that will run into the timeout every time.

hmm that makes sence…
Is there a better way to solve this? I’m really clueless currently

Does the payload you receive have any structure in it, like a fixed-length header that contains the length of the remaining data to be read? Thus you could do much more well-controlled read calls that don’t time out, but terminate exactly when the payload is fully received (on the logical level).

Of course the other way would be to shorten the receive timeout, e.g., always block until the first byte is received, then read as many bytes as you can with a timeout of 100ms, or whatever the time is for the maximum characters you expect in a response along with your given baud rate (which translates into a raw transmission time).

Also that is some crazy C++ standard library galore. In the receiving loop, you should just focus on receiving the bytes into the byte / uint8_t buffer, worry about fancy byte to hex string conversions and puhsing into a std::vector (??) later, if you need that at all… Also String(myByte, HEX) exists as a conversion function for a byte in native Arduino code.

Yeah that was just for debugging in an earlier state of the project…

But no it’s not a fixed amount of data, nor is it terminated…
Yeah i tried to put the timeout down to 280ms which works fine, but if i go any lower the transmission crashes entirely…

_uartPort = &serialPort;    
    _uartPort->setRxBufferSize(UART_RX_BUF_SIZE);
    _uartPort->setTxBufferSize(UART_TX_BUF_SIZE);
    _uartPort->begin(UART_BAUD, UART_CONFIG, rxPin, txPin);
    _uartPort->setRxTimeout(3);
    _uartPort->setTimeout(300);

That’s the init of the UART port

That’s bad protocol design then in regards to the payload format. That should be fixed at the base, either by introducing introducing an explicit length field in the payload, or implicit by saying “if you send this type of request, the answer will always be X bytes long”.

You can also use a different kind of timeout logic where the timeout (of e.g. 280ms) is not taken to be the time for the entire transmission, but for one individiual character. For example, you can blockingly receive the first byte (infinite timeout), and then read the next single byte with a much shorter timeout, say e.g. 10 milliseconds. The idea being, if the next character needs more than x milliseconds to appear (after transmission has started), there won’t be coming any more, so the transmission is done.

Yeah the problem is that I can’t change the protocol…

But the idea is great, so should i change the setTimeout() to a big value when starting to read and once I’ve received it, change it to something like 10? Or how can I implement this?

You can implement the first blocking byte read manually with while(_uart->available() == 0) { /* nothing */ } and then a _uart->read(), so you can just do setTimeout to the intra-character timeout once and then use _uart->readBytes(&myByte, 1) to read 1 byte with timeout.

I’ve done this now:

std::vector<uint8_t> Device::ReceivePayload()
{
    Serial.print("StartRead\n");

    std::vector<uint8_t> buffer;
    uint8_t c[50];
    int rxBytesCount = 0;
    int rxIndex = 0;
    int rxCount = 0;

    // std::stringstream ss;
    // ss << "ReceivePayload";
    // ss << std::hex;

    std::stringstream ss2;

    while(_uartPort->available());    

    rxBytesCount = _uartPort->read(c, 1);
    do
    {
        rxBytesCount = _uartPort->readBytes(c,50);
        rxIndex = 0;

        // Serial.print("Device. ReceivePayload RxBytesCount = ");
        // std::stringstream ss1;        
        // ss2 << std::hex;
        // ss1 << std::hex << "0x" << std::to_string(rxBytesCount);
        // Serial.println(ss1.str().c_str());

        if(rxBytesCount == 0)
            break;
            

        while (rxBytesCount > 0)
        {
            // Ignore echo
            if(rxCount >= _lastSentBuffer.size())
            {
                // ss << std::setw(2) << std::setfill('0') << (int32_t)c[rxIndex];
                // ss2 << "0x" << std::setw(2) << std::setfill('0') << static_cast<int>(c[rxIndex]) << ", ";
                buffer.push_back(c[rxIndex]);
            }
            rxIndex++;
            rxCount++;
            --rxBytesCount;
        }
    } while (true);

    // info2 = ss.str();
    // Serial.print("Device::ReceivePayload (UART)= ");
    // Serial.println(ss2.str().c_str());
    Serial.print("FinishRead\n\n\n");
    return ScpFrame::GetPayloadFromData(buffer);
}

But again it gets stuck and just stop transmitting at all…

No. You’ll want to block when there is nothing available. That’s while(!_uartPort->available());

ohh missed this one thanks

Well it still doesn’t work and just keeps resetting the esp…

Can you try something like

std::vector<uint8_t> Device::ReceivePayload()
{
    Serial.println("StartRead");
    std::vector<uint8_t> buffer;
	_uartPort->setTimeout(50); // intra-character timeout
	// Blockingly wait for at least 1 byte to be available
	// Could also do timed-read in case nothing gets ever received
    while(!_uartPort->available()) {}
	// Read first byte
	buffer.push_back((uint8_t) _uartPort->read());
	// Read others other timeout occurrs
	while(true) {
		size_t numRead;
		uint8_t val;
		numRead = _uartPort->readBytes(&val, 1); // read with timeout 
		// Nothing available after timeout?
		if (numRead == 0)
			break;
		buffer.push_back(numRead);
	}
    Serial.println("FinishRead\n");
    return ScpFrame::GetPayloadFromData(buffer);
}

Again, this suspiciously looks like some decoding of a protocol going on. You are 1000% sure there is no header and length field field in this data?

Nope that also resets the ESP…

I’m not 1000% sure but about 90 maybe… I’ll check again