這里只貼串口讀寫操作部分代碼,供大家參考學習用,該部分代碼主要實現打開串口,配置串口參數波特率為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*/
注意:使用串口讀寫時,要注意下位機的時序,不然可能會出現下位機還沒發送數據上來,你就進行讀取,導致讀取失敗的情況,時序調整可以用延時調整
