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

Linux下C语言串口通信异常:需先运行Python脚本才可正常工作

串口通信异常问题:C程序需先运行Python脚本才能正常接收数据

问题场景

Linux系统启动后直接运行C语言串口读取程序,无法接收数据;但先运行一段仅打开并关闭串口的Python脚本后,再启动C程序就能正常接收数据。

相关代码

C语言程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <time.h>

#define BUFFER_SIZE 1024

volatile sig_atomic_t stop_flag = 0;

void handle_signal(int signum)
{
    printf("Caught signal %d\n", signum);
    if (signum == SIGINT || signum == SIGTERM)
    {
        printf("Stopping...\n");
        stop_flag = 1;
        printf("Stopped\n");
    }
    printf("Exiting signal handler\n");
}

void cleanup(int serial_fd, FILE *outputFile)
{
    printf("Cleaning up...\n");
    close(serial_fd);
    fclose(outputFile);
    printf("Cleaned up\n");
}

int main(int argc, char *argv[])
{
    printf("argc: %d\n", argc);
    for (int i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n", i, argv[i]);
    }
    if (argc != 7)
    {
        printf("Usage: %s <serial_port> <output_file> <baud_rate> <parity> <data_bits> <stop_bits>\n", argv[0]);
        return EXIT_FAILURE;
    }

    const char *serialPortName = argv[1];
    const char *outputFileName = argv[2];
    int baudRate = atoi(argv[3]);
    char parity = argv[4][0];
    int dataBits = atoi(argv[5]);
    int stopBits = atoi(argv[6]);

    // Open the serial port
    int serial_fd = open(serialPortName, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (serial_fd == -1)
    {
        perror("Error opening serial port");
        return EXIT_FAILURE;
    }

    // Set serial port parameters
    struct termios tty;
    memset(&tty, 0, sizeof(tty));
    if (tcgetattr(serial_fd, &tty) != 0)
    {
        perror("Error getting serial port attributes");
        cleanup(serial_fd, NULL);
        return EXIT_FAILURE;
    }

    cfsetospeed(&tty, baudRate);
    cfsetispeed(&tty, baudRate);

    tty.c_cflag |= (CLOCAL | CREAD);
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;

    // Set parity
    if (parity == 'N')
    {
        tty.c_cflag &= ~PARODD; // No parity
    }
    else if (parity == 'E')
    {
        tty.c_cflag |= PARODD; // Even parity
    }
    else if (parity == 'O')
    {
        tty.c_cflag |= (PARENB | PARODD); // Odd parity
    }

    // Set data bits
    tty.c_cflag &= ~CSIZE;
    if (dataBits == 8)
    {
        tty.c_cflag |= CS8;
    }
    else if (dataBits == 7)
    {
        tty.c_cflag |= CS7;
    }
    else
    {
        fprintf(stderr, "Invalid number of data bits\n");
        cleanup(serial_fd, NULL);
        return EXIT_FAILURE;
    }

    // Set stop bits
    if (stopBits == 1)
    {
        tty.c_cflag &= ~CSTOPB; // 1 stop bit
    }
    else if (stopBits == 2)
    {
        tty.c_cflag |= CSTOPB; // 2 stop bits
    }
    else
    {
        fprintf(stderr, "Invalid number of stop bits\n");
        cleanup(serial_fd, NULL);
        return EXIT_FAILURE;
    }

    if (tcsetattr(serial_fd, TCSANOW, &tty) != 0)
    {
        perror("Error setting serial port attributes");
        cleanup(serial_fd, NULL);
        return EXIT_FAILURE;
    }


    if (access(outputFileName, F_OK) != -1)
    {
        // check for the file already exists, if yes, rename to filename_old_current_time
        char oldFileName[256];
        strcpy(oldFileName, outputFileName);
        strcat(oldFileName, "_old_");
        time_t now = time(NULL);
        struct tm *t = localtime(&now);
        char timeStr[256];
        strftime(timeStr, sizeof(timeStr) - 1, "%Y%m%d_%H%M%S", t);
        strcat(oldFileName, timeStr);
        rename(outputFileName, oldFileName);
    }
    // Open the output file
    FILE *outputFile = fopen(outputFileName, "wb");
    if (!outputFile)
    {
        perror("Error opening output file");
        cleanup(serial_fd, NULL);
        return EXIT_FAILURE;
    }

    // Register signal handler for graceful termination
    signal(SIGINT, handle_signal);
    signal(SIGTERM, handle_signal);

    printf("Starting...\n");
    char buffer[BUFFER_SIZE];

    while (!stop_flag)
    {
        ssize_t bytesRead = read(serial_fd, buffer, sizeof(buffer));

        if (bytesRead > 0)
        {
            fwrite(buffer, 1, bytesRead, outputFile);
            fflush(outputFile);
        } else if (bytesRead < 0) {
            perror("Error reading from serial port");
            cleanup(serial_fd, outputFile);
            return EXIT_FAILURE;
        } else {
            // No data read            
        }
    }

    printf("Stopping...\n");

    // Cleanup and close resources before exiting
    cleanup(serial_fd, outputFile);

    // Reset signal handlers to default behavior
    signal(SIGINT, SIG_DFL);
    signal(SIGTERM, SIG_DFL);

    return EXIT_SUCCESS;
}

Python脚本

import serial
import time

port_name = "/dev/ttyXR0"
baud_rate = 2000000

serial_port = serial.Serial(port_name, baud_rate)

if serial_port:
    try:
        print("opened")
    except KeyboardInterrupt:
        pass
    finally:
        serial_port.close()

原因分析

Python的pySerial库在打开串口时,会完成很多C代码遗漏的初始化操作,这些操作修正了串口的硬件或驱动状态,让后续C程序能正常工作:

  • 握手信号与硬件激活:pySerial默认会配置RTS/CTS、DTR/DSR等控制信号(比如拉低DTR),部分串口设备(尤其是工业级或定制UART)需要这些信号触发接收就绪状态,而你的C代码完全未处理这类信号。
  • 完整的termios参数初始化:pySerial会将termios所有字段重置为合理默认值,比如:
    • 禁用规范模式、回显、信号捕获:c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG)
    • 禁用软件流控制与输入预处理:c_iflag &= ~(IXON | IXOFF | IXANY)、c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL)
    • 设置原始输出模式:c_oflag &= ~OPOST
      你的C代码仅初始化了c_cflag的部分参数,其他字段可能保留系统启动后的无效值,导致串口无法正确解析输入数据。
  • 缓冲区清空:pySerial打开串口时会自动清空输入输出缓冲区(tcflush),而C代码未做此操作,系统启动后残留的缓冲区数据或错误状态会干扰后续读取。
  • 波特率的正确映射:Python脚本中用的2Mbps波特率,pySerial会自动映射为Linuxtermios对应的B2000000宏;而C代码直接传入数值,若系统不支持这种写法,会导致波特率配置错误。

解决方案

修改C代码,补充以下缺失的初始化步骤:

  1. 配置原始模式,禁用不必要的处理:

    // 禁用规范模式、回显、信号捕获
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    // 禁用软件流控制与输入预处理
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
    // 设置原始输出模式
    tty.c_oflag &= ~OPOST;
    
  2. 控制信号配置(根据设备手册调整):
    如果设备需要DTR信号激活,添加以下代码:

    int status;
    ioctl(serial_fd, TIOCMGET, &status);
    status &= ~TIOCM_DTR; // 拉低DTR,若需要拉高则改为 |= TIOCM_DTR
    ioctl(serial_fd, TIOCMSET, &status);
    
  3. 清空串口缓冲区:

    tcflush(serial_fd, TCIOFLUSH); // 清空输入输出缓冲区
    
  4. 修正波特率映射:
    将输入的波特率数值映射为termios对应的宏:

    speed_t baud;
    switch(baudRate) {
        case 9600: baud = B9600; break;
        case 115200: baud = B115200; break;
        case 2000000: baud = B2000000; break;
        // 补充其他需要支持的波特率
        default: 
            fprintf(stderr, "Unsupported baud rate\n"); 
            cleanup(serial_fd, NULL); 
            return EXIT_FAILURE;
    }
    cfsetospeed(&tty, baud);
    cfsetispeed(&tty, baud);
    
  5. 验证配置生效:
    在tcsetattr后重新读取串口参数并打印,确认所有配置正确应用。

内容的提问来源于stack exchange,提问作者Hareendran MG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:24:53