KSDK VCOM example does not work without loopback

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

KSDK VCOM example does not work without loopback

Jump to solution
4,621 Views
raymondwhite
Contributor III

I am running a FRDM-K64, using KDS 3.0 and KSDK 1.2.0. I am running a bare-metal app and pumping the usb "task" frequently

 

Below is the original function from the file virtual_com.c in the KSDK:

 

void Virtual_Com_App(void)

{

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

 

        /* Copy Buffer to Send Buff */

        for (i = 0; i < g_recv_size; i++)

        {

            g_curr_send_buf[g_send_size++] = g_curr_recv_buf[i];

        }

        g_recv_size = 0;

    }

 

    if (g_send_size)

    {

        uint8_t error;

        uint32_t size = g_send_size;

        g_send_size = 0;

 

        error = USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT,

            g_curr_send_buf, size);

 

        if (error != USB_OK)

        {

            /* Failure to send Data Handling code here */

        }

    }

    return;

}

 

As can be seen, the characters coming in from the VCOM port are immediately looped back out to the output of the port. When I run PuTTy on the PC the VCOM port works fine. I can type all day and the characters are dutifully returned.

 

I also tried keeping the loopback in-place and also sending the same characters out to a UART. If works fine that way, the characters typed appear in both the VCOM port window and the UART port window.

 

The problem is I don't want to loop back the characters on the VCOM port. So, as a test, I redirected the received characters to a UART and did not send any characters back via the VCOM port. When I do this, the very first character I type is received by the VCOM driver and sent to the UART, but then no other characters are received. So, somehow, by not echoing the received characters, it seems to inhibit the reception of more characters.

 

If I disconnect the USB from the PC and plug it back in, it happens again (only one character gets received).

 

Here is my code, edited for brevity:

 

void Virtual_Com_App(void)

{

 

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

 

        for (i = 0; i < g_recv_size; i++)

        {

          // sent character to uart here

        }

        g_recv_size = 0;

    }

 

    USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0); // I tried it without this line, same effect (only get one character).

 

    return;

}

 

What am I doing wrong?

Labels (1)
1 Solution
2,709 Views
isaacavila
NXP Employee
NXP Employee

Hi Ray

I got the point and I was investigating further to implement a full asynchronous communications. Let me try to explain what it is happening and if you have any doubt please let me know it.

First of all, the problem here is that you are using same USB Class callback that VCOM example but, in this example, loopback is requiered to get this demo to work fine.

It starts requesting for data, once it receives them, it sends a package with received data ('echo' procedure) and once it has been sent successfully, in the USB_DEV_EVENT_SEND_COMPLETE event, you request for more data and cycle is repeated. So every request either for sending or receiving is completed succesfully.

In your case, you started requesting for data and then you are sending data periodically. Once periodically data has been sent, in USB_DEV_EVENT_SEND_COMPLETE event in USB class callback you request for more data eventhough you didn't received any data before, so this request queues (using one xD entry) and cycle is repeated. It causes xD to be all used and no further requests could be made. In other words, every time a SEND_COMPLETE event is gotten, you request to receive more data eventhough you haven't received it.

I have changed some logic on it and it seems to work as you want to, it uses one bool variable to signal when data has been received and serves to schedule next request.

static bool received_data = TRUE;

VCom_USB_App_Class_Callback function schedules next recv data (Only when data has been received before and send complete is reached )

case USB_DEV_EVENT_SEND_COMPLETE:

    {

    if ((size != NULL) && (*size != 0) && !(*size % g_bulk_in_max_packet_size))

    {

        /* If the last packet is the size of endpoint, then send also zero-ended packet,

         ** meaning that we want to inform the host that we do not have any additional

         ** data, so it can flush the output.

         */

        USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    else if ((start_app == TRUE) && (start_transactions == TRUE))

    {

        if ((*data != NULL) || ((*data == NULL) && (*size == 0)))

        {

            /* User: add your own code for send complete event */

            /* has data been received? */

            if (received_data == TRUE) {

                /* Schedule buffer for next receive event */

                received_data = FALSE;

                USB_Class_CDC_Recv_Data(handle, DIC_BULK_OUT_ENDPOINT,

                        g_curr_recv_buf, g_bulk_out_max_packet_size);

            }

        }

    }

}

    break;

In Virtual_Com_App, signal when data has been received.

void Virtual_Com_App(void)

{

    int c;

    int received = 0;

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

        for (i = 0; i < g_recv_size; i++)

        {

            usb_if_vcom_put(g_curr_recv_buf[i]);

        }

        g_recv_size = 0;

        /* This flag serves to send NULL package or data package */

        received = 1;

        /* This flag inform about a receive event, trigger next request */

        received_data = TRUE;

    }

    g_send_size = 0;

    while ((c = usb_if_vcom_get()) != -1)

    {

      g_curr_send_buf[g_send_size++] = c;

      if (g_send_size == DATA_BUFF_SIZE)

      {

        // buffer is full, send it and

        // more bytes can be picked up on the next pass

        break;

      }

    }

    if (g_send_size)

    {

        uint8_t error;

        uint32_t size = g_send_size;

        g_send_size = 0;

        error = USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, g_curr_send_buf, size);

        if (error != USB_OK)

        {

            /* Failure to send Data Handling code here */

        }

    }

    else if (received == 1)

    {

      // Tell driver there is no data so it can flush output

      USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    return;

}

this time, you only request for new data when you have recevied data before. This way state machine in USB completes all its request and communications works well.

Note: You schedule another USB_Class_CDC_Recv in send_complete event due when you received a data, you either send a NULL package or send data package and both transactions will end in USB_DEV_EVENT_SEND_COMPLETE event.

I hope this can help you.

Best Regards

Isaac

-----------------------------------------------------------------------------------------------------------------------

Note: If this post answers your question, please click the Correct Answer button. Thank you!

-----------------------------------------------------------------------------------------------------------------------

View solution in original post

14 Replies
2,709 Views
isaacavila
NXP Employee
NXP Employee

Hello Raymond,

In CDC example, USB_Class_CDC_Send_Data function serves as sending host ack package as well as makes the looback function.

In your case, you need to send a NULL package only when data has been received (ACK package), try something like this:

void Virtual_Com_App(void) {

     if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

     {

        int32_t i;

        /* Copy Buffer to Send Buff */

        for (i = 0; i < g_recv_size; i++)

        {

            USB_PRINTF("Copied: %c\n\r", g_curr_recv_buf[i]);

        }

        g_recv_size = 0;

        /* Send NULL data when package has been received */

        USB_Class_CDC_Send_Data(g_app_handle, DIC_BULK_IN_ENDPOINT,

                    NULL, 0);

     }

}

Other way, every time that Virtual_Com_app is called, you are trying to send a NULL data making endpoint to be busy and crashing application.

I made a simple test of this function and it seems to work fine (next image shows what i got on UART terminal (OpenSDA terminal)).

CDC example.jpg

I hope this can help

Regards,

Isaac

0 Kudos
2,709 Views
raymondwhite
Contributor III

Hello Isaac

Thanks for the reply.

Yes, moving the call to USB_Class_CDC_Send_Data() inside of the receive block certainly helps. But, it does not solve my next problem. Below is my code is its full glory:

void Virtual_Com_App(void)

    int c;

    int received = 0;

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

        for (i = 0; i < g_recv_size; i++)

        {

          usb_if_vcom_put(g_curr_recv_buf[i]);

        }

        g_recv_size = 0;

        received = 1;

    }

    g_send_size = 0;

    while ((c = usb_if_vcom_get()) != -1)

    {

      g_curr_send_buf[g_send_size++] = c;

      if (g_send_size == DATA_BUFF_SIZE)

      {

        // buffer is full, send it and

        // more bytes can be picked up on the next pass

        break;

      }

    }

    if (g_send_size)

    {

        uint8_t error;

        uint32_t size = g_send_size;

        g_send_size = 0;

        error = USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, g_curr_send_buf, size);

        if (error != USB_OK)

        {

            /* Failure to send Data Handling code here */

        }

    }

    else if (received == 1)

    {

      // Tell driver there is no data so it can flush output?

      USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    return;

}

What I am really trying to do is asynchronously receive and send data over the VCOM port. I am not sure why the USB stack seems to tie the two directions at the hip. But to over come this, I have two buffer functions of my own usb_if_vcom_put() to eat the received data and usb_if_vcom_get() that acts as buffer for outgoing data. The rate of data going out is minimal, so the buffer full condition never actually happens.

In any event, what I see happening is that I can send and receive in this manner for a short time and then the send halts. Receive sometimes halts as well.

Digging further into the code, I find that the file khci_dev.c has functions: usb_dci_khci_free_xd() and usb_dci_khci_get_xd(). What I see is that, as characters are received, the number of calls to usb_dci_khci_get() exceed the number of calls to usb_dci_khci_free(). Thus, eventually, all the xd_entries get eaten up.

0 Kudos
2,708 Views
isaacavila
NXP Employee
NXP Employee

Hi Raymond,

Could you attach your full code?

Best Regards,

Isaac

0 Kudos
2,708 Views
raymondwhite
Contributor III

Hello Isaac

Sorry for the delay... I took the time to strip all of the extraneous code out of my project to just focus on the problem.

ZIP file of KDS 3.0 project is attached.

To use the program, run it from a FRDM-K64 board. The blue LED should blink while it is running.

The MSD will want to be formatted, just ignore it or cancel the request.

Open a PuTTy window for the USB VCOM port (9600 baud 8-N-1)

Open a PuTTy window for the OpenSDA VCOM port (UART0 57600 baud 8-N-1)

As you type characters into the USB VCOM window, they will appear in the OpenSDA VCOM window.

Every time you press ENTER, you will see the word "TEST" appear in both PuTTy windows.

However, after pressing ENTER 9 or 10 times, the word TEST will stop appearing in the USB VCOM port window.

At this point if you inspect the xd_entries counter you will see that it has been drained to near zero.

Regards

-Ray

0 Kudos
2,708 Views
isaacavila
NXP Employee
NXP Employee

Hello Raymond,

After a review on your code, i noticed that you are sending NULL package on every received data and then, once you received '\r' character, you try to send a string ("TEST"). Current demo structure needs to send NULL data only when there is not available data on device, otherwise, you should send these data to host.

For example, your current project sends both data (NULL and string):

Sending NULL and Data packages.jpg
As you can see, when device receives 0x0D, you send a NULL package (No data) and then send 5 bytes ("T E S T \0") bytes, this causes xD to get full after 9 attempts.

The right way to do this would be only send NULL package when you do not want to send data to host:

Sending Data package.jpg

As you can see, after 0x0D is received, we send the string ("TEST") instead of NULL data and this works fine, otherwise, we send NULL data. Next is code used to get this functionality:

void Virtual_Com_App(void)

{

    int c;

    int received = 0, send_ack = 1;

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

        for (i = 0; i < g_recv_size; i++)

        {

            if (g_curr_recv_buf[i] == '\r') {

                send_ack = 0;

            }

            usb_if_vcom_put(g_curr_recv_buf[i]);

        }

        g_recv_size = 0;

        if (send_ack) {

            received = 1;

        }

    }

    g_send_size = 0;

    while ((c = usb_if_vcom_get()) != -1)

    {

      g_curr_send_buf[g_send_size++] = c;

      if (g_send_size == DATA_BUFF_SIZE)

      {

        // buffer is full, send it and

        // more bytes can be picked up on the next pass

        break;

      }

    }

    if (g_send_size)

    {

        uint8_t error;

        uint32_t size = g_send_size;

        g_send_size = 0;

        error = USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, g_curr_send_buf, size);

        if (error != USB_OK)

        {

            /* Failure to send Data Handling code here */

        }

    }

    else if (received == 1)

    {

      // Tell driver there is no data so it can flush output

      USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    return;

}

You only need to send NULL character when you are not willing to send data to host, otherwise, you need to send data to host.

I will investingate further on this and try to see why xd entries are not freed.

I hope this can helps,

Best Regards,

Isaac

-----------------------------------------------------------------------------------------------------------------------

Note: If this post answers your question, please click the Correct Answer button. Thank you!

-----------------------------------------------------------------------------------------------------------------------

2,706 Views
raymondwhite
Contributor III

Hello Issac

A little more information. I just modified my code as follows:

1) Output the "TEST" string each time the LED blinks (in main()) (changes highlighted in orange):

// Event loop

  while (1)

  {

    usb_if_process();

    command_process();

    ++led_count;

    if (led_count == 200000)

    {

      GPIOB_PCOR |= (1 << 21);

    }

    else if (led_count == 400000)

    {

command_print("TEST\n");

uart.print("TEST\n");

      GPIOB_PSOR |= (1 << 21);

      led_count = 0;

    }

  }

2) Eliminate the output of "TEST" in response to '\r'. The receive characters are now simply dropped.

void command_process(void)

{

  unsigned char c;

  // In this state characters are being accumulated and parsed

  if (rx_queue_count > 0)

  {

    // remove a character from the circular buffer

    c = rx_queue_buffer[rx_queue_tail];

    uart.put(c,false);

    ++rx_queue_tail;

    if (rx_queue_tail == CMD_QUEUE_RX_BUFFER_SIZE)

    {

      rx_queue_tail = 0;

    }

    --rx_queue_count;

    if (c == '\r')

    {

//      command_print("TEST\n");

//      uart.print("TEST\n");

    }

  }

}

When I make these changes, I do not need to even type anything into the PuTTy window, hence there are no characters received, only characters sent. I still see after 10 times, he output grinds to a halt.

Regards

-Ray

0 Kudos
2,710 Views
isaacavila
NXP Employee
NXP Employee

Hi Ray

I got the point and I was investigating further to implement a full asynchronous communications. Let me try to explain what it is happening and if you have any doubt please let me know it.

First of all, the problem here is that you are using same USB Class callback that VCOM example but, in this example, loopback is requiered to get this demo to work fine.

It starts requesting for data, once it receives them, it sends a package with received data ('echo' procedure) and once it has been sent successfully, in the USB_DEV_EVENT_SEND_COMPLETE event, you request for more data and cycle is repeated. So every request either for sending or receiving is completed succesfully.

In your case, you started requesting for data and then you are sending data periodically. Once periodically data has been sent, in USB_DEV_EVENT_SEND_COMPLETE event in USB class callback you request for more data eventhough you didn't received any data before, so this request queues (using one xD entry) and cycle is repeated. It causes xD to be all used and no further requests could be made. In other words, every time a SEND_COMPLETE event is gotten, you request to receive more data eventhough you haven't received it.

I have changed some logic on it and it seems to work as you want to, it uses one bool variable to signal when data has been received and serves to schedule next request.

static bool received_data = TRUE;

VCom_USB_App_Class_Callback function schedules next recv data (Only when data has been received before and send complete is reached )

case USB_DEV_EVENT_SEND_COMPLETE:

    {

    if ((size != NULL) && (*size != 0) && !(*size % g_bulk_in_max_packet_size))

    {

        /* If the last packet is the size of endpoint, then send also zero-ended packet,

         ** meaning that we want to inform the host that we do not have any additional

         ** data, so it can flush the output.

         */

        USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    else if ((start_app == TRUE) && (start_transactions == TRUE))

    {

        if ((*data != NULL) || ((*data == NULL) && (*size == 0)))

        {

            /* User: add your own code for send complete event */

            /* has data been received? */

            if (received_data == TRUE) {

                /* Schedule buffer for next receive event */

                received_data = FALSE;

                USB_Class_CDC_Recv_Data(handle, DIC_BULK_OUT_ENDPOINT,

                        g_curr_recv_buf, g_bulk_out_max_packet_size);

            }

        }

    }

}

    break;

In Virtual_Com_App, signal when data has been received.

void Virtual_Com_App(void)

{

    int c;

    int received = 0;

    /* User Code */

    if ((0 != g_recv_size) && (0xFFFFFFFF != g_recv_size))

    {

        int32_t i;

        for (i = 0; i < g_recv_size; i++)

        {

            usb_if_vcom_put(g_curr_recv_buf[i]);

        }

        g_recv_size = 0;

        /* This flag serves to send NULL package or data package */

        received = 1;

        /* This flag inform about a receive event, trigger next request */

        received_data = TRUE;

    }

    g_send_size = 0;

    while ((c = usb_if_vcom_get()) != -1)

    {

      g_curr_send_buf[g_send_size++] = c;

      if (g_send_size == DATA_BUFF_SIZE)

      {

        // buffer is full, send it and

        // more bytes can be picked up on the next pass

        break;

      }

    }

    if (g_send_size)

    {

        uint8_t error;

        uint32_t size = g_send_size;

        g_send_size = 0;

        error = USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, g_curr_send_buf, size);

        if (error != USB_OK)

        {

            /* Failure to send Data Handling code here */

        }

    }

    else if (received == 1)

    {

      // Tell driver there is no data so it can flush output

      USB_Class_CDC_Send_Data(*g_cdc_vcom_ptr, DIC_BULK_IN_ENDPOINT, NULL, 0);

    }

    return;

}

this time, you only request for new data when you have recevied data before. This way state machine in USB completes all its request and communications works well.

Note: You schedule another USB_Class_CDC_Recv in send_complete event due when you received a data, you either send a NULL package or send data package and both transactions will end in USB_DEV_EVENT_SEND_COMPLETE event.

I hope this can help you.

Best Regards

Isaac

-----------------------------------------------------------------------------------------------------------------------

Note: If this post answers your question, please click the Correct Answer button. Thank you!

-----------------------------------------------------------------------------------------------------------------------

2,706 Views
annamol
Contributor IV

Hi Issac,

I am trying to setup a USB CDC implementation in K65 using MQX and IAR. The original project works as such in loop back. Our application is to stream and control 5 devices and push data via USB. When I push data continuously in a while(1) loop using usb_cdc_send_data() API, I get the message usb_cdc_send_data: DEV_GET_XD failed in openSDA COM port. To remove this, I followed the modifications suggested by you in the previous post. Now I can stream data as well as receive data. But at times, again this error message was coming, to avoid this, I added a delay after usb_cdc_Send_data(). Now the message doesn't come and I have control over the device.

But data rate seems to be pretty low around 1ksps,which is very low as far as USB is concerned.

How much was the data rate you were receiving?

0 Kudos
2,706 Views
francescoferrar
Contributor III

Hi

is it possible to have complete source code (usb_if_vcom_put(), usb_if_vcom_get()) ?

regards

Francesco

0 Kudos
2,706 Views
raymondwhite
Contributor III

Hello Francesco

Basically, just giving you the usb_if_vcom_put() and usb_if_vcom_get() functions won't do you much good.

A while after I applied the patch suggested in this thread it was found that we still had occasional data dropouts and hangs. I had to go into the Freescale USB demo stack with a hatchet and totally clean house. I eliminated over 3/4 of the files and most of the ridiculous #ifdefs before I finally isolated the real problem, which with was is the way the send and receive interrupts/states were being overlapped.

Regards

-Ray

0 Kudos
2,706 Views
kevinlfw
Contributor III

It would be more useful to publish what you edited in the USB stack and how you changed the state machine so it would work better.  Not only would it help others when coming across this post, but it may also help NXP patch the SDK so everyone gets the issue fixed.

0 Kudos
2,706 Views
raymondwhite
Contributor III

Hello Isaac

I tried out your patch, seems to work quite well.

Thank You!

-Ray

0 Kudos
2,706 Views
mjbcswitzerland
Specialist V

Raymond

Note that the uTasker project for the FRDM-K64F contains industrially proven USB VCOM, which can be used in the KSD framework (since it is essentially bare-metal)

µTasker Kinetis FRDM-K64F support

It allows multiple CDCs (for example 5 USB-UART bridge [or 6 on the TWR-K64F120M]) or can mixed with USB-MSD or HID composites. Also supports MODBUS via USB or software upgrades via USB-CDC.

Furthermore, it can also be simulated so that development can be performed without need for HW (or simply to accelerate general project work).

Completely free for non-commercial work, but fully supported here or at its own forum.

Regards

Mark

Kinetis: µTasker Kinetis support

K64: µTasker Kinetis FRDM-K64F support  / µTasker Kinetis TWR-K64F120M support  / µTasker Kinetis TWR-K65F180M support

USB User's Guide: http://www.utasker.com/docs/uTasker/USB_User_Guide.PDF

Composite USB: µTasker USB Device Configuration

For the complete "out-of-the-box" Kinetis experience and faster time to market

0 Kudos
2,708 Views
raymondwhite
Contributor III

Hello Isaac

Sorry for being obtuse.

I understand your explanation, but I think it misses the point. The code I provided was a contrived example that I put together to illustrate the problem I am seeing.

The way you have coded it assumes that a '\r' will always result in data being sent to the PC. While that is true for the example program I provided, the reality of the situation is that I need the VCOM port to behave truly asynchronously in the send and receive directions.

Consider the case where I could program a timer to wake up and send the "TEST" string at random intervals instead of tying it to the reception of '\r', then when a character is received there may be no characters in the send buffer, so it will send a NULL.... but, a tiny fraction of a second later, the timer could wake up and insert characters into the send buffer. This would result in in the same pair of DATA IN messages in your first table, hence the loss of an xd entry. Such a program would take much longer to exhibit the failure, but it would eventually fail.

Regards

-Ray

0 Kudos