Qt5+STM32F407+步进电机 | 通过电脑控制步进电机实现:6+2通道、速度可变、运动精确步数的教程——程序开发(3/4)

        这次项目的讲解分为4各部分,分别是简介(1/4)基础知识(2/4)程序开发(3/4)联合调试(4/4),这一次内容属于程序开发(3/4),可以对应文章标题(↑)快速定位目前处于哪一讲解环节。

程序我们分两个部分,一个是上位机基于Qt的软件界面开发,一个是下位机基于Keil的嵌入式开发。

一、Qt开发的上位机

1. 软件界面

       由于这次项目是针对自己的工作内容,所以就没有做什么设计,并且也是用来测试的,直接摆了一堆按钮能用就行,如图1.1.1:

  图 1.1.1 Qt设计的界面

       我们根据界面的内容来逐步讲解有些什么功能需要实现:

       1. 首先就是通信方式,由于上下位机通讯用的USB CDC协议,根据STM32官方例程的说明,采用的是模拟串口通信的方式,因此我们在Qt中使用QSerialPort即可。同时电脑上可能会有多个串口源,这里添加了一个QComboBox的下拉选择列表用于选择我们需要的串口;

       2. 由于电机比较多,把每个电机的控制单独列一个显得不太优雅,于是我把他们放在了一个列表里进行选择;

       3. 可以看到这里只提供了两个简单的前进、后退和1千步、1万步的选择,因此自定义输入步长也很重要,这里用了个QLineEdit来输入数据,配合QIntValidator使用户只能输入数字;

       4. 步进和后退就不用说了,定点是基于导轨上的限位反馈来实现的;

       5. 探测是6条导轨上的限位反馈,没有限位时为绿色o图片,限位时为红色x图片,这里用的QLabel加载的图片;

       6. 进度为电机运动的百分比,用了官方提供的QProgressBar控件;

       7. 紧急停止按钮顾名思义,用于停止电机运转。

       根据我的设想,上下位机之间之用于状态和控制信息,电机的各类计算和驱动全由下位机完成,未来如果有需要也可以添加SPI或I2C协议的液晶或墨水屏实现脱离上位机的离线控制。

2. 代码分析

       代码部分我根据不同组件分开说,每个组件官方文档和案例内都有较好的说明和举例,这里我就只说我自己的实现部分:

(1)QserialPort

       先说最重要的串口通信QSerialPort,串口通信有一些固定的参数,一般来说如果没有特别严格数据正确性需求 并且 短距离(小于50cm) 以及 没额外电磁干扰的情况下可以用最简单的设置方式:

QSerialPort* m_serialPort;
m_serialPort = new QSerialPort(this);

m_serialPort->setPortName(cbb_selectCom->currentText());         //当前选择的串口名字
m_serialPort->setBaudRate(10000000,QSerialPort::AllDirections); //设置波特率和读写方向
m_serialPort->setDataBits(QSerialPort::Data8);                  //数据位为8位
m_serialPort->setParity(QSerialPort::NoParity);                 //无校验位
m_serialPort->setStopBits(QSerialPort::OneStop);                //一位停止位
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);       //无流控制

       这里有一个 cbb_selectCom->currentText() 这个参数,初始化来源于下面的代码:

QComboBox* cbb_selectCom;
cbb_selectCom = ui->cbb_selectCom;

cbb_selectCom->clear();
QStringList m_portNameList = getPortNameList();
cbb_selectCom->addItems(m_portNameList);

       其中 ui->cbb_selectCom 指代的是在.ui文件中的控件,通过 getPortNameList() 函数来获取端口名称列表,返回参数类型为QStringList,函数如下:

QStringList MainWindow::getPortNameList()
{
    QStringList m_serialPortName;
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        m_serialPortName << info.portName();
        qDebug()<<"serialPortName:"<<info.portName();
    }
    return m_serialPortName;
}

       这里用了 QStringList 自带的<<符号重定义(重载), m_serialPortName << info.portName() 用于快速添加新的端口名称到 m_serialPortName 这个 QStringList 中,通过 foreach 的方式来遍历现在可用的端口,最终返回参数到 m_portNameList 并加载到 cbb_selectCom 用于界面显示和选择,选择好后就可以打开端口,方式如下:

bool MainWindow::openSerialPort()
{
    if(m_serialPort->isOpen())
    {
        m_serialPort->clear();
        m_serialPort->close();
    }

    m_serialPort->setPortName(cbb_selectCom->currentText());         //当前选择的串口名字
    m_serialPort->setBaudRate(10000000,QSerialPort::AllDirections); //设置波特率和读写方向
    m_serialPort->setDataBits(QSerialPort::Data8);                  //数据位为8位
    m_serialPort->setParity(QSerialPort::NoParity);                 //无校验位
    m_serialPort->setStopBits(QSerialPort::OneStop);                //一位停止位
    m_serialPort->setFlowControl(QSerialPort::NoFlowControl);       //无流控制

    if(m_serialPort->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口
    {
        connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::readData);
        connect(m_serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);
        enablePushButton();
        showStatusMessage(tr("Connect to %1").arg(cbb_selectCom->currentText()));
    }
    else
    {
        QMessageBox::critical(this, tr("错误"), m_serialPort->errorString());
        showStatusMessage(tr("Open COM failed"));
        return false;
    }

    return true;
}

       代码最开始先是判断端口是否已打开,如果打开了就先关闭再配置参数,随后获取当前可用的端口并通过 ReadWrite 模式进行端口初始化:成功就进行信号槽链接,并使能界面所有按钮并显示信息到用户界面的状态栏,最后返回成功参数;失败就弹出错误提示窗口并返回失败参数。这里也码一下相关的函数:

void MainWindow::writeData(const QByteArray &data)
{
    // 串口开启检测
    if(defSwitch_Off == isConnect) return;

    // 发送数据
    m_serialPort->write(data);
}
void MainWindow::readData()
{
    // 读取数据
    const QByteArray data = m_serialPort->readAll();

    // 数据完整性检测
    if(0 == data.count())
        return ;

    // 打印原生数据
    //qDebug() << "read Line: " << data;

    // 打印16进制数据
    QString ret;
    for(int i = 0; i < data.count(); ++i)
        ret.append( QObject::tr("%1,").arg((quint8)data.at(i),2,16,QLatin1Char('0')));
    //qDebug() << "read data: " << ret;

    // 其他业务逻辑
    
}
void MainWindow::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::ResourceError) {
        //QMessageBox::critical(this, tr("通信错误"), m_serialPort->errorString());
        qDebug() << "Serial port fault: " << m_serialPort->errorString();
        showStatusMessage(tr("Serial port critical fault"));
        closeSerialPort();
    }
}
void MainWindow::enablePushButton()
{
    pb_inputRun_back->setEnabled(true);
    pb_inputRun_fore->setEnabled(true);
    // 其他按钮同理
}

void MainWindow::disablePushButton()
{
    pb_inputRun_back->setEnabled(false);
    pb_inputRun_fore->setEnabled(false);
    // 其他按钮同理
}
// 提示信息
void MainWindow::showStatusMessage(const QString &message)
{
    m_statusBar->showMessage(message, 3000); // 单位为 ms
    qDebug() << message;

    //QApplication::processEvents(); //用于处理信息
}

       上面的所有控件都是经过初始化的,相比直接调用ui,这种方式更容易寻找和定位以及修改。

(2)界面初始化

       再说说界面上的按钮和对应的信号槽初始化

void MainWindow::uiInit()
{
    // 窗体设计
    setWindowTitle(tr("电机控制程序 Powered by OolongLemon (Ver 1.0)"));
    //setFixedSize( this->width (),this->height ());    // 固定窗口大小
    //setWindowFlags(Qt::WindowCloseButtonHint | Qt::MSWindowsFixedSizeDialogHint);//只保留关闭按钮、窗口大小锁定
    setWindowFlags(Qt::Dialog);                       // 窗体没有最大化最小化按钮
    setWindowIcon(QIcon(":/new/logo/logo.ico"));      // 设置任务栏和窗口图标

    // 状态栏初始化
    m_statusBar = ui->statusBar;
    Label_statusBar = new QLabel(this);
    m_statusBar->addPermanentWidget(Label_statusBar);
    m_statusBar->setStyleSheet(QString("QStatusBar::item{border: 0px}"));

    // 串口选择
    cbb_selectCom = ui->cbb_selectCom;
    pb_freshCom = ui->pb_freshCom;
    pb_connectCom = ui->pb_connectCom;

    //电机选择
    cbb_selectMot = ui->cbb_selectMot;
    pb_selectMot = ui->pb_selectMot;

    // 输入运动
    le_inputRun = ui->le_inputRun;
    pb_inputRun_back = ui->pb_inputRun_back;
    pb_inputRun_fore = ui->pb_inputRun_fore;

    // 步进
    pb_runFore_1 = ui->pb_runFore_1;
    pb_runFore_2 = ui->pb_runFore_2;

    // 后退
    pb_runBack_1 = ui->pb_runBack_1;
    pb_runBack_2 = ui->pb_runBack_2;

    // 定点
    pb_setPoint_1 = ui->pb_setPoint_1;
    pb_setPoint_2 = ui->pb_setPoint_2;

    // 探测
    label_pic_L1d = ui->label_pic_L1d;
    // 其他同理

    // 进度
    probar_1 = ui->probar_1;
    // 其他同理

    // 紧急停止
    pb_emerStop = ui->pb_emerStop;

    disablePushButton();
}

void MainWindow::uiConnect()
{
    connect(ui->pb_freshCom,        SIGNAL(clicked()), this, SLOT(slots_pb_freshCom()));
    connect(ui->pb_connectCom,      SIGNAL(clicked()), this, SLOT(slots_pb_connectCom()));

    connect(ui->pb_selectMot,       SIGNAL(clicked()), this, SLOT(slots_pb_selectMot()));

    connect(ui->pb_inputRun_back,   SIGNAL(clicked()), this, SLOT(slots_pb_inputRun_back()));
    connect(ui->pb_inputRun_fore,   SIGNAL(clicked()), this, SLOT(slots_pb_inputRun_fore()));

    connect(ui->pb_runFore_1,       SIGNAL(clicked()), this, SLOT(slots_pb_runFore_1()));
    connect(ui->pb_runFore_2,       SIGNAL(clicked()), this, SLOT(slots_pb_runFore_2()));

    connect(ui->pb_runBack_1,       SIGNAL(clicked()), this, SLOT(slots_pb_runBack_1()));
    connect(ui->pb_runBack_2,       SIGNAL(clicked()), this, SLOT(slots_pb_runBack_2()));

    connect(ui->pb_setPoint_1,      SIGNAL(clicked()), this, SLOT(slots_pb_setPoint_1()));
    connect(ui->pb_setPoint_2,      SIGNAL(clicked()), this, SLOT(slots_pb_setPoint_2()));

    connect(ui->pb_emerStop,        SIGNAL(clicked()), this, SLOT(slots_pb_emerStop()));
}

void MainWindow::moduInit()
{
    // 设置输入框
    QIntValidator *pIntVld = new QIntValidator(this);
    le_inputRun->setValidator(pIntVld);             // 输入过滤器,只接受整形(int)数字
    le_inputRun->setText("50");                     // 初始化显示 50

    // 建立串口
    m_serialPort = new QSerialPort(this);

    // 添加显示
    cbb_selectCom->clear();
    m_portNameList = getPortNameList();
    cbb_selectCom->addItems(m_portNameList);

    // 选择电机
    cbb_selectMot->clear();
    m_motorNameList << "Motor 0" << "Motor 1" << "Motor 2" << \
         
  • 16
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Qt5中控制STM32F407步进电机的方法如下: 1. 首先,你需要确保你已经安装了QtSTM32的开发环境,并且正确地连接了STM32F407开发板和电机。 2. 在Qt中,可以使用串口信来控制STM32。你可以使用Qt的QSerialPort类来实现串口信。首先,你需要设置串口的参数,包括波特率、数据位、校验位等。然后,你可以过打开串口并发送特定的命令来控制步进电机运动。 3. 在代码中,你可以使用QSerialPort类来打开串口,例如: ```cpp QSerialPort serialPort; serialPort.setPortName("COM1"); // 设置串口号 serialPort.setBaudRate(QSerialPort::Baud9600); // 设置波特率 serialPort.setDataBits(QSerialPort::Data8); // 设置数据位 serialPort.setParity(QSerialPort::NoParity); // 设置校验位 serialPort.setStopBits(QSerialPort::OneStop); // 设置停止位 serialPort.open(QIODevice::ReadWrite); // 打开串口 ``` 4. 接下来,你可以使用串口的write()函数来发送命令给STM32。根据你的需求,你可以发送不同的命令来控制步进电机运动。例如,你可以发送命令来启动电机、改变运动方向、调整电机速度等。 5. 在Qt中,你可以使用控件的setText()函数来显示文字。过调用控件的setText()函数,你可以将需要显示的文字作为参数传递进去。在你的情况下,你可以将步进电机的相关信息作为文本显示在适当的控件上。 综上所述,你可以使用Qt的QSerialPort类来实现串口信,并过发送特定的命令来控制STM32F407步进电机运动。在需要显示文字的控件上使用setText()函数来显示相关信息。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值