lugl 发表于 2024-5-1 22:27:21

【扩展模块】基于QT5的PN532_NFC读卡

【开发环境】

1、ubuntu18.04
2、瑞米派SDK
3、QT5.12
【开发步骤】
一、根据瑞米派的原理图,与PN535_NFC的原理图,它们是兼容树霉派的标准接口。根据瑞米派的原理图,我这里确定ttySC4为与PN535_NFC的UART接口。
二、为了实现串口通信,我这里顺便用QT实现了一个串口助手的工程。基实现如下:
1、打开QT设计,新建一个工程,工程中配置GCC与G++为瑞米派的SDK指定的目录,如下图示:


2、配置qmake路径如下:


3、最后配置kit如下:


4、同时由于黙认的镜像包没有拷贝libQt5Serialport.so的这个库文件,
复制SDK的目录/opt/poky/3.1.20/sysroots/aarch64-poky-linux/usr/lib64$下面,他是有libQt5SerialPort.so.5这个文件的,所以直接复制到开发板:

[*]scp libQt5Serial* root@192.168.3.128:/usr/lib64/
[*]


5、设计界面如下:


上面是一个串口助手,可以实现串口的调试。
6、主要代码如下在.pro中添加serial的引用
QT       += core gui serialport7、在mainwindow.h中添加相关头文件的引用,并使用设计器生成槽函数,添加一个接收的函数,其代码如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtSerialPort>
#include <QString>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    QSerialPort *serialPort; //定义串口指针
private slots:
   /*手动连接槽函数*/

    void manual_serialPortReadyRead();
    /*以下为mainwindow.ui文件中点击“转到槽”自动生成的函数*/
    void on_openBt_clicked();

    void on_btnSerialCheck_clicked();

    void on_sendBt_clicked();

    void on_btnClearSend_clicked();

    void on_clearBt_clicked();

    void on_chkTimeSend_stateChanged(int arg1);

    void on_bntReadIC_clicked();

private:
    Ui::MainWindow *ui;

    // 发送、接收字节计数
    long sendNum, recvNum;
    QLabel *lblSendNum;
    QLabel *lblRecvNum;
    QLabel *lblPortState;
    void setNumOnLable(QLabel *lbl, QString strS, long num);

    // 定时发送-定时器
   QTimer *timSend;
};
#endif // MAINWINDOW_H
8、在mainwindow.cpp代码如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QSerialPort"
#include "QSerialPortInfo"
#include "QMessageBox"
#include "QDateTime"
#include "QTimer"

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

    QStringList serialNamePort;

    serialPort = new QSerialPort(this);
    connect(serialPort, SIGNAL(readyRead()), this,SLOT(manual_serialPortReadyRead()));/*手动连接槽函数*/
    ui->serailCb->clear();
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){
      ui->serailCb->addItem(info.portName());
    }

    sendNum = 0;
    recvNum = 0;

    QStatusBar *sBar = statusBar();

    lblSendNum = new QLabel(this);
    lblRecvNum = new QLabel(this);
    lblPortState = new QLabel(this);
    lblPortState->setText("Conneted");

    lblPortState->setStyleSheet("color:red");

    lblSendNum->setMinimumSize(100,20);
    lblRecvNum->setMinimumSize(100,20);
    lblPortState->setMinimumSize(550,20);
    setNumOnLable(lblSendNum,"S:",sendNum);
    setNumOnLable(lblRecvNum,"R:",recvNum);

    sBar->addPermanentWidget(lblPortState);
    sBar->addPermanentWidget(lblSendNum);
    sBar->addPermanentWidget(lblRecvNum);

    // 定时发送-定时器
    timSend = new QTimer;
    timSend->setInterval(1000);// 设置默认定时时长1000ms
    connect(timSend, &QTimer::timeout, this, [=](){
      on_sendBt_clicked();
    });

}

/*手动实现接收数据函数*/
void MainWindow::manual_serialPortReadyRead()
{
    QByteArray recBuf = serialPort->readAll();;
    QString str_rev;

    // 接收字节计数
    recvNum += recBuf.size();
    // 状态栏显示计数值
    setNumOnLable(lblRecvNum, "R: ", recvNum);
    if(recvNum == 19){
      ui->lblICID->setText(QString(recBuf.toHex().toUpper().mid(10,8)));
    }
    if(ui->chk_rev_hex->checkState() == false){
      if(ui->chk_rev_time->checkState() == Qt::Checked){
            QDateTime nowtime = QDateTime::currentDateTime();
            str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
            str_rev += QString(recBuf).append("\r\n");
      }
      else{
            // 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。
            //ui->recvEdit->appendPlainText(buf);

            if(ui->chk_rev_line->checkState() == Qt::Checked){
                str_rev = QString(recBuf).append("\r\n");
            }
            else
            {
                str_rev = QString(recBuf);
            }
      }
    }else{

      // 16进制显示,并转换为大写
      QString str1 = recBuf.toHex().toUpper();//.data();
      // 添加空格
      QString str2;
      for(int i = 0; i<str1.length (); i+=1)
      {
            str2 += str1.mid (i,1);
            str2 += " ";
      }
      if(ui->chk_rev_time->checkState() == Qt::Checked)
      {
            QDateTime nowtime = QDateTime::currentDateTime();
            str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
            str_rev += str2.append("\r\n");
      }
      else
      {
            if(ui->chk_rev_line->checkState() == Qt::Checked)
                str_rev += str2.append("\r\n");
            else
                str_rev = str2;

      }
    }
    ui->recvEdit->insertPlainText(str_rev);
    ui->recvEdit->moveCursor(QTextCursor::End);

}


void MainWindow::setNumOnLable(QLabel *lbl, QString strS, long num)
{
    QString strN;
    strN.sprintf("%ld", num);
    QString str = strS + strN;
    lbl->setText(str);
}
MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_openBt_clicked()
{
   /*串口初始化*/
    QSerialPort::BaudRate baudRate;
    QSerialPort::DataBits dataBits;
    QSerialPort::StopBits stopBits;
    QSerialPort::Parity checkBits;

    // 获取串口波特率
    if(ui->baundrateCb->currentText()=="2400")
            baudRate=QSerialPort::Baud2400;
      else if(ui->baundrateCb->currentText()=="4800")
            baudRate=QSerialPort::Baud4800;
      else if(ui->baundrateCb->currentText()=="9600")
            baudRate=QSerialPort::Baud9600;
      else if(ui->baundrateCb->currentText()=="19200")
            baudRate=QSerialPort::Baud19200;
      else if(ui->baundrateCb->currentText()=="38400")
            baudRate=QSerialPort::Baud38400;
      else if(ui->baundrateCb->currentText()=="57600")
            baudRate=QSerialPort::Baud57600;
      else if(ui->baundrateCb->currentText()=="115200")
            baudRate=QSerialPort::Baud115200;
    // 获取串口数据位
      if(ui->databitCb->currentText()=="5")
            dataBits=QSerialPort::Data5;
      else if(ui->databitCb->currentText()=="6")
            dataBits=QSerialPort::Data6;
      else if(ui->databitCb->currentText()=="7")
            dataBits=QSerialPort::Data7;
      else if(ui->databitCb->currentText()=="8")
            dataBits=QSerialPort::Data8;

      // 获取串口停止位
      if(ui->stopbitCb->currentText()=="1")
            stopBits=QSerialPort::OneStop;
      else if(ui->stopbitCb->currentText()=="1.5")
            stopBits=QSerialPort::OneAndHalfStop;
      else if(ui->stopbitCb->currentText()=="2")
            stopBits=QSerialPort::TwoStop;

      // 获取串口奇偶校验位
      if(ui->checkbitCb->currentText() == "none"){
            checkBits = QSerialPort::NoParity;
      }else if(ui->checkbitCb->currentText() == "奇校验"){
            checkBits = QSerialPort::OddParity;
      }else if(ui->checkbitCb->currentText() == "偶校验"){
            checkBits = QSerialPort::EvenParity;
      }else{

      }
      // 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数

      serialPort->setPortName(ui->serailCb->currentText());
      serialPort->setBaudRate(baudRate);
      serialPort->setDataBits(dataBits);
      serialPort->setStopBits(stopBits);
      serialPort->setParity(checkBits);

      // 根据初始化好的串口属性,打开串口
      // 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。
      if(ui->openBt->text() == "打开串口"){
            if(serialPort->open(QIODevice::ReadWrite) == true){
                ui->openBt->setText("关闭串口");
                // 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)
                ui->serailCb->setEnabled(false);
            }else{
                QMessageBox::critical(this,"错误提示", "串口打开失败!!!\r\n该串口可能被占用\r\n请选择正确的串口" );
            }
            //statusBar 状态栏显示端口状态
            QString sm = "%1 OPEND, %2, 8, NONE, 1";
            QString status = sm.arg(serialPort->portName()).arg(serialPort->baudRate());
            lblPortState->setText(status);
            lblPortState->setStyleSheet("color:green");

      }else{
            serialPort->close();
            ui->openBt->setText("打开串口");
            ui->serailCb->setEnabled(true);
            QString sm = "%1 ClOSED";
            QString status = sm.arg(serialPort->portName());
            lblPortState->setText(status);
            lblPortState->setStyleSheet("color:red");
      }
}

void MainWindow::on_btnSerialCheck_clicked()
{
    ui->serailCb->clear();

    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
      ui->serailCb->addItem(info.portName());
    }

}

void MainWindow::on_sendBt_clicked()
{
    QByteArray array;

    if(ui->chk_send_hex->checkState() == Qt::Checked){
      array = QByteArray::fromHex(ui->sendEdit->toPlainText().toUtf8().data());
    }else{
      array = ui->sendEdit->toPlainText().toLocal8Bit().data();
    }

    if(ui->chk_send_line->checkState() == Qt::Checked){
      array.append("\r\n");
    }

    int a = serialPort->write(array);
    if(a>0){
      sendNum += a;
      setNumOnLable(lblSendNum,"S:", sendNum);
    }
}

void MainWindow::on_btnClearSend_clicked()
{
    ui->sendEdit->clear();

    sendNum = 0;
    setNumOnLable(lblSendNum,"S:",sendNum);
}

void MainWindow::on_clearBt_clicked()
{
    ui->recvEdit->clear();
    sendNum = 0;
    recvNum = 0;

    setNumOnLable(lblSendNum,"S:",sendNum);
    setNumOnLable(lblRecvNum, "R:", recvNum);
}

void MainWindow::on_chkTimeSend_stateChanged(int arg1)
{
    if(arg1 == 0){
      timSend->stop();
      ui->txtSendMs->setEnabled(true);
    }else{
      if(ui->txtSendMs->text().toInt() >= 10){
            timSend->start(ui->txtSendMs->text().toInt());
            ui->txtSendMs->setEnabled(false);
      }else{
            ui->chkTimeSend->setCheckState(Qt::Unchecked);
            QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 10ms\r\n请确保输入的值 >=10");
      }
    }

}

void MainWindow::on_bntReadIC_clicked()
{
    QByteArray array;
    QString sendstry = "00 00 FF 04 FC D4 4A 01 00 E1 00";
    array = QByteArray::fromHex(sendstry.toUtf8().data());
    serialPort->write(array);
}
其中on_bntReadIC_clicked,实现了发送获取发送此帧用于探测Mifare Classic 卡的命令。发送以后把IC卡帖进天线,就可以在卡号那里读出IC的卡号:


【总结】
这次米尔提供的PN532——NFC模块为标准树莓派的接口,他可以通过跳钱来使用SPI、I2C、UART来与NFC卡实现通信功能。同时瑞米派也提供了非常好的QT开发SDK。使得驱动非常之容易。接我会偿试使用刷卡实现远程门禁的认证的例程。

米尔小超人 发表于 2024-5-6 08:32:25

请问你有什么问题吗?是哪方面的,硬件还是软件?一般这种读卡模块是IIC或者SPI接口,接线正常的话,可以直接使用linux的接口编程

lugl 发表于 2024-5-6 10:21:22

米尔小超人 发表于 2024-5-6 08:32
请问你有什么问题吗?是哪方面的,硬件还是软件?一般这种读卡模块是IIC或者SPI接口,接线正常的话,可以 ...

我发了帖子,怎么只剩一下个空标题,是论坛的问题吗?
页: [1]
查看完整版本: 【扩展模块】基于QT5的PN532_NFC读卡