[M467] Virtual COM in FreeRTOS: Simplify Non-OS Example Porting to FreeRTOS

Post Reply
morgandu
Posts: 45
Joined: 28 Apr 2017, 22:47

08 Mar 2024, 15:08

[For further inquiries, please e-mail to shchen2@nuvoton.com]
[Visit https://www.nuvoton.com/iot_startup for more IoT solutions]


Virtual COM in FreeRTOS: Simplify Non-OS Example Porting to FreeRTOS
Using NuMaker-IoT-M467 and USB-HS Virtual COM as an example


Porting a non-OS example to FreeRTOS enhances scalability, multitasking capabilities, and optimizing resource utilization for embedded systems.

When using an RTOS, it’s important to avoid resource access conflicts in multitasking scenarios. Once resource conflicts are ruled out, executing a non-OS example as a single task is a faster porting method. Subsequently, if needed, add mutexes to control resource access.

Here, utilizing the NuMaker-IoT-M467 development board, we swiftly port the non-OS USB Virtual COM sample code from M467 BSP to execute on FreeRTOS.

Preparation

Hardware
  • A NuMaker-IoT-M467 development board
    01 - NuMaker-IoT-M467_F_ss.png
    01 - NuMaker-IoT-M467_F_ss.png (179.65 KiB) Viewed 3197 times
  • Two Micro USB cables
Software
  • Keil MDK v5 or later
  • M460 serials BSP
There are two ways to download M460BSP.
Create a New Project “FreeRTOS_HSUSBD_VCOM”

To quickly create a new project, simply copy an existing project. The simple FreeRTOS example “blinky” can be found in the “M460BSP\SampleCode\FreeRTOS\Blinky” directory. Copy the “Blinky” directory to a new directory and rename it to “FreeRTOS_HSUSBD_VCOM”. Now we get a new project in “M460BSP\SampleCode\FreeRTOS\FreeRTOS_HSUSBD_VCOM

The non-OS USB VCOM example that will port to FreeRTOS is in the
“M460BSP\SampleCode\StdDriver\HSUSBD_VCOM_SerialEmulator” directory.

The HSUSBD_VCOM_SerialEmulator example contains the following source code files,
  • descriptions.c
  • main.c
  • vcom_serial.c
  • vcom_serial.h
Except for main.c, there are no conflicts with FreeRTOS blinky example. Therefore, besides main.c, copy all other .c and .h files to “M460BSP\SampleCode\FreeRTOS\FreeRTOS_HSUSBD_VCOM”.

Launch the Keil and open the new created project “M460BSP\SampleCode\FreeRTOS\FreeRTOS_HSUSBD_VCOM\Keil\RTOSDemo.uvprojx”

Add descriptions.c and vcom_serial.c files to the project.
02 - Keil_c.png
02 - Keil_c.png (50.73 KiB) Viewed 3197 times


Modify main.c

Examine main.c, its structure is roughly as follows:
  • Include headers.
  • Define priority levels and other constants.
  • In main(), configure hardware, create tasks, start scheduling.
  • Configure hardware in prvSetupHardware().
  • Define other task and hook functions.
Therefore, referring to the “M460BSP\SampleCode\StdDriver\HSUSBD_VCOM_SerialEmulator\main.c”, split it according to the structure above, and add them to the “M460BSP\SampleCode\FreeRTOS\FreeRTOS_HSUSBD_VCOM\main.c”. Please refer to the code below and add the red-highlighted part (Sorry, can't change font color in code).
  • Add headers.

Code: Select all

/* Hardware and starter kit includes. */
#include "NuMicro.h"
[color=#FF0000]#include "vcom_serial.h"[/color]
  • Define priority for USB VCOM task.

Code: Select all

/* Priorities for the demo application tasks. */
#define mainFLASH_TASK_PRIORITY             ( tskIDLE_PRIORITY + 1UL )
#define mainQUEUE_POLL_PRIORITY             ( tskIDLE_PRIORITY + 2UL )
#define mainSEM_TEST_PRIORITY               ( tskIDLE_PRIORITY + 1UL )
#define mainBLOCK_Q_PRIORITY                ( tskIDLE_PRIORITY + 2UL )
#define mainCREATOR_TASK_PRIORITY           ( tskIDLE_PRIORITY + 3UL )
#define mainFLOP_TASK_PRIORITY              ( tskIDLE_PRIORITY )
#define mainCHECK_TASK_PRIORITY             ( tskIDLE_PRIORITY + 3UL )
[color=#FF0000]#define mainVCOM_TASK_PRIORITY	         ( tskIDLE_PRIORITY + 1UL )[/color]
  • Add USB VCOM definitions for functions, constants, global variables, and buffers.

Code: Select all

/* Set mainCREATE_SIMPLE_LED_FLASHER_DEMO_ONLY to 1 to create a simple demo.
Set mainCREATE_SIMPLE_LED_FLASHER_DEMO_ONLY to 0 to create a much more
comprehensive test application.  See the comments at the top of this file, and
the documentation page on the http://www.FreeRTOS.org web site for more
information. */
#define mainCREATE_SIMPLE_LED_FLASHER_DEMO_ONLY     0

[color=#FF0000]//[/color]#define CHECK_TEST

[color=#FF0000]/*-----------------------------------------------------------*/
#define vcomSTACK_SIZE			configMINIMAL_STACK_SIZE

void vStartVcomTasks( UBaseType_t uxPriority );
static portTASK_FUNCTION_PROTO( vVCOMTask, pvParameters );

STR_VCOM_LINE_CODING gLineCoding = {115200, 0, 0, 8};   /* Baud rate : 115200    */
/* Stop bit     */
/* parity       */
/* data bits    */
uint16_t gCtrlSignal = 0;     /* BIT0: DTR(Data Terminal Ready) , BIT1: RTS(Request To Send) */


#define RXBUFSIZE           512 /* RX buffer size */
#define TXBUFSIZE           512 /* RX buffer size */

#define TX_FIFO_SIZE        UART0_FIFO_SIZE  /* TX Hardware FIFO size */

/* UART0 */
volatile uint8_t comRbuf[RXBUFSIZE] __attribute__((aligned(4)));
volatile uint8_t comTbuf[TXBUFSIZE]__attribute__((aligned(4)));
uint8_t gRxBuf[EPA_MAX_PKT_SIZE] __attribute__((aligned(4))) = {0};
uint8_t gUsbRxBuf[EPB_MAX_PKT_SIZE] __attribute__((aligned(4))) = {0};


volatile uint16_t comRbytes = 0;
volatile uint16_t comRhead = 0;
volatile uint16_t comRtail = 0;

volatile uint16_t comTbytes = 0;
volatile uint16_t comThead = 0;
volatile uint16_t comTtail = 0;

uint32_t gu32RxSize = 0;
uint32_t gu32TxSize = 0;

volatile int8_t gi8BulkOutReady = 0;

/*-----------------------------------------------------------*/[/color]
  • Update main() to active the VCOM task

Code: Select all

	    /* The following function will only create more tasks and timers if
    mainCREATE_SIMPLE_LED_FLASHER_DEMO_ONLY is set to 0 (at the top of this
    file).  See the comments at the top of this file for more information. */
    //prvOptionallyCreateComprehensveTestApplication();

[color=#FF0000]	/* Start VCOM Task */
	vStartVcomTasks(mainVCOM_TASK_PRIORITY);[/color]

    printf("Toggle LED_R/Y/G(PH.4~PH.6)\n");
    printf("FreeRTOS is starting ...\n");
  • Update prvSetupHardware() to configure USB and UART

Code: Select all

static void prvSetupHardware( void )
{
[color=#FF0000]    uint32_t volatile i;[/color]

    /* Unlock protected registers */
    SYS_UnlockReg();

[color=#FF0000]    /* Enable HIRC and HXT clock */
    CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk | CLK_PWRCTL_HXTEN_Msk);

    /* Wait for HIRC and HXT clock ready */
    CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk | CLK_STATUS_HXTSTB_Msk);[/color]
	
    /* Set PCLK0 and PCLK1 to HCLK/2 */
    CLK->PCLKDIV = (CLK_PCLKDIV_APB0DIV_DIV2 | CLK_PCLKDIV_APB1DIV_DIV2);

    /* Set core clock to 200MHz */
    CLK_SetCoreClock(FREQ_200MHZ);

    /* Enable all GPIO clock */
    CLK->AHBCLK0 |= CLK_AHBCLK0_GPACKEN_Msk | CLK_AHBCLK0_GPBCKEN_Msk | CLK_AHBCLK0_GPCCKEN_Msk | CLK_AHBCLK0_GPDCKEN_Msk |
                    CLK_AHBCLK0_GPECKEN_Msk | CLK_AHBCLK0_GPFCKEN_Msk | CLK_AHBCLK0_GPGCKEN_Msk | CLK_AHBCLK0_GPHCKEN_Msk;
    CLK->AHBCLK1 |= CLK_AHBCLK1_GPICKEN_Msk | CLK_AHBCLK1_GPJCKEN_Msk;

    /* Select peripheral clock source */
    CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));
    CLK_SetModuleClock(TMR0_MODULE, CLK_CLKSEL1_TMR0SEL_HIRC, 0);

    /* Enable peripheral clock */
    CLK_EnableModuleClock(UART0_MODULE);
    CLK_EnableModuleClock(TMR0_MODULE);

[color=#FF0000]    /* Select HSUSBD */
    SYS->USBPHY &= ~SYS_USBPHY_HSUSBROLE_Msk;

    /* Enable USB PHY */
    SYS->USBPHY = (SYS->USBPHY & ~(SYS_USBPHY_HSUSBROLE_Msk | SYS_USBPHY_HSUSBACT_Msk)) | SYS_USBPHY_HSUSBEN_Msk;
    for(i = 0; i < 0x1000; i++);   // delay > 10 us
    SYS->USBPHY |= SYS_USBPHY_HSUSBACT_Msk;

    /* Enable HSUSBD module clock */
    CLK_EnableModuleClock(HSUSBD_MODULE);[/color]

    /*---------------------------------------------------------------------------------------------------------*/
    /* Init I/O Multi-function                                                                                 */
    /*---------------------------------------------------------------------------------------------------------*/
    /* Set multi-function pins for UART0 RXD and TXD */
    SET_UART0_RXD_PB12();
    SET_UART0_TXD_PB13();
    
    /* Configure PH.4, PH.5 and PH.6 as Output mode for LEDs */
    GPIO_SetMode(PH, BIT4|BIT5|BIT6, GPIO_MODE_OUTPUT);

    /* Lock protected registers */
    SYS_LockReg();

    /* Init UART to 115200-8n1 for print message */
    UART_Open(UART0, 115200);

[color=#FF0000]    /* Enable Interrupt and install the call back function */
    UART_ENABLE_INT(UART0, (UART_INTEN_RDAIEN_Msk | UART_INTEN_THREIEN_Msk | UART_INTEN_RXTOIEN_Msk));

    HSUSBD_Open(&gsHSInfo, VCOM_ClassRequest, NULL);

    /* Endpoint configuration */
    VCOM_Init();

    /* Enable HSUSBD interrupt */
    NVIC_EnableIRQ(USBD20_IRQn);
[/color]
}
  • Add task function, IRQ handlers for VCOM at the bottom of main.c

Code: Select all

[color=#FF0000]static portTASK_FUNCTION( vVCOMTask, pvParameters )
{
	printf("VCOM Task is running...\r\n");
	
	while(1)
    {
        if(HSUSBD_IS_ATTACHED())
        {
            HSUSBD_Start();
			
			gi8BulkOutReady = 0;
			
            
			while(HSUSBD_IS_ATTACHED())
			{
				VCOM_TransferData();
			}
        }
    }
}

void vStartVcomTasks(UBaseType_t uxPriority)
{
	BaseType_t xLEDTask;

	/* Spawn the task. */
	xTaskCreate( vVCOMTask, "VCOM", vcomSTACK_SIZE, NULL, uxPriority, (TaskHandle_t *) NULL );
}

void UART0_IRQHandler(void)
{
    uint8_t bInChar;
    int32_t size;
    uint32_t u32IntStatus;

    u32IntStatus = UART0->INTSTS;

    if((u32IntStatus & UART_INTSTS_RDAINT_Msk) || (u32IntStatus & UART_INTSTS_RXTOINT_Msk))
    {
        /* Receiver FIFO threshold level is reached or Rx time out */

        /* Get all the input characters */
        while((!UART_GET_RX_EMPTY(UART0)))
        {
            /* Get the character from UART Buffer */
            bInChar = UART_READ(UART0);    /* Rx trigger level is 1 byte*/

            /* Check if buffer full */
            if(comRbytes < RXBUFSIZE)
            {
                /* Enqueue the character */
                comRbuf[comRtail++] = bInChar;
                if(comRtail >= RXBUFSIZE)
                    comRtail = 0;
                comRbytes++;
            }
            else
            {
                /* FIFO over run */
            }
        }
    }

    if(u32IntStatus & UART_INTSTS_THREINT_Msk)
    {

        if(comTbytes && (UART0->INTEN & UART_INTEN_THREIEN_Msk))
        {
            /* Fill the Tx FIFO */
            size = comTbytes;
            if(size >= TX_FIFO_SIZE)
            {
                size = TX_FIFO_SIZE;
            }

            while(size)
            {
                bInChar = comTbuf[comThead++];
                UART_WRITE(UART0, bInChar);
                if(comThead >= TXBUFSIZE)
                    comThead = 0;
                comTbytes--;
                size--;
            }
        }
        else
        {
            /* No more data, just stop Tx (Stop work) */
            UART0->INTEN &= ~UART_INTEN_THREIEN_Msk;
        }
    }
}

void VCOM_TransferData(void)
{
    int32_t i, i32Len;

    /* Check if any data to send to USB & USB is ready to send them out */
    if(comRbytes && (gu32TxSize == 0))
    {
        i32Len = comRbytes;
        if(i32Len > EPA_MAX_PKT_SIZE)
            i32Len = EPA_MAX_PKT_SIZE;

        for(i = 0; i < i32Len; i++)
        {
            gRxBuf[i] = comRbuf[comRhead++];
            if(comRhead >= RXBUFSIZE)
                comRhead = 0;
        }

        NVIC_DisableIRQ(UART0_IRQn);
        comRbytes -= i32Len;
        NVIC_EnableIRQ(UART0_IRQn);

        gu32TxSize = i32Len;
        for(i = 0; i < i32Len; i++)
            HSUSBD->EP[EPA].EPDAT_BYTE = gRxBuf[i];
        HSUSBD->EP[EPA].EPRSPCTL = HSUSBD_EP_RSPCTL_SHORTTXEN;    // packet end
        HSUSBD->EP[EPA].EPTXCNT = i32Len;
        HSUSBD_ENABLE_EP_INT(EPA, HSUSBD_EPINTEN_INTKIEN_Msk);
    }

    /* Process the Bulk out data when bulk out data is ready. */
    if(gi8BulkOutReady && (gu32RxSize <= TXBUFSIZE - comTbytes))
    {
        for(i = 0; i < gu32RxSize; i++)
        {
            comTbuf[comTtail++] = gUsbRxBuf[i];
            if(comTtail >= TXBUFSIZE)
                comTtail = 0;
        }

        NVIC_DisableIRQ(UART0_IRQn);
        comTbytes += gu32RxSize;
        NVIC_EnableIRQ(UART0_IRQn);

        gu32RxSize = 0;
        gi8BulkOutReady = 0; /* Clear bulk out ready flag */
        HSUSBD_ENABLE_EP_INT(EPB, HSUSBD_EPINTEN_RXPKIEN_Msk | HSUSBD_EPINTEN_SHORTRXIEN_Msk);
    }

    /* Process the software Tx FIFO */
    if(comTbytes)
    {
        /* Check if Tx is working */
        if((UART0->INTEN & UART_INTEN_THREIEN_Msk) == 0)
        {
            /* Send one bytes out */
            UART_WRITE(UART0, comTbuf[comThead++]);
            if(comThead >= TXBUFSIZE)
                comThead = 0;

            comTbytes--;

            /* Enable Tx Empty Interrupt. (Trigger first one) */
            UART0->INTEN |= UART_INTEN_THREIEN_Msk;
        }
    }
}[/color]
For convenience, the updated example is attached below. Please create a directory named “FreeRTOS_HSUSBD_VCOM” in the “M460BSP\SampleCode\FreeRTOS\” directory and extract the attachment into this directory.
FreeRTOS_HSUSBD_VCOM.zip
(23.82 KiB) Downloaded 813 times


Build and Download Firmware

Click Build button to build example.
03 - Toolbar_Build.png
03 - Toolbar_Build.png (28.07 KiB) Viewed 3197 times

Use a Micro USB cable to connect Nu-Link2 to PC.
04 - Nu-Link2.png
04 - Nu-Link2.png (139.19 KiB) Viewed 3197 times

Download firmware.
05 - Toolbar_Download.png
05 - Toolbar_Download.png (28.67 KiB) Viewed 3197 times

Use another Micro USB cable to connect HSUSB to PC.
06 - NuMaker-IoT-M467_HSUSB.png
06 - NuMaker-IoT-M467_HSUSB.png (86.46 KiB) Viewed 3197 times

Now you can check in Windows Device Manager to see two virtual COM ports.
07 - VCOM Ports in Device Manager.png
07 - VCOM Ports in Device Manager.png (16.82 KiB) Viewed 3197 times

Use a terminal tool to open the Nu-Link VCOM and the M467 HSUSB VCOM separately.
08 - Terminal Tool Open VCOM_2.png
08 - Terminal Tool Open VCOM_2.png (22.51 KiB) Viewed 3197 times

Now you can send data from the Nu-Link VCOM and receive it on the M467 HSUSB VCOM, and vice versa. Since the blinky task is still present, you will also see the LEDs on the board blinking simultaneously.

Enjoy it. ;)

Post Reply
  • Information
  • Who is online

    Users browsing this forum: No registered users and 87 guests