cdcdf_acm_write() to send welcome message

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

I was able to get an ATMEL Start based USB CDC ACM Echo example up and running. After initializing the stack, I install a STATE callback and in the state callback I install read and write callbacks. After setting up a first read, I can write what has been read and setup a new read.

 

But: If I try to write a welcome message in the state callback using cdcdf_acm_write(), neither the message is send out (or at least, I don't see it in my terminal emulation), nor does the cdcf_acm_write() function return an error. If I dispatch the state callback to my main loop and call cdcf_acm_write() from there, I "see" the same effect (no message, no error). If I issue a delay before sending out the message, the message is received by the terminal. Debugging the problem is hard, as stepping through cdcf_acm_write() already introduces a delay.

 

Is there something obvious, I could do wrong (misconfiguration etc.)? Is this a known bug? Is there something else I could wait on, before start sending data? ( assert( cdcdf_acm_is_enabled() ) doesn't fail)

 

According to AtmelStart.env_config, I'm using version 1.2.139 configured for a ATSAMD51J19A.

 

Thanks in advance,

Torsten

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Maybe try a search here for cdcdf_acm_write. Here is something for example:

https://community.atmel.com/foru...

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Well, I've searched for that term already through this forum and through the internet. The workaround in the sited example will already not work, as it is the call to cdcdf_acm_write() that does not show the expected side effect, if called directly after the state changed to "connected".

 

I used static allocated buffer in RAM and in Flash. For being able to write flash allocated buffers, I had to configure input cache (yes, input!) for endpoint 0 to 2. (CONF_USB_EPx_I_CACHE=64). I've learned that the hard way, by debugging the library code. But in that cases, cdcdf_acm_write() at least returned an error code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is based on that thread (but without stdio), works for me.

/*
 * Code generated from Atmel Start.
 *
 * This file will be overwritten when reconfiguring your Atmel Start project.
 * Please copy examples or other code you want to keep to a separate file or main.c
 * to avoid loosing it when reconfiguring.
 */
#include "atmel_start.h"
#include "usb_start.h"

#if CONF_USBD_HS_SP
static uint8_t single_desc_bytes[] = {
    /* Device descriptors and Configuration descriptors list. */
    CDCD_ACM_HS_DESCES_LS_FS};
static uint8_t single_desc_bytes_hs[] = {
    /* Device descriptors and Configuration descriptors list. */
    CDCD_ACM_HS_DESCES_HS};
#define CDCD_ECHO_BUF_SIZ CONF_USB_CDCD_ACM_DATA_BULKIN_MAXPKSZ_HS
#else
static uint8_t single_desc_bytes[] = {
    /* Device descriptors and Configuration descriptors list. */
    CDCD_ACM_DESCES_LS_FS};
#define CDCD_ECHO_BUF_SIZ CONF_USB_CDCD_ACM_DATA_BULKIN_MAXPKSZ
#endif

static struct usbd_descriptors single_desc[]
    = {{single_desc_bytes, single_desc_bytes + sizeof(single_desc_bytes)}
#if CONF_USBD_HS_SP
       ,
       {single_desc_bytes_hs, single_desc_bytes_hs + sizeof(single_desc_bytes_hs)}
#endif
};

/** Ctrl endpoint buffer */
static uint8_t ctrl_buffer[64];
volatile bool cdcTransferRead = false;
volatile uint32_t cdcTransferReadLen;
volatile bool cdcTransferWrite = false;
volatile bool cdcConnected = false;

static bool cdcWriteDone(const uint8_t ep, const enum usb_xfer_code rc, const uint32_t count)
{
    cdcTransferWrite = false;
    return false;
}

static bool cdcReadDone(const uint8_t ep, const enum usb_xfer_code rc, const uint32_t count)
{
    cdcTransferReadLen = count;
    cdcTransferRead = false;
    return false;
}

static int32_t cdcRead(char* const buf, const uint16_t length)
{
    cdcTransferRead = true;
    if (cdcdf_acm_read((uint8_t*)buf, length) != USB_OK) {
        cdcTransferRead = false;
        return 0;
    }
    while(cdcTransferRead && cdcConnected);
    return (int32_t)cdcTransferReadLen;
}

static uint8_t outBuf[80];
static uint32_t outLen = 0;

static int32_t cdcWrite(const char* const buf, const uint16_t length)
{
    const char* end = buf + length;
    for (const char* p = buf; p < end && cdcConnected; ++p) {
        outBuf[outLen++] = *p;

        if (*p == '\n' || outLen==sizeof(outBuf)) {
            cdcTransferWrite = true;
            cdcdf_acm_write(outBuf, outLen);
            while(cdcTransferWrite && cdcConnected);
            outLen = 0;
        }
    }
    return length;
}

/**
 * \brief Callback invoked when Line State Change
 */
static bool usb_device_cb_state_c(usb_cdc_control_signal_t state)
{
    if (state.rs232.DTR) {
        /* Callbacks must be registered after endpoint allocation */
        cdcdf_acm_register_callback(CDCDF_ACM_CB_READ, (FUNC_PTR)cdcReadDone);
        cdcdf_acm_register_callback(CDCDF_ACM_CB_WRITE, (FUNC_PTR)cdcWriteDone);
        cdcConnected = true;
    }
    else {
        cdcConnected = false;
    }

    /* No error. */
    return false;
}

/**
 * \brief CDC ACM Init
 */
void cdc_device_acm_init(void)
{
    /* usb stack init */
    usbdc_init(ctrl_buffer);

    /* usbdc_register_funcion inside */
    cdcdf_acm_init();

    usbdc_start(single_desc);
    usbdc_attach();
}

/**
 * Example of using CDC ACM Function.
 * \note
 * In this example, we will use a PC as a USB host:
 * - Connect the DEBUG USB on XPLAINED board to PC for program download.
 * - Connect the TARGET USB on XPLAINED board to PC for running program.
 * The application will behave as a virtual COM.
 * - Open a HyperTerminal or other COM tools in PC side.
 * - Send out a character or string and it will echo the content received.
 */
void cdcd_acm_example(void)
{
    while (!cdcdf_acm_is_enabled()) {
        // wait cdc acm to be installed
    };

    cdcdf_acm_register_callback(CDCDF_ACM_CB_STATE_C, (FUNC_PTR)usb_device_cb_state_c);
    const char* message = "Hello, world\r\n";
    while (1) {
        if (cdcConnected) {
            cdcWrite(message, strlen(message));
            while(cdcConnected) {
                char input;
                if (cdcRead(&input, 1) == 1) {
                    cdcWrite("Got: ", 5);
                    cdcWrite(&input, 1);
                    cdcWrite("\r\n", 2);
                }
            }
        }
    }
}

void usb_init(void)
{
    cdc_device_acm_init();
}

While playing with this I have noticed that passing a constant string (from flash) like this

cdcf_acm_write("Hello, world\r\n", 14);

just does not work, the transfer has to be from RAM (hence cdcWrite is ok since it transfers from a buffer).

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Lars,

thanks a lot for all the effort you put into helping me with this. It toke me some time to integrate your example into my project. In my case, there is a USB Hub IC one the hardware, which first has to be configured and enabled. But after this, I ran your usb_init() and cdcd_acm_example() functions and I see exactly the same problem: I don't see the "Hello, world" message. But I see all subsequent "Got: " messages.

 

To be able to output string constants from flash, I've enabled input caches for the end points by setting CONF_USB_EP0_I_CACHE through CONF_USB_EP2_I_CACHE to 64. I removed that defines from the build, but I still do not see the first message.

 

Can you give me the compiler / linker invocation, where I can see, what defines you are using?

 

thanks and best regards,

Torsten

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I attach the project and build output. It should be noted I tested this in a Adafruit Grand Central M4 Express (it has a ATSAMD51P20A).

/Lars

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Lars,

one, maybe important difference I see between your code and mine (which can be found here: https://github.com/TorstenRobitz...), is that I do not busy wait on `cdcdf_acm_is_enabled()` becoming true, but instead installing the state change handler directly after the initialization. I tried to change my code accordingly, but if I start waiting for `cdcdf_acm_is_enabled()`  before installing the state change handler, this loop will never be left and no CDC device pops up on my Mac.

 

Just to be clear, you see the "Hello, World" message on your terminal emulation, when you run your example, right?

 

best regards,

Torsten

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I discovered a second difference. I've set the single_desc as if CONF_USBD_HS_SP where set, without setting CONF_USBD_HS_SP. I've tried both, setting CONF_USBD_HS_SP to 0 or 1 and setting single_desc corresponding, without any difference.

 

So far, the only workaround I've found, was to write the wellcome message 11 times. The 11th write will then make it. Or, to introduce a delay. I wonder what part of the library will be changed during the delay... 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Torsten_Robitzki wrote:
Just to be clear, you see the "Hello, World" message on your terminal emulation, when you run your example, right?

Yes.

Maybe we have a timing difference because of a CPU clocking difference, how is the CPU clocked in your case?

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good idea! 120 MHz. I will try to see if it makes a difference, if I use, for example 40MHz. But I think it will take till next week until I find time to do so.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good Morning,

now I had the chance to test your idea. I've reduced the CPU speed down to 39 MHz. But I doesn't fixed the issue (first call to cdcdf_acm_write() doesn't show the promised side effect, nor return an error).

 

Fortunately this is just for a private project, so I can life with such a workaround. 

 

Thank you very much for all the help!

 

Torsten