当前位置: 首页 > news >正文

告别串口线!用Android手机蓝牙SPP连接Arduino,实现传感器数据无线采集(附完整Kotlin代码)

用Android手机蓝牙SPP连接Arduino实现无线传感器数据采集

每次调试Arduino项目时,那根总是打结的USB线是否让你抓狂?其实你口袋里的旧Android手机就能成为完美的无线数据终端。本文将带你用蓝牙SPP协议构建一个无线传感器系统,从硬件连接到完整Kotlin应用开发,彻底摆脱线缆束缚。

1. 硬件准备与配置

1.1 所需硬件清单

  • Arduino开发板:UNO或Nano等常见型号均可
  • 蓝牙模块:HC-05(主从一体)或HC-06(从机模式)
  • 传感器:DHT11温湿度传感器(示例用)
  • 杜邦线:公对公、公对母各若干
  • Android设备:系统版本5.0以上,支持经典蓝牙

提示:HC-05模块出厂波特率通常为9600或38400,建议先用USB-TTL工具通过AT指令检查配置

1.2 硬件连接示意图

Arduino UNO HC-05蓝牙模块 5V → VCC GND → GND TX(D1) → RXD RX(D0) → TXD DHT11传感器 VCC → 5V GND → GND DATA → D2

1.3 Arduino基础代码

上传以下代码前,请先断开蓝牙模块的RX/TX连接,否则会与USB串口冲突:

#include <SoftwareSerial.h> #include <DHT.h> #define DHTPIN 2 #define DHTTYPE DHT11 SoftwareSerial bluetooth(10, 11); // RX,TX DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(9600); bluetooth.begin(9600); dht.begin(); } void loop() { float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(h) || isnan(t)) { bluetooth.println("Failed to read DHT!"); return; } String data = String(t) + "," + String(h); bluetooth.println(data); delay(2000); // 2秒采样间隔 }

2. Android端应用开发

2.1 项目基础配置

在Android Studio中新建项目后,首先在AndroidManifest.xml添加蓝牙权限:

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

2.2 蓝牙连接核心类

创建BluetoothSPPManager.kt处理所有蓝牙操作:

class BluetoothSPPManager(private val context: Context) { private val bluetoothAdapter: BluetoothAdapter? by lazy { BluetoothAdapter.getDefaultAdapter().also { adapter -> if (adapter == null) { Toast.makeText(context, "设备不支持蓝牙", Toast.LENGTH_LONG).show() } } } private var socket: BluetoothSocket? = null private var inputStream: InputStream? = null private var outputStream: OutputStream? = null // 标准SPP服务UUID private val SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") fun connect(device: BluetoothDevice, callback: (Boolean) -> Unit) { try { socket = device.createRfcommSocketToServiceRecord(SPP_UUID).apply { connect() inputStream = this.inputStream outputStream = this.outputStream callback(true) } } catch (e: IOException) { callback(false) } } fun startDataListening(onDataReceived: (String) -> Unit) { CoroutineScope(Dispatchers.IO).launch { val buffer = ByteArray(1024) while (isConnected()) { try { inputStream?.read(buffer)?.let { bytes -> if (bytes > 0) { val data = String(buffer, 0, bytes) withContext(Dispatchers.Main) { onDataReceived(data) } } } } catch (e: Exception) { disconnect() } } } } fun isConnected() = socket?.isConnected == true fun disconnect() { try { socket?.close() } catch (e: IOException) { /* 忽略关闭异常 */ } } }

2.3 设备扫描与配对UI

实现设备选择对话框:

fun showDevicePicker(context: Context, onDeviceSelected: (BluetoothDevice) -> Unit) { val pairedDevices = bluetoothAdapter?.bondedDevices ?: emptySet() val devices = pairedDevices.toTypedArray() AlertDialog.Builder(context).apply { setTitle("选择蓝牙设备") setItems(devices.map { "${it.name}\n${it.address}" }.toTypedArray()) { _, which -> onDeviceSelected(devices[which]) } setNegativeButton("取消", null) }.show() }

3. 数据解析与可视化

3.1 数据格式处理

扩展BluetoothSPPManager添加数据解析功能:

fun parseSensorData(rawData: String): SensorData? { return try { val parts = rawData.trim().split(",") if (parts.size == 2) { SensorData( temperature = parts[0].toFloat(), humidity = parts[1].toFloat() ) } else null } catch (e: Exception) { null } } data class SensorData( val temperature: Float, val humidity: Float )

3.2 实时图表实现

使用MPAndroidChart库创建动态折线图:

  1. 添加依赖:
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
  1. 图表初始化代码:
private fun setupChart() { binding.lineChart.apply { description.isEnabled = false setTouchEnabled(true) isDragEnabled = true setScaleEnabled(true) setPinchZoom(true) xAxis.apply { position = XAxis.XAxisPosition.BOTTOM granularity = 1f labelCount = 5 } axisLeft.apply { axisMinimum = 0f axisMaximum = 50f } axisRight.isEnabled = false legend.apply { form = Legend.LegendForm.LINE textSize = 12f } data = LineData( LineDataSet(null, "温度").apply { color = Color.RED setCircleColor(Color.RED) lineWidth = 2f circleRadius = 3f valueTextSize = 10f }, LineDataSet(null, "湿度").apply { color = Color.BLUE setCircleColor(Color.BLUE) lineWidth = 2f circleRadius = 3f valueTextSize = 10f } ) } }
  1. 数据更新方法:
private var timeCounter = 0f private fun updateChart(data: SensorData) { val lineData = binding.lineChart.data if (lineData != null) { lineData.getDataSetByIndex(0).apply { addEntry(Entry(timeCounter, data.temperature)) notifyDataSetChanged() } lineData.getDataSetByIndex(1).apply { addEntry(Entry(timeCounter, data.humidity)) notifyDataSetChanged() } lineData.notifyDataChanged() binding.lineChart.notifyDataSetChanged() binding.lineChart.moveViewToX(timeCounter) } timeCounter += 1f }

4. 常见问题解决方案

4.1 设备无法发现

现象:Android端扫描不到HC-05模块

排查步骤

  1. 确认蓝牙模块已进入配对模式(LED快闪)
  2. 检查模块供电是否稳定(建议使用独立5V电源)
  3. 尝试在手机系统蓝牙设置中手动配对
  4. 确认模块工作模式(AT+ROLE=0为从机模式)

4.2 连接频繁断开

优化措施

  1. 在Kotlin代码中添加心跳机制:
private fun startHeartbeat() { CoroutineScope(Dispatchers.IO).launch { while (isConnected()) { try { outputStream?.write("PING\n".toByteArray()) delay(5000) } catch (e: Exception) { disconnect() break } } } }
  1. 修改Arduino代码添加响应:
void loop() { if (bluetooth.available()) { String cmd = bluetooth.readStringUntil('\n'); if (cmd == "PING") { bluetooth.println("PONG"); } } // ...原有传感器代码... }

4.3 数据解析错误处理

增强数据校验逻辑:

fun parseSensorDataWithValidation(rawData: String): SensorData? { val validPattern = Regex("^-?\\d+\\.?\\d*,-?\\d+\\.?\\d*$") return if (validPattern.matches(rawData.trim())) { parseSensorData(rawData) } else { null } }

4.4 低功耗优化

对于长时间监测场景:

  1. 调整Arduino采样间隔至10秒以上
  2. 在Android端添加屏幕关闭时降低更新频率的逻辑:
private var updateInterval = 1000L // 默认1秒 override fun onUserInteraction() { updateInterval = 1000L // 用户操作时高频更新 } override fun onWindowFocusChanged(hasFocus: Boolean) { if (!hasFocus) { updateInterval = 5000L // 后台时低频更新 } }

这个项目最让我惊喜的是用旧手机改造成专业级的数据采集终端,实际测试中HC-05在无障碍环境下传输距离可达8米,完全满足大多数创客项目的需求。记得第一次成功接收到无线数据时,那种摆脱线缆束缚的畅快感至今难忘。

http://www.jsqmd.com/news/605726/

相关文章:

  • 2026年热门的食堂承包/企业食堂承包/江门食堂承包/饭堂承包用户好评公司 - 行业平台推荐
  • 2026年质量好的通风管道/北京通风管道厂家推荐与选型指南 - 行业平台推荐
  • 开发者专属OpenClaw:Phi-3-mini-128k-instruct辅助代码审查
  • [Python3高阶编程] - Gunicorn 源代码阅读三:建立整体认知(E2E 看看一个 HTTP 请求是如何变成 WSGI 调用的)
  • 3步解锁B站缓存视频:m4s-converter终极转换指南
  • FXAS21002CQ陀螺仪驱动开发与多实例工程实践
  • Windows Defender安全组件高效管理工具使用指南
  • 2026四川简约入户门优质品牌推荐榜:进户门/防撬门/防爆门/防盗安全门/隔音门/静音门/保温门/别墅入户门/加厚防盗门/选择指南 - 优质品牌商家
  • PEFT实战:如何为自定义模型精准定位LoraConfig中的target_modules
  • Java学习笔记_Day23(HashMap)
  • 不止于调试:用 GDB-PEDA + Pwntools 打造你的 Kali 漏洞分析工作流
  • Atlassian Agent终极指南:快速免费激活JIRA、Confluence等企业工具
  • 应用打包:使用PyInstaller将Python脚本打包成独立的.exe可执行文件
  • OpenClaw配置优化:Qwen3.5-9B模型参数与网关性能调优
  • 为什么 Apple ID 无法下载应用?
  • Speedtest进阶:结合Prometheus长期监控局域网速率
  • 2026年评价高的抗摔抗压防护箱/宁波抗摔抗压防护箱源头工厂推荐 - 行业平台推荐
  • OpenClaw硬件选择:Phi-3-mini-128k-instruct不同设备运行对比
  • 2026年SCI论文AI率要求5%以下?这3款降AI工具期刊场景亲测
  • OpenClaw飞书机器人集成:SecGPT-14B实时安全告警推送
  • 高侧非隔离栅极驱动设计要点:从P沟道到N沟道的实战解析
  • 碳汇 / 碳循环研究必备:植被净初级生产力(NPP)的模拟与预测-LPJ 模型构建、数据制备、敏感性分析与未来情景预测
  • 手撕反向传播:从计算图到代码,彻底搞懂神经网络凭什么“知错能改”
  • 2026年二手的快拼打包箱/折叠打包箱/商铺网红打包箱横向对比厂家推荐 - 行业平台推荐
  • 【2024 C++性能黑科技】:为什么你的constexpr函数没提速?揭秘AST折叠失败的6种隐式类型转换雷区
  • 2026苏州代理记账专业服务推荐指南:苏州公司注册开户、苏州公司注册资金认缴、苏州公司营业执照办理、苏州公司记账报税选择指南 - 优质品牌商家
  • Linux内核开发者笔记:ARMv8平台DMA与Cache一致性的三种解法与避坑指南
  • MySQL——SQL执行顺序
  • UE4数字孪生中的天气与交通实时模拟:高德API+VaRest插件实战教程
  • 2026南京食品销售许可证办理优质机构推荐:南京代账公司、南京保安许可证办理、南京农药兽药许可证办理、南京增值电信许可证办理选择指南 - 优质品牌商家