[fpc-pascal] How to implement a circular buffer object in pascal?

Brian vmst at golden.net
Tue Sep 8 20:16:46 CEST 2020


>Bo,
>
>Most of the users on this forum have never interfaced with hardware , as
you
>can see from the responses. Those who have interfaced with hardware have
>developed circular buffers which they  know work , and they reuse them
again
>and again.

I did this about 10 years ago when working on a PIC project with
serial data coming in from sensors and such, using Ansi-C.
And the buffer was just an array of char (byte in pascal) and there
was a read and a write index. The read index was only ever changed by
the consumer and the write index by the interrupt routine when adding
data.

The data reception was done in the interrupt and the code stuffed the
data into the array using ++ syntax for the index, with a check for
overflowing the array and then setting the index back to zero.

There was probably also an overrun detection and action if the
consumer is not fast enough, but I do not remember now...

>
>The following assumes you are receiving data from a sender , and in this
>case the sender and receiver are connected via 1Gbps Ethernet using Synapse
>(works really well).
>
>
>A thread receives data into an array of byte. RxBuffer : Array[0..1500] of
>byte  ... and copies the number of bytes received into a circular buffer ,
>which is an array of RxBuffer.

This is where I lose you, are you saying the circular buffer is not
individual bytes but something larger? What is the size of RxBuffer?
Looks like it is an array of byte, can you then make an array of such
arrays?

 ... The CicBuffer is an array of N RxBuffer , which is an array of byte.
The main loop extracts the byte array and either decodes them or type casts
them into a record structure if it is compatible with the contents of the
byte array,

Type
 RxBufferType = array[0..1500] of byte;

Var
 CirBuffer : array[0..N] of RxBufferType;

>
>In pseudo code it looks like this ...
>CircularBuffer : Array[0..N] of RxBuffer
>There is a ReadIndex and a WriteIndex , both longword or longint.
>
>The thread moves the data into the circular buffer continuously  increase
>the WriteIndex each write of RxBuffer data. If the WriteIndex > N then
>WriteIndex := 0 , otherwise it is increased by 1.
>
>The main loop (below)  moves the byte data using MOVE from CircBuffer to
>whatever structure it represents , increase the ReadIndex by one and
decodes
>the data as needed.

Looks like an array of some bigger datatype then. But if so then one
needs to be able to detect in the receiver that enough data has
arrived to make one such object to put onto the next buffer...

... you appear to be thinking of a asynchronous serial (RS-232 / 422) with
no delimiters to indicate when a complete message is received.

... think of the byte array as an Ethernet packet of any length data of
which the data structure is known. This is received using Synapse (UDP in
this case) and copied into RxBuffer byte array by a thread which is always
running until the main program is shut down. Whenever the thread receives a
"complete" packet from Synapse (it signals a complete packket) , the thread
code  then puts it into RxBuffer and then copies RxBuffer into
CircBuffer[WriteIndex] and increments the WriteIndex. If the WriteIndex < N
then WriteIndex := WriteIndex +1 else WriteIndex := 0 // wraps

The ReadIndex is incremented similar to the WriteIndex, if ReadIndex < N
then ReadIndex := ReadIndex +1 else ReadIndex := 0;

The circular buffer can be made as large as you need to "absorb" incoming
data while the main loop is busy doing something else. The "nice" aspect of
a circular buffer is that if the main loop is very slow and the incoming
data stream is very fast , the WriteIndex will eventually overrun the
ReadIndex and data is lost but nothing chokes or crashes , and eventually
the ReadIndex will catch up.

BTW : There are enough things that can go wrong implementing a circular
buffer , and while it should be possible (and very nice) to implement it as
an object , it may not be worth the effort. At least get it working as
non-object code , then decide.

>
> If ReadIndex <> WriteIndex then
>  .. do the work as described above and increment the ReadIndex
>
>I use the Free Pascal unit which allows suspending the thread while the
>ReadIndex is being increased. In the old DOS/DPMI days we would disable
>interrupts briefly.

Do you use CriticalSection for this?
No . .. I started to originally but found it wasn't necessary as it uses a
semaphore to prevent a Write occurring when the ReadIndex is being
incremented. The FPC unit syncobjs is used the create a semaphore which is
RESET before incrementing the ReadIndex and SET immediately after. This
prevents a potential race condition where data could be written to
CircBuffer at the exact time the ReadIndex is being incremented.

>If you are innterested I can send you code snippets showing exactly how to
>implement the circular buffer.
>

BTW : The circular buffer approach can also be used when transmitting data.
For example Synapse maintains its own transmit buffers , which signal when
you can send more data.



--
Sent from: http://free-pascal-general.1045716.n5.nabble.com/


More information about the fpc-pascal mailing list