基于Android与STM32的智能步进电机控制简单实现(WiFi/蓝牙双模+MQTT上云)

1.概述

本文介绍了一种基于 Android手机APPSTM32F103C8T6 微控制器和 ESP8266/HC-05 无线通信模块的 步进电机(28BYJ-48)控制系统。该系统支持 WiFi(TCP)和蓝牙(串口)双模通信,用户可通过手机APP实时调节电机的 转速 和 转向,同时 ESP8266 模块将电机运行数据通过 MQTT协议 上传至 公共服务器,实现远程监控。如果文中有不当的地方,还请各位大佬加以中指正,笔者一定会虚心求教。

大致架构:

  A[Android App] -->|WiFi TCP| B[ESP8266]
  A -->|蓝牙串口| C[HC-05]
  B -->|UART| D[STM32F103C8T6]
  C -->|UART| D
  D -->|IIC|E[OLED显示屏]
  D --> F[ULN2003驱动]
  F --> G[28BYJ48步进电机]
  B -->|MQTT| H[公共服务器--broker.emqx.io]

2.硬件准备

2.1 STM32F10C8T6(主控芯片)

核心架构存储资源关键外设
ARM Cortex-M3,72MHz主频64KB Flash + 20KB SRAMUSART(WiFi/蓝牙)、GPIO(驱动ULN2003和OLED)等

2.2 ESP8266-12E(WiFi模块)

通信协议工作模式数据传输
802.11 b/g/n(2.4GHz)STA(连接热点)/AP(自建热点)

TCP/IP(与Android APP通信)、MQTT(数据上报云端)等

2.3 HC-05(蓝牙模块)

蓝牙版本工作模式通信方式
2.0+EDR(兼容Android/iOS)主从一体(默认从模式)串口透传

HC-05蓝牙模块可以通过上电前先按住蓝牙模块上的按键,接通电源,模块上的led灯进入慢闪后再松开按键,此时已经进入AT指令模式,可以进行AT指令设置,部分AT命令如下表所示。

常见AT命令含义

AT+NAME = Yyuan

设置蓝牙名称为Yyuan
AT+ROLE=0/AT+ROLE=10为从模式 、1为主模式
AT+CMODE=0蓝牙连接模式为任意地址连接模式
AT+PSWD=1234蓝牙配对密码为1234
AT+UART=9600,0,0蓝牙通信串口波特率为9600,停止位1位,无校验位
AT+RMAAD清空配对列表

注意:AT命令后面需要换行,然后点发送命令才有效,如果没有换行,发送命令,只会被当作是字符

2.4 OLED(显示屏)

接口显示内容
IIC

电机转速、接收控制命令次数、转向

2.5 ULN2003(电机驱动)

驱动类型输入电压输出电流控制方式
达林顿晶体管阵列5V(兼容STM32 GPIO)500mA/路(可驱动28BYJ-48)四相八拍/四相单四拍/四相双四拍

ULN2003芯片是大电流驱动阵列(放大电流来提高驱动能力),可直接用来驱动步进电机,其中芯片内有非门,会将输入电平倒置。详解:uln2003驱动电路_身在江湖的郭大侠的博客-CSDN博客_uln2003

控制方式中四相指步进电机有 4 组线圈(A、B、C、D 或 A+、A-、B+、B-),每组线圈为一个“相”,八拍电机旋转一个齿距需要 8 个脉冲信号(即 8 步完成一个周期),四拍则是需4个脉冲信号一个周期。

IN111000001
IN201110000
IN300011100
IN400000111

单四拍就是每次仅通电一相线圈。

IN11000
IN20100
IN30010
IN40001

双四拍则是每次通电两组线圈。

IN11001
IN21100
IN30110
IN40011

表中1-通电,0-断电,由于ULN2003公共端为高电平,输出端为低电平导通(输出端-->步进电机-->公共端),而ULN2003芯片会将输入电平倒置,则输入端为高电平。

2.6 28BYJ-48(步进电机)

型号28BYJ-48
工作电压5V
直径28mm
减速比1:64
驱动方式ULN2003
最小步进角度5.625°/64(64步/圈,减速比1:64)

其中减速比由减速齿轮决定,减速比大概是1:64

28BYJ-48步进电机内部实图与原理图右下图可知,转子由8对交替分布的N极与S极的永磁铁组成(共16块)。定子由两组线圈(上下两组),每组线圈两个线圈组成,共四个线圈。定子磁极依其形状称为爪极,由导磁钢板冲压成型,形成八个爪极,一个线圈有八个爪极,交错排列共32个。则使用四相四拍方式控制,步距角度:360°/(4*8)=11.25°则四相八拍,步距角度:360°/(8*8)=5.625°

步进电机步进电机原理图

步进电机原理详解可参考:

【STM32】步进电机及其驱动(ULN2003驱动28BYJ-48丨按键控制电机旋转)_Include everything的博客-CSDN博客_步进电机

3.通信协议

3.1 TCP/IP协议

TCP/IP(Transmission Control Protocol/Internet Protocol)是互联网最核心的通信协议族,定义了数据如何在网络中传输和路由。它不仅是现代互联网的基础,也是物联网(IoT)、云计算等技术的底层支撑。本项目通过TCP/IP协议进行手机app端与esp8266进行通信,以此来间接控制步进电机。

3.2 MQTT协议

MQTT(Message Queuing Telemetry Transport)是一种轻量级的 发布/订阅(Pub-Sub) 消息传输协议,专为 低带宽、高延迟或不可靠网络 环境设计,广泛应用于物联网(IoT)、远程传感器通信和移动设备场景。本项目通过MQTT协议将步进电机数据上传到公共服务器。

MQTT运行在TCP之上。MQTT协议属于应用层,TCP/IP协议属于传输层。

3.3 数据帧格式

数据头----'^'

 数据尾----'$'

数据内容----Json格式字符串

显示上传数据格式如下

^{"Speed":"1.00RPM","Dir":"ClockWise"}$

控制命令格式如下

^{"Speed":"1","Dir":"1"}$

其中Json字符串中Speed键值为1--速度升1档,-1--降1档;Dir键值1--正转,0-反转

4.Android端实现

笔者在android端简单实现蓝牙通信、MQTT通信与TCP通信。其中蓝牙通信中添加bluetoothAdapter.disable()但是仍然通过app上按键关闭蓝牙,如果想要关掉可能需要手动关闭蓝牙。

源码如下:
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- 兼容所有版本的蓝牙权限配置 -->
    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />  <!-- Android 11及以下 -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!-- Android 12+ 新权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

MainActivity.java

package com.example.communication;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        Button mqttButton = findViewById(R.id.MQTT_button);
        mqttButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, MqttActivity.class);
                startActivity(intent);
            }
        });


        Button bluetoothButton = findViewById(R.id.Bluetooth_button);
        bluetoothButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, BluetoothActivity.class);
                startActivity(intent);
            }
        });

        Button tcpButton = findViewById(R.id.TCP_button);
        tcpButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, TcpClientActivity.class);
                startActivity(intent);
            }
        });
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/main"
    android:gravity="center"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 第一个按钮 -->
    <Button
        android:id="@+id/MQTT_button"
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:text="MQTT连接"
        android:textColor="#FFFFFF"
        android:layout_marginBottom="16dp"/>

    <!-- 第二个按钮 -->
    <Button
        android:id="@+id/TCP_button"
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:text="TCP连接"
        android:textColor="#FFFFFF"
        android:layout_marginBottom="16dp"/>

    <!-- 第三个按钮 -->
    <Button
        android:id="@+id/Bluetooth_button"
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:text="Bluetooh连接"
        android:textColor="#FFFFFF"
        android:layout_marginBottom="16dp"/>


</LinearLayout>

MqttHandler.java

package com.example.communication;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MqttHandler {
    public interface MqttCallback {
        void onConnected();
        void onDisconnected();
        void onMessageReceived(String topic, String message);
        void onError(String error);
    }

    private MqttClient mqttClient;
    private MqttCallback callback;

    public void connect(String brokerUrl, String clientId, MqttCallback callback) {
        this.callback = callback;
        try {
            mqttClient = new MqttClient(brokerUrl, clientId, new MemoryPersistence());
            mqttClient.setCallback(new org.eclipse.paho.client.mqttv3.MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    callback.onDisconnected();
                }

                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    callback.onMessageReceived(topic, new String(message.getPayload()));
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    // 消息发布完成
                }
            });

            MqttConnectOptions options = new MqttConnectOptions();
            options.setCleanSession(true);
            mqttClient.connect(options);
            callback.onConnected();
        } catch (MqttException e) {
            callback.onError(e.getMessage());
        }
    }

    public void disconnect() {
        if (mqttClient != null && mqttClient.isConnected()) {
            try {
                mqttClient.disconnect();
                callback.onDisconnected();
            } catch (MqttException e) {
                callback.onError(e.getMessage());
            }
        }
    }

    public void subscribe(String topic) {
        if (mqttClient != null && mqttClient.isConnected()) {
            try {
                mqttClient.subscribe(topic);
            } catch (MqttException e) {
                callback.onError(e.getMessage());
            }
        }
    }

    public void publish(String topic, String message) {
        if (mqttClient != null && mqttClient.isConnected()) {
            try {
                MqttMessage mqttMessage = new MqttMessage(message.getBytes());
                mqttClient.publish(topic, mqttMessage);
            } catch (MqttException e) {
                callback.onError(e.getMessage());
            }
        }
    }

    public boolean isConnected() {
        return mqttClient != null && mqttClient.isConnected();
    }
}

MqttActivity.java

package com.example.communication;

import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

import org.json.JSONException;

public class MqttActivity extends AppCompatActivity {
    private EditText etBrokerUrl, etClientId,etMqttMessage;
    private AutoCompleteTextView etTopic;
    private Button btnConnectMqtt, btnSubscribe, btnPublish;
    private TextView tvMqttStatus, tvMqttMessages;
    private MqttHandler mqttHandler;

    private Button btnExpedite;
    private Button btnSlowdown;
    private Button btnClockwise;
    private Button btnAnticlockwise;
    ControlMotor controlMotor = new ControlMotor();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mqtt);

        etBrokerUrl = findViewById(R.id.etBrokerUrl);
        etClientId = findViewById(R.id.etClientId);
        etTopic = findViewById(R.id.etTopic);
        etMqttMessage = findViewById(R.id.etMqttMessage);
        btnConnectMqtt = findViewById(R.id.btnConnectMqtt);
        btnSubscribe = findViewById(R.id.btnSubscribe);
        btnPublish = findViewById(R.id.btnPublish);
        tvMqttStatus = findViewById(R.id.tvMqttStatus);
        tvMqttMessages = findViewById(R.id.tvMqttMessages);
        btnExpedite = findViewById(R.id.btnexpedite);
        btnSlowdown = findViewById(R.id.btnslowdown);
        btnClockwise = findViewById(R.id.btnclockwise);
        btnAnticlockwise = findViewById(R.id.btnanticlockwise);

        mqttHandler = new MqttHandler();

        btnPublish.setEnabled(false);
        btnSubscribe.setEnabled(false);
        btnExpedite.setEnabled(false);
        btnSlowdown.setEnabled(false);
        btnClockwise.setEnabled(false);
        btnAnticlockwise.setEnabled(false);

        String[] suggestions = new String[]{"esp8266/status", "app/command"}; // 你的预设选项

        ArrayAdapter<String> adapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_dropdown_item_1line,
                suggestions
        );
        etTopic.setAdapter(adapter);

        btnExpedite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(1));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnSlowdown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(2));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnClockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(3));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnAnticlockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(4));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnConnectMqtt.setOnClickListener(v -> {
            if (mqttHandler.isConnected()) {
                mqttHandler.disconnect();
                btnConnectMqtt.setText("连接");
                tvMqttStatus.setText("状态: 未连接");
            } else {
                String brokerUrl = etBrokerUrl.getText().toString();
                String clientId = etClientId.getText().toString();

                mqttHandler.connect(brokerUrl, clientId, new MqttHandler.MqttCallback() {
                    @Override
                    public void onConnected() {
                        runOnUiThread(() -> {
                            btnConnectMqtt.setText("断开");
                            btnPublish.setEnabled(true);
                            btnSubscribe.setEnabled(true);
                            btnExpedite.setEnabled(true);
                            btnSlowdown.setEnabled(true);
                            btnClockwise.setEnabled(true);
                            btnAnticlockwise.setEnabled(true);
                            tvMqttStatus.setText("状态: 已连接");
                        });
                    }

                    @Override
                    public void onDisconnected() {
                        runOnUiThread(() -> {
                            btnConnectMqtt.setText("连接");
                            btnPublish.setEnabled(false);
                            btnSubscribe.setEnabled(false);
                            btnExpedite.setEnabled(false);
                            btnSlowdown.setEnabled(false);
                            btnClockwise.setEnabled(false);
                            btnAnticlockwise.setEnabled(false);
                            tvMqttStatus.setText("状态: 未连接");
                        });
                    }

                    @Override
                    public void onMessageReceived(String topic, String message) {
                        runOnUiThread(() -> {
                            tvMqttMessages.append("主题: " + topic + ", 消息: " + message + "\n");
                        });
                    }

                    @Override
                    public void onError(String error) {
                        runOnUiThread(() -> {
                            tvMqttStatus.setText("错误: " + error);
                        });
                    }
                });
            }
        });

        btnSubscribe.setOnClickListener(v -> {
            if (mqttHandler.isConnected()) {
                String topic = etTopic.getText().toString();
                mqttHandler.subscribe(topic);
                tvMqttMessages.append("已订阅主题: " + topic + "\n");
            }
        });

        btnPublish.setOnClickListener(v -> {
            String message = etMqttMessage.getText().toString();
            sendMessage(message);
        });
    }

    private void sendMessage(String message) {
        if (mqttHandler.isConnected()) {
            String topic = etTopic.getText().toString();
            mqttHandler.publish(topic, message);
            tvMqttMessages.append("发布到 " + topic + ": " + message + "\n");
            etMqttMessage.setText("");
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mqttHandler != null && mqttHandler.isConnected()) {
            mqttHandler.disconnect();
        }
    }
}

activity_mqtt.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/etBrokerUrl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="MQTT Broker URL"
        android:inputType="textUri"
        android:text="tcp://broker.emqx.io:1883"/>

    <EditText
        android:id="@+id/etClientId"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="客户端ID"
        android:inputType="text"
        android:text="Yyuan"/>

    <Button
        android:id="@+id/btnConnectMqtt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="连接"/>

    <!-- <EditText
        android:id="@+id/etTopic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="主题"/>-->
    <AutoCompleteTextView
        android:id="@+id/etTopic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="主题"
        android:text="esp8266/status"
        android:completionThreshold="1"
        android:inputType="text"/>

    <Button
        android:id="@+id/btnSubscribe"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="订阅"/>

    <EditText
        android:id="@+id/etMqttMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入消息"/>

    <Button
        android:id="@+id/btnPublish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发布"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btnexpedite"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="加速"/>
        <Button
            android:id="@+id/btnslowdown"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="减速"/>
        <Button
            android:id="@+id/btnclockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="正转"/>
        <Button
            android:id="@+id/btnanticlockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="反转"/>
    </LinearLayout>

    <TextView
        android:id="@+id/tvMqttStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="状态: 未连接"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/tvMqttMessages"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </ScrollView>
</LinearLayout>

TcpClient.java

package com.example.communication;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TcpClient {
    public interface TcpListener {
        void onConnected();
        void onDisconnected();
        void onMessageReceived(String message);
        void onError(String error);
    }

    private String serverIp;
    private int serverPort;
    private TcpListener listener;
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;
    private boolean isConnected = false;

    private final Handler mainHandler; // 添加主线程Handler
    public TcpClient(String serverIp, int serverPort, TcpListener listener) {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        this.listener = listener;
        this.mainHandler = new Handler(Looper.getMainLooper()); // 初始化主线程Handler
    }

    public void connect() {
        new Thread(() -> {
            try {
                socket = new Socket(serverIp, serverPort);
                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                isConnected = true;
                listener.onConnected();

                // 持续接收消息
                String message;
                while (isConnected && (message = in.readLine()) != null) {
                    listener.onMessageReceived(message);
                }
            } catch (IOException e) {
                listener.onError(e.getMessage());
            } finally {
                disconnect();
            }
        }).start();
    }

    public void disconnect() {
        isConnected = false;
        try {
            if (out != null) out.close();
            if (in != null) in.close();
            if (socket != null) socket.close();
            listener.onDisconnected();
        } catch (IOException e) {
            listener.onError(e.getMessage());
        }
    }

    private void runOnUiThread(Runnable action) {
        mainHandler.post(action);
    }

    public void sendMessage(String message) {
        if (!isConnected || out == null) {
            runOnUiThread(() -> listener.onError("未连接或输出流不可用"));
            return;
        }

        new Thread(() -> {
            try {
                out.print(message);
                out.flush();
            } catch (Exception e) {
                runOnUiThread(() -> listener.onError("发送失败: " + e.getMessage()));
                disconnect();
            }
        }).start();
    }

    public boolean isConnected() {
        return isConnected;
    }
}

TcpClientActivity.java

package com.example.communication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

import org.json.JSONException;

public class TcpClientActivity extends AppCompatActivity {
    private EditText etServerIp, etServerPort, etMessage;
    private Button btnConnect, btnSend;
    private TextView tvStatus, tvReceived;
    private TcpClient tcpClient;

    private Button btnExpedite;
    private Button btnSlowdown;
    private Button btnClockwise;
    private Button btnAnticlockwise;
    ControlMotor controlMotor = new ControlMotor();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcp_client);

        etServerIp = findViewById(R.id.etServerIp);
        etServerPort = findViewById(R.id.etServerPort);
        etMessage = findViewById(R.id.etMessage);
        btnConnect = findViewById(R.id.btnConnect);
        btnSend = findViewById(R.id.btnSend);
        tvStatus = findViewById(R.id.tvStatus);
        tvReceived = findViewById(R.id.tvReceived);
        btnExpedite = findViewById(R.id.btnexpedite);
        btnSlowdown = findViewById(R.id.btnslowdown);
        btnClockwise = findViewById(R.id.btnclockwise);
        btnAnticlockwise = findViewById(R.id.btnanticlockwise);

        btnExpedite.setEnabled(false);
        btnSlowdown.setEnabled(false);
        btnClockwise.setEnabled(false);
        btnAnticlockwise.setEnabled(false);
        btnSend.setEnabled(false);
        btnExpedite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(1));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnSlowdown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(2));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnClockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(3));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnAnticlockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(4));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnConnect.setOnClickListener(v -> {
            if (tcpClient != null && tcpClient.isConnected()) {
                tcpClient.disconnect();
                btnConnect.setText("连接");
                tvStatus.setText("状态: 未连接");
                btnExpedite.setEnabled(false);
                btnSlowdown.setEnabled(false);
                btnClockwise.setEnabled(false);
                btnAnticlockwise.setEnabled(false);
                btnSend.setEnabled(false);
            } else {
                String ip = etServerIp.getText().toString();
                int port = Integer.parseInt(etServerPort.getText().toString());
                tcpClient = new TcpClient(ip, port, new TcpClient.TcpListener() {
                    @Override
                    public void onConnected() {
                        runOnUiThread(() -> {
                            btnConnect.setText("断开");
                            tvStatus.setText("状态: 已连接");
                            btnExpedite.setEnabled(true);
                            btnSlowdown.setEnabled(true);
                            btnClockwise.setEnabled(true);
                            btnAnticlockwise.setEnabled(true);
                            btnSend.setEnabled(true);
                        });
                    }

                    @Override
                    public void onDisconnected() {
                        runOnUiThread(() -> {
                            btnConnect.setText("连接");
                            tvStatus.setText("状态: 未连接");
                        });
                    }

                    @Override
                    public void onMessageReceived(String message) {
                        runOnUiThread(() -> {
                            tvReceived.append("接收: " + message + "\n");
                        });
                    }

                    @Override
                    public void onError(String error) {
                        runOnUiThread(() -> {
                            tvStatus.setText("错误: " + error);
                        });
                    }
                });
                tcpClient.connect();
            }
        });

        btnSend.setOnClickListener(v -> {
            String message = etMessage.getText().toString();
            sendMessage(message);
        });
    }
    private void sendMessage(String message) {
        if (tcpClient != null && tcpClient.isConnected()) {
            if (!message.isEmpty()) {
                tcpClient.sendMessage(message);  // 现在这个方法内部已经处理了线程切换
                runOnUiThread(() -> {
                    tvReceived.append("发送: " + message + "\n");
                    etMessage.setText("");
                });
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (tcpClient != null) {
            tcpClient.disconnect();
        }
    }
}

activity_tcp_client.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/etServerIp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="服务器IP"
        android:inputType="text"
        android:text="192.168.174.117"/>

    <EditText
        android:id="@+id/etServerPort"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="服务器端口"
        android:inputType="number"
        android:text="8080"/>

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="连接"/>

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入消息"/>

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btnexpedite"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="加速"/>
        <Button
            android:id="@+id/btnslowdown"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="减速"/>
        <Button
            android:id="@+id/btnclockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="正转"/>
        <Button
            android:id="@+id/btnanticlockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="反转"/>
    </LinearLayout>

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="状态: 未连接"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/tvReceived"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>
</LinearLayout>

BluetoothActivity.java

package com.example.communication;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.example.communication.ControlMotor;

import org.json.JSONException;

public class BluetoothActivity extends AppCompatActivity {

    private static final String TAG = "BluetoothActivity";
    private static final int REQUEST_ENABLE_BT = 1;
    private static final int REQUEST_PERMISSIONS = 2;
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    private BluetoothAdapter bluetoothAdapter;
    private BluetoothSocket bluetoothSocket;
    private BluetoothDevice connectedDevice;
    private InputStream inputStream;
    private OutputStream outputStream;
    private ConnectedThread connectedThread;

    private Button btnEnableBluetooth;
    private Button btnDiscoverDevices;
    private Button btnSendBluetooth;

    private Button btnExpedite;
    private Button btnSlowdown;
    private Button btnClockwise;
    private Button btnAnticlockwise;
    ControlMotor controlMotor = new ControlMotor();

    private ListView lvDevices;
    private EditText etBluetoothMessage;
    private TextView tvBluetoothStatus;
    private TextView tvBluetoothMessages;

    private ArrayList<BluetoothDevice> deviceList = new ArrayList<>();
    private ArrayAdapter<String> deviceAdapter;
    private ArrayList<String> deviceNames = new ArrayList<>();

    private final Handler handler = new Handler(Looper.getMainLooper());


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth);

        initViews();
        initBluetoothAdapter();
        checkAndRequestPermissions();
        setupListeners();
        registerBluetoothReceiver();
    }

    private void initViews() {
        btnEnableBluetooth = findViewById(R.id.btnEnableBluetooth);
        btnDiscoverDevices = findViewById(R.id.btnDiscoverDevices);
        btnSendBluetooth = findViewById(R.id.btnSendBluetooth);
        lvDevices = findViewById(R.id.lvDevices);
        etBluetoothMessage = findViewById(R.id.etBluetoothMessage);
        tvBluetoothStatus = findViewById(R.id.tvBluetoothStatus);
        tvBluetoothMessages = findViewById(R.id.tvBluetoothMessages);


        btnExpedite = findViewById(R.id.btnexpedite);
        btnSlowdown = findViewById(R.id.btnslowdown);
        btnClockwise = findViewById(R.id.btnclockwise);
        btnAnticlockwise = findViewById(R.id.btnanticlockwise);

        deviceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, deviceNames);
        lvDevices.setAdapter(deviceAdapter);

        btnSendBluetooth.setEnabled(false);
        btnExpedite.setEnabled(false);
        btnSlowdown.setEnabled(false);
        btnClockwise.setEnabled(false);
        btnAnticlockwise.setEnabled(false);
    }

    private void initBluetoothAdapter() {
        try {
            bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            if (bluetoothAdapter == null) {
                tvBluetoothStatus.setText("状态: 设备不支持蓝牙");
                btnEnableBluetooth.setEnabled(false);
                return;
            }

            updateBluetoothStateUI(bluetoothAdapter.isEnabled());
        } catch (SecurityException e) {
            Log.e(TAG, "Bluetooth权限异常", e);
            Toast.makeText(this, "无法访问蓝牙功能,请检查权限", Toast.LENGTH_SHORT).show();
        }
    }

    private void checkAndRequestPermissions() {
        List<String> permissionsNeeded = new ArrayList<>();

        // 基本蓝牙权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH)
                != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.BLUETOOTH);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN)
                != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.BLUETOOTH_ADMIN);
        }

        // Android 12+ 需要的新权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT)
                    != PackageManager.PERMISSION_GRANTED) {
                permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT);
            }
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
                    != PackageManager.PERMISSION_GRANTED) {
                permissionsNeeded.add(Manifest.permission.BLUETOOTH_SCAN);
            }
        }

        // 位置权限(用于设备发现)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }

        if (!permissionsNeeded.isEmpty()) {
            ActivityCompat.requestPermissions(this,
                    permissionsNeeded.toArray(new String[0]),
                    REQUEST_PERMISSIONS);
        }
    }

    private void setupListeners() {

        btnEnableBluetooth.setOnClickListener(v -> toggleBluetooth());
        btnDiscoverDevices.setOnClickListener(v -> discoverDevices());
        btnSendBluetooth.setOnClickListener(v -> sendCommonMessage());

        btnExpedite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(1));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnSlowdown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(2));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnClockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(3));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        btnAnticlockwise.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    sendMessage(controlMotor.selectControl(4));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        lvDevices.setOnItemClickListener((parent, view, position, id) -> {
            if (position < deviceList.size()) {
                connectToDevice(deviceList.get(position));
            }
        });
    }

    private void registerBluetoothReceiver() {
        try {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            registerReceiver(bluetoothReceiver, filter);
        } catch (SecurityException e) {
            Log.e(TAG, "注册广播接收器失败", e);
        }
    }

    private void toggleBluetooth() {
        if (bluetoothAdapter == null) return;

        try {
            if (bluetoothAdapter.isEnabled()) {
                bluetoothAdapter.disable();
                Toast.makeText(this, "请手动关闭蓝牙", Toast.LENGTH_SHORT).show();
            } else {
                if (hasBluetoothConnectPermission()) {
                    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
                    tvBluetoothStatus.setText("状态: 正在请求启用蓝牙...");
                } else {
                    requestBluetoothPermissions();
                }
            }
        } catch (SecurityException e) {
            Log.e(TAG, "蓝牙操作权限异常", e);
            Toast.makeText(this, "权限不足,无法操作蓝牙", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean hasBluetoothConnectPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return ContextCompat.checkSelfPermission(this,
                    Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
        }
        return true;
    }

    private void requestBluetoothPermissions() {
        List<String> permissions = new ArrayList<>();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
            permissions.add(Manifest.permission.BLUETOOTH_SCAN);
        }
        permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);

        ActivityCompat.requestPermissions(this,
                permissions.toArray(new String[0]),
                REQUEST_PERMISSIONS);
    }

    private void discoverDevices() {
        if (!isBluetoothReady()) return;

        try {
            if (!hasDiscoveryPermissions()) {
                requestBluetoothPermissions();
                return;
            }

            deviceList.clear();
            deviceNames.clear();
            deviceAdapter.notifyDataSetChanged();

            // 获取已配对设备
            Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
            if (pairedDevices.size() > 0) {
                for (BluetoothDevice device : pairedDevices) {
                    addDeviceToList(device, true);
                }
            }

            if (bluetoothAdapter.isDiscovering()) {
                bluetoothAdapter.cancelDiscovery();
            }

            bluetoothAdapter.startDiscovery();
            tvBluetoothStatus.setText("状态: 正在搜索设备...");
        } catch (SecurityException e) {
            Log.e(TAG, "设备发现权限异常", e);
            Toast.makeText(this, "权限不足,无法搜索设备", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean hasDiscoveryPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return ContextCompat.checkSelfPermission(this,
                    Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
        }
        return ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
    }

    private void addDeviceToList(BluetoothDevice device, boolean isPaired) {
        if (!deviceList.contains(device)) {
            deviceList.add(device);

            String deviceName;
            try {
                if (hasBluetoothConnectPermission()) {
                    deviceName = device.getName() != null ? device.getName() : "未知设备";
                } else {
                    deviceName = device.getAddress(); // 没有权限时只显示地址
                }
            } catch (SecurityException e) {
                deviceName = device.getAddress(); // 捕获安全异常,只显示地址
                Log.w(TAG, "无法获取设备名称,权限不足", e);
            }

            String displayText = deviceName + "\n" + device.getAddress();
            if (isPaired) {
                displayText += " (已配对)";
            }

            if (deviceName.equals(device.getAddress())) {
                displayText += "\n(无权限查看设备名称)";
            }

            deviceNames.add(displayText);
            deviceAdapter.notifyDataSetChanged();
        }
    }

    private boolean isBluetoothReady() {
        if (bluetoothAdapter == null) {
            tvBluetoothStatus.setText("状态: 设备不支持蓝牙");
            return false;
        }
        if (!bluetoothAdapter.isEnabled()) {
            tvBluetoothStatus.setText("状态: 蓝牙未启用");
            return false;
        }
        return true;
    }

    private void connectToDevice(BluetoothDevice device) {
        if (!isBluetoothReady() || !hasBluetoothConnectPermission()) {
            requestBluetoothPermissions();
            return;
        }

        try {
            if (bluetoothAdapter.isDiscovering()) {
                bluetoothAdapter.cancelDiscovery();
            }

            new Thread(() -> {
                try {
                    bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
                    bluetoothSocket.connect();

                    connectedDevice = device;
                    inputStream = bluetoothSocket.getInputStream();
                    outputStream = bluetoothSocket.getOutputStream();

                    runOnUiThread(() -> {
                        String name;
                        try {
                            name = hasBluetoothConnectPermission() ?
                                    (device.getName() != null ? device.getName() : "未知设备") :
                                    device.getAddress();
                        } catch (SecurityException e) {
                            name = device.getAddress();
                        }

                        tvBluetoothStatus.setText("状态: 已连接到 " + name);
                        btnSendBluetooth.setEnabled(true);
                        btnExpedite.setEnabled(true);
                        btnSlowdown.setEnabled(true);
                        btnClockwise.setEnabled(true);
                        btnAnticlockwise.setEnabled(true);
                        btnDiscoverDevices.setEnabled(true);
                        appendMessage("已连接到: " + name + "\n");
                    });

                    connectedThread = new ConnectedThread();
                    connectedThread.start();

                } catch (IOException e) {
                    runOnUiThread(() -> {
                        tvBluetoothStatus.setText("状态: 连接失败");
                        Toast.makeText(BluetoothActivity.this,
                                "连接失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                    });
                    closeSocket();
                } catch (SecurityException e) {
                    runOnUiThread(() -> {
                        tvBluetoothStatus.setText("状态: 无连接权限");
                        Toast.makeText(BluetoothActivity.this,
                                "无蓝牙连接权限", Toast.LENGTH_SHORT).show();
                    });
                }
            }).start();
        } catch (SecurityException e) {
            Toast.makeText(this, "无权限取消发现或连接设备", Toast.LENGTH_SHORT).show();
        }
    }

    private void sendCommonMessage() {
        String message = etBluetoothMessage.getText().toString().trim();
        sendMessage(message);
    }
    private void sendMessage(String message) {

        if (message.isEmpty() || outputStream == null) return;

        try {
            if (!hasBluetoothConnectPermission()) {
                requestBluetoothPermissions();
                return;
            }

            outputStream.write(message.getBytes());
            appendMessage("发送: " + message + "\n");
            etBluetoothMessage.setText("");
        } catch (IOException e) {
            runOnUiThread(() -> {
                tvBluetoothStatus.setText("状态: 发送失败");
                Toast.makeText(BluetoothActivity.this, "发送失败", Toast.LENGTH_SHORT).show();
            });
        } catch (SecurityException e) {
            Toast.makeText(this, "无蓝牙发送权限", Toast.LENGTH_SHORT).show();
        }
    }

    private void appendMessage(String message) {
        handler.post(() -> tvBluetoothMessages.append(message));
    }

    private void updateBluetoothStateUI(boolean isEnabled) {
        btnEnableBluetooth.setText(isEnabled ? "禁用蓝牙" : "启用蓝牙");
        btnDiscoverDevices.setEnabled(isEnabled);
        if (!isEnabled) {
            btnSendBluetooth.setEnabled(false);
            btnExpedite.setEnabled(false);
            btnSlowdown.setEnabled(false);
            btnClockwise.setEnabled(false);
            btnAnticlockwise.setEnabled(false);
        }
    }

    private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device != null) {
                    addDeviceToList(device, false);
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                tvBluetoothStatus.setText("状态: 设备搜索完成");
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                runOnUiThread(() -> {
                    switch (state) {
                        case BluetoothAdapter.STATE_OFF:
                            tvBluetoothStatus.setText("状态: 蓝牙已禁用");
                            updateBluetoothStateUI(false);
                            break;
                        case BluetoothAdapter.STATE_TURNING_ON:
                            tvBluetoothStatus.setText("状态: 蓝牙正在开启...");
                            break;
                        case BluetoothAdapter.STATE_ON:
                            tvBluetoothStatus.setText("状态: 蓝牙已启用");
                            updateBluetoothStateUI(true);
                            break;
                        case BluetoothAdapter.STATE_TURNING_OFF:
                            tvBluetoothStatus.setText("状态: 蓝牙正在关闭...");
                            break;
                    }
                });
            }
        }
    };

    private class ConnectedThread extends Thread {
        @Override
        public void run() {
            byte[] buffer = new byte[1024];
            int bytes;

            while (!Thread.currentThread().isInterrupted()) {
                try {
                    bytes = inputStream.read(buffer);
                    String receivedMessage = new String(buffer, 0, bytes);
                    appendMessage("接收: " + receivedMessage + "\n");
                } catch (IOException e) {
                    runOnUiThread(() -> {
                        tvBluetoothStatus.setText("状态: 连接已断开");
                        btnSendBluetooth.setEnabled(false);
                    });
                    break;
                }
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == RESULT_OK) {
                tvBluetoothStatus.setText("状态: 蓝牙已启用");
                updateBluetoothStateUI(true);
            } else {
                tvBluetoothStatus.setText("状态: 蓝牙启用被拒绝");
                updateBluetoothStateUI(false);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSIONS) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }

            if (!allGranted) {
                Toast.makeText(this, "部分功能需要权限才能使用", Toast.LENGTH_SHORT).show();
            } else {
                // 权限已授予,可以重新尝试之前的操作
                if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) {
                    toggleBluetooth();
                }
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            unregisterReceiver(bluetoothReceiver);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "广播接收器未注册或已注销");
        }
        closeSocket();
        if (connectedThread != null) {
            connectedThread.interrupt();
        }
    }

    private void closeSocket() {
        try {
            if (bluetoothSocket != null) {
                bluetoothSocket.close();
                bluetoothSocket = null;
            }
        } catch (IOException e) {
            Log.e(TAG, "关闭蓝牙socket时出错", e);
        }
    }
}

activity_bluetooth.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/btnEnableBluetooth"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启用蓝牙"/>

    <Button
        android:id="@+id/btnDiscoverDevices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发现设备"/>

    <ListView
        android:id="@+id/lvDevices"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <EditText
        android:id="@+id/etBluetoothMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入消息"/>

    <Button
        android:id="@+id/btnSendBluetooth"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btnexpedite"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="加速"/>
        <Button
            android:id="@+id/btnslowdown"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="减速"/>
        <Button
            android:id="@+id/btnclockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="正转"/>
        <Button
            android:id="@+id/btnanticlockwise"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="反转"/>
    </LinearLayout>

    <TextView
        android:id="@+id/tvBluetoothStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="状态: 蓝牙未启用"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/tvBluetoothMessages"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </ScrollView>
</LinearLayout>

ControlMotor.java

package com.example.communication;


import org.json.JSONException;
import org.json.JSONObject;

public class ControlMotor {
    String HEAD="^";
    String TAIL="$";
    public String selectControl(int control) throws JSONException {
        JSONObject json = new JSONObject();
        switch (control)
        {
            case 1://加速
                json.put("Speed", "1");
                break;
            case 2://减速
                json.put("Speed", "-1");
                break;
            case 3://正转
                json.put("Dir", "1");
                break;
            case 4://反转
                json.put("Dir", "0");
                break;
            default:
                break;
        }
        String info=HEAD+json.toString()+TAIL;
        return info;
    }

}

5. STM32设计

笔者使用了cJSON库文件用来封装数据成json格式,可以在Github获取Github_cJSON步进电机采用简单的四相单四拍的方式来控制步进电机。

引脚配置

stm32

其中,蓝牙模块VCC端要连接在stm32f10的5v上,如果是3.3v,可能导致供电不足,步进电机也要连接在5v上。

源码如下:

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "string.h"
#include "stdio.h"
#include "OLED.h"
#include "stdbool.h"
#include "stdlib.h"
#include "cJSON.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define LENGTH 50
#define HEAD '^'      // 数据帧头标识符
#define TAIL '$'      // 数据帧尾标识符
/*
四项五线步进电机---28BYJ48
工作电压---5V
减速比---1:64
最小步进角度---360/8/8/64=5.625/64(8--八拍(八拍一个周期,八个周期一圈)64:1--减速比)
这里采用四相单四拍
*/
#define Get_Rotate_Speed(x) (1.0*60*1000/(8*4*64*x))  // 计算转速(RPM)的宏

uint8_t step=0;                  // 步进电机当前步数
uint32_t clock_5ms=0,count=0;    // 5ms计时器和计数器

// 电机控制数据结构体
struct Data{
    volatile int speed;          // 电机速度参数
    volatile bool direction;     // 方向标志:true=正转,false=反转
}data;

bool is_handle=false,is_update_OLED=true;  // 标志位:是否需要处理数据和更新OLED

// UART处理器结构体
typedef struct {
    UART_HandleTypeDef *huart;   // UART句柄指针
    uint8_t ch;                  // 当前接收字符
    uint8_t rxBuf[32];           // 接收缓冲区
    uint8_t idx;                 // 缓冲区索引
    bool is_begin_Rx;            // 接收开始标志
} UART_Handler_t;

// 全局UART处理器实例
UART_Handler_t uart1_handler;    // USART1处理器
UART_Handler_t uart3_handler;    // USART3处理器

/* 初始化系统信息 */
void init_info()
{
    data.speed=10;               // 默认速度
    data.direction=true;         // 默认方向为正转
    
    // 初始化USART1处理器
    uart1_handler.huart=&huart1;
    uart1_handler.idx=0;
    uart1_handler.is_begin_Rx=false;
    memset(uart1_handler.rxBuf,0,32);
    
    // 初始化USART3处理器
    uart3_handler.huart=&huart3;
    uart3_handler.idx=0;
    uart3_handler.is_begin_Rx=false;
    memset(uart3_handler.rxBuf,0,32);
}

/* 重定向printf输出到USART1 */
int fputc(int ch,FILE*f)
{
    HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,HAL_MAX_DELAY);
    return ch;
}

/* 步进电机步进控制函数 */
void Motor_Step() {
    step %= 4;  // 步数取模,保持在0-3范围内
    uint8_t phase = data.direction ?(3 - step):step;  // 根据方向确定相位
    
    // 先关闭所有相线
    HAL_GPIO_WritePin(GPIOA, IN1_Pin|IN2_Pin|IN3_Pin|IN4_Pin, GPIO_PIN_RESET);
    
    // 根据相位激活对应相线
    switch(phase) {
        case 0: HAL_GPIO_WritePin(GPIOA, IN1_Pin, GPIO_PIN_SET); break;
        case 1: HAL_GPIO_WritePin(GPIOA, IN2_Pin, GPIO_PIN_SET); break;
        case 2: HAL_GPIO_WritePin(GPIOA, IN3_Pin, GPIO_PIN_SET); break;
        case 3: HAL_GPIO_WritePin(GPIOA, IN4_Pin, GPIO_PIN_SET); break;
    }
    step++;  // 步数增加
}

/* OLED显示更新函数 */
void Display()
{
    if(is_update_OLED)  // 需要更新OLED显示
    {
				
        is_update_OLED=false;
        char ch[LENGTH];
        memset(ch,0,LENGTH);
        // 显示速度信息
        __disable_irq();  // 禁用中断保护数据
        sprintf(ch,"Speed:%.2fRPM",(float)Get_Rotate_Speed(data.speed));
        __enable_irq();   // 启用中断
        OLED_ShowString(1,1,ch);
            
        // 显示方向信息
        memset(ch,0,LENGTH);
        __disable_irq();
        strcpy(ch, data.direction ? "ClockWise    " : "AntiClockWise");
        __enable_irq();
        OLED_ShowString(3,1,"Dir:");
				
        OLED_ShowString(4,2,ch);
        
        // 显示计数器
				OLED_ShowString(2,1,"Count:");
        OLED_ShowNum(2,7,count,2);
    }
    
    if(is_handle)  // 需要处理数据
    {
        is_handle=false;
        char ch[LENGTH];
        
        // 创建JSON对象
        cJSON *root = cJSON_CreateObject();
        
        // 添加速度信息到JSON
        memset(ch,0,LENGTH);
        __disable_irq();
        sprintf(ch,"%.2fRPM",(float)Get_Rotate_Speed(data.speed));
        __enable_irq();
        cJSON_AddStringToObject(root, "Speed", ch);
        
        // 添加方向信息到JSON
        memset(ch,0,LENGTH);
        __disable_irq();
        strcpy(ch, data.direction ? "ClockWise" : "AntiClockWise");
        __enable_irq();
        cJSON_AddStringToObject(root, "Dir", ch);
        
        // 打印JSON字符串
        char *json = cJSON_Print(root);
        printf("%c%s%c",HEAD,json,TAIL);
        
        // 释放内存
        free(json);        // 释放cJSON_Print分配的内存
        cJSON_Delete(root); // 释放整个JSON树
    }
}

/* UART数据处理函数 */
void ProcessUARTData(UART_Handler_t *handler) 
{
    // 重新启用接收中断
    HAL_UART_Receive_IT(handler->huart, &handler->ch, 1);
    
    if(handler->ch == TAIL) {  // 接收到帧尾
        handler->rxBuf[handler->idx] = '\0';  // 字符串结束符
        count++;  // 计数器增加
        
        // 解析JSON数据
        cJSON *root = cJSON_Parse((char*)handler->rxBuf);
        if(root==NULL)  // 解析失败
        {
            printf("Fail Rec:%s\n", handler->rxBuf);
        }
        else  // 解析成功
        {
            // 获取速度参数
            cJSON *item1 = cJSON_GetObjectItem(root, "Speed");
            // 获取方向参数
            cJSON *item2 = cJSON_GetObjectItem(root, "Dir");
            
            if(item1!=NULL)  // 速度参数存在
            {
                int speed_adjust = 0;
    
                // 检查是否为数字类型
                if(cJSON_IsNumber(item1)) {
                    speed_adjust = cJSON_GetNumberValue(item1);
                }
                // 检查是否为字符串类型且可以转换为数字
                else if(cJSON_IsString(item1)) {
                    char *endptr;
                    speed_adjust = strtod(item1->valuestring, &endptr);
                    // 检查转换是否成功
                    if(endptr == item1->valuestring) {
                        printf("Invalid number string: %s\n", item1->valuestring);
                        speed_adjust = 0; // 设置默认值
                    }
                }
                // 计算新速度(最小限制为5)
                int speed_f = data.speed + (-5*speed_adjust); 
                data.speed = speed_f <= 5 ? 5 : speed_f;
            }
            
            if(item2!=NULL)  // 方向参数存在
            {
                if(cJSON_IsString(item2)) {
                    data.direction = strcmp(item2->valuestring, "0") != 0;
                }
                else if(cJSON_IsNumber(item2)) {
                    data.direction = item2->valueint != 0;
                }
            }
            cJSON_Delete(root);  // 释放JSON树
        }
        
        // 重置接收状态
        is_update_OLED = true;  // 标记需要更新OLED
        handler->idx = 0;
        handler->is_begin_Rx = false;
        memset(handler->rxBuf, 0, sizeof(handler->rxBuf));
    } 
    else if(handler->ch == HEAD) {  // 接收到帧头
        handler->is_begin_Rx = true;  // 开始接收数据
        handler->idx=0;  // 重置缓冲区索引
    }
    else if(handler->is_begin_Rx) {  // 正在接收数据
        handler->rxBuf[handler->idx++] = handler->ch;  // 存储数据
        if(handler->idx >= sizeof(handler->rxBuf)) {  // 缓冲区溢出
            handler->idx = 0;
            handler->is_begin_Rx = false;
        }
    }
}

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    // 硬件初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();
    HAL_Delay(1000);  // 延时等待蓝牙外设稳定
    
    // 初始化USART1
    MX_USART1_UART_Init();
    HAL_UART_Receive_IT(&huart1,&uart1_handler.ch,1);
    
    // 初始化TIM2和USART3
    MX_TIM2_Init();
    MX_USART3_UART_Init();
    HAL_UART_Receive_IT(&huart3,&uart3_handler.ch,1);
    
    // 初始化OLED和系统信息
    OLED_Init();
    HAL_TIM_Base_Start_IT(&htim2);  // 启动定时器2
    init_info();
    
    // 主循环
    while (1)
    {
        Display();  // 更新显示
    }
}



/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
/* 定时器中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM2)  // TIM2中断
        clock_5ms++;  // 5ms计数器增加
        
    if(clock_5ms%(data.speed/5)==0)  // 根据速度控制电机步进
        Motor_Step();
        
    if(clock_5ms==2000)  // 10秒周期(2000*5ms)
    {
        clock_5ms=0;
        is_handle=true;  // 标记需要处理数据
    }
}

/* UART接收完成中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 根据UART调用对应的数据处理函数
    if(huart->Instance == USART1) {
        ProcessUARTData(&uart1_handler);
    }
    else if(huart->Instance == USART3) {
        ProcessUARTData(&uart3_handler);
    }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

OLED.c  (其中OLED_Font.h文件可以见帖子最下端源码链接)

#include "OLED.h"

#include "OLED_Font.h"



/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */

void OLED_WriteCommand(uint8_t Command)
{
    uint8_t buf[2];
    buf[0] = 0x00;  // 控制字节:Co=0, D/C#=0(命令)
    buf[1] = Command;
    HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 100);  // 注意替换hi2c1为实际句柄
}

// 新版本OLED_WriteData(使用硬件I2C)
void OLED_WriteData(uint8_t Data)
{
    uint8_t buf[2];
    buf[0] = 0x40;  // 控制字节:Co=0, D/C#=1(数据)
    buf[1] = Data;
    HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 100);
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
		//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

OLED.h

#ifndef __OLED_H
#define __OLED_H

#include "math.h"

#include "i2c.h"
/*引脚配置*/
#define OLED_W_SCL(x)		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, (GPIO_PinState)(x))
#define OLED_W_SDA(x)		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, (GPIO_PinState)(x))

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

6. ESP8266设计

ESP8266烧录可以直接使用数据线连接至电脑,选择开发板和com口进行烧录程序。

ESP8266运行时,与stm32进行串口通信,需要连接:

ESP8266STM32F10
EN、VCC3V3
GND、D8(GPIO15)GND
TXDRX
RXDTX

D3(GPIO0)低电平---烧录模式,反之为运行模式

笔者使用Arduino用来编程ESP8266,其中MQTT协议需要导入PubSubClient库,工具->管理库->搜索“PubSubClient”->安装即可。

设计主要特点:1) 通过WiFi连接路由器并创建TCP服务器(8080端口);2) 接入MQTT协议与云端代理(broker.emqx.io)通信,支持esp8266/status主题发布和app/command主题订阅;3) 通过串口与STM32等设备通信,采用^$帧头帧尾协议解析数据;4) 同时处理TCP客户端连接请求,实现双向数据传输。

笔者本想采用线程来处理串口接收,MQTT和TCP,但是很容易导致栈崩溃,若只采用MQTT协议进行上传数据并与手机通信,但是通过手机订阅消息,笔者多次尝试容易产生延迟,实时性比较差。因此,才采用这种方式实现。

Arduino源码如下:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// 数据帧标识符
#define HEAD '^'  // 数据帧起始标识
#define TAIL '$'  // 数据帧结束标识

// WiFi配置
const char* ssid = "your_ssid";      // WiFi名称
const char* password = "your_password";    // WiFi密码

// MQTT配置
const char* mqtt_server = "broker.emqx.io";  // MQTT代理服务器地址
const char* mqtt_Pub_topic = "esp8266/status"; // 发布主题
const char* mqtt_Sub_topic = "app/command";  // 订阅主题
const int mqtt_port = 1883;                  // MQTT端口

// 服务器配置
WiFiServer server(8080);   // 创建TCP服务器,监听8080端口
WiFiClient tcpclient;      // TCP客户端对象

// MQTT客户端
WiFiClient espClient;      // WiFi客户端
PubSubClient client(espClient);  // MQTT客户端

volatile bool networkBusy = false;  // 网络忙标志,防止重入

void setup() {
  Serial.begin(115200);  // 初始化串口,用于与STM32通信
  delay(3000);//等待stm32串口初始化
  setup_wifi();  // 连接WiFi

  // 启动TCP服务器
  server.begin();
  Serial.println("服务器已启动");
  
  // 打印网络信息
  Serial.print("服务器IP地址: ");
  Serial.println(WiFi.localIP());
  Serial.print("服务器端口: ");
  Serial.println(8080);

  // 配置MQTT客户端
  client.setServer(mqtt_server, mqtt_port);  // 设置MQTT服务器
  client.setCallback(callback);
}

/* WiFi连接函数 */
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);  // 设置为站模式(连接路由器)
  WiFi.begin(ssid, password);  // 连接WiFi

  // 等待连接成功
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // 连接成功打印信息
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

/* MQTT消息回调函数 */
void callback(char* topic, byte* payload, unsigned int length) {
  // 打印接收到的消息信息
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  
  // 提取消息内容
  String jsonStr;
  for (int i = 0; i < length; i++) {
    jsonStr += (char)payload[i];
  }
  Serial.println(jsonStr);  // 打印完整消息
}

/* MQTT重连函数 */
void reconnect() {
  // 尝试重新连接MQTT服务器
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 尝试连接
    if (client.connect("ESP8266Client")) {  // 客户端ID
      Serial.println("connected");
      // 连接成功后订阅主题
      client.subscribe(mqtt_Sub_topic);
    } else {
      // 连接失败处理
      Serial.print("failed, rc=");
      Serial.print(client.state());  // 打印错误状态
      Serial.println(" try again in 5 seconds");
      delay(1000);
    }
  }
}

/* 串口数据处理函数 */
void handleSerialData() {
    // 检查串口是否有数据且网络不忙
    if (Serial.available() && !networkBusy) {
        networkBusy = true;  // 设置网络忙标志
        String sh = "";       // 存储接收的数据
        bool is_begin_rx = false;  // 数据接收开始标志

        // 处理所有可用数据
        while (Serial.available()) {
            char ch = Serial.read();  // 读取一个字符
            
            if (ch == HEAD) {  // 检测到帧头
                is_begin_rx = true;
                sh = "";  // 清空缓冲区
            } else if (is_begin_rx && ch == TAIL) {  // 检测到帧尾
                is_begin_rx = false;
                Serial.println("Publishing: " + sh);  // 打印要发布的消息
                
                // 确保MQTT连接正常
                while(!client.connected()) {
                  reconnect();
                } 
                // 发布消息到MQTT主题
                if (!client.publish(mqtt_Pub_topic, sh.c_str())) {
                  Serial.println("Publish failed!");  // 发布失败处理
                }
                sh = "";  // 清空缓冲区
            } else if (is_begin_rx) {  // 正在接收数据
                sh += ch;  // 添加到缓冲区
            }
            
            delay(1);  // 短暂延时
        }
        networkBusy = false;  // 清除网络忙标志
    }
}
void handleTCP()
{
  // 检查TCP客户端连接
  if (!tcpclient) {
    // 没有客户端连接,等待新连接
    tcpclient = server.available();  
  } else {
    // 有客户端已连接
    if (tcpclient.connected()) {
      // 检查客户端是否有数据可读
      if (tcpclient.available()) {
        // 丢弃HEAD之前的所有内容
        tcpclient.readStringUntil(HEAD);  
        // 读取直到TAIL的内容
        String request = tcpclient.readStringUntil(TAIL);
        Serial.print("收到消息: ");
        Serial.println(request);
        
        // 处理JSON数据(原handleJson函数未实现)
        request=HEAD+request+TAIL;  // 重新添加帧头帧尾
        Serial.println(request);
        
        // 向客户端发送响应
        tcpclient.println("ESP8266服务器已收到你的消息: " + request);
      }
    } else {
      // 客户端断开连接
      tcpclient.stop();
      Serial.println("客户端断开连接");
    }
  }
}
/* 主循环 */
void loop() {
  handleSerialData();  // 处理串口数据
  handleTCP();
  delay(100);  // 主循环延时
}

7. 效果演示

1

8.参考文献

[1] uln2003驱动电路_身在江湖的郭大侠的博客-CSDN博客_uln2003

[2]【STM32】步进电机及其驱动(ULN2003驱动28BYJ-48丨按键控制电机旋转)_Include everything的博客-CSDN博客_步进电机

[3] 28BY-J48步进电机工作原理_文析的博客-CSDN博客_28BY-J48

[4] HC-05蓝牙模块AT指令设置教程_LG小龙哥的博客-CSDN博客_HC-05

[5]【B站】28BYJ-48步进电机详解(五线四相 STM32)

[6]【B站】STM32入门教程-2023版 细致讲解 中文字幕_OLED显示屏

源码

链接: https://pan.baidu.com/s/1MdoTqNVrmLnncYsyUhNbRg?pwd=svc5 提取码: svc5

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值