linux串口通信封装(C++)

文章介绍了如何在Linux环境下使用C++对串口通信进行封装,包括串口权限的更改、Serial类的设计以及串口的打开、关闭、发送和接收功能。通过枚举类型限制了波特率、数据位、校验位和停止位的输入,确保参数的合理性。代码示例展示了如何打开串口、发送和接收数据,并提供了简单的测试程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux串口通信封装(C++)

linux 下使用 C++ 封装串口操作。

1.linux串口权限更改

  • 查看串口使用情况,linux下一切皆文件,串口在 /dev/tty*,使用ls查看:

    ls -l /dev/tty*
    
  • 对于非 root 用户,Linux串口默认不开放读写权限,可以使用文件授权方式,进行临时授权

    sudo chmod 666 串口号 #例如:sudo chmod 666 ttyS0 
    
  • 加入用户组的方式永久获得权限,用ls -l 方式可以查询到串口所在用户组,把需要授权的用户加入到用户组即可完成授权:
    在这里插入图片描述

    sudo usermod -aG 组 用户 #例如: sudo usermod -aG dialout ch 
    

2.封装思路

  • 设计一个 Serial 类,将串口的相关操作(打开、关闭、发送、接收)放到该类中去实现

  • 在 Serial 类中,对于打开串口的接口(OpenSerial),用户可以自定义串口号、波特率、数据位、校验位、停止位;

    int OpenSerial(std::string SerialID, E_BaudRate Bps, E_DataSize DataSize, E_Parity Parity, E_StopBit StopBit);
    
  • 将波特率、数据位、校验位、停止位分别放到一个枚举中,防止用户放飞自我的输入参数,如下所示,我这里只设置了常见的情况,有其他需求可以自行添加:

    typedef enum
    {
        _2400,
        _4800,
        _9600,
        _19200,
        _38400,
        _57600,
        _115200,
        _460800,
    }E_BaudRate;  //波特率
    
    typedef enum
    {
        _5,
        _6,
        _7,
        _8,
    }E_DataSize;  //数据位
    
    typedef enum
    {
        None,
        Odd,
        Even,
    }E_Parity;  //校验位
    
    typedef enum
    {
        _1,
        _2,
    }E_StopBit;  //停止位
    
  • 对于数据的接收和发送,这里的接口设置为,用户传入一个 unsigned char* 类型的数据和数据的长度,之所以这样,主要是为了适配二进制的传输:

    int Send(unsigned char *Buff, int length);
    int Recv(unsigned char *Buff, int length);
    
  • 对于接收数据,为了能让接收和发送分离,打开串口时采用非阻塞方式,另外设置一个接收数据缓冲buff,用户可以更具数据传输的大小,设置缓冲区大小,再在Serial类中开启一个专门进行接收串口数据操作的线程,改线程一直去接收数据,并将接收到的数据放入缓冲区,提供一个接口让用户去缓冲区拿接收到的数据就可以了,达到接收和发送彻底分离的效果:

    const long long RecvBufferLen = 1024;   //设置接收数据缓冲区大小
    

3.上代码

  • Serial.hpp

    #pragma once
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/signal.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <errno.h>
    #include <limits.h>
    #include <string.h>
    #include <string>
    
    
    const long long RecvBufferLen = 1024;   //设置接收数据缓冲区大小
    
    
    typedef enum
    {
        _2400,
        _4800,
        _9600,
        _19200,
        _38400,
        _57600,
        _115200,
        _460800,
    }E_BaudRate;  //波特率
    
    typedef enum
    {
        _5,
        _6,
        _7,
        _8,
    }E_DataSize;  //数据位
    
    typedef enum
    {
        None,
        Odd,
        Even,
    }E_Parity;  //校验位
    
    typedef enum
    {
        _1,
        _2,
    }E_StopBit;  //停止位
    
    
    class Serial
    {
    public:
        Serial();
        ~Serial();
    
        int OpenSerial(std::string SerialID, E_BaudRate Bps, E_DataSize DataSize, E_Parity Parity, E_StopBit StopBit);
    
        int Send(unsigned char *Buff, int length);
        int Recv(unsigned char *Buff, int length);
    
        int Close();  
    
    private:
        void RunConnect();
        void RunRecv();
        int RefreshBuffer(unsigned char *pBuf, int Len, bool RecvTypet);
    
    private:
        int nSerialID;  //串口
    
        bool b_OpenSign;   //串口打开标志
    
        struct termios ProtoOpt;   //存放串口原始配置
    };
    
    
    
  • Serial.cpp

    #include "Serial.hpp"
    #include <thread>
    #include <iostream>
    
    Serial::Serial()
    {
        b_OpenSign = false;
        nSerialID = 0;
    }
    
    Serial::~Serial()
    {
        Close();
    }
    
    
    int Serial::OpenSerial(std::string SerialID, E_BaudRate Bps, E_DataSize DataSize, E_Parity Parity, E_StopBit StopBit)
    {
    	Close();
        nSerialID = open( SerialID.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
        if (-1 == nSerialID)
        {
            /* 不能打开串口一*/
            std::string str = SerialID + " open fail !!!";
            perror(str.c_str());
            return -1;
        } 
        struct termios Opt;
        tcgetattr(nSerialID, &ProtoOpt);  //获取设备当前的设置
        
        Opt = ProtoOpt;
    
        /*设置输入输出波特率*/
        switch(Bps)
        {
            case E_BaudRate::_2400:
                cfsetispeed(&Opt,B2400);
                cfsetospeed(&Opt,B2400);
                break;
            case E_BaudRate::_4800:
                cfsetispeed(&Opt,B4800);
                cfsetospeed(&Opt,B4800);
                break;	
            case E_BaudRate::_9600:
                cfsetispeed(&Opt,B9600);
                cfsetospeed(&Opt,B9600);
                break;	
            case E_BaudRate::_19200:
                cfsetispeed(&Opt,B19200);
                cfsetospeed(&Opt,B19200);
                break;
            case E_BaudRate::_38400:
                cfsetispeed(&Opt,B38400);
                cfsetospeed(&Opt,B38400);
                break;	
            case E_BaudRate::_57600:
                cfsetispeed(&Opt,B57600);
                cfsetospeed(&Opt,B57600);
                break;														
            case E_BaudRate::_115200:
                cfsetispeed(&Opt,B115200);
                cfsetospeed(&Opt,B115200);
                break;	
            case E_BaudRate::_460800:
                cfsetispeed(&Opt,B460800);
                cfsetospeed(&Opt,B460800);
                break;						
            default		:
                printf("Don't exist baudrate %d !\n",Bps);
                return (-1);																	
        }
    
        /*设置数据位*/
        Opt.c_cflag &= (~CSIZE);
        switch( DataSize )
        {
    		case E_DataSize::_5:
                Opt.c_cflag |= CS5;
                break;
            case E_DataSize::_6:
                Opt.c_cflag |= CS6;
            case E_DataSize::_7:
                Opt.c_cflag |= CS7;
                break;
            case E_DataSize::_8:
                Opt.c_cflag |= CS8;
                break;
            default:
                /*perror("Don't exist iDataSize !");*/
                printf("Don't exist DataSize %d !\n",DataSize);
                return (-1);								
        }
    
    	/*设置校验位*/
    	switch( Parity )
    	{
    	case E_Parity::None:					/*无校验*/
    		Opt.c_cflag &= (~PARENB);
    		break;
    	case E_Parity::Odd:					/*奇校验*/
    		Opt.c_cflag |= PARENB;
    		Opt.c_cflag |= PARODD;
    		Opt.c_iflag |= (INPCK | ISTRIP);
    		break;
    	case E_Parity::Even:					/*偶校验*/
    		Opt.c_cflag |= PARENB;
    		Opt.c_cflag &= (~PARODD);
    		Opt.c_iflag |= (INPCK | ISTRIP);
    		break;				
    	default:
    		/*perror("Don't exist cParity  !");*/
    		printf("Don't exist Parity %c !\n",Parity);
    		return (-1);								
    	}
    
    	/*设置停止位*/
    	
    	switch( StopBit )
    	{
    	case E_StopBit::_1:
    		Opt.c_cflag &= (~CSTOPB);
    		break;
    	case E_StopBit::_2:
    		Opt.c_cflag |= CSTOPB;
    		break;
    	default:
    		printf("Don't exist iStopBit %d !\n",StopBit);
    		return (-1);								
    	}
    
        //如果只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:
        Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);   
        Opt.c_oflag &= ~OPOST;
    
    	tcflush(nSerialID,TCIOFLUSH);		/*刷新输入队列(TCIOFLUSH为刷新输入输出队列)*/
    
        Opt.c_cc[VTIME] = 0;	/*设置等待时间*/
    	Opt.c_cc[VMIN] = 0;	/*设置最小字符*/
    
        int Result = tcsetattr(nSerialID,TCSANOW,&Opt);  //使这些设置生效
    
    	if( Result )
    	{
    		perror("Set new terminal description error !");
    		return (-1);
    	}	
    
        b_OpenSign = true;
    
        RunRecv();
    
        return 0;
    }
    
    int Serial::Send(unsigned char *Buff, int length)
    {
    	int iLen = 0;
    	if(length <= 0)
    	{
    		printf("Send byte number error !\n");
    		return -1;
    	}
    	
    	iLen = write(nSerialID,Buff,length);
    	
    	return iLen;
    }
    
    int Serial::Recv(unsigned char *Buff, int length)
    {
        int res =  RefreshBuffer(Buff, length, true);
    
        return res;
    }
    
    int Serial::Close()
    {
        if(nSerialID > 0)
        {
            tcsetattr (nSerialID, TCSADRAIN, &ProtoOpt);  //恢复原始串口配置
        }
        close(nSerialID);
        b_OpenSign = false;
    }
    
    
    void Serial::RunRecv()
    {
        std::thread ThRecv	= std::thread
        {
            [&]()
    		{
                unsigned char RecvBuf[4096] = {0};
                while (b_OpenSign)
                {
                    usleep(10*1000);
                    if((nSerialID < 0))
    		        {
    			        continue;
    		        }
    
                    memset(RecvBuf, 0, 4096);
    
                    int res = read(nSerialID, RecvBuf, sizeof(RecvBuf));
                    //std::cout << "res = " << res << std::endl;
                    if(res > 0)
                    {
                        RefreshBuffer(RecvBuf, res, false);
                    }
                }
            }
        };
    
        ThRecv.detach();
    }
    
    
    int Serial::RefreshBuffer(unsigned char *pBuf, int Len, bool RecvTypet)
    {
        static unsigned char  Buffer[RecvBufferLen + 1] = {0};
        static int 					nSum=0;				    //	缓冲区中数据总长度
    	signed int 					nStop=0;
    
        int ren = 0;
    
        if(false == RecvTypet)
        {
            //************************ 将接收到的数据加入缓冲区中 ************************/
            //std::cout<<"recv = "<< Len <<std::endl;
            
            if((Len + nSum) <= RecvBufferLen)		//	总长度小于1K
            {
                memcpy(&Buffer[nSum], pBuf, Len);
                nSum = Len + nSum;
            }
            else
            {
                if(Len <= RecvBufferLen)			//	拷贝满1K空间,丢弃掉aucT[0]开始的字符,并进行填充,!!!!!!!!!!!
                {
                    memcpy(Buffer, pBuf, Len);
                    nSum = Len;
                }
                else						//	本次接收到的数据长度大于1K
                {
                    memcpy(Buffer, pBuf + (Len - RecvBufferLen), RecvBufferLen);
                    nSum = RecvBufferLen;
                }
            }
            //std::cout<<"----> nSum = "<< nSum <<std::endl;
            ren = 0;
        }
        else
        {
            if(Len <= 0)
            {
                return -1;
            }
            if(nSum <= 0)
            {
                return 0;
            }
            
            if(Len <= nSum)
            {
                memcpy(pBuf, Buffer, Len);
    
                nStop =  Len;
                ren = Len;
            }
            else
            {
                memcpy(pBuf, Buffer, nSum);
    
                nStop = nSum;
                ren = nSum;
            }
    
    
            //************ 移动取出数据 ***************/
            if(nStop==0)
            {
                return 0;
            }
            else if(nSum > nStop)	  // 把没有解析到的数据移动到最开始位置
            {
                for(int i=0; i<(nSum-nStop); i++)
                {
                    Buffer[i] = Buffer[nStop + i];
                }
                nSum = nSum - nStop;
            }
            else if(nSum == nStop)
            {
                nSum = 0;
            }
    
        }
    
        return ren;
    }
    
    

4.简单测试

  • main.cpp

    #include <iostream>
    #include <cstdio>
    
    #include "Serial.hpp"
    
    
    int main()
    {
        Serial s1;
        
    
        std::string str = "/dev/ttyS0";   //串口号
        
        s1.OpenSerial(str, E_BaudRate::_115200, E_DataSize::_8, E_Parity::None, E_StopBit::_1);
    
    
        while (true)
        {
            unsigned char buff[] = "123456789\r\n" ;
            s1.Send(buff, sizeof(buff));
            
            unsigned char bf[100] = {0};
            int len = s1.Recv(bf, sizeof(bf));
            //std::cout << "len = " << len << std::endl;
            if(len > 0)
            {
                for(int i=0; i<len; i++)
                {
                    printf("%.2X ", bf[i]);
                }
                std::cout << std::endl;
            }
            usleep(100*1000);
        }
        
    
        std::cout << "hello world" << std::endl;
        return 0;
    }
    
    
    
  • 编译,Makefile:

    CCC=g++
    
    OUTPUT=run
    
    LIBS := -lm -lpthread       #第三方库
    LibOS:= -L ./lib         #动态链接库OS
    OBJS = main.o Serial.o
    
    $(OUTPUT): $(OBJS)
    	$(CCC) $(OBJS) -o $(OUTPUT) $(LIBS) $(LibOS)
    
    
    %.o:%.cpp
    	$(CCC) -c -o $@ $^  -std=c++11
    
    clean:
    	rm  $(OBJS)
    	rm  $(OUTPUT)
    
  • 该封装可扩展性比较高,有其他需求可自行扩展;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值