如果未通过minicom打开一次,则无法从串行端口读取

时间:2018-09-02 15:06:32

标签: c linux serial-port blocking termios

我已经在C语言中实现了对串行端口的阻止读取。我的目标是进行一次读取,直到新数据到达为止。

这是我实现串行伪对象的方式(为了清楚起见,我删除了多线程保护)。

typedef struct
{
    int fd;
    se_serial_speed_t speed;
    se_serial_parity_t parity;
    bool flow_control;
}se_serial_t;

int se_serial_constructor(se_serial_t** self, char* serial_port)
{
    int fd;

    if(NULL != *self)
    {
        return ERR_NNULL_PTR;
    }
    else if(0 != access(serial_port, F_OK))
    {
        ERRNO("Serial port is not available");
        return ERR_ILLEGAL_PARAM;
    }
    else
    {
        if(-1 == (fd = open(serial_port, O_RDWR | O_NOCTTY)))
        {
            ERRNO("Error opening %s in rw mode", serial_port);
            return ERR_OFILE_FAIL;
        }
        else if(NULL == (*self = malloc(sizeof(se_serial_t))))
        {
            ERROR("Error allocating memory for Serial");
            return ERR_MALLOC_FAIL;
        }
        (*self)->fd = fd;
    }
    return ERR_OK;
}

int se_serial_configure_interface(se_serial_t* self, se_serial_speed_t speed, se_serial_parity_t parity, bool flow_control)
{
    struct termios options;

    if(NULL == self)
    {
        return ERR_NULL_PTR;
    }
    else
    {
        if(0 != tcgetattr(self->fd,  &options))
        {
            ERRNO("Unable to get serial port current configuration");
        }
        if(0 != cfsetospeed(&options, speed))
        {
            ERRNO("Unable to set serial port output speed");
        }
        if(0 != cfsetispeed(&options, speed))
        {
            ERRNO("Unable to set serial port input speed");
        }

        switch(parity)
        {
        case SE_SERIAL_PARITY_8N1:
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~CSTOPB;
            options.c_cflag &= ~CSIZE;
            options.c_cflag |= CS8;
            break;
        case SE_SERIAL_PARITY_7E1:
            options.c_cflag |= PARENB;
            options.c_cflag &= ~PARODD;
            options.c_cflag &= ~CSTOPB;
            options.c_cflag &= ~CSIZE;
            options.c_cflag |= CS7;
            break;
        case SE_SERIAL_PARITY_7O1:
            options.c_cflag |= PARENB;
            options.c_cflag |= PARODD;
            options.c_cflag &= ~CSTOPB;
            options.c_cflag &= ~CSIZE;
            options.c_cflag |= CS7;
            break;
        case SE_SERIAL_PARITY_7S1:
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~CSTOPB;
            options.c_cflag &= ~CSIZE;
            options.c_cflag |= CS8;
            break;
        default:
            WARNING("Unable to set serial port parity");
            break;
        }

        if(flow_control)
            options.c_cflag |= CRTSCTS;
        else
            options.c_cflag &= ~CRTSCTS;

        options.c_cc[VMIN] = 1;
        options.c_cc[VTIME] = 0;

        if(0 != tcsetattr(self->fd, TCSANOW, &options))
        {
            ERRNO("Error configuring serial port");
            return ERR_SERIAL_CONF_FAIL;
        }
        self->speed = speed;
        self->parity = parity;
        self->flow_control = flow_control;
    }
    return ERR_OK;
}

int se_serial_read(se_serial_t* self, uint8_t* buffer, int size)
{
    int bytes_read = 0;
    int ret;

    if(NULL == self)
    {
        return ERR_NULL_PTR;
    }
    else
    {
        while(bytes_read < size)
        {
            if(0 > (ret = read(self->fd, &(buffer[bytes_read]), size - bytes_read)))
            {
                ERROR("Error reading from %s : %d\n", self->serial_port, ret);
                return ERR_RFILE_FAIL;
            }
            bytes_read += ret;
        }
        size = bytes_read;
    }

    return size;
}

与我通信的设备在启动后每秒发送11字节的帧。

因此,初始化串行端口后,我将无限循环接收帧,然后打印它们。

se_serial_t* serial = NULL;
uint8_t buffer[1024] = {0};
int ret = 0;
int i;

if(0 > (ret = se_serial_constructor(&serial, "/dev/ttyUSB0")))
{
    ERROR("Error creating serial : %d", ret);
    return ERR_SERIAL_CREATION_FAIL;
}
else if(0 > (ret = se_serial_configure_interface(serial, SE_SERIAL_SPEED_B115200, SE_SERIAL_PARITY_8N1, false)))
{
    ERROR("Error configuring serial interface : %d", ret);
    return ERR_SERIAL_CONFIG_FAIL;
}

while(1)
{
    if(0 > (ret = se_serial_read(serial, buffer, 11)))
    {
        ERROR("Error reading from serial : %d", ret);
        return ret;
    }
    else
    {
        for(i=0;i<ret;i++)
        {
            printf("%02x ", buffer[i]);
        }
        printf("\n");
    }
}

我得到的结果奇怪的是,即使我知道设备正在发送帧,读取也会永远阻塞。

但是,如果我使用minicom等其他程序打开端口,则可以接收其中的帧。使用minicom打开端口并退出端口后,程序将正常运行,直到下次重新启动计算机为止。

如果我重新启动设备,则代码将阻塞,直到它开始发送帧并正确接收它们为止。

我还尝试过使用Raspberry Pi 3,以确保这不是笔记本电脑上的配置问题,但我得到的结果相同。

有人知道我为什么会出现这种行为吗?

1 个答案:

答案 0 :(得分:2)

  

我得到的结果奇怪的是,即使我知道设备正在发送帧,读取也会永远阻塞。
  ...
  使用minicom打开端口并退出端口后,程序将正常运行,直到下次重新启动计算机为止。

一点也不奇怪。
这清楚地表明您的程序对串行终端的初始化不完整,并且取决于预先存在的初始化是否合适。
(BTW “ strange” 是基于意见的描述,不传达任何有助于调试的技术信息。)

系统启动后,串行终端的默认模式通常是规范模式(用于传输文本)。
因此,串行终端的(无意的)规范 read()将阻塞,直到遇到行终止符(例如换行符或0x0A)为止。
如果源代码从不发送任何行终止符,您的程序将永远阻塞。
您可以使用stty -F /dev/ttyUSB0 -a命令来确认这一点,并发现 icanon 属性前面没有连字符。

Minicom 将串行终端配置为非规范模式,这是您的程序显然还希望串行终端运行的模式。
但是,您的程序仅配置termios参数用于波特率,成帧和流控制。
它缺少一些可靠的术语来保证串行终端的可靠运行。

如果您的程序需要非规范模式,则它必须显式配置该模式,而不是依赖于预先存在的配置。
由于还应为非规范模式设置或清除许多其他相关属性,因此宏 cfmakeraw()是对代码的最简单编辑。
插入

cfmakeraw(&options);

介于波特率和“奇偶校验”配置之间。
请注意,如果数据不是专用的ASCII文本,则使用7位数据帧可能会导致损坏,因此在程序中支持这三种模式是不协调的。

另一个明显的遗漏是启用接收器并设置本地模式:

options.c_cflag |= (CLOCAL | CREAD);

  

我正在与之通信的设备发送一个11字节的帧

顺便说一句,您在串行终端环境中使用“ frame” 是不合适的。在异步串行通信中,每个字符或字节都被成帧。您对“框架” 的引用将更恰当地称为消息,数据包或数据报。