# android 串口通信实现 **Repository Path**: nnddkj/android_SerialPortDemo ## Basic Information - **Project Name**: android 串口通信实现 - **Description**: android 串口通信实现demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-14 - **Last Updated**: 2026-08-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SerialPortLib 串口通信库使用说明 > 串口(UART) + RS485 物联网通信 Android Library > 开发者:宁工  联系电话:17776688224 在找工作,有内推最好了 --- ## 目录 1. [库简介](#1-库简介) 2. [串口与 485 的区别(重要)](#2-串口与-485-的区别重要) 3. [项目结构](#3-项目结构) 4. [快速接入(3 步)](#4-快速接入3-步) 5. [调用示例](#5-调用示例) 6. [配置项详解](#6-配置项详解) 7. [权限与系统版本兼容性(Android 5 ~ 17)](#7-权限与系统版本兼容性android-5--17) 8. [测试工具 App 使用说明](#8-测试工具-app-使用说明) 9. [MVVM 项目集成建议](#9-mvvm-项目集成建议) 10. [常见问题 FAQ](#10-常见问题-faq) 11. [版本记录](#11-版本记录) --- ## 1. 库简介 `SerialPortLib`(模块名 `:serialportlib`)是一个纯 Java 实现的 Android 串口通信库, 包含两大独立功能模块: | 功能模块 | 类 | 说明 | | -------- | -- | ---- | | 普通串口 | `SerialPortManager` | TTL / RS232 全双工串口通信 | | RS485 | `Serial485Manager` | RS485 半双工总线通信(自动方向切换) | **库的核心特点:** - **纯 Java API**:Java / Kotlin 项目均可直接调用,无任何 API 差异。 - **零第三方依赖**:库内部仅依赖 `appcompat`,集成到商业项目时无依赖冲突风险。 - **架构无关**:支持普通 Android 项目(MVC)与 MVVM 项目(把管理器封装进 Repository 即可)。 - **全版本兼容**:适配 Android 5.0(API21) ~ Android 17(API37)。 - **回调自动切主线程**:数据回调已统一切换到主线程,可直接更新 UI,无需手动 `runOnUiThread`。 - **配置对外开放**:所有参数通过 Builder 模式对外暴露,业务项目可自由设置。 --- ## 2. 串口与 485 的区别(重要) **串口通信和 485 通信是两个不同的功能,本库将它们完全独立封装,互不影响。** | 对比项 | 普通串口(UART / RS232 / TTL) | RS485 | | ------ | ------------------------------ | ----- | | 工作模式 | 全双工(可同时收发) | 半双工(同一时刻只能发或收) | | 总线形态 | 点对点 | 多点总线(一条总线可挂多个设备) | | 电平标准 | TTL / RS232 电平 | 差分信号(A/B 两线) | | 通信距离 | 较短(约 15 米内) | 较长(约 1200 米) | | 方向控制 | 无需,天然可双向同时通信 | 需要切换发送/接收方向(GPIO 或自动方向) | | 常用场景 | 板载调试串口、传感器、GPS 模块 | 工业仪表、电表、Modbus 物联网总线 | **本库中的体现:** - 串口通道:`SerialPortManager`,独立配置(设备路径 / 波特率 / 参数),打开后即收发。 - 485 通道:`Serial485Manager`,在串口配置基础上**额外独立配置**方向控制 (GPIO 控制或自动方向 + 发送前后延时),发送时自动切换方向。 - **两个通道互不影响**:可以同时打开串口和 485,各自独立收发;关闭其中一个不影响另一个。 - 初始化时分别通过 `SerialLib.get().serialManager(...)` 和 `SerialLib.get().rs485Manager(...)` 获取, 两者内部各自持有独立的文件句柄、线程与监听器集合。 --- ## 3. 项目结构 ``` SerialPortDemo/ ├── serialportlib/ # ★ 串口通信 Library(可直接移植到商业项目) │ └── src/main/ │ ├── java/com/nyw/seriallib/ │ │ ├── SerialLib.java # 库门面(统一入口,初始化/获取管理器/释放) │ │ ├── core/ │ │ │ ├── SerialPort.java # JNI 串口底层(termios 操作) │ │ │ └── SerialPortFinder.java # 自动扫描系统串口设备 │ │ ├── config/ │ │ │ ├── SerialConfig.java # 串口配置(设备/波特率/数据位/停止位/校验/流控) │ │ │ ├── Rs485Config.java # 485 方向控制配置(GPIO/自动方向/延时) │ │ │ ├── BaudRate.java # 波特率常量 │ │ │ ├── Parity.java # 校验位常量 │ │ │ └── SerialParams.java # 数据位/停止位/流控常量 │ │ ├── manager/ │ │ │ ├── SerialPortManager.java # ★ 串口管理器 │ │ │ ├── Serial485Manager.java # ★ 485 管理器 │ │ │ └── SerialChannel.java # 通道抽象基类(统一 API) │ │ ├── listener/ │ │ │ └── OnSerialDataListener.java # 数据回调监听器 │ │ ├── util/ │ │ │ ├── HexUtil.java # HEX 转换工具 │ │ │ ├── GpioUtil.java # GPIO 方向控制工具(485 用) │ │ │ └── Logger.java # 调试日志工具 │ │ └── cpp/ # JNI 原生代码(CMake) │ └── AndroidManifest.xml # 库清单(权限说明见第 7 节) ├── app/ # ★ 测试工具 App(演示库的全部用法) │ └── src/main/java/com/nyw/serialportdemo/ │ ├── DemoApp.kt # Application:SerialLib.init + setDebug │ ├── MainActivity.kt # 底部导航主界面(4 个功能页) │ ├── SerialTestFragment.kt # 串口测试页(打开/关闭/收发/HEX) │ ├── Rs485TestFragment.kt # 485 测试页(含 Modbus 快捷指令) │ ├── BaseChannelFragment.kt # 通道测试页基类(串口/485 共用逻辑) │ ├── SettingsFragment.kt # 设置页(双通道独立配置) │ ├── DemoConfigStore.kt # 配置持久化(SharedPreferences) │ └── AboutFragment.kt # 关于页(版本 + 开发者信息) └── README.md # 本使用说明 ``` **核心 API 一览(全部对外开放):** | 类 | 主要方法 | 功能 | | -- | -------- | ---- | | `SerialLib` | `init(context)` | 初始化库(Application 中调用一次) | | | `setDebug(boolean)` | 开关调试日志 | | | `get()` | 获取单例门面 | | | `serialManager(SerialConfig)` | 获取串口管理器(全局单例) | | | `rs485Manager(SerialConfig, Rs485Config)` | 获取 485 管理器(全局单例) | | | `openSerial(config, listener)` | 【一步式】打开串口 + 注册监听 | | | `closeSerial(listener)` | 【一步式】关闭串口 | | | `isSerialOpen()` | 串口是否已打开 | | | `sendSerialHex / sendSerialText / sendSerial` | 【一步式】串口发送数据 | | | `openRs485(config, rs485Config, listener)` | 【一步式】打开 485 + 注册监听 | | | `closeRs485(listener)` | 【一步式】关闭 485 | | | `isRs485Open()` | 485 是否已打开 | | | `sendRs485Hex / sendRs485Text / sendRs485` | 【一步式】485 发送数据 | | | `findDevices()` | 扫描设备全部串口 | | | `releaseAll()` | 释放全部通道 | | `SerialPortManager` / `Serial485Manager` | `open()` / `close()` | 打开 / 关闭通道 | | | `isOpen()` | 是否已打开 | | | `send(byte[])` | 发送原始字节 | | | `sendHex(String)` | 发送 HEX 字符串(如 `"AA 55 01 FF"`) | | | `sendText(String)` | 发送文本 | | | `addListener(OnSerialDataListener)` | 注册数据回调 | | | `removeListener(...)` | 移除数据回调 | | | `getBaudRates()` | 获取支持的波特率列表 | | `SimpleSerialDataListener` | `onDataReceived` | 简易监听器:只需重写数据回调 | | `SerialConfig` | `Builder` | 串口全参数配置(见第 6 节) | | `Rs485Config` | `Builder` | 485 方向控制配置(见第 6 节) | | `HexUtil` | `toHex / fromHex / isHex` | HEX 与字节互转、格式校验 | ### ★ 一步式调用(新项目推荐,最快接入方式) 无需关心管理器创建细节,三条语句即可完成串口收发: ```kotlin // 1. 打开串口(自动创建管理器 + 注册监听 + 打开,一条语句完成) SerialLib.get().openSerial( SerialConfig.Builder().setDevicePath("/dev/ttyS1").setBaudRate(BaudRate.B9600).build() ) { data, length -> tvLog.append("收到: " + HexUtil.bytesToHex(data, length) + "\n") } // 2. 发送数据(未打开时返回 false,无需判空管理器) SerialLib.get().sendSerialHex("AA 55 01 02 FF") SerialLib.get().sendSerialText("AT+RESET\r\n") // 3. 关闭串口 SerialLib.get().closeSerial(null) ``` 485 用法完全一致:`openRs485(...)` / `sendRs485Hex(...)` / `closeRs485(null)`。 --- ## 4. 快速接入(3 步) ### 第 1 步:复制模块 将项目中的 `serialportlib/` 文件夹整体复制到你的工程根目录, 并在根目录 `settings.gradle.kts` 中加入: ```kotlin include(":serialportlib") ``` ### 第 2 步:添加依赖 在 App 模块的 `build.gradle.kts`(或 Groovy 的 `build.gradle`)中添加: ```kotlin dependencies { implementation(project(":serialportlib")) } ``` ### 第 3 步:初始化 + 使用 在你的 `Application` 中初始化(仅一次): ```kotlin // Kotlin 写法 class MyApp : Application() { override fun onCreate() { super.onCreate() SerialLib.init(this) // 初始化库 SerialLib.setDebug(BuildConfig.DEBUG) // 调试期开日志,上线建议关闭 } } ``` ```java // Java 写法 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); SerialLib.init(this); SerialLib.setDebug(BuildConfig.DEBUG); } } ``` > **提示**:`init()` 内部仅保存上下文,无耗时操作,可放心在主线程调用。 --- ## 5. 调用示例 ### 5.1 Kotlin —— 串口通信 ```kotlin // 1. 配置串口(设备路径 / 波特率 / 参数) val config = SerialConfig.Builder() .setDevicePath("/dev/ttyS1") // 串口设备路径 .setBaudRate(BaudRate.B9600) // 波特率 9600 .setDataBits(SerialParams.DATA_BITS_8) .setStopBits(SerialParams.STOP_BITS_1) .setParity(Parity.NONE) .setFlowControl(SerialParams.FLOW_NONE) .build() // 2. 获取管理器(全局单例) val manager = SerialLib.get().serialManager(config) // 3. 注册数据回调(回调已在主线程,可直接更新 UI) manager.addListener(object : OnSerialDataListener { override fun onDataReceived(data: ByteArray, length: Int) { val hex = HexUtil.bytesToHex(data, length) // 转 HEX 显示 tvLog.append("收到: $hex\n") } override fun onStateChanged(isOpen: Boolean) { tvStatus.text = if (isOpen) "已打开" else "已关闭" } override fun onError(e: Exception) { Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show() } }) // 4. 打开串口 manager.open() // 5. 发送数据(三种方式任选) manager.sendText("AT+RESET\r\n") // 发送文本 manager.sendHex("AA 55 01 02 FF") // 发送 HEX manager.send(byteArrayOf(0x01, 0x03)) // 发送原始字节 // 6. 关闭串口 manager.close() ``` ### 5.2 Kotlin —— 485 通信(Modbus 为例) ```kotlin // 1. 串口基础配置 val serial = SerialConfig.Builder() .setDevicePath("/dev/ttyS2") .setBaudRate(BaudRate.B9600) .build() // 2. 485 方向控制配置(自动方向,发送前 0ms / 发送后 10ms 延时) val rs485 = Rs485Config.Builder() .setBeforeTxDelay(0) .setAfterTxDelay(10) .build() // 若板卡有 GPIO 方向控制引脚,也可: // val rs485 = Rs485Config.Builder() // .enableGpioControl(84, true) // GPIO84,发送时输出高电平 // .build() // 3. 获取 485 管理器(全局单例) val manager = SerialLib.get().rs485Manager(serial, rs485) // 4. 注册回调 + 打开 + 发送 Modbus 读保持寄存器指令 manager.addListener(object : OnSerialDataListener { /* 同上 */ }) manager.open() manager.sendHex("01 03 00 00 00 01 84 0A") // 站号01 功能码03 读1个保持寄存器 ``` ### 5.3 Java —— 完整示例 ```java // 配置串口 SerialConfig config = new SerialConfig.Builder() .setDevicePath("/dev/ttyS1") .setBaudRate(BaudRate.B9600) .setDataBits(SerialParams.DATA_BITS_8) .setStopBits(SerialParams.STOP_BITS_1) .setParity(Parity.NONE) .build(); // 获取串口管理器 SerialPortManager manager = SerialLib.get().serialManager(config); // 注册回调 manager.addListener(new OnSerialDataListener() { @Override public void onDataReceived(byte[] data, int length) { Log.i("Serial", "收到: " + HexUtil.bytesToHex(data, length)); } @Override public void onStateChanged(boolean isOpen) { Log.i("Serial", "状态: " + (isOpen ? "已打开" : "已关闭")); } @Override public void onError(Exception e) { Log.e("Serial", "错误: " + e.getMessage()); } }); // 打开并发送 manager.open(); manager.sendHex("AA 55 01 02 FF"); ``` --- ## 6. 配置项详解 ### 6.1 SerialConfig(串口配置) | Builder 方法 | 类型 | 说明 | 默认值 | | ------------ | ---- | ---- | ------ | | `setDevicePath(String)` | String | 串口设备路径(如 `/dev/ttyS1`) | `/dev/ttyS1` | | `setBaudRate(int)` | int | 波特率(可用 `BaudRate` 常量) | 9600 | | `setDataBits(int)` | int | 数据位 5/6/7/8 | 8 | | `setStopBits(int)` | int | 停止位 1/2 | 1 | | `setParity(Parity)` | Parity | 校验位(NONE/ODD/EVEN/MARK/SPACE) | NONE | | `setFlowControl(int)` | int | 流控(NONE/RTS_CTS/XON_XOFF) | NONE | 常用波特率常量(`BaudRate`):`B4800` `B9600` `B19200` `B38400` `B57600` `B115200` `B230400` `B460800` `B921600`,也支持任意非标准波特率。 常用参数常量(`SerialParams`):`DATA_BITS_5/6/7/8`、`STOP_BITS_1/2`、`FLOW_NONE/RTS_CTS/XON_XOFF`。 校验位(`Parity`):`NONE`、`ODD`(奇校验)、`EVEN`(偶校验)、`MARK`、`SPACE`。 ### 6.2 Rs485Config(485 方向控制配置) | Builder 方法 | 类型 | 说明 | 默认值 | | ------------ | ---- | ---- | ------ | | `enableGpioControl(int gpio, boolean txHigh)` | void | 使用 GPIO 方向控制(gpio 引脚号;txHigh=true 表示发送时输出高电平) | 关闭 | | `setBeforeTxDelay(long)` | long | 发送前延时(毫秒),等待方向切换稳定 | 0 | | `setAfterTxDelay(long)` | long | 发送后延时(毫秒),确保数据发送完再切回接收 | 10 | | `setAutoDirection(boolean)` | void | 自动方向控制(无 GPIO 时发送后自动切回接收) | 开启 | > **说明**:若板卡硬件上有 RE/DE 方向控制引脚接到 GPIO,推荐 `enableGpioControl`; > 若没有 GPIO 或驱动已自动处理方向,保持自动方向即可。 --- ## 7. 权限与系统版本兼容性(Android 5 ~ 17) **核心结论:串口 / 485 通信本身不需要任何 Android 运行时权限。** 串口通过 Linux 设备节点(`/dev/ttyS*`、`/dev/ttyMT*`、`/dev/ttyUSB*` 等)通信, 属于系统底层能力,无需在清单中声明权限。库的清单文件已做好版本兼容处理: - `minSdk = 21`(Android 5.0)~ `compileSdk = 37`(Android 17),全版本可编译可运行。 - 库内部无任何 `@RequiresApi` 门槛代码,所有底层调用均做了版本判断与降级处理。 ### 各版本兼容说明 | Android 版本 | API | 兼容性处理 | | ------------ | --- | ---------- | | 5.0 ~ 5.1 | 21~22 | 基础支持,串口 JNI 使用标准 termios 接口,无兼容问题 | | 6.0 ~ 7.x | 23~24 | 无需动态权限(串口不走危险权限);设备节点权限不足时库自动尝试 chmod | | 8.0 ~ 9.0 | 26~28 | 同上;测试 App 使用边缘到边缘显示时做了 inset 适配 | | 10 ~ 11 | 29~30 | 同上;无分区存储影响(库不读写公共存储) | | 12 ~ 13 | 31~33 | 兼容验证;库不使用蓝牙/定位等受限权限 | | 14 ~ 17 | 34~37 | compileSdk 37 编译,API 变更点均已适配(如前台服务/通知等未涉及) | ### 设备节点权限不足的处理 部分设备(尤其工控主板)的串口节点默认权限不足,库底层已内置处理逻辑: 1. 打开设备失败时,自动尝试 `chmod 666 设备路径` 重新打开; 2. 仍失败时抛出带中文说明的异常,测试工具中会弹出提示; 3. 若系统为封闭签名固件,需在系统定制时预置权限(`file_contexts` 或 init.rc 设置节点权限)。 ### USB 转串口设备(可选) 若使用 USB 转串口(CH340/CP2102 等),在**宿主 App** 的清单中自行添加: ```xml ``` 并在代码中处理 USB 设备授权(参考 Android USB Host API,本库暂不内置 USB 授权流程)。 --- ## 8. 测试工具 App 使用说明 `app/` 模块是一个完整的串口测试工具,用于连接硬件后收发数据、验证功能: ### 页面功能 | 页面 | 功能 | | ---- | ---- | | **串口** | 打开/关闭串口、文本/HEX 收发、换行追加、收发字节统计、HEX 显示切换 | | **485** | 485 收发(自动方向切换)、Modbus 读寄存器快捷指令、广播帧测试 | | **设置** | 串口通道与 485 通道**独立**配置(设备路径/波特率/数据位/停止位/校验/流控、485 GPIO/延时),自动扫描系统串口设备,配置自动持久化 | | **关于** | 库版本信息、功能特性列表、开发者信息(宁工 17776688224) | ### 使用流程 1. 安装 App,进入**设置**页,点击"扫描设备"查看板卡串口列表; 2. 分别配置串口通道与 485 通道参数(两通道独立保存); 3. 回到**串口**页打开串口,输入指令(如 `AT`)点击发送,观察接收区; 4. 切到 **485** 页打开 485,使用 Modbus 快捷指令测试总线设备。 > **提示**:真实调试串口时建议使用串口助手工具对比验证(如 SecureCRT、Putty 等)。 --- ## 9. MVVM 项目集成建议 本库为纯 Java 管理类 API,MVVM 项目中可轻松封装进数据层: ### Kotlin + MVVM(ViewModel + Repository) ```kotlin // Repository:封装串口数据流 class SerialRepository(private val config: SerialConfig) { private val manager = SerialLib.get().serialManager(config) private val _data = MutableStateFlow("") init { manager.addListener(object : OnSerialDataListener { override fun onDataReceived(data: ByteArray, length: Int) { _data.value = HexUtil.bytesToHex(data, length) } override fun onStateChanged(isOpen: Boolean) { _isOpen.value = isOpen } override fun onError(e: Exception) { _error.value = e.message } }) } val data: StateFlow = _data.asStateFlow() fun open() = manager.open() fun sendHex(hex: String) = manager.sendHex(hex) fun close() = manager.close() } // ViewModel:通过 StateFlow 驱动 UI class SerialViewModel(private val repo: SerialRepository) : ViewModel() { val data = repo.data fun onSend(hex: String) = repo.sendHex(hex) } ``` ### 注意点 - 管理器是全局单例,若页面配置不同,可在打开前 `setConfig(...)` 动态切换; - 建议在 `ViewModel.onCleared()` 中移除监听器(`removeListener`),避免内存泄漏; - 回调已在主线程,LiveData/Flow 赋值可直接进行。 --- ## 10. 常见问题 FAQ **Q1:打开串口提示"设备节点权限不足 / 无法打开"怎么办?** A:库已自动尝试 chmod 提权。若仍失败,检查设备路径是否正确(用设置页"扫描设备"确认)、 系统是否封闭签名(需系统定制预置权限)、是否已被其它进程占用。 **Q2:485 只发不收,或收发错乱?** A:检查方向控制配置:确认发送前后延时是否足够(一般发送后延时 5~20ms); 若用 GPIO 方向控制,确认方向电平极性是否反了(发送时高/低电平)。 **Q3:收不到数据,但发送正常?** A:先确认硬件 TX/RX 是否交叉连接;再确认波特率、校验位等参数与对端一致; 用串口助手验证硬件链路本身是否正常。 **Q4:串口和 485 能同时使用吗?** A:可以。两个通道完全独立(独立文件句柄、独立线程、独立监听器),互不影响。 **Q5:支持哪些设备路径?** A:常见为 `/dev/ttyS0~3`、`/dev/ttyMT*`(联发科)、`/dev/ttyHSL*`(高通)、 `/dev/ttyUSB*`(USB 转串口)等。设置页"扫描设备"可自动列出可用串口。 **Q6:会与其它三方库冲突吗?** A:库零第三方依赖(仅 appcompat),无冲突风险。 **Q7:测试工具 App 的 minSdk 为什么是 24?** A:测试工具为演示 UI 用了较新 AndroidX API,故 minSdk=24; **Library 本身 minSdk=21(Android 5.0)**,商业项目按需调整 App 即可。 --- ## 11. 版本记录 | 版本 | 日期 | 说明 | | ---- | ---- | ---- | | 1.0.0 | 2026-08 | 首版:串口 + 485 双通道独立封装、测试工具 App、本说明文档 | --- *本库及测试工具由 宁工(17776688224)开发维护,欢迎商业项目集成使用。*