Multiple UART initialisation - one function with multiple macro arguments

Hi,
I would like to ask you how instead of writing a few initialization functions for few uarts I can write one and pass there different Macros “USART4” , “USART1”

The best would be creating some object which would be created in one line and initialize UART, creating queues. I would like to avoid code replication for each uart, each queue.

I will appreciate hints on how this can be done.

#include <stdlib.h>
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "serial.h"
#include <avr/interrupt.h>

#define USART_BAUD_RATE(BAUD_RATE) ((float)(configCPU_CLOCK_HZ * 64 / (16 * (float)BAUD_RATE)) + 0.5)
 
static QueueHandle_t xRxedChars;
static QueueHandle_t xCharsForTx;

#define vInterruptOn() USART4.CTRLA |= (1 << USART_DREIE_bp)

#define vInterruptOff() USART4.CTRLA &= ~(1 << USART_DREIE_bp)

xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
    portENTER_CRITICAL();
    {
        /* Create the queues used by the com test task. */
        xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
        xCharsForTx = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );

        USART4.BAUD = (uint16_t)USART_BAUD_RATE(ulWantedBaud); /* set baud rate register */

        USART4.CTRLA = 1 << USART_LBME_bp       /* Loop-back Mode Enable: enabled */
                     | USART_RS485_DISABLE_gc       /* RS485 Mode disabled */
                     | 1 << USART_RXCIE_bp;     /* Receive Complete Interrupt Enable: enabled */

        USART4.CTRLB = 1 << USART_RXEN_bp       /* Receiver enable: enabled */
                     | USART_RXMODE_NORMAL_gc   /* Normal mode */
                     | 1 << USART_TXEN_bp;      /* Transmitter Enable: enabled */
    }
    
    portEXIT_CRITICAL();
    
    /* Unlike other ports, this serial code does not allow for more than one
    com port.  We therefore don't return a pointer to a port structure and can
    instead just return NULL. */
    return NULL;
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialGetChar( xComPortHandle pxPort, signed char *pcRxedChar, TickType_t xBlockTime )
{
    /* Get the next character from the buffer.  Return false if no characters
    are available, or arrive before xBlockTime expires. */
    if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
    {
        return pdTRUE;
    }
    else
    {
        return pdFALSE;
    }
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort, signed char cOutChar, TickType_t xBlockTime )
{
    /* Return false if after the block time there is no room on the Tx queue. */
    if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) != pdPASS )
    {
        return pdFAIL;
    }
    
    vInterruptOn();
    
    return pdPASS;
}
/*-----------------------------------------------------------*/

void vSerialClose( xComPortHandle xPort )
{
    /* Turn off the interrupts.  We may also want to delete the queues and/or
    re-install the original ISR. */

    portENTER_CRITICAL();
    {
        vInterruptOff();
        USART4.CTRLB &= (1 << USART_RXEN_bp);
    }
    portEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/

ISR(USART4_RXC_vect)
{
signed char ucChar, xHigherPriorityTaskWoken = pdFALSE;

    /* Get the character and post it on the queue of Rxed characters.
    If the post causes a task to wake force a context switch as the woken task
    may have a higher priority than the task we have interrupted. */
    ucChar = USART4.RXDATAL;

    xQueueSendFromISR( xRxedChars, &ucChar, &xHigherPriorityTaskWoken );

    if( xHigherPriorityTaskWoken != pdFALSE )
    {
        portYIELD_FROM_ISR();
    }
        
}

ISR(USART4_DRE_vect)
{
signed char cChar, cTaskWoken = pdFALSE;

    if( xQueueReceiveFromISR( xCharsForTx, &cChar, &cTaskWoken ) == pdTRUE )
    {
        /* Send the next character queued for Tx. */
        USART4.TXDATAL = cChar;
    }
    else
    {
        /* Queue empty, nothing to send. */
        vInterruptOff();
    }
}

Personally, I create a C++ class to represent a device like a UART.

In C, you would define a control structure that you create, and call an init function passing it the control structure and the information needed to define which port the UART is on (either just a number and the function has a table of the entries, or you pass the UART base address).

That init code can then create the needed queue and put the handle in the control struct for the UART.

1 Like

Hi Richard,
Thank you for your kind answer. I’m programming AVR128db64 in C language. I’m not sure if MPLab XC8 compiler support C++. I create/modified the code based on your comment and I’m presenting it below.
I will be appreciated for comments. I think more people might be interested in this topic., so would be cool to improve it :slight_smile:

/* BASIC INTERRUPT DRIVEN SERIAL PORT DRIVER FOR IAR AVR PORT. */
/* serial.c */

#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "serial.h"
#include <avr/interrupt.h>

#define USART_BAUD_RATE(BAUD_RATE) ((float)(configCPU_CLOCK_HZ * 64 / (16 * (float)BAUD_RATE)) + 0.5)
 

COMport COM4;

void vInterruptOn(COMport *COMno) 
{
    COMno->USARTno->CTRLA |= (1 << USART_DREIE_bp);
}

void vInterruptOff(COMport *COMno) 
{
    COMno->USARTno->CTRLA &= ~(1 << USART_DREIE_bp);
}

void xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength, USART_t *USARTno, COMport *COMno)
{
    portENTER_CRITICAL();
    {
        COMno->PutChar=xSerialPutChar;
        COMno->PutString=vSerialPutString;
        
        COMno->USARTno=USARTno;
        /* Create the queues used by the com test task. */
        COMno->xRxedCharsHandle = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
        COMno->xCharsForTxHandle= xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );

        
        COMno->USARTno->BAUD = (uint16_t)USART_BAUD_RATE(ulWantedBaud); /* set baud rate register */

        COMno->USARTno->CTRLA = 1 << USART_LBME_bp       /* Loop-back Mode Enable: enabled */
                     | USART_RS485_DISABLE_gc       /* RS485 Mode disabled */
                     | 1 << USART_RXCIE_bp;     /* Receive Complete Interrupt Enable: enabled */

        COMno->USARTno->CTRLB = 1 << USART_RXEN_bp       /* Receiver enable: enabled */
                     | USART_RXMODE_NORMAL_gc   /* Normal mode */
                     | 1 << USART_TXEN_bp;      /* Transmitter Enable: enabled */
    }
    
    portEXIT_CRITICAL();
    
    /* Unlike other ports, this serial code does not allow for more than one
    com port.  We therefore don't return a pointer to a port structure and can
    instead just return NULL. */
  
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialGetChar( signed char *pcRxedChar, TickType_t xBlockTime, COMport *COMno )
{
    /* Get the next character from the buffer.  Return false if no characters
    are available, or arrive before xBlockTime expires. */
    if( xQueueReceive( COMno->xRxedCharsHandle, &pcRxedChar, xBlockTime ) )  //check if & is needed
      {
        return pdTRUE;
    }
    else
    {
        return pdFALSE;
    }
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialPutChar( signed char *cOutChar, TickType_t xBlockTime, COMport *COMno )
{
    /* Return false if after the block time there is no room on the Tx queue. */
    if( xQueueSend( COMno->xCharsForTxHandle, &cOutChar, xBlockTime ) != pdPASS )
    {
        return pdFAIL;
    }
    
    vInterruptOn(COMno);
    
    return pdPASS;
}
/*-----------------------------------------------------------*/

void vSerialPutString( signed char *OutString, TickType_t xBlockTime, COMport *COMno )
{
    signed char *pxNext;
    
    pxNext = ( signed char * ) OutString;
    
    while( *pxNext)
    {
        COMno->PutChar(*pxNext, portMAX_DELAY, COMno);
        pxNext++;
    }
	
}

/*-----------------------------------------------------------*/
void vSerialClose( COMport *COMno )
{
    /* Turn off the interrupts.  We may also want to delete the queues and/or
    re-install the original ISR. */

    portENTER_CRITICAL();
    {
        vInterruptOff(COMno);
        COMno->USARTno->CTRLB &= (1 << USART_RXEN_bp);
    }
    portEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/

void ReceiveISR (COMport *COMno)
{
    signed char ucChar, xHigherPriorityTaskWoken = pdFALSE;

    /* Get the character and post it on the queue of Rxed characters.
    If the post causes a task to wake force a context switch as the woken task
    may have a higher priority than the task we have interrupted. */
    ucChar = COMno->USARTno->RXDATAL;

    xQueueSendFromISR( COMno->xRxedCharsHandle, &ucChar, &xHigherPriorityTaskWoken );

    if( xHigherPriorityTaskWoken != pdFALSE )
    {
        portYIELD_FROM_ISR();
    }
}

void SendISR (COMport *COMno)
{
    signed char cChar, cTaskWoken = pdFALSE;

    if( xQueueReceiveFromISR( COMno->xCharsForTxHandle, &cChar, &cTaskWoken ) == pdTRUE )
    {
        /* Send the next character queued for Tx. */
        COMno->USARTno->TXDATAL = cChar;
    }
    else
    {
        /* Queue empty, nothing to send. */
        vInterruptOff(COMno);
    }
}

ISR(USART4_RXC_vect)
{
    ReceiveISR (&COM4);
}

ISR(USART4_DRE_vect)
{
    SendISR (&COM4);
}

/* serial.h */

#ifndef SERIAL_COMMS_H
#define SERIAL_COMMS_H

#include "queue.h"
#include "portmacro.h"

typedef void * xComPortHandle;

typedef void (*PutString_p)();
typedef signed portBASE_TYPE (*PutChar_p)();

typedef struct {
    unsigned long  eWantedBaud;
    int uxQueueLength;
    USART_t *USARTno;
    QueueHandle_t xRxedCharsHandle;
    QueueHandle_t xCharsForTxHandle; 
    PutChar_p PutChar;
    PutString_p PutString;
} COMport;



void xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength, USART_t *USARTno, COMport *COMno);

signed portBASE_TYPE xSerialGetChar( signed char *pcRxedChar, TickType_t xBlockTime, COMport *COMno );

signed portBASE_TYPE xSerialPutChar( signed char *cOutChar, TickType_t xBlockTime, COMport *COMno );

void vSerialPutString( signed char *cOutChar, TickType_t xBlockTime, COMport *COMno );

void vSerialClose( COMport *COMno );



#endif /* ifndef SERIAL_COMMS_H */

/*main.c*/

#include "FreeRTOS.h"
#include "clk_config.h"
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/timer/delay.h"
#include "mcc_generated_files/timer/tca1.h"
#include "RGBcontrol.h"
#include "grideye.h"


#include <avr/interrupt.h>
//#include <avr/io.h>
#include <stdio.h>
#include <string.h>
#include <task.h>
#include <json.h>
#include <semphr.h>
#include <queue.h>
#include <portmacro.h>
#include <stdlib.h>
#include "serial.h"



#define UART_BAUD_RATE 115200
#define UART_QUEUE_SIZE 200
extern COMport COM4;

void Serial_MCU2(void *pvParameters);

int main( void )
{

    SYSTEM_Initialize();
    xSerialPortInitMinimal(UART_BAUD_RATE, UART_QUEUE_SIZE, &USART4, &COM4);

    xTaskCreate(Serial_MCU2, "SR",1024,NULL,1,NULL);
    vTaskStartScheduler();
    
    while(1)
    {
        ;        
    }
    
    return 0;
}

void Serial_MCU2(void *pvParameters)  
{
    while(1)
    {
        IO_PB7_Toggle();  //D16 MCU1 Heartbeat
        vTaskDelay( 500 / portTICK_PERIOD_MS ); 
        
        char message[] = "Hello, world!\r\n";

   
        //for (int i = 0; i < sizeof(message) - 1; i++)
        {
            //COM4.PutChar(message[i], portMAX_DELAY, &COM4);
            COM4.PutString(message, portMAX_DELAY, &COM4);
            
        }
    }
}

So far code working fine and I’m getting output sting on UART, but I’m not sure if this is the best and most efficient way of doing it.

That looks basically like what I was describing. I might not put the function pointers in the structure to the output routines, as you might as well just call them directly.

In C++ it works, as the call gets the object pointer added automatically so adds useful abstraction. If you just call PutString and pass the port object it can do everything it needs. Only if you expect different com ports to need different functions would the function pointer method make sense.

Main point, if programming in C, don’t try to copy all the C++ syntax, just use the concepts that are needed at the time.

1 Like

@richard-damon Thank you very much for your help and comments which were very useful to me.