Neil Forrester
Neil Forrester

Reputation: 5241

Why is my Raspberry Pi's serial port not writeable? write() writes zero bytes. select() times out

Backstory: I recently got a Raspberry Pi 4 Model B and a Protoneer RPI CNC Hat, which it controls via the serial port (/dev/ttyAMA0). After I put it all together and it didn't work at all (either in Minicom or bCNC), I've been gradually trying to zero in on the source of the problem. My oscilloscope shows that I can control the relevant pins when using them as GPIOs, so I do not suspect a fundamental hardware problem. However, I am completely unable to provoke a response from the pins when using them as a serial port. I have written the below C program to characterize the issue as exactly as possible.

Problem: I have a serial port, /dev/ttyAMA0, and it doesn't work.

$ sudo cat /proc/tty/driver/ttyAMA
serinfo:1.0 driver revision:
0: uart:PL011 rev2 mmio:0xFE201000 irq:34 tx:59596 rx:3105 RTS|CTS|DTR

$ ls -l /dev/ttyAMA0
crw-rw---- 1 root dialout 204, 64 Jul 11 08:24 /dev/ttyAMA0

$ groups
pi adm dialout cdrom sudo audio video plugdev games users input netdev gpio i2c spi

$ dmesg | grep tty
[    0.000258] console [tty0] enabled
[    0.420424] fe201000.serial: ttyAMA0 at MMIO 0xfe201000 (irq = 34, base_baud = 0) is a PL011 rev2
[    0.425685] fe215040.serial: ttyS0 at MMIO 0x0 (irq = 36, base_baud = 62500000) is a 16550
[    1.857049] systemd[1]: Created slice system-getty.slice.

$ echo hello | cat - > /dev/ttyAMA0
cat: write error: No space left on device

I can successfully open() it, but when I use select() to wait for it to become writeable, it times out. When I attempt to write to it anyway, write() successfully writes 0 bytes.

/* Shamelessly stolen from Stack Overflow, and then modified (in the finest tradition).
 * https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c */

#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;
}

int main()
{
    char *portname = "/dev/ttyAMA0";
    int fd;
    int wlen;

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd == -1) {
        perror("open: error");
        return -1;
    }
    /* baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);

    /* select() on the serial port file descriptor to wait for it to be writeable.
     * It never does become writeable. Removing this section does not change
     * the behavior of the following call to write() */
    fd_set wfds;
    struct timeval tv;
    int select_retval;
    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    select_retval = select(fd + 1, NULL, &wfds, NULL, &tv);
    if (select_retval == -1)
    {
        perror("select: error");
        return -1;
    }
    else if (select_retval)
    {
        printf("select: fd can be written now\n");
    }
    else
    {
        /* This is what happens */
        printf("select: fd did not become writeable within 5 seconds\n");
    }

    /* Write some output. This returns 0 bytes written and no errors. */
    wlen = write(fd, "Hello!\n", 7);
    if (wlen == -1)
    {
        perror("write: error");
        return -1;
    }
    printf("write: wrote %d bytes\n", wlen);

    tcdrain(fd);
}

Output:

$ gcc -Wall -Werror -std=gnu17 -o serial-test serial-test.c
$ ./serial-test 
select: fd did not become writeable within 5 seconds
write: wrote 0 bytes

I am not a serial port wizard. I'd be happy to perform any experiment you suggest. Please help me.

Upvotes: 2

Views: 3119

Answers (1)

bruno
bruno

Reputation: 32586

refering to How do I make serial work on the Raspberry Pi3 (or later model) :

The miniUART is now available on /dev/ttyS0

so you have to write on /dev/ttyS0 rather than /dev/ttyAMA0

Upvotes: 3

Related Questions