Managing data flow from a communication peripheral can get very tricky as you have just found out. The solutions to this can take many forms and are generally dependant of what your application is trying to do. But you may be interested in the way it was done in a video processing application I worked on.
It took video frames from a capture card and passed them on to numerous threads for simultaneous display in multiple places. Its job was to pass the frame to the following:
- A video display board (1 or more),
- A software frame decoder for display in a window
- A broadcast stream to Ethernet for display in a window on another PC
All displays were exactly in sync.
In a simple system where one thread gets the data and one processes it, the FIFO solution is the way to go, although you would really only need one thread as the other should actually be an ISR. But you still use a FIFO.
If you need to share data across several threads then things are a bit more complicated. Again the solution depends of how you are going to use the data. Are you just going to act on its content or are you going to write back to the buffer that contains it. This affects whether you need to take a copy or not, but generally copies are bad if you have limited processing power.
In my application I just had one provider and many consumers. The data was not changed in between them. In its simplest form the architecture was one ISR per input or output and a single thread joining them all together. The whole thing was done using the simple message queue, one per consumer.
So here’s how it was done.
A number of buffers were created at power up and placed in a pool. The pool is global among the threads/ISRs that need it. The buffers each have a reference counter and a count of how much data they contain. They also had stuff like mutexes etc. as required.
A Capture initialiser takes a buffer from the pool and configures the DMA to point at the buffer. From this point on the sequence is:
- The Capture card ISR is called when DMA is complete. It places the ref to the buffer into the message queue, gets another buffer from the pool, sets up the next DMA and exits. Its about 5 line of code.
In a UART driver you can use an empty pool to set the flow control.
- The main buffer handler is at the end of the message queue and it has one output queue for each consumer, however many there are. Its job is to take the buffer (by its reference) from the input queue then set its reference count to the number of outputs and place a copy of the reference in each of the out queues.
- The video card driver is a DMA driven ISR. It takes the next buffer in the queue and starts a DMA operation. The previous now used buffer has its reference counter decremented and if it is zero the buffer is put back in to the pool, completing the cycle. Only when all consumers have finished with the buffer is it released.
Each consumer is slightly different, and may or may not copy the data at this point.
There is very little code at each stage and the buffers flow round in a circle. basically you need one buffer for each stage in the sequence plus one. Most of the code is actually for setup, not execution.
The advantages of this system are:
The connections between stages are very simple so it is easy to add extra stages if required. I had up to six as the system was dynamically configurable.
The buffers don't actually have to be as big as a whole data packet. In this case they were big enough for a whole video frame but in another system we had a scatter/gather DMA so each buffer only had a fragment of a frame.
In a UART system with an undefined stream The ISR would gradually fill up the buffer and when it was full, post the buffer in the queue. There would need to be a timer to make sure slow data was always sent. So if after a few mS only a few characters were received the buffer would be sent any way. USB CDC (USB to com port) drivers work this way.
I don’t know CMSISS RTX but a quick look shows that it has a memory pool facility that looks perfect for this job. You would just need to define a buffer. Maybe something like this
typedef struct {
mutex_t lock;
uint32_t RefCount;
uint32_t DataCount;
uint8_t data[BUFF_SIZE];;
} poolBuffer_t;