You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
First of all, I think we can all agree that we appreciate the hard work the developers put into the Serial/HardwareSerial development. That being said, I think it's time for a few changes that would significantly increase the efficiency and utility while still maintaining a simple-to-use interface:
A Serial* (Serial, Serial1, Serial2, etc) object is automatically instantiated as a global variable for each detected hardware UART (and USB CDC where applicable). While the auto-instantiation makes things "simpler", it comes at a great cost for very little benefit. Each auto-instantiated Serial object has two buffers (transmit/receive) with fixed sizes determined based on the SRAM available on the AVR. This means that, even if the UART is completely unused in the user's program, the Serial instance's TX/RX buffers take up a considerable amount of available SRAM. On a board like the Arduino Micro, the single hardware UART takes up 128 bytes of SRAM for the TX+RX buffers, which is 5% of the chip's 2560 bytes of SRAM. And for boards with more than one hardware UART, this disadvantage is multiplied. The only real advantage is from a user's perspective, which makes it so they don't have to declare/instantiate it themselves. It would be much more efficient to have the user instantiate only the HardwareSerial objects that they need, which should certainly be simple enough for even a beginner user. The HardwareSerial header could provide an enumeration of "define-specified" available UARTs based on the available UBRR*H defines. This enumeration could be provided to a Serial or HardwareSerial constructor to allow the user to specify which UART they want the instantiated object to be tied to. The enumeration could be a define (e.g. SERIAL1) or could be an actual enum (although I don't think the enum parameter fits the design pattern of official Arduino libraries as a whole).
We really need user-configurable TX/RX buffer sizes. This could be implemented as a constructor overload, with a default constructor that uses default values set as they currently are (e.g. 64 bytes each for SRAM > 1024 bytes). This would give the user much more control over how much buffer space they really need (which could be more or less than the 64 bytes each, or even have different sizes for TX vs RX for example). Users would have much more flexibility in the sizes of packets that they send/receive, and could also help save valuable SRAM where needed.
It would be great to have direct read-access to the RX buffer as a whole. In most practical applications I've seen of Serial receiving, the user winds up having to create their own additional buffer (wasting even more SRAM) to read data out of the RX circular buffer so it can be analyzed before it's consumed. Users need the ability to analyze data in the RX buffer directly to ensure the most optimal use of SRAM. For example, lets say I'm receiving a GPS NMEA string, which is variable in size and contains a checksum that needs to be analyzed. I'd have to create my own additional buffer, read bytes out of the RX buffer using Serial.read() until I receive the footer byte + checksum. This has to happen first because the checksum needs to be validated before attempting to consume/parse the data in the earlier parts of the packet (otherwise the parsing operations are a waste of cycles). If I had direct read access to the RX buffer as a whole, I could do this in the existing RX buffer without having to create an additional buffer of my own, validate the checksum, and then finally consume data from the RX buffer and parse it. I know the RX buffer is implemented as a circular buffer which makes it more difficult for the user to read from directly, but helper functions such as begin() and end() could generate iterators from the head/tail, or create a peek overload that takes an index/position relative to the head, where index/position 0 is the head. Just examples, I'm sure there are plenty of ways to skin that cat especially in a way that meets the Arduino design pattern and/or stream inheritance.
The text was updated successfully, but these errors were encountered:
pcdangio
changed the title
Serial: recommended redesign for higher efficiency/utility, particularly w.r.t. buffers
Serial: recommended updates for higher efficiency/utility, particularly w.r.t. buffers
Nov 27, 2024
After further testing, I have an update for item 1:
It seems like the HardwareSerial-based global variables (Serial, Serial1, etc) are defined, but optimized out by the compiler/linker if the object isn't used. However, on certain AVR models (such as the 32U4), the USB serial (Serial_) global instance defined in CDC.cpp do not get optimized out, even if the instance is not used directly in the sketch.
For CDC serial (Serial_), USB_API.h declares an _rx_buffer on the stack within Serial_, with s defined by SERIAL_BUFFER_SIZE derived from ram size. However, the implementation of Serial_ in CDC.cpp doesn't use _rx_buffer at all, its reading directly from the USB FIFO buffer. So the _rx_buffer is instantiated as part of the Serial_ object, but never used, so it winds up consuming/wasting SERIAL_BUFFER_SIZE bytes.
First of all, I think we can all agree that we appreciate the hard work the developers put into the Serial/HardwareSerial development. That being said, I think it's time for a few changes that would significantly increase the efficiency and utility while still maintaining a simple-to-use interface:
The text was updated successfully, but these errors were encountered: