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 区别
这是工控领域最常被问的问题,直接上对比表:
| 特性 | RS232 | RS485 |
|---|---|---|
| 传输方式 | 全双工 | 半双工 |
| 电平标准 | ±3V~±15V(负逻辑) | 差分信号 ±2V~±6V |
| 通信距离 | 约 15 米 | 约 1200 米 |
| 节点数 | 1 对 1 | 1 对多(最多 32/128/256 节点) |
| 抗干扰 | 较弱 | 强(差分传输) |
| 典型接口 | DB9(9 针) | 两根线 A/B |
| 工控用途 | 近距离调试 | 长距离组网、Modbus 总线 |
一句话记:RS232 适合桌面调试,RS485 才是工控现场的硬骨头。RS485 两根线走差分信号,抗干扰能力碾压 232,车间里几十个传感器挂一根线没问题。
1.3 串口核心参数
| 参数 | 说明 | 常见值 |
|---|---|---|
| 波特率 | 每秒传输的 bit 数 | 9600 / 19200 / 38400 / 115200 |
| 数据位 | 每帧数据位数 | 8 |
| 停止位 | 帧结束标志 | 1 |
| 校验位 | 数据校验方式 | None |
| 流控 | 流量控制 | None / RTS/CTS |
工控设备最经典的配置:9600, 8N1(波特率 9600,8 数据位,无校验,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-android
3.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-android(GitHub: mik3y/usb-serial-for-android),支持主流 USB 转串口芯片:
| 芯片 | 厂商 | 常见模块 |
|---|---|---|
| CH340 | 沁恒 | CH340G、CH340C |
| CP210x | Silicon Labs | CP2102、CP2104 |
| FTDI | FTDI | FT232RL |
| PL2303 | Prolific | PL2303HX |
| CH9102 | 沁恒 | 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.mContext=context;this.mCallback=callback;this.mUsbManager=(UsbManager)context.getSystemService(Context.USB_SERVICE);}/** * 查找并打开第一个可用的USB串口设备 */publicbooleanopen(intbaudRate){// 1. 查找所有USB设备List<UsbSerialDriver>drivers=UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager);if(drivers.isEmpty()){Log.e("UsbSerial","未找到USB串口设备");returnfalse;}// 2. 取第一个驱动UsbSerialDriverdriver=drivers.get(0);UsbDevicedevice=driver.getDevice();// 3. 申请权限if(!mUsbManager.hasPermission(device)){PendingIntentpi=PendingIntent.getBroadcast(mContext,0,newIntent("com.android.example.USB_PERMISSION"),PendingIntent.FLAG_IMMUTABLE);mUsbManager.requestPermission(device,pi);returnfalse;}// 4. 打开连接UsbDeviceConnectionconnection=mUsbManager.openDevice(device);if(connection==null)returnfalse;mSerialPort=driver.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. 启动收发管理器mSerialIoManager=newSerialInputOutputManager(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();mSerialIoManager=null;}try{if(mSerialPort!=null){mSerialPort.close();}}catch(IOExceptione){// ignore}mSerialPort=null;}}四、ttyS 节点方案:JNI/Native 层直接操作
工控主板通常在/dev/下暴露串口设备节点,如/dev/ttyS1、/dev/ttyS3、/dev/ttyMT0等。这种方案需要通过JNI调用 C/C++ 层的open()/read()/write()系统调用。
4.1 C 层串口操作核心
#include<fcntl.h>#include<termios.h>#include<unistd.h>intopen_serial(constchar*device,intbaudrate){intfd=open(device,O_RDWR|O_NOCTTY|O_NDELAY);if(fd<0)return-1;structtermiosoptions;tcgetattr(fd,&options);// 设置波特率speed_tspeed;switch(baudrate){case9600:speed=B9600;break;case19200:speed=B19200;break;case115200:speed=B115200;break;default:speed=B9600;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解析代码示例:
privatefinalByteArrayOutputStreammRecvBuffer=newByteArrayOutputStream();publicvoidhandleData(byte[]newData){mRecvBuffer.write(newData,0,newData.length);byte[]buf=mRecvBuffer.toByteArray();while(buf.length>=8){// 最小帧长8字节// 找帧头intstart=indexOf(buf,(byte)0xAA);if(start<0){mRecvBuffer.reset();return;}if(start>0){// 丢弃帧头前的脏数据mRecvBuffer.reset();mRecvBuffer.write(buf,start,buf.length-start);buf=mRecvBuffer.toByteArray();}// 读取数据长度intdataLen=buf[3]&0xFF;intframeLen=2+1+1+1+dataLen+1+1;// 头+地址+功能+长度+数据+校验+尾if(buf.length<frameLen){return;// 数据不够,等下一次}// 校验CRCbyte[]frame=newbyte[frameLen];System.arraycopy(buf,0,frame,0,frameLen);if(frame[frameLen-1]==(byte)0x55){// 完整帧,回调onFrameReceived(frame);}// 移除已解析的数据mRecvBuffer.reset();if(buf.length>frameLen){mRecvBuffer.write(buf,frameLen,buf.length-frameLen);}buf=mRecvBuffer.toByteArray();}}协议帧解析是串口编程最容易出 bug 的地方。核心思路:缓冲区 + 找帧头 + 长度校验 + CRC 校验,四个步骤缺一不可。
六、工控场景常见串口设备
| 设备类型 | 典型协议 | 接口 | 波特率 |
|---|---|---|---|
| 温湿度传感器 | Modbus RTU | RS485 | 9600 |
| 继电器模块 | 自定义 ASCII/HEX | RS485 | 9600 |
| PLC(西门子/三菱) | Modbus RTU / 自有协议 | RS485 | 9600~19200 |
| 条码扫描枪 | 串口透传 | RS232 | 115200 |
| RFID 读卡器 | Wiegand / 串口协议 | RS232/485 | 9600 |
| 电磁流量计 | Modbus RTU | RS485 | 9600 |
| 变频器 | Modbus RTU | RS485 | 19200 |
串口通信是工控 Android 开发的基石。无论是 USB 方案还是 ttyS 方案,核心都是"正确配置参数 + 稳定收发数据 + 可靠解析协议帧"。把这三个环节吃透,对接任何串口设备都不慌。
