diff --git a/utility/SerialFirmata.cpp b/utility/SerialFirmata.cpp index 8b703e13..02efcf73 100644 --- a/utility/SerialFirmata.cpp +++ b/utility/SerialFirmata.cpp @@ -14,11 +14,19 @@ - handlePinMode calls Firmata::setPinMode - Last updated October 16th, 2016 + Last updated March 16th, 2020 */ #include "SerialFirmata.h" +// The RX and TX hardware FIFOs of the ESP8266 hold 128 bytes that can be +// extended using interrupt handlers. The Arduino constants are not available +// for the ESP8266 platform. +#if !defined(SERIAL_RX_BUFFER_SIZE) && defined(UART_TX_FIFO_SIZE) +#define SERIAL_RX_BUFFER_SIZE UART_TX_FIFO_SIZE +#endif + + SerialFirmata::SerialFirmata() { #if defined(SoftwareSerial_h) @@ -29,6 +37,12 @@ SerialFirmata::SerialFirmata() #endif serialIndex = -1; + +#if defined(FIRMATA_SERIAL_RX_DELAY) + for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) { + maxRxDelay[i] = FIRMATA_SERIAL_RX_DELAY; // @todo provide setter + } +#endif } boolean SerialFirmata::handlePinMode(byte pin, int mode) @@ -56,13 +70,17 @@ boolean SerialFirmata::handleSysex(byte command, byte argc, byte *argv) Stream *serialPort; byte mode = argv[0] & SERIAL_MODE_MASK; byte portId = argv[0] & SERIAL_PORT_ID_MASK; + if (portId >= SERIAL_READ_ARR_LEN) return false; switch (mode) { case SERIAL_CONFIG: { long baud = (long)argv[1] | ((long)argv[2] << 7) | ((long)argv[3] << 14); serial_pins pins; - +#if defined(FIRMATA_SERIAL_RX_DELAY) + lastBytesAvailable[portId] = 0; + lastBytesReceived[portId] = 0; +#endif if (portId < 8) { serialPort = getPortFromId(portId); if (serialPort != NULL) { @@ -229,6 +247,10 @@ void SerialFirmata::reset() serialIndex = -1; for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) { serialBytesToRead[i] = 0; +#if defined(FIRMATA_SERIAL_RX_DELAY) + lastBytesAvailable[i] = 0; + lastBytesReceived[i] = 0; +#endif } } @@ -302,6 +324,10 @@ void SerialFirmata::checkSerial() if (serialIndex > -1) { +#if defined(FIRMATA_SERIAL_RX_DELAY) + unsigned long currentMillis = millis(); +#endif + // loop through all reporting (READ_CONTINUOUS) serial ports for (byte i = 0; i < serialIndex + 1; i++) { portId = reportSerial[i]; @@ -316,27 +342,53 @@ void SerialFirmata::checkSerial() continue; } #endif - if (serialPort->available() > 0) { - Firmata.write(START_SYSEX); - Firmata.write(SERIAL_MESSAGE); - Firmata.write(SERIAL_REPLY | portId); - - if (bytesToRead == 0 || (serialPort->available() <= bytesToRead)) { - numBytesToRead = serialPort->available(); + int bytesAvailable = serialPort->available(); + if (bytesAvailable > 0) { +#if defined(FIRMATA_SERIAL_RX_DELAY) + if (bytesAvailable > lastBytesAvailable[portId]) { + lastBytesReceived[portId] = currentMillis; + } + lastBytesAvailable[portId] = bytesAvailable; +#endif + if (bytesToRead <= 0 || (bytesAvailable <= bytesToRead)) { + numBytesToRead = bytesAvailable; } else { numBytesToRead = bytesToRead; } - +#if defined(FIRMATA_SERIAL_RX_DELAY) + if (maxRxDelay[portId] >= 0 && numBytesToRead > 0) { + // read and send immediately only if + // - expected bytes are unknown and the receive buffer has reached 50 % + // - expected bytes are available + // - maxRxDelay has expired since last receive (or time counter wrap) + if (!((bytesToRead <= 0 && bytesAvailable >= SERIAL_RX_BUFFER_SIZE/2) + || (bytesToRead > 0 && bytesAvailable >= bytesToRead) + || (maxRxDelay[portId] > 0 && (currentMillis < lastBytesReceived[portId] || (currentMillis - lastBytesReceived[portId]) >= maxRxDelay[portId])))) { + // delay + numBytesToRead = 0; + } + } +#endif // relay serial data to the serial device - while (numBytesToRead > 0) { - serialData = serialPort->read(); - Firmata.write(serialData & 0x7F); - Firmata.write((serialData >> 7) & 0x7F); - numBytesToRead--; + if (numBytesToRead > 0) { +#if defined(FIRMATA_SERIAL_RX_DELAY) + lastBytesAvailable[portId] -= numBytesToRead; +#endif + Firmata.write(START_SYSEX); + Firmata.write(SERIAL_MESSAGE); + Firmata.write(SERIAL_REPLY | portId); + + // relay serial data to the serial device + while (numBytesToRead > 0) { + serialData = serialPort->read(); + Firmata.write(serialData & 0x7F); + Firmata.write((serialData >> 7) & 0x7F); + numBytesToRead--; + } + + Firmata.write(END_SYSEX); } - Firmata.write(END_SYSEX); } - } } } diff --git a/utility/SerialFirmata.h b/utility/SerialFirmata.h index 365fde48..a05b761a 100644 --- a/utility/SerialFirmata.h +++ b/utility/SerialFirmata.h @@ -15,7 +15,7 @@ - Defines FIRMATA_SERIAL_FEATURE (could add to Configurable version as well) - Imports Firmata.h rather than ConfigurableFirmata.h - Last updated October 16th, 2016 + Last updated March 11th, 2020 */ #ifndef SerialFirmata_h @@ -30,6 +30,32 @@ #include #endif +// If defined and set to a value between 0 and 255 milliseconds the received bytes +// will be not be read until until one of the following conditions are met: +// 1) the expected number of bytes have been received +// 2) the serial receive buffer is filled to 50 % (default size is 64 bytes) +// 3) the delay since the last received byte exceeds the configured FIRMATA_SERIAL_RX_DELAY +// hints: 5 bytes at 9600 baud take 5 ms, human perception of a delay starts at 50 ms +// This feature can significantly reduce the load on the transport layer when +// the byte receive rate is equal or lower than the average Firmata main loop execution +// duration by preventing single byte transmits if the underlying Firmata stream supports +// transmit buffering (currently only available with EthernetClientStream). The effect +// can be increased with higher values of FIRMATA_SERIAL_RX_DELAY. +// Notes +// 1) Enabling this feature will delay the received data and may concatenate +// bytes into one transmit that would otherwise be transmitted separately. +// 2) The usefulness and configuration of this feature depends on the baud rate and the serial message type: +// a) continuous streaming at higher baud rates: enable but set to 0 (receive buffer store & forward) +// b) messages: set to a value below min. inter message delay (message store & forward) +// c) continuous streaming at lower baud rates or random characters: undefine or set to -1 (disable) +// 3) Smaller delays may not have the desired effect, especially with less powerful CPUs, +// if set to a value near or below the average Firmata main loop duration. +// 4) The Firmata stream write buffer size must be equal or greater than the max. +// serial buffer/message size and the Firmata frame size (4 bytes) to prevent fragmentation +// on the transport layer. +//#define FIRMATA_SERIAL_RX_DELAY 50 // [ms] +#define FIRMATA_SERIAL_RX_DELAY 50 + #define FIRMATA_SERIAL_FEATURE // Serial port Ids @@ -194,6 +220,12 @@ class SerialFirmata: public FirmataFeature int serialBytesToRead[SERIAL_READ_ARR_LEN]; signed char serialIndex; +#if defined(FIRMATA_SERIAL_RX_DELAY) + byte maxRxDelay[SERIAL_READ_ARR_LEN]; + int lastBytesAvailable[SERIAL_READ_ARR_LEN]; + unsigned long lastBytesReceived[SERIAL_READ_ARR_LEN]; +#endif + #if defined(SoftwareSerial_h) Stream *swSerial0; Stream *swSerial1;