【QT】 Qt 串口编程

常见串口通信协议

串口是一种数据传输接口,通常用于在计算机和外部设备之间传输数据。串口有多种通信协议,其中最常用的是RS232、RS485和UART等协议。

  • 不同的串口通信协议的区别主要是通信的距离不一样!

TTL 接口

数码设备和单片机中的TTL电平如下:

  • 输入: 高电平: 大于2V 低电平: 小于1.2V
  • 输出: 高电平: 大于2.4V 低电平: 小于0.8V

所以接台式机RS232接口与设备连接需要用RS232转TTL电路,或者现在最常用的USB转TTL

在这里插入图片描述

RS232接口

特性描述
类型异步串行通信
通信模式点对点(1 对 1)
电压标准±12V(高)、-12V(低)
传输距离最长 15 米
信号线TX(发送)、RX(接收)、RTS、CTS、DTR、DSR、GND
  • 常用于计算机串口(COM 口)通信,如老式鼠标、调制解调器、医疗设备等

以前的台式机,在机箱背面都有九针DB9接口,信号电平逻辑遵照RS-232规则。

台式机九针D型接口中,虽然有九针实际用到的只有3个,2脚RXD,3脚TXD ,5是GND。

而各种数码设置中的逻辑信号与RS232高低不同,因此不能直接对接。

右图为例针的用途说明如下(1号针位于端口左上角)

在这里插入图片描述

RS-485(工业常用)


在这里插入图片描述

特性描述
类型异步串行通信
通信模式多点通信(1 对多)
电压标准-7V ~ +12V
传输距离最大 1200 米
信号线A(+)、B(-)、GND

RS-485 适用于

  • 工业自动化(PLC、传感器)
  • 远距离通信
  • 多设备总线

串口通信引脚连接图

UART引脚连接方法

  • RXD:数据输入引脚,数据接受。
  • TXD:数据发送引脚,数据发送。

在这里插入图片描述

在这里插入图片描述

            开发板 与 RFID 读卡模块连接

对于两个芯片之间的连接,两个芯片GND共地,同时TXD和RXD交叉连接。这里的交叉连接的意思就是,芯片1的RxD连接芯片2的TXD,芯片2的RXD连接芯片1的TXD。这样,两个芯片之间就可以进行TTL电平通信了

QT 串口编程

Qt串口模块提供了两个类一个是QSerialPort提供访问串口的函数,另外一个类QSerialPortInfo提供有关现有串行端口的信息。如果要在你的项目中使用QSerialPortQSerialPortInfo要包含下面两个头文件

#include <QSerialPort>
#include <QSerialPortInfo>

编译的时候需要连接串口模块,可以在qmake.pro(项目工程文件中添加模块)

QT += serialport

QSerialPort 串口类

构造函数

QSerialPort(const QSerialPortInfo &serialPortInfo, QObject *parent = nullptr)
//name:串口驱动设备路径名  parent:父类指针 
QSerialPort(const QString &name, QObject *parent = nullptr)
QSerialPort(QObject *parent = nullptr)

常用函数

bool QSerialPort::open(QIODevice::OpenMode mode)  //打开串口驱动  
void QSerialPort::close()                         //关闭串口


--------------------示例---------------------

QSerialPort   *port =  new QSerialPort("/dev/ttySAC2",this);
 
 if(port->open(QIODevice::ReadWrite)) 
 {
     //打开串口成功 
 }else 
 {
     //打开串口失败 
 }

串口参数配置枚举

//波特率
enum BaudRate { Baud1200, Baud2400, Baud4800, Baud9600, Baud19200,, UnknownBaud }
//数据位
enum DataBits { Data5, Data6, Data7, Data8, UnknownDataBits }
//流控制
enum FlowControl { NoFlowControl, HardwareControl, SoftwareControl, UnknownFlowControl }
//校验
enum Parity { NoParity, EvenParity, OddParity, SpaceParity, MarkParity, UnknownParity }
//串口错误
enum SerialPortError { NoError, DeviceNotFoundError, PermissionError, OpenError, NotOpenError,, UnknownError }
//停止位
enum StopBits { OneStop, OneAndHalfStop, TwoStop, UnknownStopBits }

串口参数配置函数

//设置串口名称
setPortName(const QString &name)
//设置波特率
setBaudRate(qint32 baudRate, QSerialPort::Directions directions = AllDirections)
//设置数据位
setDataBits(QSerialPort::DataBits dataBits)
//设置停止位
setStopBits(QSerialPort::StopBits stopBits)
//设置校验位
setParity(QSerialPort::Parity parity)
//设置流控制
setFlowControl(QSerialPort::FlowControl flowControl)
//向串口发送数据
write(const char *data, qint64 maxSize)
//从串口接收数据
read(char *data, qint64 maxSize)
//获取串口缓冲区中的可用字节数
bytesAvailable() const

QSerialPortInfo串口信息类

获取当前系统所有可用串口的信息

//获取当前系统所有可以使用的串口
[static] QList<QSerialPortInfo> QSerialPortInfo::availablePorts() 
//串口描述信息
QString QSerialPortInfo::description() const;
//串口名
QString QSerialPortInfo::portName() const;

综合示例

需求:设置一个串口助手

UI 界面设计

在这里插入图片描述


代码实现

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_pushButton_openclose_clicked();

    void on_pushButton_send_clicked();

private:
    Ui::MainWindow *ui;
    QSerialPort *serial;     // 串口对象

};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

     setWindowTitle("串口助手");  // 设置窗口标题

    //获取系统所有可用接口显示到comboBox上
    for(QSerialPortInfo &info:QSerialPortInfo::availablePorts())
    {
        qDebug() << info.portName() << info.description();
        QSerialPort port(info);//创建串口对象
        if(port.open(QIODevice::ReadWrite))//空闲
        {
            ui->comboBox_port->addItem(port.portName());
            //关闭
            port.close();
        }else//被占用
        {
            ui->comboBox_port->addItem(port.portName() + "(被占用)");
        }
    }

    //初始化串口对象
    serial = new QSerialPort(this);
    //绑定可读信号
    //连接接收数据的信号和槽
    QObject::connect(serial,&QSerialPort::readyRead,this,[&](){
        //接收所有数据
        QByteArray arr = serial->readAll();
        //显示
        if(!arr.isEmpty()){
            ui->textBrowser->append(QString(arr));
        }
    });

}

MainWindow::~MainWindow()
{

    delete ui;
}

//打开/关闭串口
void MainWindow::on_pushButton_openclose_clicked()
{
    if(ui->pushButton_openclose->text() == "打开串口")//打开
    {
        QString portName = ui->comboBox_port->currentText();//获取名字
        serial->setPortName(portName);//设置名字
        if(!serial->open(QIODevice::ReadWrite))
        {
            QMessageBox::warning(this,"提示","打开失败!");
            serial->deleteLater();//关闭串口
            return;
        }

        //设置波特率
        switch(ui->comboBox_baudrate->currentIndex()){
        case 0:
            serial->setBaudRate(QSerialPort::Baud2400);
            break;
        case 1:
            serial->setBaudRate(QSerialPort::Baud4800);
            break;
        case 2:
            serial->setBaudRate(QSerialPort::Baud9600);
            break;
        case 3:
            serial->setBaudRate(QSerialPort::Baud38400);
            break;
        case 4:
            serial->setBaudRate(QSerialPort::Baud115200);
            break;
        }

        //设置数据位
        switch(ui->comboBox_databits->currentIndex()){
        case 0:
            serial->setDataBits(QSerialPort::Data5);
            break;
        case 1:
            serial->setDataBits(QSerialPort::Data6);
            break;
        case 2:
            serial->setDataBits(QSerialPort::Data7);
            break;
        case 3:
            serial->setDataBits(QSerialPort::Data8);
            break;
        }

        //设置校验位
        switch(ui->comboBox_parity->currentIndex()){
        case 0:
            serial->setParity(QSerialPort::NoParity);
            break;
        case 1:
            serial->setParity(QSerialPort::OddParity);
            break;
        case 2:
            serial->setParity(QSerialPort::EvenParity);
            break;
        }

        //设置停止位
        switch(ui->comboBox_stopbits->currentIndex()){
        case 0:
            serial->setStopBits(QSerialPort::OneStop);
            break;
        case 1:
            serial->setStopBits(QSerialPort::OneAndHalfStop);
            break;
        case 2:
            serial->setStopBits(QSerialPort::TwoStop);
            break;
        }

        //关闭流控制
        serial->setFlowControl(QSerialPort::NoFlowControl);

        //关闭选项菜单,使能发送
        ui->comboBox_port->setEnabled(false);
        ui->comboBox_baudrate->setEnabled(false);
        ui->comboBox_databits->setEnabled(false);
        ui->comboBox_parity->setEnabled(false);
        ui->comboBox_stopbits->setEnabled(false);

        ui->pushButton_send->setEnabled(true);

        ui->pushButton_openclose->setText("关 闭 串 口");

    }else
    {
        serial->clear();
        serial->close();
        serial->deleteLater();

        ui->comboBox_port->setEnabled(true);
        ui->comboBox_baudrate->setEnabled(true);
        ui->comboBox_databits->setEnabled(true);
        ui->comboBox_parity->setEnabled(true);
        ui->comboBox_stopbits->setEnabled(true);

        ui->pushButton_send->setEnabled(false);

        ui->pushButton_openclose->setText("打 开 串 口");
    }


}

void MainWindow::on_pushButton_send_clicked()
{
    //获取要发送的数据
    QByteArray arr = ui->textEdit->toPlainText().toUtf8();
    serial->write(arr);
}

串口模拟

串口模拟工具.zip

安装后进入下图界面添加模拟串口

在这里插入图片描述

复制刚刚写的工程文件生成第二份,记得改名字及工程构建路径


在这里插入图片描述


在这里插入图片描述


效果

注意:波特率这些串口参数实际选什么取决于你的串口,请查阅厂家给的手册

LInux 串口编程

Linux提供了一系列的接口函数来访问串口,通过调用这些函数就能实现串口传输。步骤非常固定

Linux 串口设备文件

在 Linux 中,串口设备以 文件形式 存在,通常位于:

设备路径设备类型
/dev/ttyS0/dev/ttyS1标准串口(COM 口)
/dev/ttyUSB0/dev/ttyUSB1USB-串口
/dev/ttyAMA0树莓派 UART 串口
/dev/ttySAC0(调试串口)/dev/ttySAC1/dev/ttySAC2/dev/ttySAC3gec6818 开发板

相关的头文件和数据结构

在这里插入图片描述

#include <termios.h>

#define NCCS 32
struct termios
{
    tcflag_t c_iflag;           /* 输入模式标志 */
    tcflag_t c_oflag;           /* 输出模式标志 */
    tcflag_t c_cflag;           /* 控制模式标志 */
    tcflag_t c_lflag;           /* 本地模式标志 */
    cc_t c_line;                /* 线路规程 */
    cc_t c_cc[NCCS];            /* 控制属性 */
    speed_t c_ispeed;           /* 输入速度 */
    speed_t c_ospeed;           /* 输出速度 */
    #define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
    #define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};

termios 结构体成员详解

成员描述
c_iflag输入模式标志(影响数据接收)
c_oflag输出模式标志(影响数据发送)
c_cflag控制模式标志(设置波特率、数据位等)
c_lflag本地模式标志(影响终端行为,如回显)
c_line线路规程(一般不使用)
c_cc[NCCS]控制字符(如 VTIMEVMIN
c_ispeed输入波特率(cfsetispeed()
c_ospeed输出波特率(cfsetospeed()

c_iflag(输入模式标志)

c_iflag 用于 控制输入数据的处理方式(如换行转换、流控)。

标志位作用
IGNBRK忽略输入的 BREAK 信号
BRKINTBREAK 产生中断
IGNPAR忽略奇偶校验错误
PARMRK标记奇偶校验错误
INPCK启用输入奇偶校验
ISTRIP截断高位 bit
IXON启用软件流控(XON/XOFF)
IXOFF启用接收端流控
IXANY允许任何字符启动输出

c_oflag(输出模式标志)

c_oflag 用于 控制数据如何发送(仅适用于终端,不影响串口)。

标志位作用
OPOST处理输出数据(默认启用)
ONLCR\n 转换成 \r\n

c_cflag(控制模式标志)😍

c_cflag 设置波特率、数据位、校验位、停止位

标志位作用
CS5 / CS6 / CS7 / CS8设置数据位数(5、6、7、8 位)
PARENB启用奇偶校验
PARODD奇校验(默认偶校验)
CSTOPB2 个停止位(默认 1 个)
CREAD启用接收功能
CLOCAL忽略 modem 控制信号

c_lflag(本地模式标志)

c_lflag 控制 终端行为(如回显、信号处理)。

标志位作用
ICANON规范模式(启用行缓冲)
ECHO输入时回显字符
ECHOEERASE键删除前一个字符
ECHOKKILL键删除整行
ISIG启用 INTRQUITSUSP信号

c_cc[NCCS](控制字符)

用于 控制超时、数据接收行为

控制字符作用
VTIME读取超时(单位 100ms)
VMIN读取最小字节数

c_ispeed / c_ospeed(波特率)

设置输入/输出波特率。

波特率宏定义
9600B9600
115200B115200

串口编程步骤

Linux 下的串口通信主要分为 5 个步骤

  1. 打开串口open()
  2. 配置串口参数termios
  3. 读写数据read() / write()
  4. 刷新 & 控制串口tcflush() /tcdrain()
  5. 关闭串口close()

综合示例

#include <stdio.h>      // 标准输入输出
#include <stdlib.h>     // 标准库(如 malloc, free)
#include <string.h>     // bzero, strlen
#include <unistd.h>     // read, write, close
#include <fcntl.h>      // open, O_RDWR
#include <errno.h>      // 错误处理
#include <termios.h>    // 串口配置(B9600, CS8, etc)
#include <pthread.h>    // 线程库(pthread_t, pthread_create)


int serialfd;
//线程的任务函数,接收串口信息
void *recvserialmsg(void *arg)
{
	char rbuf[100];
	while(1)
	{
		bzero(rbuf,100);
		read(serialfd,rbuf,100); //接收串口数据
		printf("串口接收到的数据是: %s\n",rbuf);
	}
}

//封装函数配置串口
int set_serial()
{
    //声明设置串口的结构体
    struct termios termios_new;

    //清空结构体
    bzero(&termios_new,sizeof(termios_new));

    //cfmakeraw()设置终端属性,就是设置termios结构体中的各个参数
    cfmakeraw(&termios_new);

    //设置波特率,双向通信
    cfsetispeed(&termios_new,B9600);
    cfsetospeed(&termios_new,B9600);

    //CLOCAL 和 CREAD分别用于本地连接和接受使能 因此 首先要通过位掩码的方式激活这两个选项
    termios_new.c_cflag |= CLOCAL | CREAD;

    //通过位掩码设置数据为8位 linux中的串口编程和QT一样(代码都是固定写法)
    termios_new.c_cflag &= ~CSIZE; //清空标志位
    termios_new.c_cflag |= CS8;    // CS7 CS6 CS5 

    //设置无奇偶校验
    termios_new.c_cflag &= ~PARENB; //PARENB:是否使用奇偶校验位,若使用,则设置奇偶校验位类型

    //设置停止位为1
    termios_new.c_cflag &= ~CSTOPB; 

    //可设置接受字符和等待时间 无特殊要求可以将其设置为 0
    termios_new.c_cc[VTIME] = 2;//等待时间
    termios_new.c_cc[VMIN] = 1;//接受字符

    //用于清空输入/输出缓冲区
    tcflush(serialfd,TCIFLUSH); //TCIFLUSH:刷新输入缓冲区

    //完成配置后,可以使用以下函数激活串口设置
    tcsetattr(serialfd,TCSANOW,&termios_new); //TCSANOW:立即执行
    return 0;
}

int main()
{
    pthread_t id;//定义线程id
    char sbuf[100];
    //打开串口驱动
    serialfd = open("/dev/ttySAC1",O_RDWR);
    if (serialfd == -1)
    {
        perror("打开串口失败");
        return -1;
    }

    //配置串口
    set_serial();

    //收发信息
	//创建线程专门接收信息
	pthread_create(&id,NULL,recvserialmsg,NULL);
	
	//主函数专门发送信息
	while(1)
	{
		bzero(sbuf,100);
		printf("请输入要发送的数据!\n");
		scanf("%s",sbuf);
		write(serialfd,sbuf,strlen(sbuf));
	}
	
	//关闭串口
	close(serialfd);
	return 0;
    
}

参考资料

串口编程.ppt

RFID 模块的开发

在这里插入图片描述

RFID(Radio Frequency Identification,射频识别) 是一种
无线通信技术,通过 射频信号 实现
无接触式数据传输和识别,可用于
物联网(IoT)、物流、门禁、支付系统等

工作原理

  1. RFID 读写器 发出 无线射频信号
  2. RFID 标签(Tag) 内部的 芯片+天线 接收信号,并返回数据。
  3. RFID 读写器 解析数据,进行存储或操作。

RFID 组成部分

1️⃣ RFID 标签(RFID Tag)

作用:存储数据,贴在 物品、卡片、设备 上。

  • 无源标签(Passive RFID)无电池,靠读写器供电,廉价,适合 门禁卡、商品管理
  • 有源标签(Active RFID)内置电池,发射信号,适合 远距离追踪(如车辆定位)

2️⃣ RFID 读写器(RFID Reader)

作用:发射射频信号,并读取标签数据。

  • 固定式读写器:安装在固定位置,如 门禁、仓库出入口
  • 手持式读写器:移动式,如 快递扫描枪

3️⃣RFID 天线

作用:用于无线数据传输,决定通信距离。

4️⃣ RFID 数据管理系统

作用:处理 RFID 读取的数据,连接 数据库、物联网系统、ERP

RFID 读卡流程

在这里插入图片描述

UART 接口一帧的数据格式为 1 个起始位,8 个数据位、无奇偶校验位、1 个停止位,波特率固定为 9600。

数据帧

在这里插入图片描述

字段长度说明
FrameLen1 byte整个数据帧的总长度(从 FrameLen 到 ETX 的字节数)
SEQ/CmdType1 byte包号(SEQ)或命令类型(CmdType),标识命令类别(如设备控制或 ISO14443A)
Cmd/Status1 byte具体命令(主机发送时)或状态(从机应答时)
Length1 byteInfo 字段的长度(N bytes)
InfoN bytes可变长度的信息内容(如参数或返回数据)
BCC1 byte校验和,用于验证数据完整性
ETX1 byte结束符,固定为 0x03,标志数据帧结束

校验和规则

在这里插入图片描述

在这里插入图片描述

请求数据帧设计

char  Request[8]={0};
Request[0] =  0x07; //帧长 
Request[1] =  0x02; //命令类型
Request[2] =  0x41; //命令
Request[3] =  0x01; //数据长度 
Request[4] =  0x52; //数据信息  
//计算校验和 
int BCC = 0; 
for(int i=0;i<Request[0]-2;i++)
{
    BCC ^= Request[i];
}
Request[5] = ~BCC;   //校验和 
Request[6] = 0x03;   //结束标记
 
 
//发送数据帧给 RFID 模块 
port->write(Request,7);

应答数据的处理

在这里插入图片描述

void MainWindow::read_rfid()
{
    QByteArray bye  = port->readAll();
//    //遍历数据帧的每一位
//    for(int i=0;i<bye.length();i++)
//    {
//        qDebug() << (int)bye.at(i);
//    }
    //判断状态位,确定是否请求成功!
    if(bye.length() == 0x08 && bye.at(2) == 0x00)
    {
        qDebug() << "Request  Successful";
        //设计防碰撞数据帧 
        
          
        //发送防碰撞协议                                                    
 
    }else
    {
        qDebug() <<  "Request  fail";
    }
}

防碰撞数据帧设计

在这里插入图片描述

//设计防碰撞协议  
char  AntiCollision[9]={0};
AntiCollision[0] = 0x08; //帧长
AntiCollision[1] = 0x02; //命令类型 
AntiCollision[2] = 0x42; //命令  
AntiCollision[3] = 0x02; //数据长度  
AntiCollision[4] = 0x93; 
AntiCollision[5] = 0x00; 
//计算校验和 
int BCC = 0; 
for(int i=0;i<Request[0]-2;i++)
{
    BCC ^= Request[i];
}
AntiCollision[6] = ~BCC; 
AntiCollision[7] = 0x03;
 
//发送防碰撞协议 
port->write(AntiCollision,8);

应答数据的处理

在这里插入图片描述

f(bye.length() == 0x0a && bye.at(2) == 0x00)
{
     qDebug() << "AntiCollision  Successful";
     
     //卡号  4  5  6  7  
     QString  id = QString::asprintf("%d%d%d%d",bye.at(7),bye.at(6),bye.at(5),bye.at(4));
     qDebug() << "ID:" << id;
}else
{
    qDebug() << "RFID  fail";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值