linux C串口常规设置参考

本文详细介绍串口通信的基本配置方法及其实现过程,并提供了一个完整的串口配置模板。此外,还介绍了如何打开、读写串口,以及相关的串口控制函数。

据通信的基本方式可分为并行通信与串行通信两种。
· 并行通信是指利用多条数据传输线将一个资料的各位同时传送。它的特点是传输速度
快,适用于短距离通信,但要求传输速度较高的应用场合。
· 串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用
简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
串口设置详解
本节主要讲解设置串口的主要方法。
如前所述,设置串口中最基本的包括波特率设置,校验位和停止位设置。串口的设置主
要是设置struct termios结构体的各成员值,如下所示:
#include
struct termio
{      
      unsigned short c_iflag; /* 输入模式标志 */
      unsigned short c_oflag; /* 输出模式标志 */
      unsigned short c_cflag; /* 控制模式标志*/
      unsigned short c_lflag; /*本地模式标志 */
      unsigned char c_line; /* line discipline */
      unsigned char c_cc[NCC]; /* control characters */
};
在这个结构中最为重要的是c_cflag,通过对它的赋值,用户可以设置波特率、字符大小、
数据位、停止位、奇偶校验位和硬件流控等。另外c_iflag 和c_cc 也是比较常用的标志。在
此主要对这3 个成员进行详细说明。
                 c_cflag支持的常量名称
CBAUD        波特率的位掩码
B0           0波特率(放弃DTR)
B1800        1800波特率
B2400        2400波特率
B4800        4800波特率
B9600        9600波特率
B19200       19200波特率
B38400       38400波特率
B57600       57600波特率
B115200      115200波特率
EXTA         外部时钟率
EXTB         外部时钟率
CSIZE        数据位的位掩码
CS5          5个数据位
CS6          6个数据位
CS7          7个数据位
CS8          8个数据位
CSTOPB       2个停止位(不设则是1个停止位)
CREAD        接收使能
PARENB       校验位使能
PARODD       使用奇校验而不使用偶校验
HUPCL        最后关闭时挂线(放弃DTR)
CLOCAL       本地连接(不改变端口所有者)
LOBLK        块作业控制输出
CNET_CTSRTS  硬件流控制使能

      c_iflag支持的常量名称
INPCK        奇偶校验使能
IGNPAR       忽略奇偶校验错误
PARMRK       奇偶校验错误掩码
ISTRIP       除去奇偶校验位
IXON         启动出口硬件流控
IXOFF        启动入口软件流控
IXANY        允许字符重新启动流控
IGNBRK       忽略中断情况
BRKINT       当发生中断时发送SIGINT信号
INLCR        将NL映射到CR
IGNCR        忽略CR
ICRNL        将CR映射到NL
IUCLC        将高位情况映射到低位情况
IMAXBEL      当输入太长时回复ECHO
      c_cc 支持的常量名称
VINTR     中断控制,对应键为CTRL+C
VQUIT     退出操作,对应键为CRTL+Z
VERASE    删除操作,对应键为Backspace(BS)
VKILL     删除行,对应键为CTRL+U
VEOF      位于文件结尾,对应键为CTRL+D
VEOL      位于行尾,对应键为Carriage return(CR)
VEOL2     位于第二行尾,对应键为Line feed(LF)
VMIN      指定了最少读取的字符数
VTIME     指定了读取每个字符的等待时间

串口控制函数
Tcgetattr         取属性(termios结构)
Tcsetattr         设置属性(termios结构)
cfgetispeed     得到输入速度
Cfgetospeed           得到输出速度
Cfsetispeed            设置输入速度
Cfsetospeed           设置输出速度
Tcdrain           等待所有输出都被传输
tcflow           挂起传输或接收
tcflush           刷清未决输入和/或输出
Tcsendbreak           送BREAK字符
tcgetpgrp              得到前台进程组ID
tcsetpgrp               设置前台进程组ID

      [color=#ff0000]完整的串口配置模板,实用!把常用的选项在函数里面列出,可大大方便用户的调试使用[/color]

int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
    struct termios newtio,oldtio;
    /*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
    if ( tcgetattr( fd,&oldtio) != 0)
    {
        perror("SetupSerial 1");
         return -1;
     }
    bzero( &newtio, sizeof( newtio ) );
    /*步骤一,设置字符大小*/
    newtio.c_cflag |= CLOCAL | CREAD;
    newtio.c_cflag &= ~CSIZE;
    /*设置停止位*/
    switch( nBits )
    {
        case 7:
            newtio.c_cflag |= CS7;
            break;
        case 8:
            newtio.c_cflag |= CS8;
            break;
    }
/*设置奇偶校验位*/
    switch( nEvent )
    {
        case 'O': //奇数
            newtio.c_cflag |= PARENB;
            newtio.c_cflag |= PARODD;
            newtio.c_iflag |= (INPCK | ISTRIP);
        break;
        case 'E': //偶数
            newtio.c_iflag |= (INPCK | ISTRIP);
            newtio.c_cflag |= PARENB;
            newtio.c_cflag &= ~PARODD;
        break;
            case 'N': //无奇偶校验位
            newtio.c_cflag &= ~PARENB;
        break;
    }
    /*设置波特率*/
    switch( nSpeed )
    {
        case 2400:
            cfsetispeed(&newtio, B2400);
            cfsetospeed(&newtio, B2400);
        break;
        case 4800:
            cfsetispeed(&newtio, B4800);
            cfsetospeed(&newtio, B4800);
        break;
        case 9600:
            cfsetispeed(&newtio, B9600);
            cfsetospeed(&newtio, B9600);
        break;
        case 115200:
            cfsetispeed(&newtio, B115200);
            cfsetospeed(&newtio, B115200);
        break;
        case 460800:
            cfsetispeed(&newtio, B460800);
            cfsetospeed(&newtio, B460800);
        break;
        default:
            cfsetispeed(&newtio, B9600);
            cfsetospeed(&newtio, B9600);
        break;
    }
    /*设置停止位*/
    if( nStop == 1 )
        newtio.c_cflag &= ~CSTOPB;
    else if ( nStop == 2 )
        newtio.c_cflag |= CSTOPB;
    /*设置等待时间和最小接收字符*/ 
    newtio.c_cc[VTIME] = 0;
    newtio.c_cc[VMIN] = 0;
    /*处理未接收字符*/
    tcflush(fd,TCIFLUSH);
    /*激活新配置*/
    if((tcsetattr(fd,TCSANOW,&newtio))!=0)
    {
        perror("com set error");
        return -1;
    }
    printf("set done!\n");
    return 0;

            
            
            
            
           

[b]串口使用详解[/b]
在配置完串口的相关属性后,就可对串口进行打开,读写操作了。其使用方式与文件操作一样,区别在于串口是一个终端设备。

[b]打开串口[/b]
fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);

   Open函数中除普通参数外,另有两个参数O_NOCTTY和O_NDELAY。
   O_NOCTTY: 通知linix系统,这个程序不会成为这个端口的控制终端。
   O_NDELAY: 通知linux系统不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。
然后,恢复串口的状态为阻塞状态,用于等待串口数据的读入。用fcntl函数:
       fcntl(fd, F_SETFL, 0);

   接着,测试打开的文件描述府是否引用一个终端设备,以进一步确认串口是否正确打开。
       isatty(STDIN_FILENO);
串口的读写与普通文件一样,使用read,write函数。
       read(fd,buff,8);
       write(fd,buff,8);
实例


#i nclude stdio.h>
#i nclude string.h>
#i nclude sys/types.h>
#i nclude errno.h>
#i nclude sys/stat.h>
#i nclude fcntl.h>
#i nclude unistd.h>
#i nclude termios.h>
#i nclude stdlib.h>
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
    struct termios newtio,oldtio;
    if ( tcgetattr( fd,&oldtio) != 0) { 
        perror("SetupSerial 1");
        return -1;
    }
    bzero( &newtio, sizeof( newtio ) );
    newtio.c_cflag |= CLOCAL | CREAD; 
    newtio.c_cflag &= ~CSIZE; 
    switch( nBits )
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }
    switch( nEvent )
    {
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'E': 
        newtio.c_iflag |= (INPCK | ISTRIP);
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
    case 'N': 
        newtio.c_cflag &= ~PARENB;
        break;
    }
switch( nSpeed )
    {
    case 2400:
        cfsetispeed(&newtio, B2400);
        cfsetospeed(&newtio, B2400);
        break;
    case 4800:
        cfsetispeed(&newtio, B4800);
        cfsetospeed(&newtio, B4800);
        break;
    case 9600:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    case 115200:
        cfsetispeed(&newtio, B115200);
        cfsetospeed(&newtio, B115200);
        break;
    default:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    }
    if( nStop == 1 )
        newtio.c_cflag &= ~CSTOPB;
    else if ( nStop == 2 )
    newtio.c_cflag |= CSTOPB;
    newtio.c_cc[VTIME] = 0;
    newtio.c_cc[VMIN] = 0;
    tcflush(fd,TCIFLUSH);
    if((tcsetattr(fd,TCSANOW,&newtio))!=0)
    {
        perror("com set error");
        return -1;
    }
    printf("set done!\n");
    return 0;
}
int open_port(int fd,int comport)
{
    char *dev[]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2"};
    long vdisable;
    if (comport==1)
    {    fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
        if (-1 == fd){
            perror("Can't Open Serial Port");
            return(-1);
        }
        else 
            printf("open ttyS0 .....\n");
    }
    else if(comport==2)
    {    fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
        if (-1 == fd){
            perror("Can't Open Serial Port");
            return(-1);
        }
        else 
            printf("open ttyS1 .....\n");
    }
    else if (comport==3)
    {
        fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
        if (-1 == fd){
            perror("Can't Open Serial Port");
            return(-1);
        }
        else 
            printf("open ttyS2 .....\n");
    }
    if(fcntl(fd, F_SETFL, 0)0)
        printf("fcntl failed!\n");
    else
        printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
    if(isatty(STDIN_FILENO)==0)
        printf("standard input is not a terminal device\n");
    else
        printf("isatty success!\n");
    printf("fd-open=%d\n",fd);
    return fd;
}
int main(void)
{
    int fd;
    int nread,i;
    char buff[]="Hello\n";
    if((fd=open_port(fd,1))0){
        perror("open_port error");
        return;
    }
    if((i=set_opt(fd,115200,8,'N',1))0){
        perror("set_opt error");
        return;
    }
    printf("fd=%d\n",fd);
//    fd=3;
    nread=read(fd,buff,8);
    printf("nread=%d,%s\n",nread,buff);
    close(fd);
    return;
 
}

<think>好的,用户想了解在Linux下如何单独控制串口的TX脚。我需要先回忆一下Linux下的串口编程相关知识。通常,串口通信是通过读写设备文件,比如/dev/ttyS0或者/dev/ttyUSB0,然后使用termios库来配置参数,比如波特率、数据位等。不过,用户的问题是关于单独控制TX引脚,也就是发送线,可能需要直接操作这个引脚的电平状态,而不仅仅是发送数据。 首先,常规串口操作中,当发送数据时,TX脚会由UART硬件自动控制,发送高低电平。但用户可能想要手动控制TX脚的高低,比如用于某种信号控制,而不经过UART的数据传输协议。这时候可能需要绕过正常的UART驱动,直接操作GPIO。 不过,串口的TX引脚通常是由UART控制器管理的,如果该引脚是复用为GPIO,可能需要先将其配置为GPIO模式,这涉及到硬件和具体芯片的配置。例如,在树莓派或者其他嵌入式平台,可能需要通过修改设备树或者使用特定的工具来切换引脚功能。 另外,Linux的GPIO子系统提供了用户空间的接口,比如/sys/class/gpio,可以导出GPIO并控制方向(输入输出)和值(高低电平)。但前提是TX引脚对应的GPIO可以被导出和控制。例如,如果UART的TX脚对应的GPIO编号是某个数字,可能需要先释放UART功能,再将其作为GPIO使用。 还有一种方法可能是使用串口的调制解调器控制信号,比如RTS或DTR,这些引脚通常可以通过ioctl系统调用来控制。例如,使用TIOCMGET和TIOCMSET命令来设置或清除这些控制线。不过,TX本身可能不属于调制解调器控制线,所以这种方法可能不适用。 或者,是否有办法通过关闭UART的自动控制,手动操纵TX引脚?可能需要进入某种测试模式,或者通过特定的驱动设置。例如,某些UART驱动支持“loopback”模式,或者允许手动控制发送引脚。不过这需要具体硬件的支持,并且可能需要内核驱动的配合。 举个例子,在某些情况下,关闭UART的传输功能后,TX引脚可能会处于高电平或低电平。例如,在串口配置中设置波特率”为0,可能让TX保持某个状态。或者使用stty命令设置相关参数,但不确定是否能直接控制TX的电平。 另外,可能需要通过硬件流控制相关的设置,比如使用CRTSCTS,但可能同样不直接涉及TX脚的控制。 总结下来,可能的解决方案有: 1. 将TX引脚重新配置为GPIO模式,通过GPIO子系统控制。 2. 使用特定于硬件的工具或内核模块来操作UART的TX引脚。 3. 查找是否有其他方法通过串口驱动本身的接口来设置TX状态。 需要提醒用户,直接操作TX引脚可能影响正常的串口通信功能,需谨慎使用。同时,具体步骤可能因硬件平台和Linux发行版的不同而有所差异,需要查阅相关文档或硬件手册。 现在需要将这些思路整理成步骤,比如首先检查是否可以将TX作为GPIO,如何查找GPIO编号,如何导出和设置方向,然后控制电平。或者,如果无法重新配置引脚功能,是否有其他替代方法。 另外,用户可能需要代码示例,比如使用C语言或shell脚本来操作GPIO或者使用ioctl。不过,如果TX无法作为独立GPIO使用,可能需要其他方法。 可能还需要提到,某些SoC的UART控制器允许通过寄存器直接控制TX引脚,但这通常需要编写内核驱动,对用户来说可能比较复杂。 因此,在回答时应该分情况讨论:一种是引脚可配置为GPIO的情况,另一种是使用串口驱动本身的方法。同时,需要说明可能的限制和注意事项。在</think> Linux 系统下,单独控制串口的 TX(发送)引脚通常需要绕过常规串口数据传输协议,直接操作硬件引脚电平。以下是几种常见方法及实现步骤: --- ### 一、将 TX 引脚复用为 GPIO(硬件层操作) 如果硬件支持引脚复用(如嵌入式平台),可将 TX 引脚切换为 GPIO 模式,通过 GPIO 子系统控制其电平。 #### 步骤示例(以树莓派为例): 1. **确认引脚编号**: 查询硬件手册,确认 TX 引脚对应的 GPIO 编号(例如树莓派 UART0_TX 对应 GPIO 14)。 2. **禁用串口驱动**: 防止内核驱动占用引脚。在树莓派中,需通过 `raspi-config` 或修改 `/boot/config.txt` 禁用默认串口: ```bash sudo raspi-config # 选择 "Interfacing Options" → 禁用 Serial Port ``` 3. **导出并控制 GPIO**: 使用 `sysfs` 接口或 `libgpiod` 工具操作: ```bash # 导出 GPIO14 echo 14 | sudo tee /sys/class/gpio/export # 设置为输出模式 echo out | sudo tee /sys/class/gpio/gpio14/direction # 设置高电平 echo 1 | sudo tee /sys/class/gpio/gpio14/value # 设置低电平 echo 0 | sudo tee /sys/class/gpio/gpio14/value ``` --- ### 二、通过串口调制解调器控制信号(软件层) 若无法复用引脚,可尝试利用串口的调制解调器控制线(如 RTS/DTR),但这些信号通常独立于 TX 引脚。 #### 使用 `ioctl` 控制 RTS/DTR: ```c #include <fcntl.h> #include <unistd.h> #include <termios.h> int main() { int fd = open("/dev/ttyS0", O_RDWR); int status; // 获取当前状态 ioctl(fd, TIOCMGET, &status); // 设置 RTS 为高电平(需硬件支持) status |= TIOCM_RTS; ioctl(fd, TIOCMSET, &status); close(fd); return 0; } ``` --- ### 三、强制 TX 引脚电平(特殊场景) 某些 UART 驱动允许通过调试接口强制电平,但需谨慎操作。 #### 示例:通过 `stty` 强制 TX 状态 ```bash # 设置波特率为 0(可能让 TX 保持空闲状态,通常为高电平) stty -F /dev/ttyS0 0 ``` --- ### 四、内核模块或硬件寄存器操作 对于高级用户,可通过编写内核模块直接操控 UART 寄存器(需查阅芯片手册): ```c // 示例:通过内存映射操作 UART 寄存器 void set_tx_level(int level) { volatile uint32_t *reg = (uint32_t*)mmap(...); if (level) *reg |= TX_PIN_MASK; else *reg &= ~TX_PIN_MASK; } ``` --- ### 注意事项 1. **硬件差异**:不同设备的引脚复用和驱动支持差异较大,需查阅具体文档。 2. **冲突风险**:直接控制引脚可能导致串口通信异常,操作前建议断开其他设备。 3. **权限问题**:操作 `/dev` 设备或 GPIO 需 root 权限或用户组(如 `dialout`、`gpio`)。 --- ### 总结 - **推荐方法**:优先尝试复用为 GPIO(方法一),通用性较强。 - **快速验证**:可通过 `sysfs` 或 `gpiod` 工具手动测试电平。 - **高级需求**:涉及内核开发时,建议参考芯片手册和现有驱动代码。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值