Android 串口底层基础:USB 串口、RS485/232 驱动识别、串口通信框架入门

📅 2026/7/27 0:02:23
Android 串口底层基础:USB 串口、RS485/232 驱动识别、串口通信框架入门
Android 串口底层基础USB 串口、RS485/232 驱动识别、串口通信框架入门工控板子要跟传感器、继电器、PLC 对话串口就是那根嗓子。搞不定串口工控项目等于哑巴。一、串口通信基础串口通信是最经典的设备间通信方式核心是UART通用异步收发传输器协议。数据按位bit逐个传输不需要时钟线双方靠约定的波特率同步。1.1 UART 协议帧结构一个完整的 UART 数据帧[空闲] [起始位] [D0 D1 D2 D3 D4 D5 D6 D7] [校验位] [停止位] 高电平 1bit 5~9bit数据 0/1bit 1/1.5/2bit起始位1 个低电平标志数据帧开始数据位5/6/7/8/9 位常用 8 位校验位奇校验Odd、偶校验Even、无校验None停止位1/1.5/2 位高电平标志帧结束1.2 RS232 vs RS485 区别这是工控领域最常被问的问题直接上对比表特性RS232RS485传输方式全双工半双工电平标准±3V~±15V负逻辑差分信号 ±2V~±6V通信距离约 15 米约 1200 米节点数1 对 11 对多最多 32/128/256 节点抗干扰较弱强差分传输典型接口DB99 针两根线 A/B工控用途近距离调试长距离组网、Modbus 总线一句话记RS232 适合桌面调试RS485 才是工控现场的硬骨头。RS485 两根线走差分信号抗干扰能力碾压 232车间里几十个传感器挂一根线没问题。1.3 串口核心参数参数说明常见值波特率每秒传输的 bit 数9600 / 19200 / 38400 / 115200数据位每帧数据位数8停止位帧结束标志1校验位数据校验方式None流控流量控制None / RTS/CTS工控设备最经典的配置9600, 8N1波特率 96008 数据位无校验1 停止位。碰到不确定的设备先试这个。二、Android 串口两种方案Android 平台操作串口主要有两条路线2.1 方案对比方案原理适用场景优缺点USB Host 模式通过 USB OTG 连接 USB 转串口模块平板/手机无原生串口通用性强但需 USB 权限延迟略高ttyS 设备节点直接读写 /dev/ttySx 节点工控板自带 UART 引脚延迟低、性能好需 root 或系统权限简单说用商用平板做项目选 USB Host 方案用定制工控主板如 RK3568、全志 A40i选 ttyS 方案性能更高。三、USB 串口方案USB Host API 与 usb-serial-for-android3.1 USB Host API 基础Android 3.1 引入 USB Host 支持通过UsbManager管理连接的 USB 设备。流程如下获取UsbManager实例枚举已连接的 USB 设备申请 USB 权限打开设备获取UsbDeviceConnection找到正确的UsbEndpoint输入/输出端点通过bulkTransfer()收发数据3.2 usb-serial-for-android 开源库自己裸写 USB 协议太痛苦社区有成熟的开源库usb-serial-for-androidGitHub: mik3y/usb-serial-for-android支持主流 USB 转串口芯片芯片厂商常见模块CH340沁恒CH340G、CH340CCP210xSilicon LabsCP2102、CP2104FTDIFTDIFT232RLPL2303ProlificPL2303HXCH9102沁恒CH9102F引入依赖dependencies { implementation com.github.mik3y:usb-serial-for-android:3.4.6 }3.3 完整代码示例publicclassUsbSerialHelper{privatefinalContextmContext;privatefinalUsbManagermUsbManager;privateUsbSerialPortmSerialPort;privateSerialInputOutputManagermSerialIoManager;publicinterfaceSerialDataCallback{voidonDataReceived(byte[]data);voidonError(Exceptione);}privateSerialDataCallbackmCallback;publicUsbSerialHelper(Contextcontext,SerialDataCallbackcallback){this.mContextcontext;this.mCallbackcallback;this.mUsbManager(UsbManager)context.getSystemService(Context.USB_SERVICE);}/** * 查找并打开第一个可用的USB串口设备 */publicbooleanopen(intbaudRate){// 1. 查找所有USB设备ListUsbSerialDriverdriversUsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager);if(drivers.isEmpty()){Log.e(UsbSerial,未找到USB串口设备);returnfalse;}// 2. 取第一个驱动UsbSerialDriverdriverdrivers.get(0);UsbDevicedevicedriver.getDevice();// 3. 申请权限if(!mUsbManager.hasPermission(device)){PendingIntentpiPendingIntent.getBroadcast(mContext,0,newIntent(com.android.example.USB_PERMISSION),PendingIntent.FLAG_IMMUTABLE);mUsbManager.requestPermission(device,pi);returnfalse;}// 4. 打开连接UsbDeviceConnectionconnectionmUsbManager.openDevice(device);if(connectionnull)returnfalse;mSerialPortdriver.getPorts().get(0);try{mSerialPort.open(connection);// 配置串口参数9600, 8N1, 无流控mSerialPort.setParameters(baudRate,8,UsbSerialPort.STOPBITS_1,UsbSerialPort.PARITY_NONE);mSerialPort.setDTR(true);// 某些设备需要mSerialPort.setRTS(true);// 5. 启动收发管理器mSerialIoManagernewSerialInputOutputManager(mSerialPort,newSerialInputOutputManager.Listener(){OverridepublicvoidonNewData(byte[]data){if(mCallback!null){mCallback.onDataReceived(data);}}OverridepublicvoidonRunError(Exceptione){if(mCallback!null){mCallback.onError(e);}}});// 在独立线程中运行newThread(mSerialIoManager).start();returntrue;}catch(IOExceptione){mCallback.onError(e);returnfalse;}}/** * 发送数据 */publicvoidsend(byte[]data){if(mSerialPort!null){try{mSerialPort.write(data,1000);// 超时1000ms}catch(IOExceptione){mCallback.onError(e);}}}/** * 关闭串口 */publicvoidclose(){if(mSerialIoManager!null){mSerialIoManager.stop();mSerialIoManagernull;}try{if(mSerialPort!null){mSerialPort.close();}}catch(IOExceptione){// ignore}mSerialPortnull;}}四、ttyS 节点方案JNI/Native 层直接操作工控主板通常在/dev/下暴露串口设备节点如/dev/ttyS1、/dev/ttyS3、/dev/ttyMT0等。这种方案需要通过JNI调用 C/C 层的open()/read()/write()系统调用。4.1 C 层串口操作核心#includefcntl.h#includetermios.h#includeunistd.hintopen_serial(constchar*device,intbaudrate){intfdopen(device,O_RDWR|O_NOCTTY|O_NDELAY);if(fd0)return-1;structtermiosoptions;tcgetattr(fd,options);// 设置波特率speed_tspeed;switch(baudrate){case9600:speedB9600;break;case19200:speedB19200;break;case115200:speedB115200;break;default:speedB9600;break;}cfsetispeed(options,speed);cfsetospeed(options,speed);// 8N1配置options.c_cflag~PARENB;// 无校验options.c_cflag~CSTOPB;// 1停止位options.c_cflag~CSIZE;options.c_cflag|CS8;// 8数据位options.c_cflag|(CLOCAL|CREAD);// 启用接收tcsetattr(fd,TCSANOW,options);returnfd;}4.2 权限处理访问/dev/ttySx需要 root 或将 app 加入对应权限组。工控固件通常在init.rc中配置chmod666/dev/ttyS1或在 SELinux 策略中放行。没有 root 的设备USB Host 方案是唯一选择。五、串口数据收发流程与协议帧解析5.1 串口数据的粘包问题串口是字节流没有消息边界概念。一次read()可能收到半条数据也可能收到两条半数据。必须靠自定义协议帧来解析。5.2 典型协议帧结构[帧头][设备地址][功能码][数据长度][数据][校验][帧尾] 0xAA 0x01 0x03 0x04 .... CRC 0x55解析代码示例privatefinalByteArrayOutputStreammRecvBuffernewByteArrayOutputStream();publicvoidhandleData(byte[]newData){mRecvBuffer.write(newData,0,newData.length);byte[]bufmRecvBuffer.toByteArray();while(buf.length8){// 最小帧长8字节// 找帧头intstartindexOf(buf,(byte)0xAA);if(start0){mRecvBuffer.reset();return;}if(start0){// 丢弃帧头前的脏数据mRecvBuffer.reset();mRecvBuffer.write(buf,start,buf.length-start);bufmRecvBuffer.toByteArray();}// 读取数据长度intdataLenbuf[3]0xFF;intframeLen2111dataLen11;// 头地址功能长度数据校验尾if(buf.lengthframeLen){return;// 数据不够等下一次}// 校验CRCbyte[]framenewbyte[frameLen];System.arraycopy(buf,0,frame,0,frameLen);if(frame[frameLen-1](byte)0x55){// 完整帧回调onFrameReceived(frame);}// 移除已解析的数据mRecvBuffer.reset();if(buf.lengthframeLen){mRecvBuffer.write(buf,frameLen,buf.length-frameLen);}bufmRecvBuffer.toByteArray();}}协议帧解析是串口编程最容易出 bug 的地方。核心思路缓冲区 找帧头 长度校验 CRC 校验四个步骤缺一不可。六、工控场景常见串口设备设备类型典型协议接口波特率温湿度传感器Modbus RTURS4859600继电器模块自定义 ASCII/HEXRS4859600PLC西门子/三菱Modbus RTU / 自有协议RS4859600~19200条码扫描枪串口透传RS232115200RFID 读卡器Wiegand / 串口协议RS232/4859600电磁流量计Modbus RTURS4859600变频器Modbus RTURS48519200串口通信是工控 Android 开发的基石。无论是 USB 方案还是 ttyS 方案核心都是正确配置参数 稳定收发数据 可靠解析协议帧。把这三个环节吃透对接任何串口设备都不慌。