This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

V4.20 USBD CDC using RTX

Hi,

I have been struggling to get a fully working example of USBD CDC happening using the RTX.

I modified the source at
C:\Keil\ARM\Boards\Keil\MCB1700\RL\USB\Device\RTX\Memory\

to create a CDC example.

This seems to work well in that it connects and I have a serial port created on the host. The descriptors are all okay.

Using USBlyzer I can see the descriptors transferring okay, and using Tera term I can set the COM Port up okay.

When I send a character from Tera Term, USBlyzer captures the transferred character, and I can see the interrupt for the Endpoint 2 (corresponding to the CDC Bulk Endpoint defined in the usb_config.c wizard) BUT I do not get any characters appearing using usbd_cdc_ser_availchar (&numAvailByte);

Now I have run the CDC example from NXP for the LPC17xx in the latest CMSIS lpc17xx.cmsis.driver.library.zip and this works well. So I know my hardware is okay and my PC Host has the driver installed okay.

My main problem is the complete lack of documentation from Keil on how to use the CDC with RTX. There are no complete examples.

There is no description on when/how to use the
usbd_cdc_ser_initport()
i.e., is this the virtual serial port over the USB OR is it another serial port for using with the usbd_cdc_ser_usb2serial() and usbd_cdc_ser_serial2usb(). If the latter, then how does one specify which serial port?

Do I need to call usbd_cdc_ser_openport() before seeing data over the virtual serial port?

There is no explanation on the returned value from
void usbd_cdc_ser_linestate(U16* lineState);

So I am finding this hard going.
Has anyone else managed to get USB CDC working with the RTX?

My example code for USB Loopback is...


/*----------------------------------------------------------------------------
 *      RL-ARM - USB
 *----------------------------------------------------------------------------
 *      Name:    USBD_Demo.c
 *      Purpose: USB Device Demonstration
 *      Rev.:    V4.20
 *----------------------------------------------------------------------------
 *      This code is part of the RealView Run-Time Library.
 *      Copyright (c) 2004-2011 KEIL - An ARM Company. All rights reserved.
 *---------------------------------------------------------------------------*/

#include <RTL.h>
#include <rl_usb.h>
#include <LPC17xx.h>

/*---------------------------------------------------------------------------
  Reads character from USB buffer and resends them back out the USB buffer
 *---------------------------------------------------------------------------*/
void usbd_vcom_usb2usb(void)
{
        static S8       serBuf [32];
        static S32  numBytesToRead, numBytesRead;
        static S32      numAvailByte;

        /* Check if any USB CDC serial data available */
        usbd_cdc_ser_availchar (&numAvailByte);

        /* If USB CDC data available, then read in and loop back (limit to 32 chars at a time) */
        if (numAvailByte > 0)
        {
                numBytesToRead = numAvailByte > 32 ? 32 : numAvailByte;
                numBytesRead = usbd_cdc_ser_read (&serBuf[0], &numBytesToRead);
                if (numBytesRead > 0)
                {
                        usbd_cdc_ser_write(&serBuf[0], &numBytesRead);
                }
        }
}
/*----------------------------------------------------------------------------
  Initialises the USBD Serial Port and then Opens it.
 *---------------------------------------------------------------------------*/
__task void init (void)
{
  os_tsk_prio_self(100);
  usbd_init();                          /* USB Device Initialization          */
  usbd_connect(__TRUE);                 /* USB Device Connect                 */
  os_tsk_prio_self(1);
  usbd_cdc_ser_initport(9600, 8, 0, 1);
  usbd_cdc_ser_openport();
  while (1)
  {                                             /* Loop forever                       */
    usbd_vcom_usb2usb();
    usbd_vcom_chkserstate();
  }
  usbd_cdc_ser_closeport();
}

int main (void) {
  SystemInit ();                        /* Initialize Clocks                  */
  os_sys_init(init);                    /* Init RTX and start 'init'          */
}

Also, my definitions for usbconfig.c are ...
#define USBD_CDC_ENABLE 1
#define USBD_CDC_EP_INTIN 1
#define USBD_CDC_WMAXPACKETSIZE 16
#define USBD_CDC_BINTERVAL 2
#define USBD_CDC_HS_ENABLE 0
#define USBD_CDC_HS_WMAXPACKETSIZE 16
#define USBD_CDC_HS_BINTERVAL 2
#define USBD_CDC_EP_BULKIN 2
#define USBD_CDC_EP_BULKOUT 2
#define USBD_CDC_WMAXPACKETSIZE1 64
#define USBD_CDC_HS_ENABLE1 0
#define USBD_CDC_HS_WMAXPACKETSIZE1 64
#define USBD_CDC_HS_BINTERVAL1 0
#define USBD_CDC_CIF_STRDESC L"USB_CDC"
#define USBD_CDC_DIF_STRDESC L"USB_CDC1"
#define USBD_CDC_BUFSIZE 64
#define USBD_CDC_OUTBUFSIZE 128

Parents Reply Children
  • Hello again,

    a search in the CDC driver and all I found was this (in rl_usb.h):

    /* USB Device functions exported from USB CDC Class module                    */
    extern void  usbd_cdc_init           (void);
    extern void  usbd_vcom_serial2usb    (void);
    extern void  usbd_vcom_usb2serial    (void);
    extern void  usbd_vcom_chkserstate   (void);
    
    /* USB Device user functions imported to USB CDC Class module                 */
    extern void  usbd_cdc_ser_openport   (void);
    extern void  usbd_cdc_ser_closeport  (void);
    extern void  usbd_cdc_ser_initport   (U32 baudrate, U32 databits, U32 parity, U32 stopbits);
    extern void  usbd_cdc_ser_availchar  (S32 *availChar);
    extern S32   usbd_cdc_ser_write      (const S8 *buffer, S32 *length);
    extern S32   usbd_cdc_ser_read       (S8 *buffer, const S32 *length);
    extern void  usbd_cdc_ser_linestate  (U16 *lineState);
    

    All "user functions" works only on the UART1 of the lpc23xx / lpc24xx. I don't know why there are in the USB example?

    Only the "exported" function are for the usb comunication. The bad thing now: this function only work with the uart together. So you can implement an UART-USB translater like FTDI-Chips! There is no chances to manipulate this function. Maybe only use UART2 hardware wired to UART1 (but before I do this I buy FTDI!). The usb function not in source code only in lib -> no chance to modify it.

    Result: The CDC-Keil drive is absolut useless (only you want an Uart - Usb translater)!
    Write your own, have fun and post after two years when you finished the result. Thank you.

  • You have to almost completely rewrite the usbd_user_cdc.c file if you intend to send and receive from USB (not old USART.) To start, I created my own local send and receive buffers and stubbed out the usbd_cdc_ser_initport(), usbd_cdc_ser_closeport(), usbd_cdc_ser_openport() functions.

    Now to understand the way these functions work, ignore the function names because they cause lots of confusion since they were designed for USB->RS-232 and RS-232->USB transfer.

    Keil's library function usbd_vcom_usb2serial() calls the user function usbd_cdc_ser_write() when new data arrives on the incoming USB endpoint. Use this function to store incoming data into your local buffer.

    When sending data, use the user functions usbd_cdc_ser_availchar() and usbd_cdc_ser_read() - Keil's library function usbd_vcom_serial2usb() polls the usbd_cdc_ser_availchar() and when the returned value is > 0, it will call the user function usbd_cdc_ser_read(). So when you're ready to send data, make the usbd_cdc_ser_char_avail() to return the size of bytes you want to send. My function looks like this:

    void usbd_cdc_ser_availchar (S32 *availChar)
    { *availChar = bSendReady ? (UsbOutPacketSize % MAX_PACKET_SZ) : 0;
    }

    Use usbd_cdc_ser_read() to copy data from your local buffer into the library's buffer (S8 *buffer). The length parameter should be the same as what you set in the usbd_cdc_ser_availchar() Here's my function:

    S32 usbd_cdc_ser_read (S8 *buffer, const S32 *length)
    { S32 bytesToRead, bytesRead;

    /* Read *length bytes, block if *bytes are not available */
    bytesToRead = *length;
    bytesToRead = (bytesToRead < (*length)) ? bytesToRead : (*length);
    bytesRead = 0;

    while (bytesToRead--)
    { *buffer++ = UsbOutPacket[usUsbOutRdPtr++]; bytesRead++;
    if (usUsbOutRdPtr >= UsbOutPacketSize) // Don't send data we don't have break;
    }

    if (usUsbOutRdPtr >= UsbOutPacketSize) // Pkts will be broken up and sent in 63 byte chunks bSendReady = __FALSE;

    return (bytesRead);
    }

    Your main loop can be similar to the demo. I found this sequence to work most efficiently. The usbd_cdc_ser_linestate() function is not required either.

    // Main loop or RTX task
    usbd_vcom_usb2serial(); // Read USB incoming data
    ModbusUSB->Poll(); // Process packet
    usbd_vcom_serial2usb(); // Write outgoing USB necessary

    Hope this helps!

  • Hello Louie,

    I thank you so much. It looks you investigate a lot of time. Your description looks very good (very better then the Keil CDC manual!).

    I will try it today.

    Thanks again for your post and time.

  • Hello (last time),

    i have implemented Louies post and it works!!! So thank you very much for your work. Below my usbd_user_cdc.c file with an minimal example. It echos everything you send to the com port. If somebody have problem with the vcom.inf file (so I am) so maybe this post will help: http://www.keil.com/forum/17039/

    Attention It seems the rx and tx runs in a seperate task. If you send a long string it can happen, that both (tx and rx) read and write at the same time to the same variable. This cause to cut the string send back. To prevent this behavior you have to implement independent buffers.

    Question One Question: If somebody know the max USB buffer size (where I can find it) please post. Thank you

    Here the usbd_use_cdc.c

    /*----------------------------------------------------------------------------
     *      RL-ARM - USB
     *----------------------------------------------------------------------------
     *      Name:    usbd_user_cdc.c
     *      Purpose: Communication Device Class User module
     *      Rev.:    V4.20
     *----------------------------------------------------------------------------
     *      This code is part of the RealView Run-Time Library.
     *      Copyright (c) 2004-2011 KEIL - An ARM Company. All rights reserved.
     *---------------------------------------------------------------------------*/
    
    #include <RTL.h>
    #include <rl_usb.h>
    #include <usb.h>
    
    // I don't know the correct max buffer size of the USB buffer?
    #define MAX_USB_BUFFER 64
    char data_buffer[MAX_USB_BUFFER];
    int data_length = 0;
    
    /*----------------------------------------------------------------------------
      read data from data buffer
     *----------------------------------------------------------------------------*/
    S32 usbd_cdc_ser_read (S8 *buffer, const S32 *length)
    {
            S32 bytesRead;
            for(bytesRead = 0; (bytesRead < (*length)) &&
               (MAX_USB_BUFFER > bytesRead); ++bytesRead)
            {
                    buffer[bytesRead] = data_buffer[bytesRead];
            }
            data_length = 0;
            return (bytesRead);
    }
    
    /*----------------------------------------------------------------------------
      write data to the data buffer
     *----------------------------------------------------------------------------*/
    S32 usbd_cdc_ser_write (const S8 *buffer, S32 *length)
    {
            S32 bytesWritten;
            for(bytesWritten = 0; (bytesWritten < (*length)) &&
               (MAX_USB_BUFFER > bytesWritten); ++bytesWritten)
            {
                    data_buffer[bytesWritten] = buffer[bytesWritten];
            }
            data_length = bytesWritten;
            return (bytesWritten);
    }
    
    /*----------------------------------------------------------------------------
      check if character(s) are available at the data buffer
     *----------------------------------------------------------------------------*/
    void usbd_cdc_ser_availchar (S32 *availChar)
    {
            if(MAX_USB_BUFFER >= data_length)
            {
                    *availChar = data_length;
            }
            else
            {
                    *availChar = MAX_USB_BUFFER;
            }
    }
    

    P.S.: If somebody read this and works by keil: Please update your very poor documentation! Thank you.

  • Hello,

    thank you for your code.
    I modified your code but impossible to separate the writing (micro to USB) of the reading (USB to micro).
    I can just change the variable "data_length" in the function usbd_cdc_ser_write(), when I try to change the variable "data_length" in the function usbd_cdc_ser_availchar() this bug.
    I tried many things, but it will not work.
    now I have to wait for a reception to send.

    Keil does not really simplifies life with his USB stack.

  • Hello JB,

    so I write it is only a minimal example. It echos the incoming string.
    So you want to separete the write / read functions you have to implement two data_buffers e.g. tx_buffer and rx_buffer. The same you have to do with the data_length e.g. tx_length and rx_length.
    I hope this help.

  • Hello dieter,

    thank you for your quick reply.

    I used two buffers (RX and TX).
    Look my code. I did something very simple, but it doesn't work.

    char rxbuffer[MAX_USB_BUFFER];
    char txbuffer[MAX_USB_BUFFER]="Hello world";
    U8 tx_length=0;
    
    /*----------------------------------------------------------------------------
      (micro to PC)
     *----------------------------------------------------------------------------*/
    S32 usbd_cdc_ser_read (S8 *buffer, const S32 *length)
    {
            U32 bytesRead;
    
            for(bytesRead = 0; (bytesRead < (*length)) && (MAX_USB_BUFFER > bytesRead); ++bytesRead)
            {
                    buffer[bytesRead] = txbuffer[bytesRead];
            }
            tx_length=0;
            return (bytesRead);
    }
    
    /*----------------------------------------------------------------------------
       (PC to micro)
     *----------------------------------------------------------------------------*/
    S32 usbd_cdc_ser_write (const S8 *buffer, S32 *length)
    {
            S32 bytesWritten;
            static U8 cptRX=0;
    
            for(bytesWritten = 0; (bytesWritten < (*length)) && (MAX_USB_BUFFER > bytesWritten); ++bytesWritten)
            {
                    rxbuffer[cptRX] = buffer[bytesWritten];
                    cptRX++;
                    if(cptRX>63)
                            cptRX=0;
            }
    
            return (bytesWritten);
    }
    
    /*----------------------------------------------------------------------------
     available data for sending
     *----------------------------------------------------------------------------*/
    void usbd_cdc_ser_availchar (S32 *availChar)
    {
            if(tx_length)
                    *availChar = tx_length;
            else
                    *availChar = 0;
    }
    
    

    When tx_length is modified in my main(), this doesn't work.
    So when tx_length is modified in my usbd_cdc_ser_write(), this works.

    When I make a breakpoint in the function usbd_cdc_ser_availchar(), the variable "availChar" is modified. But it doesn't go in the function usbd_cdc_ser_read();
    Why ? (i don't know, I don't understand)
    I'm doing something wrong?

  • Hello JB,

    I am sorry. I test only my minimal example and think everthing works so I can use it in one / two month when I implement USB.

    But you have right usbd_cdc_ser_availchar is called but never usbd_cdc_ser_read()!!! usbd_cdc_ser_write() works.

    So back to my statement above: This class is useless!

  • Hello Dieter Gnaier.

    Look for the USBD_CDC_DepInEmpty in the map-file.
    It is very relative with call of function usbd_cdc_ser_read():

    step 1. compiler checks result returned from the usbd_cdc_ser_availchar();
    step 2. if result not equal to zero, go to step 3.
    step 3. compiler checks USBD_CDC_DepInEmpty (this lays in the RAM).
    step4. if USBD_CDC_DepInEmpty is NOT equal to zero then usbd_cdc_ser_read() will be called.

    p.s. But stack is really useless I think too. Sorry for poor english.