[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
- Two Micro USB cables
- Keil MDK v5 or later
- M460 serials BSP
- Visit https://www.nuvoton.com/tool-and-softwa ... t-package/
Click M460 Series BSP to download it, then extract the ZIP in your specified directory. - Use git tool to clone https://github.com/OpenNuvoton/M460BSP.git in your specified directory.
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
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.
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.
- 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]
Build and Download Firmware
Click Build button to build example.
Use a Micro USB cable to connect Nu-Link2 to PC.
Download firmware.
Use another Micro USB cable to connect HSUSB to PC.
Now you can check in Windows Device Manager to see two virtual COM ports.
Use a terminal tool to open the Nu-Link VCOM and the M467 HSUSB VCOM separately.
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.
![Wink ;)](./images/smilies/icon_e_wink.gif)