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

告别有线调试!用Android手机蓝牙SPP连接Arduino,实现无线串口通信(附完整代码)

用Android手机蓝牙SPP打造无线Arduino调试神器

每次调试Arduino项目时,拖着USB线在面包板和电脑之间来回切换,是不是觉得特别麻烦?想象一下这样的场景:当你正在调试一个温湿度监测项目,传感器数据突然异常,而你只需要掏出手机就能实时查看串口输出,甚至直接发送控制指令——这就是蓝牙SPP协议带来的无线调试革命。

1. 硬件准备与蓝牙模块选型

工欲善其事,必先利其器。在开始无线调试之旅前,我们需要选择合适的硬件组合。市面上常见的蓝牙模块主要分为两类:经典蓝牙(如HC-05、HC-06)和低功耗蓝牙(BLE)。对于串口调试这种需要持续稳定连接的场景,经典蓝牙模块是更合适的选择。

主流蓝牙模块对比表:

型号类型工作电压通信距离特点
HC-05经典蓝牙3.3-6V10米主从一体,支持AT指令配置
HC-06经典蓝牙3.3-6V10米从机模式,配置简单
HM-10BLE3.3V30米低功耗,兼容iOS/Android

提示:HC-05和HC-06价格亲民且稳定可靠,是Arduino项目的理想选择。购买时注意选择带有电平转换电路的版本,可直接连接Arduino的5V引脚。

连接蓝牙模块到Arduino非常简单,只需四根线:

// Arduino与HC-05连接示意图 // Arduino HC-05 // 5V ---- VCC // GND ---- GND // TX ---- RX // RX ---- TX

初次使用时,可能需要通过AT指令配置模块参数。准备一个USB转TTL模块,按以下步骤操作:

  1. 连接USB转TTL到HC-05(注意交叉连接TX/RX)
  2. 打开串口工具,设置波特率38400
  3. 发送AT指令测试连接(HC-05需在未配对状态下进入AT模式)
  4. 常用配置指令:
    • AT+NAME=MyBT修改设备名称
    • AT+PSWD=1234设置配对密码
    • AT+UART=9600,0,0设置串口参数

2. Android端蓝牙调试APP开发

有了硬件基础,接下来我们开发一个功能完整的蓝牙调试APP。使用Android Studio新建项目,添加蓝牙权限:

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

注意:从Android 6.0开始,蓝牙扫描需要位置权限。在实际应用中应该动态请求这些权限。

蓝牙操作的核心流程封装:

class BluetoothSPPHelper(private val context: Context) { private val bluetoothAdapter: BluetoothAdapter? by lazy { BluetoothAdapter.getDefaultAdapter().also { adapter -> if (adapter == null) { Toast.makeText(context, "设备不支持蓝牙", Toast.LENGTH_SHORT).show() } } } // 检查并请求开启蓝牙 fun enableBluetooth(activity: Activity, requestCode: Int) { if (bluetoothAdapter?.isEnabled == false) { val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) activity.startActivityForResult(enableBtIntent, requestCode) } } // 获取已配对设备列表 fun getPairedDevices(): List<BluetoothDevice> { return bluetoothAdapter?.bondedDevices?.toList() ?: emptyList() } // 建立SPP连接 fun connect(device: BluetoothDevice, callback: (BluetoothSocket?) -> Unit) { val uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") // SPP标准UUID val socket = device.createRfcommSocketToServiceRecord(uuid) GlobalScope.launch(Dispatchers.IO) { try { socket.connect() withContext(Dispatchers.Main) { callback(socket) } } catch (e: IOException) { withContext(Dispatchers.Main) { callback(null) } } } } }

数据收发的核心实现:

class BluetoothDataTransfer(private val socket: BluetoothSocket) { private val inputStream: InputStream = socket.inputStream private val outputStream: OutputStream = socket.outputStream private var isListening = false // 发送数据 fun send(data: String) { try { outputStream.write(data.toByteArray()) } catch (e: IOException) { Log.e("BluetoothSPP", "发送失败", e) } } // 开始接收数据 fun startListening(callback: (String) -> Unit) { if (isListening) return isListening = true GlobalScope.launch(Dispatchers.IO) { val buffer = ByteArray(1024) while (isListening) { try { val bytes = inputStream.read(buffer) if (bytes > 0) { val receivedData = String(buffer, 0, bytes) withContext(Dispatchers.Main) { callback(receivedData) } } } catch (e: IOException) { Log.e("BluetoothSPP", "接收中断", e) break } } } } // 停止接收 fun stopListening() { isListening = false } // 关闭连接 fun close() { try { stopListening() socket.close() } catch (e: IOException) { Log.e("BluetoothSPP", "关闭连接失败", e) } } }

3. Arduino端代码优化与无线调试技巧

Arduino端的代码需要与Android应用配合。以下是一个完整的无线调试示例,支持接收手机指令并返回传感器数据:

#include <SoftwareSerial.h> SoftwareSerial BT(10, 11); // RX, TX void setup() { Serial.begin(9600); BT.begin(9600); pinMode(LED_BUILTIN, OUTPUT); Serial.println("蓝牙调试系统就绪"); BT.println("蓝牙调试系统就绪"); } void loop() { // 处理来自手机的指令 if (BT.available()) { String command = BT.readStringUntil('\n'); command.trim(); if (command == "LED_ON") { digitalWrite(LED_BUILTIN, HIGH); BT.println("LED已开启"); } else if (command == "LED_OFF") { digitalWrite(LED_BUILTIN, LOW); BT.println("LED已关闭"); } else if (command == "GET_TEMP") { float temp = readTemperature(); // 假设的温度读取函数 BT.print("当前温度:"); BT.println(temp); } else { BT.print("未知指令:"); BT.println(command); } } // 调试信息通过蓝牙和USB串口同时输出 static unsigned long lastSend = 0; if (millis() - lastSend > 2000) { lastSend = millis(); String debugInfo = "系统运行时间:" + String(millis()/1000) + "秒"; Serial.println(debugInfo); BT.println(debugInfo); } }

提升无线调试稳定性的技巧:

  • 在数据包首尾添加特殊字符(如<数据>)作为帧标识
  • 实现简单的校验和验证
  • 设置超时重发机制
  • 重要数据采用问答式通信(发送后等待确认)
// 改进后的数据发送函数 void sendSafe(String data) { String packet = "<" + data + "|" + checksum(data) + ">"; BT.print(packet); } // 简单的校验和计算 int checksum(String str) { int sum = 0; for (int i = 0; i < str.length(); i++) { sum += str.charAt(i); } return sum % 256; }

4. 实战项目:无线环境监测系统

将所学知识整合到一个实际项目中,我们创建一个无线环境监测系统。系统通过蓝牙将传感器数据实时传输到手机,并支持远程控制。

所需材料清单:

  • Arduino Uno
  • HC-05蓝牙模块
  • DHT22温湿度传感器
  • 光敏电阻
  • 面包板和连接线

电路连接示意图:

DHT22数据引脚 -- Arduino D2 光敏电阻 -- A0 HC-05 TX -- Arduino D10 (SoftwareSerial RX) HC-05 RX -- Arduino D11 (SoftwareSerial TX)

完整Arduino代码:

#include <SoftwareSerial.h> #include <DHT.h> #define DHTPIN 2 #define DHTTYPE DHT22 SoftwareSerial BT(10, 11); // RX, TX DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(9600); BT.begin(9600); dht.begin(); pinMode(A0, INPUT); pinMode(LED_BUILTIN, OUTPUT); BT.println("环境监测系统就绪"); } void loop() { // 处理指令 if (BT.available()) { String cmd = BT.readStringUntil('\n'); cmd.trim(); if (cmd == "GET_DATA") { sendSensorData(); } else if (cmd == "LED_ON") { digitalWrite(LED_BUILTIN, HIGH); BT.println("LED开启"); } else if (cmd == "LED_OFF") { digitalWrite(LED_BUILTIN, LOW); BT.println("LED关闭"); } } // 定时发送数据 static unsigned long lastSend = 0; if (millis() - lastSend > 5000) { lastSend = millis(); sendSensorData(); } } void sendSensorData() { float h = dht.readHumidity(); float t = dht.readTemperature(); int light = analogRead(A0); if (isnan(h) || isnan(t)) { BT.println("传感器读取失败"); return; } String data = "温度:" + String(t) + "℃ " + "湿度:" + String(h) + "% " + "光照:" + String(light); BT.println(data); }

配套的Android APP界面应包含以下功能区域:

  1. 蓝牙设备连接状态显示
  2. 传感器数据实时展示区域
  3. 历史数据曲线图
  4. 控制指令发送按钮
  5. 原始数据接收窗口

数据可视化实现示例:

// 使用MPAndroidChart库绘制实时曲线 fun setupChart() { val chart: LineChart = findViewById(R.id.chart) chart.description.isEnabled = false chart.setTouchEnabled(true) chart.setPinchZoom(true) val xAxis: XAxis = chart.xAxis xAxis.position = XAxis.XAxisPosition.BOTTOM xAxis.granularity = 1f val leftAxis: YAxis = chart.axisLeft leftAxis.axisMinimum = 0f leftAxis.axisMaximum = 100f chart.axisRight.isEnabled = false // 温度数据集 val tempEntries = ArrayList<Entry>() val tempSet = LineDataSet(tempEntries, "温度").apply { color = Color.RED setCircleColor(Color.RED) lineWidth = 2f circleRadius = 3f } // 湿度数据集 val humiEntries = ArrayList<Entry>() val humiSet = LineDataSet(humiEntries, "湿度").apply { color = Color.BLUE setCircleColor(Color.BLUE) lineWidth = 2f circleRadius = 3f } chart.data = LineData(tempSet, humiSet) } // 更新图表数据 fun updateChart(temp: Float, humi: Float) { val chart: LineChart = findViewById(R.id.chart) val data = chart.data val tempSet = data.getDataSetByIndex(0) val humiSet = data.getDataSetByIndex(1) tempSet.addEntry(Entry(tempSet.entryCount.toFloat(), temp)) humiSet.addEntry(Entry(humiSet.entryCount.toFloat(), humi)) data.notifyDataChanged() chart.notifyDataSetChanged() chart.moveViewToX(data.entryCount.toFloat()) }

在项目开发过程中,我遇到最棘手的问题是蓝牙连接在Android不同版本上的兼容性问题。特别是Android 12以后对蓝牙权限管理的改变,需要特别注意以下几点:

  1. 在AndroidManifest.xml中声明新的蓝牙权限
  2. 运行时精确请求BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限
  3. 处理用户拒绝权限的情况,提供合理的回退方案
  4. 后台位置权限的特殊处理
// Android 12+蓝牙权限处理 private fun checkBluetoothPermissions(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { ContextCompat.checkSelfPermission( this, Manifest.permission.BLUETOOTH_SCAN ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( this, Manifest.permission.BLUETOOTH_CONNECT ) == PackageManager.PERMISSION_GRANTED } else { ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED } }
http://www.jsqmd.com/news/640469/

相关文章:

  • 在JetBrains IDE中解锁Markdown编辑的超能力
  • LHM与其他3D重建工具对比:为什么它能在秒级完成
  • 告别头屑烦恼!天然植萃洁发油,温和去屑不反复 - 新闻快传
  • 如何用AKShare快速获取股票数据:5个技巧解决数据获取难题
  • 全文降AI的技术原理解读:工具是怎么做到整篇降率的
  • moonlight-android虚拟控制器完全配置教程:从零打造专属游戏布局
  • 从OpenClaw看AI Agent架构设计,三大工程理念解锁可控高效智能助手
  • 第N篇:实战中精准定位fastjson版本的指纹探测技术解析
  • SLF4J迁移工具使用教程:从传统日志框架平滑过渡到SLF4J
  • 树链剖分例题
  • 如何实现多色位图的智能矢量转换:Vectorizer技术深度解析
  • 【2026奇点智能技术大会权威解码】:医学影像分析三大范式跃迁与临床落地时间表
  • 3步搞定!终极Cursor Pro免费方案:彻底解锁AI编程神器完整教程
  • 实验室与科研首选:高精度光声光谱仪测评,这三大厂商正在重新定义“灵敏” - 品牌推荐大师1
  • Motrix 浏览器扩展:颠覆性架构解析与实战部署指南
  • # 低代码平台实战:用 Python 快速构建可视化数据看板(附完整代码与部署流
  • Cursor Pro免费使用终极指南:如何绕过限制实现永久Pro功能体验
  • 软件测试如何转型产品经理?成功案例全解析
  • 2026年4月评价高的增压器维修厂商推荐,高压油泵精细维修,供油稳定更持久 - 品牌推荐师
  • 为什么说实习是低成本的职业试错 - 新闻快传
  • 终极开源本地实时语音识别工具TMSpeech:高效、安全、零延迟的完整解决方案
  • plog扩展开发实战:自定义格式化器与附加器完全指南
  • Qwen-Image-Edit-F2P生产环境部署:防火墙/日志/tail-f排障实操手册
  • 全文降AI的好处:从知网检测算法角度解读为什么要全文处理
  • 朗岱植物蛋白液体灌装机的介绍 - 品牌推荐大师1
  • RoboMaster开发板C型嵌入式开发终极指南:从零到机器人专家
  • 考研数学二核心公式速查手册(基础篇)
  • Hyperlapse.js项目架构分析:理解模块化设计与事件驱动机制
  • Python 异步的传染性;langgragh并行工作流;
  • ABAP开发实战:Range Table的5种高效用法与性能优化技巧