基于Qt的串口上位机控制蓝牙小车程序

Qt串口上位机控制蓝牙小车解决方案,包含通信协议、UI设计、代码实现和硬件对接。

一、系统架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Qt上位机软件                           │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │  串口配置   │  │  连接管理   │  │  状态显示   │       │
│  │  • 端口选择 │  │  • 连接/断开│  │  • 连接状态 │       │
│  │  • 波特率   │  │  • 自动重连 │  │  • 电池电量 │       │
│  │  • 数据位   │  │  • 心跳检测 │  │  • 信号强度 │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │  运动控制   │  │  模式切换   │  │  参数配置   │       │
│  │  • 方向控制 │  │  • 手动模式 │  │  • 速度调节 │       │
│  │  • 速度滑块 │  │  • 自动模式 │  │  • 转向灵敏度│       │
│  │  • 急停按钮 │  │  • 循迹模式 │  │  • PID参数  │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │  数据监控   │  │  日志记录   │  │  固件升级   │       │
│  │  • 实时曲线 │  │  • 操作日志 │  │  • 在线升级 │       │
│  │  • 传感器数据│  │  • 错误日志 │  │  • 版本管理 │       │
│  │  • 调试信息 │  │  • 导出功能 │  │  • 备份恢复 │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
└─────────────────────────────────────────────────────────────┘
                           │ 串口通信 (UART)
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    HC-05/HC-06 蓝牙模块                    │
└─────────────────────────────────────────────────────────────┘
                           │ 串口透传
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                     STM32/ESP32 主控                        │
├─────────────────────────────────────────────────────────────┤
│  • 电机驱动 (L298N/TB6612)                                 │
│  • 编码器反馈 (速度闭环)                                    │
│  • 超声波避障 (HC-SR04)                                    │
│  • 红外循迹 (TCRT5000)                                     │
│  • 舵机云台 (SG90)                                         │
│  • 电池管理 (电压监测)                                     │
└─────────────────────────────────────────────────────────────┘

二、通信协议设计

2.1 数据帧格式

cpp 复制代码
// 数据帧结构
typedef struct {
    uint8_t header;      // 帧头 0xAA
    uint8_t length;      // 数据长度
    uint8_t command;     // 命令字
    uint8_t data[32];    // 数据载荷
    uint8_t checksum;    // 校验和
    uint8_t footer;     // 帧尾 0x55
} DataFrame;

// 命令字定义
enum CommandType {
    CMD_HEARTBEAT = 0x01,      // 心跳包
    CMD_MOVE_FORWARD = 0x10,   // 前进
    CMD_MOVE_BACKWARD = 0x11,  // 后退
    CMD_TURN_LEFT = 0x12,      // 左转
    CMD_TURN_RIGHT = 0x13,     // 右转
    CMD_STOP = 0x14,           // 停止
    CMD_SET_SPEED = 0x20,      // 设置速度
    CMD_GET_STATUS = 0x30,     // 获取状态
    CMD_SET_MODE = 0x40,       // 设置模式
    CMD_FIRMWARE_UPDATE = 0x50 // 固件升级
};

2.2 控制指令详解

cpp 复制代码
// 运动控制指令
#pragma pack(push, 1)
typedef struct {
    uint8_t direction;    // 方向: 0=停止, 1=前进, 2=后退, 3=左转, 4=右转
    uint8_t speed_left;   // 左轮速度 (0-255)
    uint8_t speed_right;  // 右轮速度 (0-255)
    uint8_t acceleration; // 加速度 (0-100)
} MotionControl;

// 状态反馈
typedef struct {
    uint8_t battery_level; // 电池电量 (0-100%)
    uint16_t voltage;      // 电池电压 (mV)
    int16_t left_speed;    // 左轮实际速度 (rpm)
    int16_t right_speed;   // 右轮实际速度 (rpm)
    uint8_t obstacle_distance; // 障碍物距离 (cm)
    uint8_t temperature;   // 温度 (℃)
    uint8_t mode;          // 当前模式
} CarStatus;
#pragma pack(pop)

三、Qt项目实现

3.1 项目文件结构

复制代码
BluetoothCarController/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── MainWindow.h
│   ├── MainWindow.cpp
│   ├── SerialPortManager.h
│   ├── SerialPortManager.cpp
│   ├── ProtocolParser.h
│   ├── ProtocolParser.cpp
│   ├── CarController.h
│   ├── CarController.cpp
│   ├── DataLogger.h
│   └── DataLogger.cpp
├── ui/
│   ├── MainWindow.ui
│   ├── ControlPanel.ui
│   └── StatusMonitor.ui
├── resources/
│   ├── icons/
│   ├── styles/
│   └── config/
└── docs/

3.2 串口管理类 (SerialPortManager.h)

cpp 复制代码
#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QByteArray>

class SerialPortManager : public QObject
{
    Q_OBJECT

public:
    explicit SerialPortManager(QObject *parent = nullptr);
    ~SerialPortManager();

    // 串口操作
    bool openPort(const QString &portName, int baudRate);
    void closePort();
    bool isOpen() const;
    
    // 发送数据
    bool sendData(const QByteArray &data);
    bool sendCommand(uint8_t command, const QByteArray &payload = QByteArray());
    
    // 获取可用串口
    static QStringList getAvailablePorts();
    static QStringList getBaudRates();

signals:
    void portOpened(bool success);
    void portClosed();
    void dataReceived(const QByteArray &data);
    void connectionLost();
    void errorOccurred(const QString &error);

private slots:
    void onReadyRead();
    void onErrorOccurred(QSerialPort::SerialPortError error);
    void onHeartbeatTimeout();

private:
    QSerialPort *serialPort;
    QTimer *heartbeatTimer;
    bool connected;
    
    // 协议相关
    QByteArray receiveBuffer;
    static const uint8_t FRAME_HEADER = 0xAA;
    static const uint8_t FRAME_FOOTER = 0x55;
    
    uint8_t calculateChecksum(const QByteArray &data);
    bool parseDataFrame(const QByteArray &frame);
};

#endif // SERIALPORTMANAGER_H

3.3 串口管理类实现 (SerialPortManager.cpp)

cpp 复制代码
#include "SerialPortManager.h"
#include <QDebug>

SerialPortManager::SerialPortManager(QObject *parent)
    : QObject(parent), serialPort(nullptr), connected(false)
{
    serialPort = new QSerialPort(this);
    heartbeatTimer = new QTimer(this);
    
    connect(serialPort, &QSerialPort::readyRead, this, &SerialPortManager::onReadyRead);
    connect(serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::onErrorOccurred);
    connect(heartbeatTimer, &QTimer::timeout, this, &SerialPortManager::onHeartbeatTimeout);
    
    heartbeatTimer->setInterval(1000); // 1秒心跳
}

SerialPortManager::~SerialPortManager()
{
    closePort();
}

bool SerialPortManager::openPort(const QString &portName, int baudRate)
{
    serialPort->setPortName(portName);
    serialPort->setBaudRate(baudRate);
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);
    
    if (serialPort->open(QIODevice::ReadWrite)) {
        connected = true;
        heartbeatTimer->start();
        emit portOpened(true);
        qDebug() << "串口打开成功:" << portName;
        return true;
    } else {
        emit portOpened(false);
        qDebug() << "串口打开失败:" << serialPort->errorString();
        return false;
    }
}

void SerialPortManager::closePort()
{
    if (serialPort->isOpen()) {
        heartbeatTimer->stop();
        serialPort->close();
        connected = false;
        emit portClosed();
        qDebug() << "串口已关闭";
    }
}

bool SerialPortManager::sendCommand(uint8_t command, const QByteArray &payload)
{
    if (!connected) return false;
    
    QByteArray frame;
    frame.append(FRAME_HEADER);
    frame.append(static_cast<char>(payload.size() + 1)); // 长度包含命令字
    frame.append(static_cast<char>(command));
    frame.append(payload);
    
    // 计算校验和
    uint8_t checksum = calculateChecksum(frame.mid(1)); // 从长度字节开始
    frame.append(static_cast<char>(checksum));
    frame.append(FRAME_FOOTER);
    
    return sendData(frame);
}

bool SerialPortManager::sendData(const QByteArray &data)
{
    if (!serialPort->isOpen()) return false;
    
    qint64 bytesWritten = serialPort->write(data);
    return bytesWritten == data.size();
}

void SerialPortManager::onReadyRead()
{
    receiveBuffer.append(serialPort->readAll());
    
    // 解析数据帧
    while (receiveBuffer.size() >= 4) {
        int headerIndex = receiveBuffer.indexOf(FRAME_HEADER);
        if (headerIndex == -1) {
            receiveBuffer.clear();
            return;
        }
        
        if (headerIndex > 0) {
            receiveBuffer.remove(0, headerIndex);
        }
        
        if (receiveBuffer.size() < 4) return;
        
        uint8_t length = static_cast<uint8_t>(receiveBuffer[1]);
        int frameSize = 4 + length; // 头+长+数据+校验+尾
        
        if (receiveBuffer.size() < frameSize) return;
        
        QByteArray frame = receiveBuffer.mid(0, frameSize);
        if (parseDataFrame(frame)) {
            receiveBuffer.remove(0, frameSize);
        } else {
            receiveBuffer.remove(0, 1); // 移除错误的头
        }
    }
}

bool SerialPortManager::parseDataFrame(const QByteArray &frame)
{
    if (frame.size() < 4) return false;
    if (static_cast<uint8_t>(frame[0]) != FRAME_HEADER) return false;
    if (static_cast<uint8_t>(frame[frame.size()-1]) != FRAME_FOOTER) return false;
    
    uint8_t length = static_cast<uint8_t>(frame[1]);
    uint8_t command = static_cast<uint8_t>(frame[2]);
    
    if (length < 1) return false;
    
    QByteArray payload = frame.mid(3, length - 1);
    uint8_t receivedChecksum = static_cast<uint8_t>(frame[3 + length - 1]);
    
    // 验证校验和
    QByteArray dataForChecksum = frame.mid(1, 2 + payload.size());
    uint8_t calculatedChecksum = calculateChecksum(dataForChecksum);
    
    if (receivedChecksum != calculatedChecksum) {
        qDebug() << "校验和错误!";
        return false;
    }
    
    // 处理有效数据帧
    emit dataReceived(payload);
    return true;
}

uint8_t SerialPortManager::calculateChecksum(const QByteArray &data)
{
    uint8_t checksum = 0;
    for (int i = 0; i < data.size(); ++i) {
        checksum += static_cast<uint8_t>(data[i]);
    }
    return checksum;
}

QStringList SerialPortManager::getAvailablePorts()
{
    QStringList ports;
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos) {
        ports << info.portName();
    }
    return ports;
}

void SerialPortManager::onHeartbeatTimeout()
{
    if (connected) {
        sendCommand(CMD_HEARTBEAT);
    }
}

3.4 小车控制类 (CarController.h)

cpp 复制代码
#ifndef CARCONTROLLER_H
#define CARCONTROLLER_H

#include <QObject>
#include <QTimer>
#include "SerialPortManager.h"

class CarController : public QObject
{
    Q_OBJECT

public:
    explicit CarController(QObject *parent = nullptr);
    
    // 运动控制
    void moveForward(int speed = 100);
    void moveBackward(int speed = 100);
    void turnLeft(int speed = 100);
    void turnRight(int speed = 100);
    void stop();
    void emergencyStop();
    
    // 速度控制
    void setSpeed(int leftSpeed, int rightSpeed);
    void setAcceleration(int accel);
    
    // 模式切换
    enum CarMode {
        MODE_MANUAL = 0,
        MODE_AUTO = 1,
        MODE_TRACKING = 2,
        MODE_OBSTACLE_AVOID = 3
    };
    void setMode(CarMode mode);
    
    // 状态查询
    void requestStatus();
    CarStatus getCurrentStatus() const { return currentStatus; }

signals:
    void statusUpdated(const CarStatus &status);
    void connectionStatusChanged(bool connected);
    void errorOccurred(const QString &error);

private slots:
    void onDataReceived(const QByteArray &data);
    void onConnectionLost();

private:
    SerialPortManager *serialManager;
    CarStatus currentStatus;
    QTimer *statusTimer;
    
    void parseStatusData(const QByteArray &data);
};

#endif // CARCONTROLLER_H

3.5 小车控制类实现 (CarController.cpp)

cpp 复制代码
#include "CarController.h"
#include <QDebug>

CarController::CarController(QObject *parent)
    : QObject(parent)
{
    serialManager = new SerialPortManager(this);
    statusTimer = new QTimer(this);
    
    connect(serialManager, &SerialPortManager::dataReceived, 
            this, &CarController::onDataReceived);
    connect(serialManager, &SerialPortManager::connectionLost,
            this, &CarController::onConnectionLost);
    connect(statusTimer, &QTimer::timeout, this, &CarController::requestStatus);
    
    statusTimer->setInterval(1000); // 每秒请求一次状态
}

void CarController::moveForward(int speed)
{
    MotionControl motion;
    motion.direction = 1; // 前进
    motion.speed_left = static_cast<uint8_t>(speed);
    motion.speed_right = static_cast<uint8_t>(speed);
    motion.acceleration = 50;
    
    QByteArray payload(reinterpret_cast<const char*>(&motion), sizeof(MotionControl));
    serialManager->sendCommand(CMD_MOVE_FORWARD, payload);
}

void CarController::turnLeft(int speed)
{
    MotionControl motion;
    motion.direction = 3; // 左转
    motion.speed_left = static_cast<uint8_t>(speed * 0.5);  // 左轮慢
    motion.speed_right = static_cast<uint8_t>(speed);       // 右轮快
    motion.acceleration = 50;
    
    QByteArray payload(reinterpret_cast<const char*>(&motion), sizeof(MotionControl));
    serialManager->sendCommand(CMD_TURN_LEFT, payload);
}

void CarController::stop()
{
    serialManager->sendCommand(CMD_STOP);
}

void CarController::emergencyStop()
{
    stop();
    // 可以添加额外的紧急处理
    qDebug() << "紧急制动!";
}

void CarController::setSpeed(int leftSpeed, int rightSpeed)
{
    MotionControl motion;
    motion.direction = 0; // 停止方向,仅设置速度
    motion.speed_left = static_cast<uint8_t>(leftSpeed);
    motion.speed_right = static_cast<uint8_t>(rightSpeed);
    motion.acceleration = 100;
    
    QByteArray payload(reinterpret_cast<const char*>(&motion), sizeof(MotionControl));
    serialManager->sendCommand(CMD_SET_SPEED, payload);
}

void CarController::setMode(CarMode mode)
{
    uint8_t modeValue = static_cast<uint8_t>(mode);
    QByteArray payload;
    payload.append(static_cast<char>(modeValue));
    serialManager->sendCommand(CMD_SET_MODE, payload);
}

void CarController::requestStatus()
{
    serialManager->sendCommand(CMD_GET_STATUS);
}

void CarController::onDataReceived(const QByteArray &data)
{
    if (data.size() >= static_cast<int>(sizeof(CarStatus))) {
        parseStatusData(data);
        emit statusUpdated(currentStatus);
    }
}

void CarController::parseStatusData(const QByteArray &data)
{
    if (data.size() >= static_cast<int>(sizeof(CarStatus))) {
        memcpy(&currentStatus, data.constData(), sizeof(CarStatus));
        
        qDebug() << "收到状态更新:";
        qDebug() << "  电池电量:" << currentStatus.battery_level << "%";
        qDebug() << "  电池电压:" << currentStatus.voltage << "mV";
        qDebug() << "  左轮速度:" << currentStatus.left_speed << "rpm";
        qDebug() << "  右轮速度:" << currentStatus.right_speed << "rpm";
        qDebug() << "  障碍物距离:" << currentStatus.obstacle_distance << "cm";
    }
}

3.6 主窗口UI设计 (MainWindow.ui XML)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1000</width>
    <height>700</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>蓝牙小车控制系统</string>
  </property>
  
  <!-- 中央控件 -->
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    
    <!-- 左侧控制面板 -->
    <widget class="QGroupBox" name="controlPanel">
     <property name="title">
      <string>运动控制</string>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout">
      
      <!-- 方向控制按钮 -->
      <widget class="QGridLayout" name="directionLayout">
       <item row="0" column="1">
        <widget class="QPushButton" name="btnForward">
         <property name="text">
          <string>↑ 前进</string>
         </property>
         <property name="minimumSize">
          <size>
           <width>80</width>
           <height>60</height>
          </size>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QPushButton" name="btnLeft">
         <property name="text">
          <string>← 左转</string>
         </property>
         <property name="minimumSize">
          <size>
           <width>80</width>
           <height>60</height>
          </size>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QPushButton" name="btnStop">
         <property name="text">
          <string>■ 停止</string>
         </property>
         <property name="minimumSize">
          <size>
           <width>80</width>
           <height>60</height>
          </size>
         </property>
         <property name="styleSheet">
          <string>QPushButton { background-color: red; color: white; }</string>
         </property>
        </widget>
       </item>
       <item row="1" column="2">
        <widget class="QPushButton" name="btnRight">
         <property name="text">
          <string>右转 →</string>
         </property>
         <property name="minimumSize">
          <size>
           <width>80</width>
           <height>60</height>
          </size>
         </property>
        </widget>
       </item>
       <item row="2" column="1">
        <widget class="QPushButton" name="btnBackward">
         <property name="text">
          <string>↓ 后退</string>
         </property>
         <property name="minimumSize">
          <size>
           <width>80</width>
           <height>60</height>
          </size>
         </property>
        </widget>
       </item>
      </widget>
      
      <!-- 速度控制 -->
      <widget class="QGroupBox" name="speedGroup">
       <property name="title">
        <string>速度控制</string>
       </property>
       <layout class="QVBoxLayout" name="speedLayout">
        <widget class="QLabel" name="lblSpeed">
         <property name="text">
          <string>速度: 100%</string>
         </property>
        </widget>
        <widget class="QSlider" name="speedSlider">
         <property name="orientation">
          <enum>Qt::Horizontal</enum>
         </property>
         <property name="minimum">
          <number>0</number>
         </property>
         <property name="maximum">
          <number>255</number>
         </property>
         <property name="value">
          <number>100</number>
         </property>
        </widget>
       </layout>
      </widget>
      
      <!-- 模式选择 -->
      <widget class="QGroupBox" name="modeGroup">
       <property name="title">
        <string>工作模式</string>
       </property>
       <layout class="QVBoxLayout" name="modeLayout">
        <widget class="QRadioButton" name="rbManual">
         <property name="text">
          <string>手动控制</string>
         </property>
         <property name="checked">
          <bool>true</bool>
         </property>
        </widget>
        <widget class="QRadioButton" name="rbAuto">
         <property name="text">
          <string>自动避障</string>
         </property>
        </widget>
        <widget class="QRadioButton" name="rbTracking">
         <property name="text">
          <string>循迹模式</string>
         </property>
        </widget>
       </layout>
      </widget>
      
     </layout>
    </widget>
    
    <!-- 右侧状态面板 -->
    <widget class="QGroupBox" name="statusPanel">
     <property name="title">
      <string>状态监控</string>
     </property>
     <layout class="QVBoxLayout" name="statusLayout">
      
      <!-- 连接状态 -->
      <widget class="QGroupBox" name="connectionGroup">
       <property name="title">
        <string>连接状态</string>
       </property>
       <layout class="QVBoxLayout" name="connLayout">
        <widget class="QLabel" name="lblConnectionStatus">
         <property name="text">
          <string>未连接</string>
         </property>
         <property name="styleSheet">
          <string>QLabel { color: red; font-weight: bold; }</string>
         </property>
        </widget>
        <widget class="QPushButton" name="btnConnect">
         <property name="text">
          <string>连接</string>
         </property>
        </widget>
       </layout>
      </widget>
      
      <!-- 电池状态 -->
      <widget class="QGroupBox" name="batteryGroup">
       <property name="title">
        <string>电池状态</string>
       </property>
       <layout class="QVBoxLayout" name="batteryLayout">
        <widget class="QProgressBar" name="batteryProgress">
         <property name="value">
          <number>85</number>
         </property>
        </widget>
        <widget class="QLabel" name="lblBatteryVoltage">
         <property name="text">
          <string>电压: 7.4V</string>
         </property>
        </widget>
       </layout>
      </widget>
      
      <!-- 实时数据显示 -->
      <widget class="QGroupBox" name="realTimeGroup">
       <property name="title">
        <string>实时数据</string>
       </property>
       <layout class="QFormLayout" name="rtLayout">
        <item row="0" column="0">
         <widget class="QLabel" name="label_1">
          <property name="text">
           <string>左轮速度:</string>
          </property>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QLabel" name="lblLeftSpeed">
          <property name="text">
           <string>0 rpm</string>
          </property>
         </widget>
        </item>
        <item row="1" column="0">
         <widget class="QLabel" name="label_2">
          <property name="text">
           <string>右轮速度:</string>
          </property>
         </widget>
        </item>
        <item row="1" column="1">
         <widget class="QLabel" name="lblRightSpeed">
          <property name="text">
           <string>0 rpm</string>
          </property>
         </widget>
        </item>
        <item row="2" column="0">
         <widget class="QLabel" name="label_3">
          <property name="text">
           <string>障碍物距离:</string>
          </property>
         </widget>
        </item>
        <item row="2" column="1">
         <widget class="QLabel" name="lblObstacleDist">
          <property name="text">
           <string>-- cm</string>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
      
     </layout>
    </widget>
    
   </layout>
  </widget>
  
  <!-- 菜单栏 -->
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1000</width>
     <height>25</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>文件</string>
    </property>
    <addaction name="actionSaveConfig"/>
    <addaction name="actionLoadConfig"/>
    <addaction name="separator"/>
    <addaction name="actionExit"/>
   </widget>
   <widget class="QMenu" name="menuTools">
    <property name="title">
     <string>工具</string>
    </property>
    <addaction name="actionFirmwareUpdate"/>
    <addaction name="actionCalibration"/>
   </widget>
   <addaction name="menuFile"/>
   <addaction name="menuTools"/>
  </widget>
  
  <!-- 状态栏 -->
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
</ui>

3.7 主窗口实现 (MainWindow.cpp)

cpp 复制代码
#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , carController(nullptr)
{
    ui->setupUi(this);
    
    // 初始化串口管理器
    serialManager = new SerialPortManager(this);
    carController = new CarController(this);
    carController->setSerialManager(serialManager);
    
    // 初始化UI
    initUI();
    initConnections();
    
    // 更新可用串口列表
    updateSerialPorts();
}

void MainWindow::initUI()
{
    // 设置窗口图标和样式
    setWindowIcon(QIcon(":/icons/car_icon.png"));
    
    // 初始化串口配置下拉框
    ui->comboBoxPort->addItems(SerialPortManager::getAvailablePorts());
    ui->comboBoxBaudRate->addItems({"9600", "19200", "38400", "57600", "115200"});
    ui->comboBoxBaudRate->setCurrentText("115200");
    
    // 设置速度滑块
    ui->speedSlider->setRange(0, 255);
    ui->speedSlider->setValue(100);
    
    // 初始状态
    updateConnectionStatus(false);
}

void MainWindow::initConnections()
{
    // 连接按钮信号
    connect(ui->btnConnect, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
    connect(ui->btnForward, &QPushButton::pressed, this, &MainWindow::onForwardPressed);
    connect(ui->btnBackward, &QPushButton::pressed, this, &MainWindow::onBackwardPressed);
    connect(ui->btnLeft, &QPushButton::pressed, this, &MainWindow::onLeftPressed);
    connect(ui->btnRight, &QPushButton::pressed, this, &MainWindow::onRightPressed);
    connect(ui->btnStop, &QPushButton::clicked, this, &MainWindow::onStopClicked);
    
    // 速度滑块
    connect(ui->speedSlider, &QSlider::valueChanged, this, &MainWindow::onSpeedChanged);
    
    // 模式切换
    connect(ui->rbManual, &QRadioButton::toggled, this, &MainWindow::onModeChanged);
    connect(ui->rbAuto, &QRadioButton::toggled, this, &MainWindow::onModeChanged);
    connect(ui->rbTracking, &QRadioButton::toggled, this, &MainWindow::onModeChanged);
    
    // 控制器信号
    connect(carController, &CarController::statusUpdated, 
            this, &MainWindow::onStatusUpdated);
    connect(carController, &CarController::connectionStatusChanged,
            this, &MainWindow::updateConnectionStatus);
}

void MainWindow::onConnectClicked()
{
    if (serialManager->isOpen()) {
        serialManager->closePort();
        ui->btnConnect->setText("连接");
    } else {
        QString portName = ui->comboBoxPort->currentText();
        int baudRate = ui->comboBoxBaudRate->currentText().toInt();
        
        if (serialManager->openPort(portName, baudRate)) {
            ui->btnConnect->setText("断开");
            carController->requestStatus();
        }
    }
}

void MainWindow::onForwardPressed()
{
    int speed = ui->speedSlider->value();
    carController->moveForward(speed);
}

void MainWindow::onSpeedChanged(int value)
{
    ui->lblSpeed->setText(QString("速度: %1%").arg(value * 100 / 255));
}

void MainWindow::onStatusUpdated(const CarStatus &status)
{
    // 更新电池状态
    ui->batteryProgress->setValue(status.battery_level);
    ui->lblBatteryVoltage->setText(QString("电压: %1V").arg(status.voltage / 1000.0));
    
    // 更新速度显示
    ui->lblLeftSpeed->setText(QString("%1 rpm").arg(status.left_speed));
    ui->lblRightSpeed->setText(QString("%1 rpm").arg(status.right_speed));
    
    // 更新障碍物距离
    if (status.obstacle_distance < 255) {
        ui->lblObstacleDist->setText(QString("%1 cm").arg(status.obstacle_distance));
    } else {
        ui->lblObstacleDist->setText("-- cm");
    }
    
    // 更新连接状态
    updateConnectionStatus(true);
}

void MainWindow::updateConnectionStatus(bool connected)
{
    if (connected) {
        ui->lblConnectionStatus->setText("已连接");
        ui->lblConnectionStatus->setStyleSheet("QLabel { color: green; font-weight: bold; }");
    } else {
        ui->lblConnectionStatus->setText("未连接");
        ui->lblConnectionStatus->setStyleSheet("QLabel { color: red; font-weight: bold; }");
    }
}

四、STM32下位机代码

4.1 蓝牙接收处理 (bluetooth.c)

c 复制代码
#include "bluetooth.h"
#include "motor_control.h"

#define RX_BUFFER_SIZE 128
uint8_t rx_buffer[RX_BUFFER_SIZE];
uint8_t rx_index = 0;

void Bluetooth_Init(void)
{
    // 初始化USART1用于蓝牙通信
    USART_InitTypeDef USART_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA9(TX)和PA10(RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART1);
        
        if (rx_index < RX_BUFFER_SIZE) {
            rx_buffer[rx_index++] = data;
        }
        
        // 解析数据帧
        if (data == 0x55) { // 帧尾
            ParseDataFrame(rx_buffer, rx_index);
            rx_index = 0;
            memset(rx_buffer, 0, RX_BUFFER_SIZE);
        }
    }
}

void ParseDataFrame(uint8_t *data, uint16_t length)
{
    if (length < 4) return;
    if (data[0] != 0xAA) return; // 检查帧头
    
    uint8_t command = data[2];
    uint8_t *payload = &data[3];
    
    switch (command) {
        case CMD_MOVE_FORWARD:
            HandleMotionControl(payload);
            break;
        case CMD_STOP:
            Motor_Stop();
            break;
        case CMD_GET_STATUS:
            SendStatusResponse();
            break;
        // ... 其他命令处理
    }
}

参考代码 基于Qt的串口上位机控制蓝牙小车程序 www.youwenfan.com/contentcsv/122865.html

五、硬件连接说明

5.1 硬件清单

模块 型号 数量 备注
主控板 STM32F103C8T6 1 72MHz, 64KB Flash
蓝牙模块 HC-05 1 蓝牙2.0,串口透传
电机驱动 L298N 1 双路直流电机驱动
直流电机 12V减速电机 2 带编码器
电源 18650锂电池 2 7.4V 2200mAh
稳压模块 LM2596 1 5V输出给STM32
超声波 HC-SR04 1 避障传感器
循迹模块 TCRT5000 2 红外循迹

5.2 接线图

复制代码
STM32F103C8T6    HC-05蓝牙模块
PA9  (TX1)  -----> RX
PA10 (RX1)  -----> TX
3.3V       -----> VCC
GND        -----> GND

STM32F103C8T6    L298N电机驱动
PB6  (PWM)  -----> ENA
PB7  (PWM)  -----> ENB
PB8        -----> IN1
PB9        -----> IN2
PB10       -----> IN3
PB11       -----> IN4
5V         -----> 5V
GND        -----> GND

六、使用说明

6.1 软件安装

  1. 下载并安装Qt Creator(推荐Qt 5.15+)
  2. 克隆项目代码:git clone https://github.com/your-repo/BluetoothCarController.git
  3. 打开CMakeLists.txt或.pro文件
  4. 编译运行

6.2 硬件配置

  1. 将HC-05蓝牙模块配置为从机模式,波特率115200
  2. 烧录STM32下位机程序
  3. 连接电机和传感器
  4. 上电测试

6.3 操作步骤

  1. 打开Qt上位机软件
  2. 选择正确的串口号和波特率
  3. 点击"连接"按钮
  4. 使用方向按钮控制小车移动
  5. 观察状态监控面板的数据

七、故障排除

问题 解决方法
无法连接蓝牙 检查HC-05是否配对,确认波特率设置
小车不动作 检查电机驱动接线,确认STM32程序正常运行
控制延迟大 降低数据刷新频率,优化通信协议
电池显示异常 校准电压检测电路,检查分压电阻
相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript