Quantcast
Viewing all articles
Browse latest Browse all 5

A Matter of Interrupts and Mutual Exclusion – Part 2

Without doubt, the presence of the FIFO buffer helps (as we have seen before). The pieces of software that are needed to handle the FIFO are:

  • the producer: Rx ISR (the receive interrupt service routine)
  • the consumer: GetChar() function itself.

Assuming the USART is identified by the name USART1 and the implementation does not involve an RTOS yet, their code will look as follows:

Producer code (Rx ISR)

void  Com_ISR  (void)                       // USART Rx handler (ISR)
{/////////////////////////////////////////////////////////////////////////////////////////////
    COM_RX_BUFFER *pcom_buf;                // pointer to COM Rx buffer
    U8             b_char;                  // byte holder
 
    . . . . . .                             // identify the interrupt
    pcom_buf = Com_GetBufPtr (USART1);      // obtain the pointer to the circular buffer
    . . . . . .                             // do other things as required
    b_char = USART_ReceiveData (USART1);    // get one byte from USART1
    Com_WriteRxBuf (pcom_buf, b_char);      // write the byte into the circular buffer
    . . . . . .                             // do other things as required
} ////////////////////////////////////////////////////////////////////////////////////////////

Consumer code (GetChar):

S32   ComGetChar   (U32 timeout)            // the "GetChar()" function
{/////////////////////////////////////////////////////////////////////////////////////////////
    COM_RX_BUFFER *pcom_buf;                // pointer to COM buffer
    U8             b_val;                   // byte holder
 
    pcom_buf = Com_GetBufPtr (USART1);      // obtain the pointer to the circular buffer
    . . . . . .                             // do other things as required
    b_val = Com_ReadRxByte (pcom_buf);      // extract the character from the circular buffer
    return (S32)b_val & 0xFF;               // return the character as a 32-bit signed int
} ////////////////////////////////////////////////////////////////////////////////////////////

Image may be NSFW.
Clik here to view.
FIFO as a Circular Buffer
Simple, isn’t it? Well, not that simple. A quick look will find two functions that are not implemented yet (Com_WriteRxBuf and Com_ReadRxByte) and one parameter (timeout) that is not used in ComGetChar(). I will ignore the parameter for now and concentrate on the two functions just mentioned. Their role is to be the interface between the code above and the FIFO buffer we intend to use.

At a second glance the embedded developer will realize that the producer and the consumer run in two different contexts independent to each other: the ISR and the main thread where the function ComGetChar() is invoked sharing the FIFO buffer (common resource). As consequence, the access to the buffer needs to disciplined ensuring that only one process — producer or consumer — can access the buffer at one given time, i.e. mutual exclusion policy.

Let’s examine the FIFO buffer implementation. The following picture shows one of the most used methods of designing a FIFO in software. With a buffer of a given size and two pointers (named here Head and Tail) the FIFO can be described as a data structure and, eventually, as a data type — COM_RX_BUFFER:

typedef struct                    // Rx Circular Bufer structure
{
    U8 volatile *rx_buf;          // pointer to (volatile) Rx buffer
    U32          rx_buf_size;     // Rx buffer size
    U32 volatile rx_head;         // Rx head index
    U32 volatile rx_tail;         // Rx tail index
    U32 volatile rx_count;        // Rx byte counter
} COM_RX_BUFFER;

As C programmers know, the relationship between pointers and arrays allow us to interchange subscripting with C pointers — I will prefer the first for clarity. I added a counter to keep track of how many bytes we have in the FIFO without the need of subtracting the head and tail pointers. The idea is simple: whenever an item is inserted I will use the tail pointer (one end of the buffer) and whenever an item is extracted I will use the head pointer (the other end of the buffer).

U8  Com_ReadRxByte  (COM_RX_BUFFER *pcom_buf)
{/////////////////////////////////////////////////////////////////////////////////////////////
    U8     b_val;                                  // byte holder

    b_val = pcom_buf->rx_buf[pcom_buf->rx_head];   // extract one byte and
    pcom_buf->rx_head = (pcom_buf->rx_head + 1) %  // advance the head index and wrap
                         pcom_buf->rx_buf_size;    // around if past end of buffer
    pcom_buf->rx_count--;                          // decrement the byte counter
    return b_val;                                  // return the extracted byte
} ////////////////////////////////////////////////////////////////////////////////////////////

void  Com_WriteRxBuf  (COM_RX_BUFFER *pcom_buf, U8 b_char)
{/////////////////////////////////////////////////////////////////////////////////////////////
    pcom_buf->rx_buf [pcom_buf->rx_tail] = b_char; // insert one byte
    pcom_buf->rx_tail = (pcom_buf->rx_tail + 1) %  // advance the tail index and wrap
                         pcom_buf->rx_buf_size;    // around if past end of buffer
    pcom_buf->rx_count++;                          // increment the byte counter
} ////////////////////////////////////////////////////////////////////////////////////////////

Look at the code: now you’ve got the idea. But don’t rush to use these functions yet. They may hide some flaws or omissions at least. Can you identify them? Please send me your comments. In the next posting I will examine in detail the problem of mutual exclusion and a way to solve it.


Viewing all articles
Browse latest Browse all 5

Trending Articles