首页 > Java > java教程 > 正文

基于HC-05的多传感器数据传输与Android应用解析教程

DDD
发布: 2025-08-15 10:56:01
原创
395人浏览过

基于HC-05的多传感器数据传输与Android应用解析教程

本教程详细阐述了如何通过HC-05蓝牙模块将多个超声波传感器数据发送至Android应用程序,并实现数据的有效分离与显示。核心方法是采用行结束符(如换行符\n)对数据进行帧化,确保每条消息的完整性。接收端则通过逐字节读取并缓冲数据,直到检测到行结束符,再将完整消息传递给UI线程进行解析和更新,从而解决了数据传输混乱和解析困难的问题。

1. 问题背景与挑战

在物联网应用中,常会遇到从微控制器(如arduino)通过蓝牙模块(如hc-05)向移动应用发送多路传感器数据的情况。例如,使用三个超声波传感器测量不同方向的距离,并将这些距离值实时显示在android应用中的三个独立textview中。

原始的发送方式,如简单地连续使用Serial1.print()发送不同传感器的值,而不加明确的分隔符,会导致一个常见的问题:蓝牙传输线程在接收数据时可能以任意的“数据块”(burst)形式进行,这可能导致一个完整的消息被拆分成多个接收块,或者多个消息混杂在一个接收块中。这使得Android应用难以准确地识别和分离出每个传感器的独立数据。例如,如果Arduino发送"Left Sensor 10cm"、"Right Sensor 20cm"、"Center Sensor 30cm",Android端可能一次性收到"Left Sensor 10cmRight Sensor 20cmCenter Sensor 30cm",或者收到"Left Sensor 10cmR"然后是"ight Sensor 20cmCenter Sensor 30cm",这都使得直接解析变得异常困难。

2. 解决方案:基于行结束符的数据帧化

解决此问题的关键在于在数据发送端(Arduino)对每条消息进行“帧化”(framing),即为每条消息定义一个明确的结束标志。最常用的方法是使用换行符(\n)作为消息的结束符。这样,接收端(Android)就可以通过识别这个结束符来判断一条完整的消息是否已经到达。

2.1 Arduino(发送端)代码修改

原始的Arduino代码使用了Serial1.print()和delay()来发送数据,但没有明确的行结束符,且每次发送后都有delay(500),这可能导致数据发送不连续或接收端难以同步。

原始发送代码片段:

Serial1.print("Left Sensor ");
Serial1.print((String) distanceL + " cm" );
delay(500);
Serial1.println(" "); // 这里只发送了一个空行,而不是数据行的结束符

Serial1.print("Right Sensor ");
Serial1.print((String) distanceR + " cm" );
delay(500);
Serial1.println(" ");

Serial1.print("Center Sensor ");
Serial1.print((String) distanceC + " cm" );
delay(500);
Serial1.println(" ");
登录后复制

修改后的Arduino发送代码:

为了确保每条数据都是一个完整的“行”,我们应该使用Serial1.println()来发送数据,它会自动在末尾添加换行符(\r\n)。同时,为了方便Android端解析,每条消息应包含一个明确的标识符(如“Left Sensor”、“Right Sensor”、“Center Sensor”)以及具体的数值。

void sensor() {
  int durationL, distanceL;
  int durationR, distanceR;
  int durationC, distanceC;

  // 左传感器测量
  digitalWrite(LtriggerPin, HIGH);
  delayMicroseconds(10); // 使用微秒延迟
  digitalWrite(LtriggerPin, LOW);
  durationL = pulseIn(LechoPin, HIGH);
  distanceL = (durationL / 2) / 29.1;

  // 右传感器测量
  digitalWrite(RtriggerPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(RtriggerPin, LOW);
  durationR = pulseIn(RechoPin, HIGH);
  distanceR = (durationR / 2) / 29.1;

  // 中间传感器测量
  digitalWrite(CtriggerPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(CtriggerPin, LOW);
  durationC = pulseIn(CechoPin, HIGH);
  distanceC = (durationC / 2) / 29.1;

  // 发送数据,每条数据后带换行符
  Serial1.print("Left Sensor ");
  Serial1.println(distanceL); // println 会自动添加换行符
  // 适当的延迟,确保蓝牙模块有时间发送,但不必太长
  delay(100); // 调整此延迟以适应你的应用需求

  Serial1.print("Right Sensor ");
  Serial1.println(distanceR);
  delay(100);

  Serial1.print("Center Sensor ");
  Serial1.println(distanceC);
  delay(100);
}
登录后复制

注意: 这里的delay(100)是为了确保每条消息发送之间有短暂间隔,防止在某些低速蓝牙连接下数据过快导致丢失,但实际应用中,如果Android接收端处理得当,可以适当缩短甚至移除这些延迟。

2.2 Android(接收端)代码修改

Android端蓝牙通信通常在一个独立的线程(如ConnectedThread)中进行数据的读取。原始的run()方法试图一次性读取所有可用字节,并使用SystemClock.sleep(100)等待更多数据,但这并不能保证读取到的是一个完整的消息帧。

原始run()方法片段:

使用JSON进行网络数据交换传输 中文WORD版
使用JSON进行网络数据交换传输 中文WORD版

本文档主要讲述的是使用JSON进行网络数据交换传输;JSON(JavaScript ObjectNotation)是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成,非常适合于服务器与客户端的交互。JSON采用与编程语言无关的文本格式,但是也使用了类C语言的习惯,这些特性使JSON成为理想的数据交换格式。 和 XML 一样,JSON 也是基于纯文本的数据格式。由于 JSON 天生是为 JavaScript 准备的,因此,JSON的数据格式非常简单,您可以用 JSON 传输一个简单的 St

使用JSON进行网络数据交换传输 中文WORD版 0
查看详情 使用JSON进行网络数据交换传输 中文WORD版
@Override
public void run() {
    byte[] buffer = new byte[1024];  // buffer store for the stream
    int bytes; // bytes returned from read()
    while (true) {
        try {
            bytes = mmInStream.available(); // 获取可用字节数
            if(bytes != 0) {
                buffer = new byte[1024]; // 每次都新建buffer,可能导致数据丢失
                SystemClock.sleep(100); // 暂停等待更多数据
                bytes = mmInStream.available();
                bytes = mmInStream.read(buffer, 0, bytes); // 读取数据
                hesler.obtainMessage(MainActivity.MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget(); // 发送给UI线程
            }
        } catch (IOException e) {
            e.printStackTrace();
            break;
        }
    }
}
登录后复制

修改后的Android run()方法:

为了正确处理带结束符的数据流,run()方法需要进行逐字节读取和缓冲。当检测到换行符时,才将缓冲区中的完整数据作为一个消息发送到UI线程。

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; // 需要导入 Arrays 类

// ... 其他代码 ...

public class ConnectedThread extends Thread {
    private final InputStream mmInStream;
    private final Handler hesler; // 假设 hesler 是指向主线程 Handler 的引用

    // 构造函数等...

    @Override
    public void run() {
        byte[] buffer = new byte[1024];  // 缓冲区,用于存储接收到的字节
        int c; // 从输入流读取的一个字节,或-1表示流结束
        int position = 0; // 当前在缓冲区中的写入位置

        // 持续监听输入流,直到发生异常
        while (true) {
            try {
                // 从输入流中读取一个字节
                c = mmInStream.read();
                if (c == -1) { // 如果流结束
                    break; // 停止接收
                }

                if (c == '\n') { // 如果检测到换行符(消息结束)
                    // 复制已接收的完整行数据到新数组
                    // 注意:这里需要处理 '\r',因为 Arduino 的 println 会发送 '\r\n'
                    // 通常,我们只关心 '\n' 作为行结束标志,'\r' 可以忽略或一同复制
                    // 为了简化,这里我们假设只处理 '\n',并复制到当前 position
                    // 如果需要精确移除 '\r',可以检查 buffer[position-1] == '\r'
                    int actualLength = position;
                    if (position > 0 && buffer[position - 1] == '\r') {
                        actualLength--; // 移除回车符
                    }
                    byte[] copyBuffer = Arrays.copyOf(buffer, actualLength);

                    // 将完整消息发送到UI活动
                    hesler.obtainMessage(MainActivity.MESSAGE_READ, actualLength, -1, copyBuffer)
                            .sendToTarget();
                    position = 0; // 重置缓冲区位置,准备接收下一条消息
                } else if (c != '\r') { // 忽略回车符,只存储有效数据字节
                    // 将字节存储到缓冲区
                    if (position < buffer.length) { // 防止缓冲区溢出
                        buffer[position] = (byte) c;
                        position++; // 推进位置,准备存储下一个字节
                    } else {
                        // 缓冲区溢出处理,例如清空缓冲区或发送错误消息
                        position = 0; // 简单处理:清空缓冲区并重新开始
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}
登录后复制

代码解释:

  • mmInStream.read(): 逐字节读取,这是处理流数据最可靠的方式。
  • c == '\n': 当读取到换行符时,表示一个完整的消息已经到达。
  • Arrays.copyOf(buffer, actualLength): 创建一个包含当前完整消息内容的新字节数组,避免直接使用大缓冲区的一部分,这有助于内存管理和数据传递。actualLength确保只复制实际有用的数据(移除了可能的\r)。
  • hesler.obtainMessage().sendToTarget(): 将完整的消息(作为字节数组)发送到UI线程的Handler。
  • position = 0: 接收完一条消息后,重置缓冲区指针,为下一条消息做准备。
  • if (position < buffer.length): 简单的缓冲区溢出检查,防止写入超出数组范围。

2.3 Android UI线程(Handler)数据解析与更新

当Handler接收到MESSAGE_READ消息时,msg.obj中包含的就是一个完整的、以换行符结尾(但已在run()方法中移除换行符)的字节数组,代表一条来自传感器的完整数据行。此时,我们需要将这个字节数组转换为字符串,然后根据字符串中的关键字来识别是哪个传感器的数据,并提取数值更新到对应的TextView。

修改后的handleMessage()方法片段:

// 在 MainActivity 或其他包含 Handler 的类中
handler = new Handler(Looper.getMainLooper()){
    @Override
    public void handleMessage(Message msg){
        if(msg.what == MESSAGE_READ){
            // msg.obj 现在是一个完整的字节数组,msg.arg1 是其长度
            String readMessage = new String((byte[]) msg.obj, 0, msg.arg1, StandardCharsets.UTF_8);

            // 根据关键字解析数据并更新对应的TextView
            if (readMessage.contains("Left Sensor")) {
                // 提取数字部分,例如 "Left Sensor 123" -> "123"
                String distanceStr = readMessage.replaceAll("[^0-9]", ""); // 移除所有非数字字符
                TvL.setText(distanceStr + " cm");
            } else if (readMessage.contains("Right Sensor")) {
                String distanceStr = readMessage.replaceAll("[^0-9]", "");
                TvR.setText(distanceStr + " cm");
            } else if (readMessage.contains("Center Sensor")) {
                String distanceStr = readMessage.replaceAll("[^0-9]", "");
                TvC.setText(distanceStr + " cm");
            }

        }

        if(msg.what == CONNECTING_STATUS){
            // 保持原有的连接状态显示逻辑
            if(msg.arg1 == 1)
                Tv3.setText(getString(R.string.BTConnected) + msg.obj);
            else
                Tv3.setText(getString(R.string.BTconnFail));
        }
    }
};
登录后复制

代码解释:

  • new String((byte[]) msg.obj, 0, msg.arg1, StandardCharsets.UTF_8): 将接收到的字节数组转换为UTF-8编码的字符串。msg.arg1确保只转换实际接收到的有效字节数。
  • readMessage.contains("Left Sensor"): 通过检查字符串是否包含特定关键字来判断是哪个传感器的数据。
  • readMessage.replaceAll("[^0-9]", ""): 这是一个正则表达式,用于从字符串中提取所有数字。它会移除字符串中所有非数字的字符,只留下距离值。
  • TvL.setText(...): 将提取到的距离值更新到对应的TextView。

3. 注意事项与最佳实践

  • 错误处理与健壮性: 在实际应用中,应考虑更复杂的错误处理,例如蓝牙连接断开、数据格式不正确、缓冲区溢出等情况。
  • 数据格式优化: 如果对传输效率有更高要求,可以考虑发送更简洁的数据格式,例如只发送数字,并使用固定的顺序或更紧凑的协议(如CSV格式,L:123,R:456,C:789\n),这样解析时不需要字符串查找,可以直接按位置或分隔符分割。
  • 线程安全: UI操作必须在主线程进行。本教程中通过Handler将数据传递到主线程是正确的做法。
  • 缓冲区大小: buffer的大小应根据预期接收的最大消息长度来设定,以避免溢出。如果消息可能很长,可以考虑动态调整缓冲区大小或使用更高级的流处理技术。
  • Arduino delay(): Arduino代码中的delay()会阻塞程序执行。如果需要更复杂的任务或更快的响应,应避免使用长delay(),转而使用非阻塞的计时器或状态机。对于简单的传感器读取和蓝牙发送,短时间的delay()通常可以接受。

4. 总结

通过在发送端(Arduino)利用Serial.println()添加明确的行结束符,并在接收端(Android)的蓝牙读取线程中实现逐字节读取、缓冲和基于结束符的消息组装,我们能够有效地解决多路传感器数据传输中的数据分包和解析问题。这种方法确保了每条消息的完整性,使得Android应用可以准确地识别并显示来自不同传感器的数据,从而构建稳定可靠的物联网应用。

以上就是基于HC-05的多传感器数据传输与Android应用解析教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号