在 Ubuntu 下通过 C APP程序实现串口发送数据并接收返回数据

一、前言

使用 C 应用进行串口调用需要手动配置串口的各项参数,并且 Ubuntu 下的串口是通过读写文件实现的,所以还需要设置权限。

二、源码分析

serial.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>      // 文件控制定义(如 O_RDWR)
#include <unistd.h>     // Unix 标准函数(如 read/write)
#include <termios.h>    // 串口配置(波特率等)
#include <errno.h>      // 错误号定义

static int fd;
#ifndef CRTSCTS
#define CRTSCTS 020000000000  /* Flow control.  八进制值,等同于 0x80000000*/
#endif
/**
 * @Description: 打开串口并返回文件描述符
 * @param {char} *device: 串口设备
 * @return {*} 成功返回 0,失败返回 -1
 */
int open_serial_port(const char *device) {
    fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("无法打开串口设备");
        return -1;
    }

    // 恢复串口阻塞模式(等待数据)
    fcntl(fd, F_SETFL, 0);

    // 配置串口参数
    struct termios options;
    tcgetattr(fd, &options);

    // 设置波特率 115200 
    cfsetispeed(&options, B115200);
    cfsetospeed(&options, B115200);

    // 8N1(8数据位,无校验,1停止位)
    options.c_cflag &= ~PARENB;   // 无校验
    options.c_cflag &= ~CSTOPB;   // 1停止位
    options.c_cflag &= ~CSIZE;    // 清除数据位掩码
    options.c_cflag |= CS8;       // 8数据位

    // 启用接收,忽略 modem 控制线
    options.c_cflag |= (CLOCAL | CREAD);

    // 关闭流控
    options.c_cflag &= ~CRTSCTS;  // 无硬件流控
    options.c_iflag &= ~(IXON | IXOFF | IXANY);  // 无软件流控

    // 原始输入模式(不处理特殊字符)
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    // 原始数据(RAW)输出
    options.c_oflag &= ~OPOST;

    // 最小读取字符数和超时(单位:0.1秒)
    options.c_cc[VMIN] = 1;      // 至少读取1个字符
    options.c_cc[VTIME] = 10;    // 超时 1秒(10 * 0.1s)

    // 应用配置
    if (tcsetattr(fd, TCSANOW, &options) != 0) {
        perror("串口配置失败");
        close(fd);
        return -1;
    }
    return 0;
}

/**
 * @Description: 关闭串口
 * @return {*}
 */
void close_serial_port(void) {
    close(fd);
}

/**
 * @Description: 发送数据并接收返回
 * @param {unsigned char} *tx_data
 * @param {int} tx_len
 * @param {unsigned char} *rx_buf
 * @param {int} rx_buf_size
 * @return {*} 接收数据长度
 */
int send_and_receive(const unsigned char *tx_data, int tx_len, unsigned char *rx_buf, int rx_buf_size) {
    // 发送数据
    int written = write(fd, tx_data, tx_len);
    if (written != tx_len) {
        perror("发送数据失败");
        return -1;
    }
    printf("已发送 %d 字节: ", written);
    for (int i = 0; i < written; i++) {
        printf("%02X ", tx_data[i]);
    }
    printf("\n");

    // 接收数据
    int received = read(fd, rx_buf, rx_buf_size);
    if (received < 0) {
        perror("接收数据失败");
        return -1;
    }
    // .* 表示精度(precision)由后面的参数动态指定(这里用 length 变量控制)
    printf("收到 %d 字节: ", received);
    printf("%.*s", received, rx_buf);
    printf("\n");

    return received;
}

这里有个地方需要注意,宏定义 CRTSCTS 硬件流控默认是关闭的,如果想要使用需要先启用 __USE_MISC 宏定义:

cpp 复制代码
#ifdef __USE_MISC
# define CBAUD	 000000010017 /* Baud speed mask (not in POSIX).  */
# define CBAUDEX 000000010000 /* Extra baud speed mask, included in CBAUD.
				 (not in POSIX).  */
# define CIBAUD	 002003600000 /* Input baud rate (not used).  */
# define CMSPAR  010000000000 /* Mark or space (stick) parity.  */
# define CRTSCTS 020000000000 /* Flow control.  */
#endif

在代码中显式定义 _GNU_SOURCE,这些特性测试宏会自动启用 __USE_MISC:

cpp 复制代码
#define _GNU_SOURCE   // 或 #define _BSD_SOURCE

而我为了方便,直接在源文件进行了宏定义 CRTSCTS 硬件流控的定义:

cpp 复制代码
#ifndef CRTSCTS
#define CRTSCTS 020000000000  /* Flow control.  八进制值,等同于 0x80000000*/
#endif

serial.h

cpp 复制代码
#ifndef SERIAL_H
#define SERIAL_H

#include <stdint.h>

int open_serial_port(const char *device);
void close_serial_port(void);
int send_and_receive(const unsigned char *tx_data, int tx_len, unsigned char *rx_buf, int rx_buf_size);


#endif

三、设置权限

在 Linux 系统中尝试打开串口设备(如 /dev/ttyCH341USB0 或 /dev/ttyUSB0)时遇到权限不足(Permission denied) 错误,将当前用户加入 dialout 组:

bash 复制代码
sudo usermod -a -G dialout $USER

这样用户就可以读写串口设备文件了。

相关推荐
猫猫的小茶馆31 分钟前
【STM32】预分频因子(Prescaler)和重装载值(Reload Value)
c语言·stm32·单片机·嵌入式硬件·mcu·51单片机
JeffersonZU32 分钟前
Linux/Unix文件IO(文件描述符、原子操作、文件数据结构、open、read、write、fcntl、dup)
linux·c语言·unix·gnu
szekl1 小时前
HDMI 2.0 4×2矩阵切换器412HN——多信号输入输出的高清解决方案
linux·矩阵·计算机外设·电脑·ekl
weixin_399380691 小时前
k8s一键部署tongweb企业版7049m6(by why+lqw)
java·linux·运维·服务器·云原生·容器·kubernetes
阿巴~阿巴~1 小时前
Linux基本命令篇 —— uname命令
linux·运维·服务器
天空之城夢主2 小时前
KVM高级功能部署
linux·服务器
pipip.3 小时前
UDP————套接字socket
linux·网络·c++·网络协议·udp
智者知已应修善业4 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
朱包林6 小时前
day45-nginx复杂跳转与https
linux·运维·服务器·网络·云计算
孙克旭_6 小时前
day045-nginx跳转功能补充与https
linux·运维·nginx·https