I am mostly concerned with writing C++ code on "bare metal" microcontroller based embedded systems (no OS or RTOS). I have an issue that I have encountered a number of times in various forms that it seems to me there is no "properly elegant" solution to. It has just troubled me again, so I thought I would raise it here to see if any one can suggest a better way...
The problem typically concerns moving data between a hardware peripheral and data buffers in memory in response to interrupts generated by the hardware peripheral. A simple example be would receiving and transmitting data through a UART, with the incoming (received) bytes and outgoing (transmitted) bytes being buffered through two RAM based (typically ring) buffers, and the UART hardware generating two separate interrupts - Byte Received (aka Receiver Not Empty) and Byte Transmitted (aka Transmitter Empty). For simplicity, let's say no hardware FIFOs or DMA are involved, no error conditions occur (parity errors, buffer overruns, etc.) and there is no OS or RTOS.
Handling incoming (received) data is easy. Every time the UART receives a byte, it fires it's Byte Received interrupt, and the interrupt handler reads the byte from the UART's Receiver and writes it to the incoming data buffer. If the "sender" runs out of data to send, the UART stops receiving bytes, so it stops firing it's Byte Received interrupt and the Interrupt Handler code "automatically" stops transferring data from the UART to the Buffer. When the "sender" resumes sending data, the UART resumes receiving bytes and firing it's Byte Received interrupt, so the interrupt handler code "automatically" resumes transferring the data from the UART to the Buffer. Some other process can asynchronously consume (read and process) the received bytes out of the incoming data buffer, and the only complication this code has to deal with is that some of the time the incoming buffer might be empty (have no data available). Importantly, note that neither the code that implements the incoming data buffer, nor the code that consumes the received bytes that have been placed in the incoming buffer, need to do anything to start and stop the flow of received bytes from the UART to the incoming data buffer, and therefore they don't need to "know anything about" the specifics of the hardware that received the data and how to control it (i.e., the UART and it's Interrupts). This "knowledge" only needs to exist in the UART Driver and Interrupt Handler code, so the buffer and received data consuming code can be quite "generic". This is all pretty straight-forward, and seems to me to be “quite elegant".
But it's handling outgoing (transmitted) data that troubles me. Let’s start assuming there are already bytes waiting to be transmitted in the outgoing buffer and we are already in the process of transmitting these. Every time the UART transmits a byte, it fires it's Byte Transmitted interrupt, and the interrupt handler code reads the next byte from the outgoing data buffer and writes it to the UART's Transmitter, then the process repeats... Until there is no more data in the outgoing data buffer. At this point, when the UART transmits the last byte and fires it's Byte Transmitted interrupt, the interrupt handler code finds the outgoing data buffer is empty and there are no more bytes to transmit, so it disables the UART transmitter's interrupt to stop it firing continually. Later, some other process asynchronously writes some more bytes into the outgoing data buffer, ready for the UART to transmit. But to get the outgoing data "flowing" again, something needs to restart the UART Transmit cycle, probably by re-enabling the UART's Byte Transmitted interrupt so that the interrupt will fire again and the interrupt handler will resume moving bytes from the outgoing data buffer to the UART's transmitter. As I see it, this means the outgoing buffer code, or the process writing data into the outgoing buffer, has to "know" about the need to restart the outgoing data flow, and has to "do something special" to initiate this, typically leading to the outgoing buffer code being written to work specifically with the UART (or other specific hardware) so that it knows how to re-enable the Hardware's Transmit interrupt. This is also pretty easy to do, and is more-or-less how I have done this to-date, but compared to the receive process I described above, this always seems to me to be somewhat "less than elegant".
Another way I have dealt with this is to have an independent bit of code regularly checking if there is any data in the outgoing buffer and if so re-enabling the Transmit interrupt, but this also seems somewhat "less than elegant". It adds overhead, adds latency between data being added to the buffer and transmission actually commencing, and it's difficult to see how it works if you don't know this independent bit of code is being run repeatedly from somewhere.
In fact, as I see it, in the "properly elegant" solution I desire, the incoming and outgoing buffers should be able to be exactly the same code (two instances of the same class) and should know nothing about the hardware they are "connected to". Similarly, the application code reading from and writing to the buffers should also not need to know anything about the hardware the buffers are "connected to". This would make all this code quite generic, and it should then be easy to change which hardware interface the data buffers are "connected to" by changing only which interrupt handlers "connect to" which data buffers, and without having to change the buffer or application code in any way...?
But as I see it the need to "restart the transmit cycle" prevents this. I would be very interested to know if anyone can recommend a solution or an overall better approach to this.
Thanking you,
Martin.