1

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.

Neil Forrester
  • 5,101
  • 29
  • 32
  • 1
    Have you tried `write()` without `select()`? Try to make a [mre] to find the problem. – EOF Jul 11 '20 at 16:48
  • Yes, that's what I did first. The `select()` does not affect the result of the `write()`. – Neil Forrester Jul 11 '20 at 16:50
  • if you need to be *root* (sudo) to read `/proc/tty/driver/ttyAMA` why are you writing in it by hand (`echo hello | cat - > /dev/ttyAMA0` whose can be `echo hello > /dev/ttyAMA0`) or your program without being *root* again ? – bruno Jul 11 '20 at 17:00
  • Running my program as root does not change the result (just tried it). I can read and write `/dev/ttyAMA0` as non-root because I am in the relevant group (`dialout`). It's just the `/proc/tty/driver` directory (containing diagnostics) that is only readable by root. – Neil Forrester Jul 11 '20 at 17:04
  • @NeilForrester ok, sorry then – bruno Jul 11 '20 at 17:09
  • 1
    It's all right, I appreciate any help I can get. :) – Neil Forrester Jul 11 '20 at 17:10
  • @bruno: Possibly interesting fact: if I take your suggestion to run `echo hello > /dev/ttyAMA0`, my shell hangs. Control-C will not get me a prompt back. I have to "kill -KILL ` it from another terminal. – Neil Forrester Jul 11 '20 at 17:15
  • 2
    Ah, so may be it is blocked writting? Anyway may be look at https://raspberrypi.stackexchange.com/a/69721 and the link https://raspberrypi.stackexchange.com/a/45571 supposing it is the same on a PI3 and a PI4 – bruno Jul 11 '20 at 17:15
  • 1
    Do you get _anything_ [as seen on scope] when in uart mode? What baud rate? I note 62500000 from `dmesg` [seems a bit high?]. What about cable wiring for DTR/RTS/CTS/etc? Are you sure _both_ sides clear `CRTSCTS`? What program is running on each side? – Craig Estey Jul 11 '20 at 17:18
  • @bruno is the winner! Writing to `/dev/ttyS0` makes my scope wiggle! Post that as an answer and you get my upvote! – Neil Forrester Jul 11 '20 at 17:21
  • Your link to the raspberrypi stack exchange solved it. – Neil Forrester Jul 11 '20 at 17:24
  • regarding: `printf("select: fd did not become writeable within 5 seconds\n");` this is a significant error (and should have been output to `stderr`, not `stdout`) After this error, the code continues as if the `select()` had been successful. That is a logic error. It should be exiting the program. – user3629249 Jul 12 '20 at 22:25
  • regarding: `int wlen;` the function: `write()` returns a `ssize_t`, not an `int` – user3629249 Jul 12 '20 at 22:28

1 Answers1

3

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

bruno
  • 32,421
  • 7
  • 25
  • 37