1

I am learning C and decided to build a small LCD status panel for one of my servers. This server runs FreeBSD but I, as a matter of discipline, wants to keep the code portable, so I also test in Linux.

So I decided to try to build the code from this answer as a learning exercise: How to open, read, and write from serial port in C? I built the code from the answer by sawdust:

#define TERMINAL    "/dev/ttyUSB0"

#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

void set_mincount(int fd, int mcount)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error tcgetattr: %s\n", strerror(errno));
        return;
    }

    tty.c_cc[VMIN] = mcount ? 1 : 0;
    tty.c_cc[VTIME] = 5;        /* half second timer */

    if (tcsetattr(fd, TCSANOW, &tty) < 0)
        printf("Error tcsetattr: %s\n", strerror(errno));
}


int main()
{
    char *portname = TERMINAL;
    int fd;
    int wlen;
    char *xstr = "Hello!\n";
    int xlen = strlen(xstr);

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);
    //set_mincount(fd, 0);                /* set to pure timed read */

    /* simple output */
    wlen = write(fd, xstr, xlen);
    if (wlen != xlen) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd);    /* delay for output */


    /* simple noncanonical input */
    do {
        unsigned char buf[80];
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
#ifdef DISPLAY_STRING
            buf[rdlen] = 0;
            printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
#endif
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Timeout from read\n");
        }               
        /* repeat read to get full message */
    } while (1);
}

The code builds fine and without warning. My Arduino is set to only clear the LCD screen if there's something new coming via serial, and it answers back OK when it updates the screen. I confirmed it works fine via screen.

For testing, I wrote manually some random characters to the screen and then launched the code. It works fine in Linux, but in FreeBSD the LCD goes blank.

So I launched lldb to see what's going on, set a few breakpoints in the relevant places and started going through the code. I couldn't see any problems and, to my surprise, the LCD was showing the test text. But whenever I launch it directly, I only get a blank screen. I inserted a few sleeps in the code to see if, by any chance, FreeBSD was not running something too fast or screwing up with timers, but nothing helped.

Do you have any ideas on how I could better leverage lldb to find out what is going on or an idea of what the problem could be?

Shiunbird
  • 87
  • 8
  • 1
    The problem is not clearly described. What exactly is the relationship between a serial link, an Arduino, and this "*small LCD status panel for one of my servers*"? Is the LCD panel supposed to display the **printf()** messages of the program? Seems like you have not detected or mentioned any "*different serial port behaviour*" at all, but rather only different behavior of the LCD panel. Coincidence is not correlation. IOW what happens if you replace that program above with a simple "Hello world" program that loops? – sawdust Dec 13 '22 at 04:52
  • 1
    `"/dev/ttyUSB0"` doesn't sound right to me. `cuaU` or `ttyU` usually are for serial comm. on FreeBSD. Also, I agree with the @sawdust's comment. Optionally, perhaps a simple "thank you" to those that tried to help you (as @sawdust did) would be great too ... ;o) – Valery S. Dec 13 '22 at 17:00
  • Hi @ValeryS and sawdust The device path /dev/ttyUSB0 I have replaced with the correct paths when I built in in Linux and FreeBSD. – Shiunbird Dec 14 '22 at 17:52
  • @Shiunbird: this is not a question of path but of driver. I don't know any FreeBSD drivers (USB/TTL) that create a node like `/dev/ttyUSB0`. So, better is to read the doc about this : [FreeBSD Handbook - Chapter 28. Serial Communications](https://docs.freebsd.org/en/books/handbook/serialcomms/) ; FYI, very old releases of FreeBSD provided something like `usb0` through `usbd`, forget this. `Arduino` devices are recognized as `cuaU` and `ttyU` using the `ucom` driver. You have to enable user access if you want to read them. Remember to add some timeout mangmt specific to the `Arduino` too ... – Valery S. Dec 14 '22 at 19:23
  • @Shiunbird: 1. I cannot understand how you can open `/dev/ttyUSB0` in FreeBSD. So, could you provide some info (in reply to comment) on what you call your server plz, what Arduino board do you use, what does `usbconfig show_ifdrv` say and if you see any of you `USB0` device node, and perhaps search for some device node links in `/etc/devfs*` pointing to `USBn`2. Then we will see how to properly open the serial of your `Arduino` according to the Arduino's bootloader in use (eg "Caterina", ...). – Valery S. Dec 25 '22 at 10:25
  • Hello everyone, thanks for the help. So I did some progress here. If I go to lldb and set a breakpoint at wlen = write(fd, xstr, xlen) and, once it stops, simply continue without any intervention, it works and I get text on the LCD connected to the arduino. But if I run it thoroughly in lldb without stopping, it doesn't work. As an experiment, I tried doing a simple sleep(1) after that line, to no avail. It is reading back from the arduino fine. If I set the arduino to just send some text back to the PC every second, it prints both directly and on lldb. – Shiunbird Dec 29 '22 at 13:06
  • I managed to find a solution. Sleeping for 2 seconds after fd = open(portname, O_RDWR | O_NOCITY | O_SYNC) does the trick. I'm resisting to post an answer because I can't explain why... it is probably about the difference how much FreeBSD and Linux wait for the port to be ready. Thoughts? @sawdust – Shiunbird Dec 29 '22 at 15:30
  • @Shiunbird : I told you (comment #4), but you missed it. Without any knowledges about your devices - host and Arduino ((comment #5) - it's really difficult to provide any other solution or "thoughts". You need answers but didn't provide any answers yourself ! Really funny to me. Happy new year anyway. – Valery S. Jan 01 '23 at 14:12
  • I provided the answer. 2 seconds sleep works. Happy New Year! – Shiunbird Jan 27 '23 at 09:58

1 Answers1

0

Sleeping for 2 seconds after opening the port before trying any other operation did the trick for me in FreeBSD

fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
sleep(2);
if (fd < 0) {
    printf("Error opening %s: %s\n", portname, strerror(errno));
    return -1;
}
Shiunbird
  • 87
  • 8