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模式读时的延时,以十分之一秒位单位,設置超時時間 ms 單位,設置阻塞后要關閉這裏,設0
    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)
{   
  //tcflush(fd, TCIOFLUSH);         //刷串口清缓存,该函数可以清除 read 函数的串口缓存
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*/

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

posted @ 2021-02-19 15:05  白菜没我白  阅读(8304)  评论(0编辑  收藏  举报