Linux-QT串列埠通訊
Linux-QT串列埠通訊
環境:Ubuntu18.04 QT4.8.6
1.QT新建Qt Console Application
#include <QCoreApplication>
#include "ThreadTest.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ThreadTest * thTest = new ThreadTest();
thTest->start();
return a.exec();
}
View Code2.新建一個串列埠類SerialPort
.h檔案
#ifndef SERIALPORT_H
#define SERIALPORT_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <QString>
#include <QDebug>
class SerialPort
{
public:
// 構造
SerialPort(QString devName);
// 析構
~SerialPort();
public:
// 設定波特率等
int set_port_attr (int baudrate, int databit,
const char *stopbit, char parity, int vtime,int vmin );
// 資料位
void set_data_bit (struct termios *opt, unsigned int databit);
// 校驗位
void set_parity (struct termios *opt, char parity);
// 停止位
void set_stopbit (struct termios *opt, const char *stopbit);
// 寫資料
int Write(const uchar *data,const int len);
// 讀資料
int Read(void *data,const int len, const int waitms);
// 設定波特率
void set_baudrate (struct termios *opt, uint baudrate);
private:
int serialPortFd;
};
#endif // SERIALPORT_H
View Code
.CPP檔案
#include "SerialPort.h"
// 構造
SerialPort::SerialPort(QString devName)
{
serialPortFd = open(devName.toAscii().data(), O_RDWR | O_NOCTTY);
qDebug() << "serialPortFd = " << serialPortFd;
}
// 設定波特率等
int SerialPort::set_port_attr (int baudrate, int databit,
const char *stopbit, char parity, int vtime,int vmin ){
struct termios opt;
tcgetattr(serialPortFd, &opt);
set_baudrate(&opt, baudrate);
opt.c_cflag |= CLOCAL | CREAD; /* | CRTSCTS */
set_data_bit(&opt, databit);
set_parity(&opt, parity);
set_stopbit(&opt, stopbit);
opt.c_oflag = 0;
//opt.c_lflag |= 0;
opt.c_lflag &= ~(ICANON | ECHO | ECHOE);
opt.c_oflag &= ~OPOST;
opt.c_cc[VTIME] = vtime;
opt.c_cc[VMIN] = vmin;
tcflush (serialPortFd, TCIFLUSH);
return (tcsetattr (serialPortFd, TCSANOW, &opt));
}
// 設定波特率
void SerialPort::set_baudrate (struct termios *opt, uint baudrate)
{
cfsetispeed(opt, baudrate);
cfsetospeed(opt, baudrate);
}
// 資料位
void SerialPort::set_data_bit (struct termios *opt, unsigned int databit)
{
opt->c_cflag &= ~CSIZE;
switch (databit) {
case 8:
opt->c_cflag |= CS8;
break;
case 7:
opt->c_cflag |= CS7;
break;
case 6:
opt->c_cflag |= CS6;
break;
case 5:
opt->c_cflag |= CS5;
break;
default:
opt->c_cflag |= CS8;
break;
}
}
// 校驗位
void SerialPort::set_parity (struct termios *opt, char parity)
{
switch (parity) {
case 'N': /* 無校驗 */
case 'n':
opt->c_cflag &= ~PARENB;
break;
case 'E': /* 偶校驗 */
case 'e':
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD;
break;
case 'O': /* 奇校驗 */
case 'o':
opt->c_cflag |= PARENB;
opt->c_cflag |= ~PARODD;
break;
default: /* 其它選擇為無校驗 */
opt->c_cflag &= ~PARENB;
break;
}
}
// 停止位
void SerialPort::set_stopbit (struct termios *opt, const char *stopbit)
{
if (0 == strcmp (stopbit, "1")) {
opt->c_cflag &= ~CSTOPB; /* 1位停止位t */
} else if (0 == strcmp (stopbit, "1.5")) {
opt->c_cflag &= ~CSTOPB; /* 1.5位停止位 */
} else if (0 == strcmp (stopbit, "2")) {
opt->c_cflag |= CSTOPB;
} else {
opt->c_cflag &= ~CSTOPB; /* 1 位停止位 */
}
}
// 寫資料
int SerialPort::Write(const uchar *data,const int len){
int returnLength = write(serialPortFd, data, len); /* 向串列埠傳送字串 */
return returnLength;
}
// 讀資料 waitms超時時間
int SerialPort::Read(void *data,const int len, const int waitms){
fd_set inputs;
struct timeval timeout;
FD_ZERO(&inputs);
FD_SET(serialPortFd,&inputs);
timeout.tv_sec = waitms/1000;
timeout.tv_usec = (waitms%1000)*1000;
int returnLength = select(FD_SETSIZE,&inputs,(fd_set *)NULL,(fd_set *)NULL,&timeout);
if (returnLength == 0){
//qDebug() << "read timeout!\n";
return -1;
}
//qDebug() << "ComDevice::Read 5";
if (returnLength == -1){
qDebug() << "select read device error!\n";
return -2;
}
returnLength = read(serialPortFd, data, len); /* 在串列埠讀取字串 */
return returnLength;
}
// 析構
SerialPort::~SerialPort(){
}
View Code
3.請求串列埠資料的執行緒類ThreadTest
.h檔案
#ifndef THREADTEST_H
#define THREADTEST_H
#include <QThread>
#include <QDateTime>
#include "SerialPort.h"
class ThreadTest : public QThread
{
public:
ThreadTest();
~ThreadTest();
private:
// 執行緒
virtual void run();
// 請求實時資料
void RequestRealData();
// 列印資料
void PrintData(QString str, uchar *data, int length);
private:
// 校驗和,左閉右開
ushort GetCheckSum(uchar * data, int startIndex, int endIndex);
// 十六進位制轉ASCII
uchar CharToAscii(uchar bHex);
// ASCII轉十六進位制
uchar AsciiToChar(uchar bChar);
private:
// 串列埠
SerialPort * serialPort;
};
#endif // THREADTEST_H
View Code
.CPP檔案
#include "ThreadTest.h"
ThreadTest::ThreadTest()
{
serialPort = new SerialPort("/dev/ttymxc5");
serialPort->set_port_attr(9600, 8, "1", 'N',20,255);
}
// 執行緒
void ThreadTest::run(){
while (TRUE) {
RequestRealData();
sleep(2);
}
}
// 請求實時資料
void ThreadTest::RequestRealData(){
// 清除串列埠記憶體中資料
// uchar clearData[148];
// int clearLen = serialPort->Read(clearData, 148, 1000);
// qDebug() << "clearLen = " << clearLen;
uchar dataReq[18] = {0x7E,0x31,0x31,0x30,0x31,0x32,0x41,0x34,0x34,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,0x0D };
ushort checkSum = GetCheckSum(dataReq, 1, 13);
dataReq[13] = CharToAscii((checkSum >> 12)&0x0F);
dataReq[14] = CharToAscii((checkSum >> 8)&0x0F);
dataReq[15] = CharToAscii((checkSum >> 4)&0x0F);
dataReq[16] = CharToAscii((checkSum >> 0)&0x0F);
PrintData(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz") + " Send: ",
dataReq, 18);
// 2020.12.04 02:58:35
serialPort->Write(dataReq, 18);
usleep(20 * 1000);
uchar realDataAck[148];
int ret1 = serialPort->Read(realDataAck, 148, 1000);
int ret2 = 0;
// if(ret1 < 148){
// ret1 = ret1 < 0 ? 0 : ret1;
// usleep(1000 * 100);
// ret2 = serialPort->Read(realDataAck+ret1, 148-ret1, 1000);
// }
if(ret1+ret2 <= 0){
return;
}
PrintData(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz") + " Revice: ",
realDataAck, ret1+ret2);
// ParseAlarmDataAck(realDataAck, ret1);
}
// 校驗和,左閉右開
ushort ThreadTest::GetCheckSum(uchar * data, int startIndex, int endIndex){
ushort sum = 0;
for(int i = startIndex; i < endIndex; i++){
sum += data[i];
}
sum = (~sum) + 1;
return sum;
}
// 十六進位制轉ASCII
uchar ThreadTest::CharToAscii(uchar bHex){
if((bHex>=0)&&(bHex<=9)){
return bHex + 0x30;
}
return bHex += 0x37;
}
// ASCII轉十六進位制
uchar ThreadTest::AsciiToChar(uchar bChar){
if((bChar>=0x30)&&(bChar<=0x39)){
return bChar - 0x30;
}else if((bChar>=0x41)&&(bChar<=0x46)){
// Capital
return bChar - 0x37;
}
// littlecase
return bChar - 0x57;
}
// 列印資料
void ThreadTest::PrintData(QString str, uchar *data, int length){
// return;
for(int i = 0; i < length; i++){
str += QString("%1 ").arg(data[i], 2, 16, QLatin1Char('0'));
}
qDebug() << "R:\t" << str;
}
ThreadTest::~ThreadTest()
{
if(serialPort != NULL){
delete serialPort;
serialPort = NULL;
}
}
View Code
相關注釋程式碼中都有了,相對比較簡單,主要是讀寫操作,借用了一個例項,有些BCD碼的轉換和校驗位的處理,實際操作中可以替換掉處理。
注意事項:
串列埠名字肯定是不一樣的,圖紙上都會有;
檢驗位、資料位、奇偶校驗這些設定;
超時時間;
請求資料的長度,接收資料的長度,這些不如在Windows上好用,都是需要自己注意處理的,有些程式碼是遮蔽的,比如是請求兩次達到你想要的長度,這些可以防止一次請求不完;
讀請求應答ParseAlarmDataAck這個遮蔽的方法可以用來處理資料,這兒只是列印顯示了下;
下面是測試用例,
執行環境:ARM V7開發板 232串列埠
1. 開發板接出線轉USB接入到電腦上,開啟電腦除錯軟體,如下圖所示,波特率、校驗位設定好,開啟串列埠就可以讀取到資料了;
2.設定串列埠除錯軟體傳送資料,點選收到回答後發下一幀,即在接收到串列埠的資料就發一幀應答資料,傳送區2,點選自動發,16進位制傳送;傳送區1 2 3都是獨立的,都可以傳送,咱們用一個即可;
3.檢視開發板上列印的日誌,即接收到的資料,列印的Send是傳送的資料,Revice就是接收到的資料,接收到資料之後,自己寫一個ParseAlarmDataAck方法就可以處理資料啦,根據具體協議來操作;
相對比較簡單,QT5是有了自帶的串列埠操作類,但貌似用QT4.8.6的更多呀...