nrf5340 transfer data with UART and CDC_ACM, know when to add data to UART buffer

I want to transfer a bunch of data over the virtual com port to a PC using the USB port on the nrf5340.  I'm using the CDC ACM sample: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/samples/subsys/usb/cdc_acm/README.html

That sample takes a character in and pushes the character back out.  Simple and it works. 

Since I want to dump a bunch of data, I started doing the same uart_fifo_fill() command.  It appears I can only add 1024 bytes to that at a time, which is fine.  That function returns the number of bytes it send (or added to the buffer). 

My question is, how do I know when I can add more data to that buffer?  When can I call that command again.  There were a couple other functions in UART.H and the one when TX was completed is not implemented and the other is "TX buffer can take at least 1 byte of data" but that doesn't seem ideal.  I want to know when I can send 1024 bytes of data again.  

What's the ideal method for pushing a bunch of data through the UART so that it runs as fast as possible and I know when I can push more data into the buffer?

Parents
  • Hi,

    You can register a callback using uart_callback_set() to register a callback, which will be called when a transfer is finished (an also in some other cases, so you need to check the event type in the callback). This includes the number of bytes that was sent (see uart_event_tx).

  • Thanks for getting back to me. 

    The example uses:

    uart_irq_callback_set(dev, interrupt_handler);

    Is that different than the uart_callback_set()?  And since I already setup the above callback, do I check for a different event in the same interrupt_handler() function?  Do I use the one you mentioned?  Do I need to setup 2?

  • Hi,

    Ah, yes. I did not check the sample. That use the interrupt driven approach, so in that case uart_irq_callback_set() is used (see intro in the UART API documentation for a brief overview of the different approaches). With this API you don't get an event specifically when a Tx operation is completed, but you will get the callback for any UART interrupt. So in this case you could for instance expand the interrupt_handler() in the sample to check the buffer filling using ring_buf_size_get() every time it is called.

  • Again, I really do appreciate the help and you taking the time to get back to me. 

    Isn't the ring buffer in this example just a place to put the data that comes in the RX line?  It has nothing to do with the TX at all.  Other than in this example we take that and push it into the UART FIFO. 

    My "problem" is that I want to push tons of data out the UART and right now, I don't see a good way to do it.  Or a good way know when the last time I put something into the FIFO of the UART that it's done and can take more.  The uart fifo fill function takes what you send it and at most will send 1024 bytes.  But if I come back and send another 1024 bytes too quickly it replies with "I sent 635" or something.  And when I tested this, it appears that it sends the last 635 of the data that I add to it.  So I can't even try again with the remainder of my buffer. 

    irq tx ready gives you "if the tx buffer can accept at least 1 byte", that doesn't help unless I'm sending data one byte at a time. 

    tx complete says: Note that this function is not useful to check if UART TX can accept more data, use uart_irq_tx_ready() for that.  And in my example even if I wanted to use it, it's not implemented.  Can I do something to make it so that it IS implemented?

    The only way I got this to work is fifo fill and then wait 10ms.  And then do it again monitoring the response from fifo fill so that if it replies with anything less than what I push to it, I know my data transmission failed and I have to wait longer.  It kinda gets the job done, sort of but it's so terrible. 

    Am I going about this all wrong?  Is there a better way to send a ton of data on the UART as fast as possible? 

  • Hi,

    You are right, uart_irq_tx_ready() was what I should have referred to before and not ring_buf_size_get() (will update the post in case other read it in the future). I do not see any other obvious way than what I described before (but with uart_irq_tx_ready()). With that approach, you will not spend too much time polling.

    Is it a problem that you may not be able to write all data at once? I understand it is a bit annoying as the application code must keep track of what is buffered, but that is how this is done by other "users" in the SDK. See for instance the implementation of uart_isr_callback() in the logger.

  • It's not a problem, I have 8 gigabits that I want to transmit (at most, it's a flash chip and I want to dump the memory, yes I know it's gonna take a while).  Slight smile  So I know I have to split it up, and it's gonna be the only job for the micro when this happens so I don't care if it takes 100% of CPU cycles, I just want to minimize the time spent uart-ing. 

    The problem I saw with the uart_irq_tx_ready() is that it says that it returns true if it can take at least 1 byte.  Ok, but what if I want to send 1024 bytes?  Or more than 1, it doesn't tell me how many bytes I can send just that 1 is ok, any more????  Guess?  Does that mean I have to send 1 byte at a time? 

    I quickly checked out the logger example you have, and I'm not sure I understand it, or if it looks like it's any better.  It does look like they keep track of things a bit, but maybe I'm missing something. 

    Thanks again for taking the time to post stuff for me here.

  • And I'm trying to send a bunch of data, in order.  I believe I tested this already but it seems like such the wrong way to do things that I must have screwed it up.  But when you say, fill fifo with X bytes it returns the number of bytes that it DID send.  But when I tested this keeping track of the values in and out, it looked like it took the LAST bytes. 

    For instance if I said uart_fifo_fill with 10 bytes and I did it 3 times (really quickly and these numbers are small so it's easy to type and think about).  And I'm trying to send the values 1, 2, 3, ... up to 30.  And the first time uart_fifo_fill returned 10, second time uart_fifo_fill returned 5, the 3rd time it returned 10. 

    On the other end of the uart I got:

    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30.

    If it would have sent:

    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30.

    I could see that it took 5 bytes, and then be smart about it and try to send the last 5, but instead it took the last 5 and threw away the first 5.  That means the first time I send X bytes and it actually sent less my whole transmission is bad and I have to start over. 

    Maybe I have to try this again since, I can't imagine this is a good way to function. It's totally possible I screwed something up when I tested this before.

Reply
  • And I'm trying to send a bunch of data, in order.  I believe I tested this already but it seems like such the wrong way to do things that I must have screwed it up.  But when you say, fill fifo with X bytes it returns the number of bytes that it DID send.  But when I tested this keeping track of the values in and out, it looked like it took the LAST bytes. 

    For instance if I said uart_fifo_fill with 10 bytes and I did it 3 times (really quickly and these numbers are small so it's easy to type and think about).  And I'm trying to send the values 1, 2, 3, ... up to 30.  And the first time uart_fifo_fill returned 10, second time uart_fifo_fill returned 5, the 3rd time it returned 10. 

    On the other end of the uart I got:

    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30.

    If it would have sent:

    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30.

    I could see that it took 5 bytes, and then be smart about it and try to send the last 5, but instead it took the last 5 and threw away the first 5.  That means the first time I send X bytes and it actually sent less my whole transmission is bad and I have to start over. 

    Maybe I have to try this again since, I can't imagine this is a good way to function. It's totally possible I screwed something up when I tested this before.

Children
  • stevenminnick said:
    Ok, but what if I want to send 1024 bytes?  Or more than 1, it doesn't tell me how many bytes I can send just that 1 is ok, any more????  Guess?  Does that mean I have to send 1 byte at a time? 

    Try to send as much as you want, and check the return value of uart_fifo_fill(). This is the number of bytes that was sent.

    stevenminnick said:
    I quickly checked out the logger example you have, and I'm not sure I understand it, or if it looks like it's any better.  It does look like they keep track of things a bit, but maybe I'm missing something. 

    This point with linking to this is that it does try to send as much data as it has, and check how much was sent. And attempts to send the rest later. And so on. (See how the variable called curr_offset is increased by the return value of uart_fifo_fill() and this is done repeatedly until all data has been sent.

    stevenminnick said:
    Maybe I have to try this again since, I can't imagine this is a good way to function. It's totally possible I screwed something up when I tested this before.

    I don't see the code you used, but so I cannot say what could have been wrong. But I really suggest you just do exactly as the function from the logging subsystem that we have already mentioned. This is facing the exact same problem as you (potentially need to transfer more data than what you can do in a single call to uart_fifo_fill(), and you can do the exact same thing.

  • So I was testing the way Einor was showing me.  I believe this is the way they listed it in that logging example. 

                        buffLength += snprintf(buffer+buffLength, 1000-buffLength, "%d,",(int16_t)data);

                        if (buffLength > 1000)
                        {
                            send_len = 0;
                            buffPointer = 0;

                            while (send_len < buffLength && buffLength > 0)
                            {
                                send_len = uart_fifo_fill(dev, buffer+buffPointer, buffLength);
                                printk ("\nTried to print %d bytes only did %d", buffLength, send_len);
                                if (buffLength != send_len)
                                {
                                    printk (", failed %d bytes", buffLength - send_len);
                                }
                                buffLength = buffLength - send_len;
                                buffPointer = buffPointer + send_len;
                            }
                            memset(buffer, 0, sizeof(buffer));
                            buffLength = 0;
                            buffPointer = 0;
                            k_sleep(K_MSEC(4));
                        }

    So I've got a bunch of data, I'm taking that and packing my buffer.  What you see above is in my loop where I'm going through the data one int at a time.  Once my buffer length is > 1000, I try to send it to the fifo.  There is logic in here, where not only will it print, but if the fifo fill command returns a value that's not = to what I tried it will try to send the remainder. 

    The way this seems to work, if I send < 1024 bytes and the uart is empty, it works great.  So I put a sleep for 4ms in that loop.  What you see above works.  Because every time I come around, I'm sending < 1024 bytes and I wait an appropriate amount of time before sending again.  But let's look at this next item.

                        buffLength += snprintf(buffer+buffLength, 4096-buffLength, "%d,",(int16_t)data);

                        if (buffLength > 4000)
                        {
                            send_len = 0;
                            buffPointer = 0;

                            while (send_len < buffLength && buffLength > 0)
                            {
                                send_len = uart_fifo_fill(dev, buffer+buffPointer, buffLength);
                                printk ("\nTried to print %d bytes only did %d", buffLength, send_len);
                                if (buffLength != send_len)
                                {
                                    printk (", failed %d bytes", buffLength - send_len);
                                }
                                buffLength = buffLength - send_len;
                                buffPointer = buffPointer + send_len;
                            }
                            memset(buffer, 0, sizeof(buffer));
                            buffLength = 0;
                            buffPointer = 0;
                        }

    Here, the buffer is 4096, and once I pack 4000 bytes in my buffer, then I'm ready to send, I also don't "wait" in the loop.

    But the weird part is, my buffer packs numbers that increment.  And if I use the first code I posted, all the data comes out perfect.  Every time I send fifo fill with less than 1024 bytes, it returns the bytes that it did send which is the amount I tried to send.  Then I wait, hoping to wait long enough to not cause an error.

    When I use the second batch of code here, I have packed the same data, and I said ok fifio fill, send 4000 bytes and it returns with 1024, which I take it to mean that it was going to send only 1024, and to send the rest I'm going to have to try later. 

    BUT here's the weird part, since I tried to send more bytes than the fifo would take, this is in the MIDDLE of the first 1024 bytes that it sends.

    130,131,132,133,134,135,136,137,138,13994,395,396,3,143,144,145,146,

    If I try to send < 1024 bytes (cause that's all that will fit) this data comes out perfect, incrementing numbers.  When I try to send more than it can take, in the middle of what it DID take I get data corruption.

  • Hi,

    The code snippets here looks good, so I suspect the corruption is caused by something else. Could it be that the source buffer is modified by another thread while it is being transmitted, for instance because it is re-used too early?

  • I don't believe the source buffer is modified by anything else.  It's a single threaded application.  And it's created right before sending.  Unless I have a crazy weird memory corruption bug somewhere.  But the fact that when I send less bytes than the max to the UART with fifo fill, it always works.  And when I don't it always fails.  Same code just calling fifo fill with more than it can take and no wait between calls. 

  • I see, then I don't know. Are you able to make a minimal example that demonstrate this issue and runs on a DK? If so I can test on my end.

Related