linux串口编程入门

一、串口的物理协议

串口的物理层协议规定了串口的电气特性,有RS232,RS485,RS422协议。
RS-232与RS-485的区别在于:
1、传输方式不同。 RS-232采取不平衡传输方式,即所谓单端通讯。而RS485则采用平衡传输,即差分传输方式。
2、传输距离不同。RS-232适合本地设备之间的通信,传输距离一般不超过20m。而RS-485的传输距离为几十米到上千米。
3、RS-232 只允许一对一通信,而RS-485 接口在总线上是允许连接多达128个收发器。
4、电平特性不同:RS232逻辑“1”为-3—-15V;逻辑“0”:+3—+15V。RS485逻辑“1”以两线间的电压差+2V ~ +6V表示,逻辑“0”以两线间的电压差-6V ~ 2V表示。

二、串口的应用层协议

1.可以自定义内部的软件通信协议
2.可以用Modbus协议

三、linux串口编程

在linux中一切皆文件。在串口编程中不管是RS232,RS485标准,软件代码一般都没有任何区别,也就是说,编程是可以不管物理层协议的,只要按照既定的应用层协议来收发数据即可。
串口编程就是对串口进行初始化,并进行读写操作。而串口的初始化包括设定波特率,停止位,奇偶校验位,数据位等参数。

1.首先需要打开串口,才能对串口进行操作

	fd = open(Name, O_RDWR);
    if(fd == -1)
    {
        printf("serialport open() error\n");
        return -1;
    }
    else
    {
        //测试是否为终端设备      
        if(0 == isatty(STDIN_FILENO))  
        {  
            printf("this is not a terminal device\n");  
            return -1;
        }
        else
        {
            printf ("open ");
            printf ("%s  ", ttyname(fd)); //获取终端设备路径
            printf ("succesfully \n");
        }
    }

2.对串口进行参数设置

//*******************************************************************************
//* 函数名: Set_Speed()
//* 功能:   设置串口的波特率
//* 参数:  fd :串口的文件描述符
//*		    speed:波特率
//*	返回值:	 0:成功 -1:失败
//*******************************************************************************
int g_speed_arr[]={B1200,B2400,B4800,B9600,B19200,B38400,B57600,B115200,B230400,B230400,B460800,B921600};
int g_name_arr[] ={1200,2400,4800,9600,19200,38400,57600,115200,230400,230400,460800,921600};

int Set_Speed(int Fd, int Speed)
{
    int i;
    int ret;
    struct termios opt;

    ret = tcgetattr(Fd, &opt);
    if(-1 == ret)
    {
        printf("tcgetattr() error\n");
        return -1;
    }

    for (i = 0; i < sizeof(g_speed_arr)/sizeof(int); i++)
    {
        if (Speed == g_name_arr[i])
        {
            tcflush(Fd, TCIOFLUSH);

            if(-1 == cfsetispeed(&opt, g_speed_arr[i]))
            {
                printf("cfsetispeed error\n");
                return -1;
            }
            if(-1 == cfsetospeed(&opt, g_speed_arr[i]))
            {
                printf("cfsetospeed error\n");
                return -1;
            }

            ret = tcsetattr (Fd, TCSANOW, &opt);
            if (ret != 0)
            {
                printf("tcsetattr() error\n");
                return -1;
            }

            tcflush(Fd, TCIOFLUSH);
        }
    }

    return 0;
}

a)波特率数组里面的成员如:B115200,是在 头文件#include<termios.h>的宏定义
b)通过tcgetattr函数获取原有串口的参数设置,保存到termios 结构体中,后续需要操作termios 结构体的成员来对参数进行设置,termios 这个结构体是设置串口参数的关键。
c)通过cfsetispeed和cfsetospeed来设置输入输出波特率
d)设置为波特率后需要通过tcsetattr函数来对参数写入。

//*******************************************************************************
//* 函数名: Set_Parameter()
//* 功能:   设置串口的其他参数,包括数据位、停止位和奇偶校验位
//* 参数:  fd :串口的文件描述符
//*		   Data_Bits:数据位
//*		   Stop_Bits:数据位
//*		   Parity:数据位
//*	返回值:	 0:成功 -1:失败
//*******************************************************************************
int Set_Parameter(int Fd, int Data_Bits, int Stop_Bits, int Parity)
{
    struct termios opt;

    if(-1 == tcgetattr (Fd, &opt)) //获取串口参数
    {
        printf("tcgetattr() error\n");
        return -1;
    }

    //1.根据屏蔽字标志清空数据位,并设置
    opt.c_cflag &= ~CSIZE;
    switch(Data_Bits)
    {
        case 7:
            opt.c_cflag |= CS7;
        break;

        case 8:
            opt.c_cflag |= CS8;
        break;

        default:
            printf("Unsupported data size\n");
            return -1;
    }
    
    //2.设置奇偶校验位
    switch(Parity)
    {
        case 'n':
        case 'N':
            opt.c_cflag &= ~PARENB;         //不校验
            opt.c_iflag &= ~INPCK;	
            break;

        case 'o':
        case 'O':
            opt.c_cflag |= (PARODD | PARENB); //奇校验
            opt.c_iflag |= INPCK;	
            break;

        case 'e':
        case 'E':
            opt.c_cflag |= PARENB;	        //偶校验
            opt.c_cflag &= ~PARODD;
            opt.c_iflag |= INPCK;	
            break;

        case 's':  
		case 'S': //设置为空校验 
            opt.c_cflag &= ~PARENB;  
            opt.c_cflag &= ~CSTOPB;  
            break;   

        default:
            printf("Unsupported parity\n");
            return -1;
    }

    switch(Stop_Bits)
    {
        case 1:
            opt.c_cflag &= ~CSTOPB;
            break;
        case 2:
            opt.c_cflag |= CSTOPB;
            break;
        default:
            printf("Unsupported stop bits\n");
            return -1;
    }

    tcflush(Fd, TCIFLUSH); //清空缓冲区,包括输入缓冲区(驱动程序已经接收到的数据)和输出缓冲区(已经准备好的数据,但还没发送出去)

    //设置超时时间和最小返回字节
    opt.c_cc[VTIME] = 150;  //这个是超时时间,时间为分秒数(分秒为秒的1/10)
    opt.c_cc[VMIN] = 0;	

    if(-1 == tcsetattr(Fd, TCSANOW, &opt))  //修改好了结构体里面的数据后,开始设置串口
    {
        printf("tcsetattr error\n");
        return -1;
    }
    tcflush(Fd, TCIFLUSH);

    //设置完成后,再次读取,与设置好的是否一致

    return 0;
}

a)、根据APUE(UNIN 环境高级编程)所言,即便tcsetattr该函数没有出错,返回成功,我们也有责任检查该函数是否执行了所有要求的动作。这就意味着,在调用tcsetattr设置所希望的属性后,需要再次调用tcgetattr,然后将实际串口属性与所希望的属性相比较,已检测两者是否有区别。上述代码还没有完成这一步。

3、串口读写函数

//*******************************************************************************
//* 函数名: Write_Serial_Data()
//* 功能:   读取串口数据
//* 参数:  Fd:串口的文件描述符
//*        Data:发送的数据帧
//*        Data_Len:数据长度
//*	返回值:	0:成功 -1:失败
//*******************************************************************************
int Write_Serial_Data(int Fd, char *Data,int Data_Len)
{
    int len;
    len = write(Fd,Data,Data_Len);  
    if(len == Data_Len)  
    {  
        printf("send data is %s\n",Data);
        return len;  
    }       
    else     
    {               
        tcflush(Fd,TCOFLUSH);  
        return -1;  
    }  
    return -1;  
}
//*******************************************************************************
//* 函数名: Read_Serial_Data()
//* 功能:   读取串口数据
//* 参数:  无
//*	返回值:	0:成功 -1:失败
//*******************************************************************************
void Read_Serial_Data(const int Fd)
{
    char buf[255]={0};
    int nread = 0;
    int fd = Fd;
    fd_set rset;
    
    //采用多路复用IO模型
    while(1)
    {
        FD_ZERO(&rset);
        FD_SET(fd, &rset);

        select(fd+1, &rset, NULL,NULL,NULL);//将会阻塞等待,直到有数据来

        if(FD_ISSET(fd, &rset)) //如果是Fd的数据来了
        {
           
            while((nread = read(fd, buf, sizeof(buf))) > 0)
            {
                printf("nread = %d,%s\n",nread, buf);
                memset(buf, 0 , sizeof(buf));

                //对数据进行解析

            }  
        }
    }
}

a)、串口的读写操作也就是对read/write函数的调用
b)、IO操作模型,主要有四类。阻塞IO、非阻塞IO,多路复用IO、异步IO。
阻塞IO:即一个死循环,包含着一个read函数,read函数会一直阻塞等待,直到到数据的来临(浪费资源,等待时机太久,CPU资源浪费)
非阻塞IO:人为得将IO口设置为非阻塞模式,即read函数有没有数据都立即返回,一直循环读取(浪费资源,CPU做了很多无用功,在没有数据来临事也进行读取操作,不断重复,知道数据来临)
多路复用IO:通过select函数,来等到一个或者多个IO口的数据,如果有其中一个IO口准备好了数据,那么select函数返回。本质上讲,多路复用IO也是一个阻塞IO模型,一样需要等待而浪费资源,但是它有一个明显的优点,就是可以在单线程中处理多个IO的输入输出,而不用多线程。因为select函数可以同时监听多个IO。
异步IO:类似于如果当前没有数据读取,则执行其他有用的任务,一旦有数据到来,才会通知去处理。

一个完整的demo代码如下

//*******************************************************************************
//* 函数名: Set_Speed()
//* 功能:   设置串口的波特率
//* 参数:  fd :串口的文件描述符
//*		    speed:波特率
//*	返回值:	 0:成功 -1:失败
//*******************************************************************************
int Set_Speed(int Fd, int Speed)
{
    int i;
    int ret;
    struct termios opt;

    ret = tcgetattr(Fd, &opt);
    if(-1 == ret)
    {
        printf("tcgetattr() error\n");
        return -1;
    }

    for (i = 0; i < sizeof(g_speed_arr)/sizeof(int); i++)
    {
        if (Speed == g_name_arr[i])
        {
            tcflush(Fd, TCIOFLUSH);

            if(-1 == cfsetispeed(&opt, g_speed_arr[i]))
            {
                printf("cfsetispeed error\n");
                return -1;
            }
            if(-1 == cfsetospeed(&opt, g_speed_arr[i]))
            {
                printf("cfsetospeed error\n");
                return -1;
            }

            ret = tcsetattr (Fd, TCSANOW, &opt);
            if (ret != 0)
            {
                printf("tcsetattr() error\n");
                return -1;
            }

            tcflush(Fd, TCIOFLUSH);
        }
    }

    return 0;
}


//*******************************************************************************
//* 函数名: Set_Parameter()
//* 功能:   设置串口的其他参数,包括数据位、停止位和奇偶校验位
//* 参数:  fd :串口的文件描述符
//*		   Data_Bits:数据位
//*		   Stop_Bits:数据位
//*		   Parity:数据位
//*	返回值:	 0:成功 -1:失败
//*******************************************************************************
int Set_Parameter(int Fd, int Data_Bits, int Stop_Bits, int Parity)
{
    struct termios opt;

    if(-1 == tcgetattr (Fd, &opt))
    {
        printf("tcgetattr() error\n");
        return -1;
    }

    //1.根据屏蔽字标志清空数据位,并设置
    opt.c_cflag &= ~CSIZE;
    switch(Data_Bits)
    {
        case 7:
            opt.c_cflag |= CS7;
        break;

        case 8:
            opt.c_cflag |= CS8;
        break;

        default:
            printf("Unsupported data size\n");
            return -1;
    }
    
    //2.设置奇偶校验位
    switch(Parity)
    {
        case 'n':
        case 'N':
            opt.c_cflag &= ~PARENB;         //不校验
            opt.c_iflag &= ~INPCK;	
            break;

        case 'o':
        case 'O':
            opt.c_cflag |= (PARODD | PARENB); //奇校验
            opt.c_iflag |= INPCK;	
            break;

        case 'e':
        case 'E':
            opt.c_cflag |= PARENB;	        //偶校验
            opt.c_cflag &= ~PARODD;
            opt.c_iflag |= INPCK;	
            break;

        case 's':  
		case 'S': //设置为空校验 
            opt.c_cflag &= ~PARENB;  
            opt.c_cflag &= ~CSTOPB;  
            break;   

        default:
            printf("Unsupported parity\n");
            return -1;
    }

    switch(Stop_Bits)
    {
        case 1:
            opt.c_cflag &= ~CSTOPB;
            break;
        case 2:
            opt.c_cflag |= CSTOPB;
            break;
        default:
            printf("Unsupported stop bits\n");
            return -1;
    }

    tcflush(Fd, TCIFLUSH);

    //设置超时时间和最小返回字节
    opt.c_cc[VTIME] = 150;
    opt.c_cc[VMIN] = 0;

    if(-1 == tcsetattr(Fd, TCSANOW, &opt))
    {
        printf("tcsetattr error\n");
        return -1;
    }
    tcflush(Fd, TCIFLUSH);

    //设置完成后,再次读取,与设置好的是否一致

    return 0;
}
 

//*******************************************************************************
//* 函数名: Serial_Init()
//* 功能:   初始化串口
//* 参数:  无
//*	返回值:	-1:失败  否则:串口的文件描述符
//*******************************************************************************
int Serial_Init(char *Name, int Speed, int Data_Bits, int Stop_Bits, char Parity)
{
    int fd;
    int ret;
    pthread_t id;
    fd = open(Name, O_RDWR);
    if(fd == -1)
    {
        printf("serialport open() error\n");
        return -1;
    }
    else
    {
        //测试是否为终端设备      
        if(0 == isatty(STDIN_FILENO))  
        {  
            printf("this is not a terminal device\n");  
            return -1;
        }
        else
        {
            printf ("open ");
            printf ("%s  ", ttyname(fd)); //获取终端设备路径
            printf ("succesfully \n");
        }
    }

    if(-1 == Set_Speed(fd, Speed))
    {
        printf("set_speed Error\n");
        return -1;
    }

    if(-1 == Set_Parameter(fd, Data_Bits, Stop_Bits, Parity))
    {
        printf("Set Parity Error\n");
        return -1;
    }

    //创建获取数据线程
	ret= pthread_create(&id,NULL,(void*)Read_GM_Data,NULL ); 
	if(ret)
	{
		printf("create Read_Data pthread error\n");
		return -1;
	}

    return fd;
}

//*******************************************************************************
//* 函数名: Read_Serial_Data()
//* 功能:   读取串口数据
//* 参数:  无
//*	返回值:	0:成功 -1:失败
//*******************************************************************************
void Read_Serial_Data(const int Fd)
{
    char buf[255]={0};
    int nread = 0;
    int fd = Fd;
    fd_set rset;
    
    while(1)
    {
        FD_ZERO(&rset);
        FD_SET(fd, &rset);

        select(fd+1, &rset, NULL,NULL,NULL);//将会阻塞等待,直到有数据来

        if(FD_ISSET(fd, &rset)) //如果是Fd的数据来了
        {
           
            while((nread = read(fd, buf, sizeof(buf))) > 0)
            {
                printf("nread = %d,%s\n",nread, buf);
                memset(buf, 0 , sizeof(buf));

                //解析数据

            }  
        }
    }
}


//*******************************************************************************
//* 函数名: Write_Serial_Data()
//* 功能:   读取串口数据
//* 参数:  Fd:串口的文件描述符
//*        Data:发送的数据帧
//*        Data_Len:数据长度
//*	返回值:	0:成功 -1:失败
//*******************************************************************************
int Write_Serial_Data(int Fd, char *Data,int Data_Len)
{
    int len;
    len = write(Fd,Data,Data_Len);  
    if(len == Data_Len)  
    {  
        printf("send data is %s\n",Data);
        return len;  
    }       
    else     
    {               
        tcflush(Fd,TCOFLUSH);  
        return -1;  
    }  
    return -1;  
}

参考:
APUE 第 14 章 高级IO
APUE 第 18 章 高级IO

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值