zoukankan      html  css  js  c++  java
  • Linux下实现串口读写操作

      这里只贴串口读写操作部分代码,供大家参考学习用,该部分代码主要实现打开串口,配置串口参数波特率为115200、停止位1、数据位8、无校验位,发送2个数据,等待接收24个数据。代码是在QT窗体程序里实现,界面添加了了一个按钮,3个文本框,按下去发送2个数据,等待接收到下位机发送上来的24个数据后,把接收到的数据通过调试信息打印出来,然后再等待接收一次4个数据,再把接收到的数据通过调试信息打印出来,最后把发送数据长度、接收数据长度、串口句柄在文本框显示出来。

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QMessageBox>
    
    
    #include <QTime>
    #include <QDebug>
    
    #include  <fcntl.h>
    #include  "stdio.h"
    #include  "termios.h"
    #include  "unistd.h"
    #include  "limits.h"
    #include  <stdint.h>
    #include  "time.h"
     //===================
    #include <sys/select.h>
    #include <sys/time.h>
     //===================
    #define   UART_DEV   "/dev/ttyS0"
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_pushButton_2_clicked()
    {
        int fd =0;
        int RxLen=0;
        int flag =0;
    
        uint8_t RxBuff[100];
        uint8_t SenBuff[2]={0xaa,0xbb};
    
        //==========串口打开============//
        fd = open(UART_DEV ,O_RDWR|O_NOCTTY);
        if(fd<0)
            QMessageBox::information(NULL, "COM","COM Open Fail !");
    
        //==========配置串口============//
        struct  termios opt;          //配置串口的属性定义在结构体struct termios中
    
        tcgetattr(fd, & opt);         //获取终端控制属性
    
        cfsetispeed(& opt, B115200);  //指定输入波特率
        cfsetospeed(& opt, B115200);  //指定输出波特率
    
        /* c_lflag 本地模式 */
        opt.c_cflag &= ~ INPCK;           //不启用输入奇偶检测
        opt.c_cflag |= (CLOCAL |  CREAD); //CLOCAL忽略 modem 控制线,CREAD打开接受者
    
        /* c_lflag 本地模式 */
        opt.c_lflag &= ~(ICANON | ECHO | ECHOE |  ISIG); //ICANON启用标准模式;ECHO回显输入字符;ECHOE如果同时设置了 ICANON,字符 ERASE 擦除前一个输入字符,WERASE 擦除前一个词;ISIG当接受到字符 INTR, QUIT, SUSP, 或 DSUSP 时,产生相应的信号
    
        /* c_oflag 输出模式 */
        opt.c_oflag &= ~ OPOST;             //OPOST启用具体实现自行定义的输出处理
        opt.c_oflag &= ~(ONLCR | OCRNL);    //ONLCR将输出中的新行符映射为回车-换行,OCRNL将输出中的回车映射为新行符
    
        /* c_iflag 输入模式 */
        opt.c_iflag &= ~(ICRNL |  INLCR);          //ICRNL将输入中的回车翻译为新行 (除非设置了 IGNCR),INLCR将输入中的 NL 翻译为 CR
        opt.c_iflag &= ~(IXON | IXOFF | IXANY);    //IXON启用输出的 XON/XOFF流控制,IXOFF启用输入的 XON/XOFF流控制,IXANY(不属于 POSIX.1;XSI) 允许任何字符来重新开始输出
    
        /* c_cflag 控制模式 */
        opt.c_cflag &= ~ CSIZE;     //字符长度掩码,取值为 CS5, CS6, CS7, 或 CS8,加~就是无
        opt.c_cflag |=  CS8;        //数据宽度是8bit
        opt.c_cflag &= ~ CSTOPB;    //CSTOPB设置两个停止位,而不是一个,加~就是设置一个停止位
        opt.c_cflag &= ~ PARENB;    //PARENB允许输出产生奇偶信息以及输入的奇偶校验,加~就是无校验
    
        /* c_cc[NCCS] 控制字符 */
        opt.c_cc[VTIME] = 0 ;   //等待数据时间(10秒的倍数),每个单位是0.1秒  20就是2秒
        opt.c_cc[VMIN] = 255 ;    //最少可读数据,非规范模式读取时的最小字符数,设为0则为非阻塞,如果设为其它值则阻塞,直到读到到对应的数据,就像一个阀值一样,比如设为8,如果只接收到3个数据,那么它是不会返回的,只有凑齐8个数据后一齐才READ返回,阻塞在那儿
        /* new_cfg.c_cc[VMIN]   =   8;//DATA_LEN;
           new_cfg.c_cc[VTIME]  =   20;//每个单位是0.1秒  20就是2秒
           如果这样设置,就完全阻塞了,只有串口收到至少8个数据才会对READ立即返回,或才少于8个数据时,超时2秒也会有返回
           另外特别注意的是当设置VTIME后,如果read第三个参数小于VMIN ,将会将VMIN 修改为read的第三个参数*/
    
        /*TCIFLUSH  刷清输入队列
          TCOFLUSH  刷清输出队列
          TCIOFLUSH 刷清输入、输出队列*/
        tcflush(fd, TCIOFLUSH);         //刷串口清缓存
        tcsetattr(fd, TCSANOW, &opt);   //设置终端控制属性,TCSANOW:不等数据传输完毕就立即改变属性
    
        //==========串口发送============//
        int TxLen = write(fd,SenBuff,2);  //如果你要连续发几包数据到下位机,需要对不同包之间的数据发生加延时,不然会出现粘包的情况;或者你还可以在下位机进行拆包处理 
    
        while(1)
        {
         /*
          添加sleep延时,让下位机串口数据全到缓存区,否则会出现只读到一部分数据情况,具体原因未知,可能虚拟机导致,不过该方法会导致接收变慢;
          或者可以
    直接修改opt.c_cc[VMIN]的值,改为阻塞接收,这里就是用来这个方法;      
    或者直接修改为一次接收一个数据,根据累计接收到的数据判断你是否接收完成*/ 
    //sleep(1); //==========串口接收============//
             while( ((RxLen = read(fd, RxBuff, 24)) > 0) )
             {            
                 for(int i=0;i<RxLen;i++)
                    qDebug("Rbuff[%d] = %x",i,RxBuff[i]);
                 flag=1;
                 break;
             }
             if(flag==1)
                break;
        }
    
        while(1)
        {
            //==========串口接收============//
             while( ((RxLen = read(fd, RxBuff, 4)) > 0)   )
             {
                 for(int i=0;i<RxLen;i++)
                    qDebug("4 Rbuff[%d] = %x",i,RxBuff[i]);
                 flag=1;
                 break;
             }
             if(flag==1)
                break;
        }
    
        flag=0;
    
        ui->textA->setText(QString::number(fd));
        ui->textB->setText(QString::number(RxLen));
        ui->textC->setText(QString::number(TxLen));
    
        ::close(fd);
    
    }
    运行结果如下:

     注意:使用串口需要在sudo环境下运行程序,否则要把用户组添加到串口的组

    ===========================================================================================

    为了便于使用,下面的代码是我把上面的串口操作进行了整理封装的,供大家参考学习使用,实测可用,转载请标明出处

      头文件

    #ifndef MYUART_H
    #define MYUART_H
    
    #include  <fcntl.h>
    #include  "stdio.h"
    #include  "termios.h"
    #include  "unistd.h"
    #include  "limits.h"
    #include  <stdint.h>
    #include  "time.h"
    #include  <string.h>
    //#define   UART_DEV   "/dev/ttyS0"  /* 要操作的串口号 */
    
    int OpenUart(char* UART_DEV);
    int UartSend(int fd, uint8_t *SenBuff, long len);
    int UartRead(int fd, uint8_t *RxBuff, long RxLen);
    void UartClose(int fd);
    
    
    #endif // MYUART_H

      串口操作文件

     /*
     *File:实现串口的基本操作
    */
    #include "MyUart.h"
    
    /*
     * 函数名  : SetOpt
     * 函数功能: 设置串口的相关基本参数,这里固定了波特率为115200,数据位8,校验位无,停止位1
     * 传入参数: fd 设备描述符
     * 返回值  : 无
    */
    void SetOpt(int fd)
    {
        static  struct termios termold, termnew;
        tcgetattr(fd, &termold) ;
        bzero(&termnew, sizeof(termnew));
    
        termnew.c_iflag &= ~(ICRNL | IGNCR) ;
        termnew.c_cflag |= CLOCAL | CREAD;   //CLOCAL:忽略modem控制线  CREAD:打开接受者
        termnew.c_cflag &= ~CSIZE;
        termnew.c_cflag |= CS8;
        termnew.c_cflag &= ~PARENB;
    
        cfsetispeed(&termnew, B115200);
        cfsetospeed(&termnew, B115200);
    
        termnew.c_cflag &=  ~CSTOPB;
        termnew.c_cc[VTIME]  = 1;    //VTIME:非cannoical模式读时的延时,以十分之一秒位单位
        termnew.c_cc[VMIN] = 0;       //VMIN:非canonical模式读到最小字符数
        tcflush(fd, TCIFLUSH);
        tcsetattr(fd, TCSANOW,&termnew);
    }
    
    
    /*
     * 函数名  : OpenUart
     * 函数功能: 串口打开,如果打开成功,会返回一个设备描述符,失败返回-1
     * 传入参数: UART_DEV 要操作的串口号
     * 返回值  : fd 设备描述符
    */
    int OpenUart(char* UART_DEV)
    {
        int fd=0;
    
        /*第1个参数:想要打开的文件路径名,或者文件名
         *第2个参数:open_Status:文件打开方式,可采用下面的文件打开模式:
              O_RDONLY:以只读方式打开文件
              O_WRONLY:以只写方式打开文件
              O_RDWR:以读写方式打开文件
              O_APPEND:写入数据时添加到文件末尾
              O_CREATE:如果文件不存在则产生该文件,使用该标志需要设置访问权限位mode_t
              O_EXCL:指定该标志,并且指定了O_CREATE标志,如果打开的文件存在则会产生一个错误
              O_TRUNC:如果文件存在并且成功以写或者只写方式打开,则清除文件所有内容,使得文件长度变为0
              O_NOCTTY:如果打开的是一个终端设备,这个程序不会成为对应这个端口的控制终端,如果没有该标志,任何一个输入,例如键盘中止信号等,都将影响进程。
              O_NONBLOCK:该标志与早期使用的O_NDELAY标志作用差不多。程序不关心DCD信号线的状态,如果指定该标志,进程将一直在休眠状态,直到DCD信号线为0。
              O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非搁置模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会搁置程序动作,直到有数据或写入完成;
              它们的差别在于设立O_NDELAY会使I/O函式马上回传0,但是又衍生出一个问题,因为读取到档案结尾时所回传的也是0,这样无法得知是哪中情况;因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。
           第3个参数:设置文件访问权限的初始值
        */
        fd = open(UART_DEV , O_RDWR|O_NOCTTY);
        if (fd < 0)
        {
           return -1;
        }
        SetOpt(fd);
        return  fd;
    }
    
    
    /*
     * 函数名  : UartSend
     * 函数功能: 串口发送数据
     * 传入参数: fd 设备描述符, *SenBuff 传输数据首地址, len 要发送字符大小
     * 返回值  : 发送字符大小
    */
    int UartSend(int fd, uint8_t *SenBuff, long len)
    {
        int TxLen;
        TxLen = write(fd,SenBuff,len);
        return TxLen;
    }
    
    
    /*
     * 函数名  : UartRead
     * 函数功能: 串口接收数据
     * 传入参数: fd 设备描述符, *RxBuff 接收传输数据Buff首地址, Rxlen 要接收字符大小
     * 返回值  : 接收字符大小
    */
    int UartRead(int fd, uint8_t *RxBuff, long RxLen)
    {
        int GetRxLen=0;
        while(RxLen){
            GetRxLen += read(fd, RxBuff+GetRxLen, 1);
            RxLen--;
        }
        //GetRxLen = read(fd, RxBuff, RxLen); //把参数fd所指的文件传送sizeof (RxBuff)个字节到RxBuff指针所指的内存中
        return GetRxLen ;
    }
    
    
    /*
     * 函数名  : UartClose
     * 函数功能: 关闭串口
     * 传入参数: fd 设备描述符
     * 返回值  : 无
    */
    void UartClose(int fd)
    {
        close(fd);
    }
    
    /*END*/

    注意:使用串口读写时,要注意下位机的时序,不然可能会出现下位机还没发送数据上来,你就进行读取,导致读取失败的情况,时序调整可以用延时调整

  • 相关阅读:
    20100822 动漫店 员工卡缓存出现问题。
    性能报告——使用AOP与DYNAMICProxy的orm性能测试
    性能报告——反射创建对象
    抓虾 老板体会。
    Pixysoft.framework.configuration 性能提升报告
    今天你抛弃了ASP.NET了吗?问题篇
    20100922 项目重构计划
    经过一年时间的沉淀 再次回首 TCP Socket服务器编程
    对Google Me的社会化网络 “The Real Life Social Network” 翻译
    20100718 项目重构计划
  • 原文地址:https://www.cnblogs.com/xingboy/p/14416196.html
Copyright © 2011-2022 走看看