You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用termios与sigaction时UART触发异常问题求助

问题分析与解决方案

核心问题

  1. 重复打开/关闭串口设备:每次SIGIO触发时都重新open/close /dev/ttymxc4,会破坏串口的异步通知(O_ASYNC)属性和内核接收缓冲区状态,导致SIGIO触发逻辑紊乱。
  2. 串口配置缺失关键属性:复制的ttymxc0配置中CREAD为0,这会导致串口不启用接收功能,属于异常状态。
  3. 文件状态标志覆盖:fcntl(fd, F_SETFL)被调用两次,第二次设置O_ASYNC时会覆盖之前的FNDELAY,导致非阻塞模式失效。
  4. 信号处理未配置重启标志:nanosleep是可中断系统调用,SIGIO触发后会直接返回错误,主循环会快速执行,加剧SIGIO触发的异常。

分步修复

1. 全局管理串口文件描述符

将串口fd改为全局变量,初始化时打开一次,避免重复open/close:

#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define RxBufsize 256
int uart_fd = -1; // 全局串口fd
uint8_t read_buf[RxBufsize] = {0};
ssize_t SizeOfRxBuffer = 0;
bool newPactAvaible = false;

2. 修正UART初始化函数

  • 合并文件状态标志设置,保留FNDELAY和O_ASYNC
  • 确保串口启用接收(设置CREAD)
  • 复用其他串口配置后修正关键属性:
void initUART(){
    uart_fd = open("/dev/ttymxc4", O_RDWR | O_NOCTTY | O_NDELAY);
    if (uart_fd == -1) {
        perror("Failed to open UART");
        return;
    }

    // 配置SIGIO信号处理
    struct sigaction saio;
    saio.sa_handler = signal_SIGIO_of_TTY;
    saio.sa_flags = SA_RESTART; // 让可中断系统调用自动重启
    sigemptyset(&saio.sa_mask);
    saio.sa_restorer = NULL;
    sigaction(SIGIO, &saio, NULL);

    // 设置异步通知属性
    fcntl(uart_fd, F_SETOWN, getpid());
    fcntl(uart_fd, F_SETFL, FNDELAY | O_ASYNC); // 合并设置非阻塞和异步

    // 配置串口参数(复用ttymxc0的配置,修正CREAD)
    struct termios termAttr;
    int ttymxc0 = open("/dev/ttymxc0", O_RDWR | O_NOCTTY | O_NDELAY);
    if (ttymxc0 != -1) {
        tcgetattr(ttymxc0, &termAttr);
        close(ttymxc0);
        // 强制启用接收功能
        termAttr.c_cflag |= CREAD;
        // 配置VMIN和VTIME:固定数据包长度44字节,超时1秒(单位0.1秒)
        termAttr.c_cc[VMIN] = 44;
        termAttr.c_cc[VTIME] = 10;
        tcsetattr(uart_fd, TCSANOW, &termAttr);
    } else {
        perror("Failed to open ttymxc0 for config");
        // 手动配置串口参数备选逻辑
        termAttr.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
        termAttr.c_iflag = IGNPAR;
        termAttr.c_oflag = 0;
        termAttr.c_lflag = 0;
        termAttr.c_cc[VMIN] = 44;
        termAttr.c_cc[VTIME] = 10;
        tcflush(uart_fd, TCIFLUSH);
        tcsetattr(uart_fd, TCSANOW, &termAttr);
    }
}

3. 修正UART读取函数

直接使用全局fd读取,只处理实际接收到的数据:

void UART_read_tty(){
    if (uart_fd == -1) return;
    // 清空缓冲区
    memset(read_buf, 0, sizeof(read_buf));
    SizeOfRxBuffer = read(uart_fd, read_buf, RxBufsize);
    if (SizeOfRxBuffer > 0) {
        newPactAvaible = true;
        printf("SIG received\n");
        for(ssize_t i=0; i < SizeOfRxBuffer; i++){
            printf("%x ", read_buf[i]);
        }
        printf("\n");
    } else {
        // 读取失败或无数据,重置标志
        newPactAvaible = false;
    }
}

4. 修正信号处理函数

void signal_SIGIO_of_TTY(int status){
    (void) status;
    UART_read_tty();
}

5. 主循环优化

处理nanosleep被信号打断的情况,确保休眠逻辑稳定:

#include <errno.h>

int main(){
    initUART();
    struct timespec ts = {0, 10 * 1000000}; // 10ms休眠
    while(1){
        if(newPactAvaible){
            getReadBuf_UART();
            evaluateCMD_UART();
            newPactAvaible = false; // 处理完后重置标志
        }
        do_read_system_V_Queue();

        // 处理nanosleep被信号打断的情况,自动重启休眠
        while(nanosleep(&ts, &ts) == -1 && errno == EINTR);
    }
    // 退出时关闭串口
    if (uart_fd != -1) close(uart_fd);
    return 0;
}

关键说明

  • VMIN和VTIME配置:如果数据包长度固定为44字节,设置VMIN=44、VTIME=10(超时1秒),内核会在接收到完整数据包后触发SIGIO,或超时后返回已接收数据,避免提前触发。
  • SA_RESTART标志:让nanosleep等可中断系统调用在被SIGIO打断后自动重启,保证主循环休眠逻辑稳定,不会因信号打断导致CPU占用异常。
  • 避免重复open/close:串口的异步通知属性和接收缓冲区状态绑定在文件描述符上,每次close后状态丢失,重新open需重新配置,这是SIGIO异常的核心原因。

内容的提问来源于stack exchange,提问作者Julian Müller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 23:15:44