Wednesday, July 7, 2021

K82 USB CDC Virtual Comm

In this article, we will discuss about using K82 as a USB CDC virtual com device. Setting up environment for K82 development is discussed in previous article at the following link.

http://cool-emerald.blogspot.com/2020/01/getting-started-with-frdm-k82f.html



SDK VCOM Example

Let us start with MCUXpresso's SDK example 'frdmk82f_dev_cdc_vcom_bm' using FRDM K82 development board. Click 'Import SDK example(s)' in the lower left pane, choose frdmk82f SDK, and click next as shown in the following figure.


Figure. Importing SDK example.


Then choose 'dev_cdc_vcom_bm' and click 'Finish'. We will rename the project as 'K82_VCom'.

The example will work regardless of SDK Debug Console setting (whether Semihost or UART) which is at separate com port (UART4) on OpenSDA USB connector. For this example, you need to connect another USB cable to K82F USB connector which is located next to OpenSDA USB connector. The virtual com device works on both Windows 10 and Linux straight away. USB device description for the device can be changed in "source/usb_device_descriptor.c" around line number 230.

Once you build and run the debug session, a com port will appear on the connected computer. You can use a serial terminal program to open the com port and send characters. They will be echo back. But the example needs you to activate DTR line before sending any character. If you send characters without activating DTR line, they won't be echo back even if you try to activate DTR later.

We can modify 'USB_DeviceCdcVcomCallback' function in virtual_com.c file to set startTransactions to 1 regardless of whether DTE is activated or not. At line 387, change the value to set startTransactions to 0 as follows.

/* DTE_DEACTIVATED */
if (1 == s_cdcVcom.attach)
{
        s_cdcVcom.startTransactions = 1; // <- replace 0 with 1 here
}


Another issue to take note about the demo is that the com port will stop receiving if not echo back. The reason is that everytime it receives data, it must send back data to acknowledge and it must call receive function again to wait further data. If there is no data to reply, you can send back NULL with size zero. In the sent event callback, it calls receive function to prepare to receive next data. If you want to send data without receing, the receive function call must be skipped. To tackle the issue, I use two boolean flags as shown below.

bool _bUSBToTx = false;
bool _bUSBToRx = false;


And in the AppTask function, whenever data is received, those two flags are set to true.

if ((0 != s_recvSize) && (0xFFFFFFFFU != s_recvSize))
{
        int32_t i;

        /* Copy Buffer to Send Buff */
        for (i = 0; i < s_recvSize; i++)
        {
                ...
        }
        s_recvSize = 0;

        // add following two lines to set the flags
        _bUSBToTx = true;
        _bUSBToRx = true;
}


And in the following sending part, add code to send NULL even if there is no data to reply.

if (s_sendSize)
{
        _bUSBToTx = false;

        ...
        
        error = USB_DeviceCdcAcmSend(s_cdcVcom.cdcAcmHandle, USB_CDC_VCOM_BULK_IN_ENDPOINT, s_currSendBuf, size);

        if (error != kStatus_USB_Success)
        {
        /* Failure to send Data Handling code here */
        }
}
else if(_bUSBToTx){
        _bUSBToTx = false;
        error = USB_DeviceCdcAcmSend(s_cdcVcom.cdcAcmHandle, USB_CDC_VCOM_BULK_IN_ENDPOINT, NULL, 0);
        if (error != kStatus_USB_Success)
        {
                /* Failure to send Data Handling code here */
        }
}


And in the 'USB_DeviceCdcVcomCallback' around line 193, in the 'kUSB_DeviceCdcEventSendResponse' part, add an if statement to prepare receiving only when the flag for receiving is set to avoid consuming resources repeatedly and ending up in error.

else if ((1 == s_cdcVcom.attach) && (1 == s_cdcVcom.startTransactions))
{
        if ((epCbParam->buffer != NULL) || ((epCbParam->buffer == NULL) && (epCbParam->length == 0)))
        {
                /* User: add your own code for send complete event */
                /* Schedule buffer for next receive event */
                if(_bUSBToRx){
                        _bUSBToRx = false;
                        error = USB_DeviceCdcAcmRecv(handle, USB_CDC_VCOM_BULK_OUT_ENDPOINT, s_currRecvBuf,
                                                g_UsbDeviceCdcVcomDicEndpoints[0].maxPacketSize);
#if defined(FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED) && (FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED > 0U) && \
defined(USB_DEVICE_CONFIG_KEEP_ALIVE_MODE) && (USB_DEVICE_CONFIG_KEEP_ALIVE_MODE > 0U) &&             \
defined(FSL_FEATURE_USB_KHCI_USB_RAM) && (FSL_FEATURE_USB_KHCI_USB_RAM > 0U)
        s_waitForDataReceive = 1;
        USB0->INTEN &= ~USB_INTEN_SOFTOKEN_MASK;
#endif
                }
        }
}


Ring Buffered VCom

I modified the vcom example to use ring buffer which can be found at the following repository.

github.com/yan9a/k82/tree/main/K82_VCom

The example only echo back the characters between '0' and '8' after making an increment.

No comments:

Post a Comment

Comments are moderated and don't be surprised if your comment does not appear promptly.