Redirect STDIO to USB CDC serial port?

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

I'd like to redirect STDIO to USB CDC (virtual serial port). SAME54 + ASF4 project set up with Atmel Start.

 

It seems that Atmel Start supports redirecting STDIO to a USART, but not to USB.

My current workaround is to use sprintf() to print formatted text to a buffer, and cdcdf_acm_write() to write the buffer to USB CDC:

 

	char buffer[50];

	sprintf(buffer, "One plus one is %i\n", 2);
	cdcdf_acm_write((uint8_t *)buffer, strlen(buffer));

 

It's functional, but not pretty. I'd much prefer to use printf() directly. 

 

Anyone have a solution? 

 

[cross-posted to the AVR Freaks ASF4 forum in reply to someone who had this problem 2+ years ago, but not clear if it was ever solved] 

Last Edited: Fri. Mar 15, 2019 - 11:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I played with this in the cdc start example by adding the stdio redirect (the "TARGET IO" from that will be unused). All code changed is in usb_start.c. Seems ok but not much tested. It works by providing a read and a write function so a stdio_io_init() call can be made.
I could not get it reliable (was also really slow) without buffering in the output hence the write function is a lot more complicated than read.

#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;

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(struct io_descriptor *const io_descr, uint8_t *const buf, const uint16_t length)
{
    cdcTransferRead = true;
    if (cdcdf_acm_read(buf, length) != USB_OK) {
        cdcTransferRead = false;
        return 0;
    }
    while(cdcTransferRead);
    return (int32_t)cdcTransferReadLen;
}

static uint8_t outBuf[80];
static uint32_t outLen = 0;
static int32_t cdcWrite(struct io_descriptor *const io_descr, const uint8_t *const buf, const uint16_t length)
{
    size_t i = 0;
    uint16_t left = length;
    const uint8_t* p = buf;
    while(left > 0) {
        bool transfer = false;
        for(i = 0; !transfer && outLen < sizeof(outBuf) && i < left; i++) {
            outBuf[outLen++] = p[i];
            if (p[i] == '\n') {
                transfer = true;
            }
        }
        if (outLen < sizeof(outBuf) && i == left && !transfer) {
            break;
        }
        cdcTransferWrite = true;
        cdcdf_acm_write(outBuf, outLen);
        while(cdcTransferWrite);
        left -= i;
        p += i;
        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);
        static struct io_descriptor  cdcIo;
        cdcIo.write = cdcWrite;
        cdcIo.read = cdcRead;
        stdio_io_init(&cdcIo);
    }
    else {
        stdio_io_init(NULL);
    }

    /* 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();
}

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);

}

void usb_init(void)
{

    cdc_device_acm_init();
}

/Lars

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

Thanks very much for this sample! I'm just getting started with the CDC and it clarifies this side of the plumbing.

 

After reading the write code, I took a stab at simplifying.

static int32_t cdcWrite(struct io_descriptor *const io_descr, const uint8_t *const buf, const uint16_t length)
{
    const uint8_t* end = buf + length;
    for (uint8_t* p = buf; p < end; ++p) {
        outBuf[outLen++] = *p;

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

 

- Frank (The Things Network New York)

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

Yet another version of the CDC callbacks. Thank you Lajon for providing initial version!
 

volatile bool cdcTransferRead = false, cdcTransferWrite = false;
volatile uint32_t cdcTransferReadLen, cdcTransferWriteLen;

static bool cdcWriteDone(const uint8_t ep, const enum usb_xfer_code rc, const uint32_t count)
{
    cdcTransferWriteLen = 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(struct io_descriptor *const io_descr, uint8_t *const buf, const uint16_t length)
{    
    cdcTransferRead = true;
    if (cdcdf_acm_read(buf, length) != USB_OK)
    {
        cdcTransferRead = false;
        return 0;
    }
    while(cdcTransferRead);
    return (int32_t) cdcTransferReadLen;
}

static int32_t cdcWrite(struct io_descriptor *const io_descr, const uint8_t *const buf, const uint16_t length)
{      
    cdcTransferWrite = true;
    if (cdcdf_acm_write(buf, length) != USB_OK)
    {
        cdcTransferWrite = false;
        return 0;
    }
    while(cdcTransferWrite);
    return (int32_t) cdcTransferWriteLen;
}

 

Last Edited: Mon. Oct 19, 2020 - 11:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

hi, i don't understand the part on " adding the stdio redirect (the "TARGET IO" from that will be unused) ", i managed to make a code that writes consistently but can't read.

Also while debugging i notice that it never enters cdcWriteDone and dcdReadDone, i think my write works because i made this change:

 

where millis() is a custom timer that return millis variation.

 

i removed the part of

as i don't understand what its doing.

need help getting to read usb data

 

 

Last Edited: Wed. Jun 15, 2022 - 10:32 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

and also i want to know if i'm understanding well what usb functions do from debugging and experimenting.

 

this is the examble, from what i see when usb finish recibing data, it calls bulk_out and when it finish sending data it calls bulk_in.

 

next we have:

             cdcdf_acm_read((uint8_t *)usbd_cdc_buffer, sizeof(usbd_cdc_buffer));

from what i can see it tells the machine that for the next receiving event to save it in the buffer, up to the position of the size of the buffer?

from my tests cdcdf_acm_read() must be called after each read operation, or usb will ignore data received and bulk_out(or receive interruption) won't be called.

 

for sending data:

   cdcdf_acm_write((uint8_t *)usbd_cdc_buffer, count);

this can be called anytime, and it would send the data through usb as long  as it is properly initiatied with cdc_device_acm_init(void).

there isn't any necesity for callbacks function, but as it is asynchornus, if you don't wait for proper write done callback, it could overlap and that would result on garbage, that why on my last post the wait for cdcTransferWrite with timeout of 10 milliseconds works, as 10 milliseconds is enough to send most outputs, if not every output.

 

I still don't understand the part of stio redirect or the need for it. If you could explained it to me i would appreciate it.

 

Edit: So i found part of my problem, on why callbacks functions weren't working, it tourns out that i left serial monitor on pc open, so when i upload program with changes and starte debugging, write works, but callbacks never happens. It seems that for usb connection to properly register, com connection must be closed, and it should be open only once usb on atsam has properly started, so i can make the right synchronization.

 

 

 

Last Edited: Thu. Jun 16, 2022 - 03:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nico ro wrote:
I still don't understand the part of stdio redirect or the need for it

You don't have to redirect stdio, might simplify if you are used to it (printf etc).

 

And that echo example is very confusing, I have written about that more than once here e.g.:

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

/Lars

 

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

Btw, post code as text not images, this makes it possible to search (and copy). The code edit button looks like <> in  the toolbar.

/Lars