Today I will modify the code to support FIFO empty/full checking. Having the rx_counter to keep track of the actual content of the buffer it will make this task easy:
BOOL Com_IsRxBufEmpty (COM_RX_BUFFER *pcom_buf) {//////////////////////////////////////////////////////////////////////////////////////// return (pcom_buf->rx_count == 0); // return TRUE if empty } /////////////////////////////////////////////////////////////////////////////////////// BOOL Com_IsRxBufFull (COM_RX_BUFFER *pcom_buf) {//////////////////////////////////////////////////////////////////////////////////////// return (pcom_buf->rx_count >= pcom_buf->rx_buf_size); // return TRUE if full } ///////////////////////////////////////////////////////////////////////////////////////
Or, if you like, write them as macros:
#define Com_IsRxBufEmpty(pcb) (pcb->rx_count == 0) #define Com_IsRxBufFull(pcb) (pcb->rx_count >= pcb->rx_buf_size)
(we will talk more about when to use macros vs. when to use functions).
Now we have the ability to handle the situations where knowing if the FIFO is full or empty is important.
For example, in the Com_ISR() function, if the FIFO is full and characters continue to come, one decision may be to drop them (in place of overwriting the buffer):
void Com_ISR (void) // USART Rx handler (ISR) {//////////////////////////////////////////////////////////////////////////////////////// COM_RX_BUFFER *pcom_buf; // pointer to COM 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 if (!Com_IsRxBufFull (pcom_buf)) // if FIFO buffer not full { Com_WriteRxBuf (pcom_buf, b_char); // write the byte into the circular buffer } // . . . . . . // do other things as required } ///////////////////////////////////////////////////////////////////////////////////////
I’m not going to argue about this way of solving the FIFO overrun situation over another solution – the programmer who knows the application can decide what is the best way to handle this.
In the case of ComGetChar(), the attempt to extract a byte from a FIFO that is empty will lead, most likely, to waiting. Let’s imagine for a moment that we have the ability to delay the code execution for a number of milliseconds. The parameter timeout will indicate the amount of time the function will wait for a character to arrive before returning with an error code. Be aware that I do not say how the delay is actually implemented (in some cases the delay can be invoked by a system function call, if available):
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 while (Com_IsRxBufEmpty (pcom_buf)) // as long as the FIFO is empty { if (timeout == 0) // if time is out { return (-1); // return -1 as error code } else // otherwise { timeout--; // update the delay counter } DelayMilliseconds (1); // delay one millisecond and wait more } b_val = Com_ReadRxByte (pcom_buf); // extract character from the circular buffer return (S32)b_val & 0xFF; // return character as a 32-bit signed int } ///////////////////////////////////////////////////////////////////////////////////////
Essentially, when the FIFO is empty, the execution will stall for timeout milliseconds. The caller should be aware of this. In the context of a multi-tasking environment, delaying (or looping for long times), in the right conditions, will not stop other tasks to be executed. However, in a simple foreground/background environment where the CPU executes one thread that may only be interrupted by ISR execution, waiting can freeze the application.
There are many ways of avoiding this active waiting.
- The easiest way is to check the FIFO status before invoking ComGetChar(): if there are characters, the function can be called without time penalty: if not, the program can continue on a different path (assuming the data is not temporarily needed). This is also the essence of cooperative multi-tasking: when one thread reaches the point where it has to wait it yields control to a different thread and when that thread needs to wait the yield process continues. Cooperative multi-tasking can be implemented in many ways: state machines, co-routines, etc.
- Another way is to use a preemptive multi-tasking approach where the tasks are interrupted from time to time (even when no I/O is involved) and executed according to a pre-established CPU sharing policy. This way no task can freeze the entire system while waiting for data or an event to happen. What a modern preemptive RTOS does is just this – it multiplexes the CPU based on a certain policy involving priorities, time sharing and cooperative primitives. But this is a much longer discussion…
In the following post I will return to the mutual exclusion problem in the context of the simple situation of a single tasking with interrupts (or foreground/background) programming environment. Have patience and post your comments here!