0

I am new to C programming so this may be a basic question.

I have an Arduino talking to my PC over serial. The problem I am encountering is my PC is reading faster then serial data coming in (probably a common problem).

The messages that will be sent between PC and arduino will have some defined package structure. For this example, each message starts with an ASCII '<' and ends with a '>'

I am able to open/create the serial port and receive data (copied some code from stackoverflow), however I would like to modify the program to:

1) Read all available data from the serial port into a message buffer, non blocking so if no data is available continue to #2

2) Start at the beginning of the message buffer for a full message, if there is a '<' keep searching until a '>' is found. Once a full message is found, print the message to the screen, and erase the message from the buffer, moving any non processed data to the beginning of the buffer (This is the part I am really stuck on). If no '>' is found, continue to #1

I would like to copy the logic/functions to the arduino so both arduino and PC are communicating the same way.

Here is the code I copied that opens the serial port and continuously reads anything from the serial port

#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 = "/dev/ttyACM0";
    int fd;
    int wlen;

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

    /* simple output */
    wlen = write(fd, "<Hello Arduino!>", 7);
    if (wlen != 7) {
        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) {
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
        } 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);
}
sawdust
  • 16,103
  • 3
  • 40
  • 50
user2840470
  • 919
  • 1
  • 11
  • 23
  • `for (p = buf; rdlen-- > 0; p++)` this loop is incorrect. First element of for is the initial state, second should only perform the breaking test and the third the in-loop action. `for (p = buf; rdlen>0; rdlen--,p++)` should be better. But many people find indexes simpler: `for (int i=0; i – Alain Merigot Jan 26 '19 at 22:55
  • @AlainMerigot Although inelegant, OP's `for` loop _is_ [technically] correct. – Craig Estey Jan 26 '19 at 23:22
  • Have you considered using a ring queue? What is the maximum you'll store if you _don't_ find a closing `>` before you declare/assume an error? Is that 80 as your example suggests? – Craig Estey Jan 26 '19 at 23:27
  • I don't see a question in there anywhere. What's stopping you from modifying the code as you require? What don't you know how to do or what are you trying to do that's not working? This site really needs a specific question that can be answered in order to work. Just stating a problem isn't sufficient. We need to know specifically what help you need, what you're stuck on, what you can't figure out. – David Schwartz Jan 27 '19 at 01:08
  • *"This is the part I am really stuck on"* -- You need to use an intermediate buffer, not the system buffer. Since you copied my code (without any attribution), see https://stackoverflow.com/questions/54392470/how-to-handle-buffering-serial-data/54394770#54394770 for more code. – sawdust Mar 13 '22 at 23:41

1 Answers1

-1

(1) Set the file descriptor to nonblocking when opening or just use poll() to check if there is data to read. If you needed to get into the kernel buffer and check for how many bytes are in the buffer, you could use ioctl(fd, FIONREAD, &result), but as someone pointed out you really shouldn't need to. Either way, man ioctl_tty(2) has details about the ioctls available for ttys.

(2) You just need to check for '<' in your buffer as you iterate through the input. Something like this:

int in_tag = 0;
for (;;) {
   // read block
   // for each char 'c'
       if (in_tag) {
           if (c == '>') in_tag = 0;
       } else if (c == '<') in_tag = 1;

of course, figuring out how to handle things like "<>" is a whole other question....

Bob Shaffer
  • 635
  • 3
  • 13