Home Reading file descriptor blocks in Linux
Reply: 1

Reading file descriptor blocks in Linux

Gordon
1#
Gordon Published in 2018-01-12 19:16:19Z

This is a continuation of a previous question: Problems reading and writing to the same file descriptor in Linux but many changes have been made.

The project is Debian Linux running on a single board computer. The main program is a webserver (using the libwebsockets library) with motor control and GPS plugins. My question involves reading bytes from a file descriptor in the motor control code which is in C.

The fd is read/write and the read and write functions are each running on their own thread. A packet consists of 19 bytes and the write function is working.

Init port call:

    mc_fd = InitPort("/dev/ttyS1", "COM2", O_RDWR, B115200);

Code for opening port/fd:

int InitPort( char *port, char *name, int oflags, speed_t baudRate ) {

    int fd, rg, rs;                                 // File descriptor
    fd = open(port, oflags);                // Open the port like a file
    assert(fd > 0);                         // Open returns -1 on error

    struct termios options;                 // Initialize a termios struct
    rg = tcgetattr(fd, &options);               // Populate with current attributes
    if( rg < 0 ) {
        printf("Failed to get attr: %d, %s\n", fd, strerror(errno));
    }
    cfsetospeed (&options, baudRate);       // Set baud rate out
    cfsetispeed (&options, baudRate);       // Set baud rate in (same as baud rate out)

    options.c_cflag &= ~CSIZE;              // Clear bit-length flag so it can be set
        //8N1 Serial Mode
        options.c_cflag &= ~CSTOPB;         // Set stop bit:        1
        options.c_cflag &= ~CRTSCTS;        // Set flow control:    none

    options.c_cc[VMIN]  = 1;
    options.c_cc[VTIME] = 2;
    cfmakeraw(&options);
    options.c_cflag |= (CLOCAL | CREAD);    // Enable receiver, and set local mode
    rs = tcsetattr(fd, TCSANOW, &options);      // Set new attributes to hardware
    if( rs < 0 ) {
        printf("Failed to set attr: %d, %s\n", fd, strerror(errno));
    }
    return fd;
}

The read function identifies the packet by header characters (0xA552) and then stuffs bytes into an array to receive the 16-byte body of the packet.

Receive function:

void *ReceiveMotorPacket( void *arg )
{
    // Receive 16 byte packet for status, control, and motor position:
    //  [0] command1
    //  [2] status1
    //  [4] motor1Pos
    //  [8] command2
    // [10] status2
    // [12] motor2Pos

    int checksum, idx;
    receiveState = MOTOR_HEAD_1;

    while( 1 ) {

        // receive byte from motor
        read( mc_fd, incomingByte, 1 );

        inChar = *incomingByte;

        // FSM to receive packet
        switch( receiveState ) {

            case MOTOR_HEAD_1:
                if( inChar == 0xA5 ) {
                    receiveState = MOTOR_HEAD_2;
                    checksum = 0;
                }
                break;

            case MOTOR_HEAD_2:
                if( inChar == 0x52 ) {
                    receiveState = MOTOR_DATA;
                    char idx = 0;
                    checksum = 0xA5 + 0x52;
                } else {        printf
                    receiveState = MOTOR_HEAD_1;
                }
                break;

            case MOTOR_DATA:
                readPacket[idx++] = inChar;
                checksum += inChar;
                if( idx >= 17 ) {
                    receiveState = MOTOR_CHECKSUM;
                }
                break;

            case MOTOR_CHECKSUM:
                if( checksum == inChar ) {

                    printf("readPacket = %016X\n", readPacket);

                    // Check status bits and set global variables
                    mc.az_status    = *( signed short * )&readPacket[2];
                    mc.el_status    = *( signed short * )&readPacket[10];

                    // If motor is disabled, set global variable for feedback to GUI
                    if( !az.enable ) {
                        mc.az_position  = *( signed int   * )&readPacket[4];
                    }

                    if( !el.enable ) {
                        mc.el_position  = *( signed int   * )&readPacket[12];
                    }

                    receiveState = MOTOR_HEAD_1;

                } else {
                    printf("checksum error!\n");
                    receiveState = MOTOR_HEAD_1;
    }
                break;

            default:

                break;
            }
        }
    }

I am unable to receive any characters and the read() just blocks the operation of the receive thread. The rest of the program executes normally.

Thank you for your input.

sawdust
2#
sawdust Reply to 2018-01-16 06:04:06Z

Init port call:

Typically serial terminals that are not consoles should be opened with the O_NOCTTY option.

Receive function:

This code is difficult to evalute because of missing declarations for numerous variables including incomingByte, inChar, and mc_fd.

Variable mc_fd seems to be used uninitialized.

The return code of the read() is not evaluated.
(BTW reading only one byte per syscall is inefficient. See parsing complete messages from serial port)

I am unable to receive any characters and the read() just blocks the operation of the receive thread.

If you think you're having issues receiving data from the serial terminal, then you need to create a Minimal, Complete, and Verifiable example instead of the state machine that you have posted.

But you may be mischaracterizing the problem as "unable to receive" because your state machine has a bug (and discards any/all data that it reads).

while( 1 ) {
    ...
    receiveState = MOTOR_HEAD_1;
    ... 
    switch( receiveState ) {
       ...
    }
}

The initial state value MOTOR_HEAD_1 is unconditionally assigned to the state variable receiveState within the while loop, so the switch statement will always and only execute the first case.
This initialization has to be performed outside the loop (or when the hunt for a packet starts, e.g. after a complete packet has been received).


Addendum

You've silently corrected the state variable bug, but failed to improve the code or add any new details.
In other words you seem to persist in guessing that the read operation is blocking.
Yes, the read() of a serial terminal is expected to block when there is no more data to be returned to the caller.
There will be no more data when all of the data that has been received up to that point has been provided to the caller.

Your code has additional bugs that could read all of the received data, and yet still expect to read more data which will never arrive.
Your description of your problem as a read blocking issue is an example of not following this advice: Describe the problem's symptoms, not your guesses.

The additional bugs in your code include:

  1. The index variable is not initialized.

            case MOTOR_HEAD_2:
                if( inChar == 0x52 ) {
                    ...
                    char idx = 0;
                    ...
                } else {        printf
                    ...
    

The index variable idx that is used to store data into the readPacket[] array is never initialized.
Your attempt to perform that initialization is a bug since it occurs within an if statement on a local variable that only has scope within the block. This initialization has no effect whatsoever on the autovar that is used to index readPacket[].

Note that you have a stray printf in the else line, which is an indication of a sloppy post.

  1. There's an "off by one" bug.

            case MOTOR_DATA:
                ...
                if( idx >= 17 ) {
                    ...
                }
    

Presumably idx is supposed to be initialized with zero, and the first payload byte is indexed as byte 0.
So the 16th and last payload byte would be indexed as byte 15.
Therefore the comparison should be whether idx is greater than or equal to 16 (rather than the 17 used).
Consequently your program waits for a total of 20 bytes that will never arrive, since only 19 bytes comprise a packet.

  1. This will not display the contents of the buffer.

                    printf("readPacket = %016X\n", readPacket);
    

If the program blocks at the read() syscall, then that does not necessarily indicate an issue with termios or the serial port.
Usually it simply means that there is no data available.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.360287 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO