2

I have a Raspberry Pi 3 and I'm currently use read(2) system call to read data from serial port. At this moment, I'm reading byte by byte from port with;

rc = read(uart_fd, &byte, 1);

I'm wondering that if there is a better approach instead of reading one byte at a time. I know I can read 100 bytes and do a for loop for how much bytes I actually read from file (rc). I'm just curious about that is the size effects performance. Which value should I choose? I assume that this value depends hardware buffer size, where can I find this size?

EDIT:
My Serial Port setup comes from FTDI. So I attach FTDI to USB Port and use open(2) syscall with /dev/ttyUSBx.

Black Glix
  • 155
  • 1
  • 6

2 Answers2

2

Assuming we're talking about the hardware UART here, although that is not the default behaviour for the Pi 3 where

the PL011 UART is connected to the BT module, while the mini UART is used for Linux console output..

see here and here for how to fix that.


As for the hardware buffer: a minor problem - no official datasheet is available for the Pi 3's BCM2837 (see here: Where can I find the documentation for the BCM2837?).

At the Foundations website (here and here) we learn that:

The underlying architecture of the BCM2837 is identical to the BCM2836. The only significant difference is the replacement of the ARMv7 quad core cluster with a quad-core ARM Cortex A53 (ARMv8) cluster.

and

The underlying architecture in BCM2836 is identical to BCM2835. The only significant difference is the removal of the ARM1176JZF-S processor and replacement with a quad-core Cortex-A7 cluster.

So the BCM2837 is supposedly identical to the BCM2835, except for, uhm, all that changed. Interestingly this part of the Foundations documentation - The Raspberry Pi UARTs - which refers to both the most recent models, the Raspberry Pi 3 and Raspberry Pi Zero W, also links to the spec of the BCM2835 (btw: a noteworthy link for the issue of the UART, the mini UART and Bluetooth).

The BCM2835's datasheet (secondary link) reveals on p. 175 that

the UART provides:

  • Separate 16x8 transmit and 16x12 receive FIFO memory.

So the hardware buffer would seem to be 16 bytes deep and 12 bit wide (receive buffer).

Hardware buffer size however is only half the truth since read() does not directly read the content of the SoC's hardware buffer but needs to go through the OS and the hardware driver. This adds another layer of buffering (configurable but typically larger buffer size of 4 kB) and among other stuff sets the specifics of how the Receive FIFO trigger levels are handled (sidenote: The Secrets of UART FIFO is worth a read). serial_core.h is the place to investigate this further.

Retrieving single bytes from this buffer is by far the slowest approach. Use a good chunk of the OS-level buffer (the 4 kB) to improve this - depending on your need of required throughput or acceptable latency.

Glorfindel
  • 620
  • 1
  • 8
  • 15
Ghanima
  • 15,855
  • 15
  • 61
  • 119
  • Firstly to clearify, my uart setup comes from ftdi. So I attach ftdi to usb port and use open() syscall with /dev/ttyUSBx. Moreover, you are definitely right about that I actually read through kernel, not directly SoC's buffer. I missed it. Since for the different SoC's, there will be different buffer size, i just curious about does linux kernel optimize this? Another question, is 16x12 FIFO memory means 16 UART packet (Start bit, 8 data bits, 1 parity bit and 2 stop bits)? What will happen if I configure 7N1? Will SoC's buffer still store 16 packet (last bit space become useless) ? – Black Glix Dec 27 '17 at 19:59
  • I am pretty sure that using less width of the FIFO does not increase available depth, that's just not how it works. I doubt the Kernel will do much optimization, the hardware driver will come with a certain setting and that is that. With an USB based UART reading larger chunks will still be benefitial, after all it has to put the data into USB data packets adding more overhead. – Ghanima Dec 27 '17 at 20:13
1

You can certainly read multiple bytes using read.

The comments about FIFO are misleading, even though they are important to ensure high speed troughput, are inaccessable.

The kernel drivers almost certainly provide buffering, but again this is inaccessable. (In the 1970s I wrote my own drivers which used a 256 character circular buffer.)

The following code fragment is an example of how you could write such a buffered interface (into a user supplied buffer).

This ia actually an add-on to a wiringPi library routine (AFAIK installed by default on the Raspbian) which I use to provide an equivalent to fgets. Similar code could be used with any library.

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

#include <wiringPi.h>
#include <wiringSerial.h>

#define MAXLINE 20

/*
 * serialOpenB:
 *  Open and initialise the serial port, 
 *  setting canonical mode, ignore CR on input, no timeout
 *********************************************************************************
 */
int serialOpenB (const char *device, const int baud, const int max)
{
    struct termios options ;
    int fd = serialOpen(device, baud);
    // Get and modify current options:
    tcgetattr (fd, &options) ;

    options.c_lflag |= ICANON;  // set canonical mode (line by line)
    options.c_iflag |= IGNCR;   // ignore CR on input
    options.c_cc [VMIN] = max-1;    // return if max-1 bytes received
    options.c_cc [VTIME] = 0;   // no timeout

    tcsetattr (fd, TCSANOW | TCSAFLUSH, &options) ;

    usleep (10000) ;    // 10mS

    return fd ;
}


/* Get a newline-terminated string of finite length.
 *********************************************************************************
 */

char * serialGets (char *buf, const int n, const int fd)
{
    int m;
    m = read (fd, buf, n);
    *(buf+m) = '\0';
    return (buf) ;
}

The following is a test program

#include <stdio.h>
#include <time.h>
#include <string.h>

#include <wiringPi.h>
#include <wiringSerial.h>

#define MAXLINE 120
extern int serialOpenB (const char *device, const int baud, const int n);
extern char * serialGets (char *buf, const int n, const int fd);
char * rstrip(char *s) {
    char *p = s + strlen(s)-1;
    while((*p == '\n')||(*p == '\n')) {
        p--;
    }
    *p = '\0';
    return s;
}
int main ()
{
    char tbuf[20];
    time_t ctm;
    struct tm *ltime;
    char line[MAXLINE];

    puts("SerialTest\n");

// Always initialise wiringPi. Use wiringPiSys() if you don't need
//  (or want) to run as root
    wiringPiSetupSys () ;
    int fd = serialOpenB("/dev/ttyAMA0", 9600, MAXLINE);
    for(;;) {
        serialGets(line, MAXLINE, fd);
        ctm = time(NULL);   // log time
        ltime = localtime (&ctm);
        strftime (tbuf, 20, "%F %T", ltime);
        printf("%s %s\n", rstrip(line), tbuf);
        fflush(stdout);
    }
    return 0 ;
}
Milliways
  • 59,890
  • 31
  • 101
  • 209