Select Page

Develop general purpose Serial Communication Driver using RTOS APIs

Serial Communication Driver using RTOS APIs

Driver APIs

The Driver should provide the following APIs:

  • uart_get_char()
    Remove Character from the Ring Buffer
  • uart_put_char()
    Output a character
  • uart_put_string()
    Output string
  • uart_is_empty()
    See If Rx Character Buffer is Empty
  • uart_is_full()
    See if Tx character Buffer is Full

Ring (Circular) Buffer

#define  UART_RX_BUF_SIZE      64    /* Number of characters in Rx ring buffer */
#define  UART_TX_BUF_SIZE      64    /* Number of characters in Tx ring buffer */
 
typedef struct {
    int16_t RingBufRxCtr;           /* Number of characters in the Rx ring buffer */
    ATOM_SEM RingBufRxSem;          /* Rx semaphore */
    uint8_t *RingBufRxInPtr;        /* Pointer to where next character will be inserted */
    uint8_t *RingBufRxOutPtr;       /* Pointer from where next character will be extracted */
    uint8_t RingBufRx[UART_RX_BUF_SIZE]; /* Ring buffer character storage (Rx) */
    uint16_t RingBufTxCtr;          /* Number of characters in the Tx ring buffer */
    ATOM_SEM RingBufTxSem;          /* Tx semaphore  */
    uint8_t *RingBufTxInPtr;        /* Pointer to where next character will be inserted */
    uint8_t *RingBufTxOutPtr;       /* Pointer from where next character will be extracted */
    uint8_t RingBufTx[UART_TX_BUF_SIZE]; /* Ring buffer character storage (Tx) */
} UART_RING_BUF;

UART Initialization

void init_serial(void)
{
    Setup UART;           /* configure for 115200,8,1,N and Rx int enabled */
    Initialize Ring Buffer;
    Initialize Rx Semaphore to 0;
    Initialize Tx Semaphore to Tx Ring Buffer Size (UART_TX_BUF_SIZE);
}

Receiving Data

When using an interrupt-driven scheme, an interrupt is generated when a byte arrives through the serial port. The interrupt handler reads the byte from the port, which generally clears the interrupt source. At this point you have a choice of either processing the byte received in the ISR or putting the byte into some sort of buffer to let a background process handle the data. When we use a buffer, the size of the buffer depends on how quickly the background process can get control of the CPU to process the information.

We can process incoming data almost as quickly as we receive it with out doing so in an ISR. To accomplish this, a semaphore is added to the management of the ring buffer as shown in the following figure:

 

 

 

 

 

 

 

  1. 1.Application waits on semaphore
  2. When a byte is received, the ISR reads the byte from the port and
  3. Deposits in the ring buffer
  4. The ISR then signals the semaphore to indicate to the waiting task that a byte was received.
  5. Signaling the semaphore makes the waiting task ready to run. When the ISR completes, the kernel determines if the waiting task is now the highest priority task ready to get the CPU. If it is, the ISR resumes the task waiting for the byte (assuming a preemptive kernel). The application code then extracts the byte from the ring buffer and performs whatever processing is required.

The following pseudocode for both the ISR and the interface function to the application is given below:

void uart_rx_isr(void)
{
    uint8_t c;
 
    Save processor context;
    Tell OS that we are processing an ISR;
    c = Get byte from RX port;
    if(Rx Ring Buffer is not Full) {
        Put received byte into Ring Buffer;
        Signal Rx Semaphore;
    }
    Tell OS that we are exiting an ISR;
    Restore processor context;
    Return from Interrupt;
}
 
uint8_t uart_get_char(uint16_t timeout, uint8_t *err)
{
    uint8_t c;
 
    Wait for byte to be received (using semaphore with timeout);
    if(timeout) {
        *err = Time out error;
        return (0);
    }
    Disable interrupts;
    c = Get byte from Ring Buffer;
    Enable interrupts;
    *err = No error;
    return (c);
}

Transmitting Data

Transmission of bytes works somewhat like byte reception. The background process deposits bytes in an output buffer. When the transmitter on the UART is ready to send a byte, an interrupt is generated, the byte is extracted from the buffer, and the ISR outputs the byte.

Buffering of data makes a lot of sense when you have to transmit a relatively large amount of data on the serial port, such as the contents of a disk file. Output buffering using a ring buffer is shown in the following figure. It shows how you can make use of a real-time kernel’s semaphore used as a traffic light pausing the sending task when the ring buffer is full. To send data, the task waits for the semaphore.

  1. To send data, the task waits for the semaphore
  2. If the ring buffer is not full, the task proceeds to deposit the byte into the ring buffer
  3. Transmitter interrupts are enabled if the byte deposited is the first byte in the ring buffer.
  4. The transmit interrupt ISR extracts “oldest” byte from the ring buffer and
  5. signals the semaphore
  6. to indicate that the ring buffer has room to accept another character. The ISR then outputs the byte to the UART.

It is important to note that TxSem needs to be a counting semaphore, and the semaphore must be initialized to the size of the ring buffer. The pseudocode for both the interface function to the application and the ISR is given below.

uint8_t uart_put_char(uint8_t c, uint16_t timeout)
{
    Wait for space in the Tx Ring Buffer (using semaphore timeout)
    if(timeout) {
        return Timeout error
    }
    Disable interrupts;
    Put byte to send "c" into the Ring Buffer
    if(This is the first byte in the Tx Ring Buffer) {
        Enable Tx Interrupts;
    }
    Enable interrupts;
    return No error;
}
 
void uart_tx_isr(void)
{
    uint8_t c;
 
    Save processor context;
    if(Tx Ring Buffer not empty) {
        c = Get next byte to send from Tx Ring Buffer;
        Output character "c" to Tx port;
        Signal Tx semaphore;
    } else {
        Disable Tx Interrupts;
    }
    Restore processor context;
    Return from Interrupt;
}