serialport是谷歌的一个关于串口通信的框架,它封装了方法,帮助我们更好的使用串口,进行软件和硬件之前的通信
效果图:手机效果图是大屏android,无截图按钮,图片可能和结果稍有出处
1.在app下的builder.gradle下面添加依赖:
//gogle serialPort
implementation 'com.aill:AndroidSerialPort:1.0.8'
2.封装一个串口传输类
package com.example.administrator.testz;
import java.io.InputStream;
import java.io.OutputStream;
* 串口传输类。在这个类里会启动两个线程分别对串口进行监听输入与数据输出。
* @author xxs
public class SerialTransceiver {
private InputStream inputStream;
private OutputStream outputStream;
private WriteRunnable write;
private ReadRunnable read;
private ReceiverListener listener;
private byte[] writeBuffer;
private int writeIn, writeOut;
* 构造函数
public SerialTransceiver() {
writeBuffer = new byte[1024];
writeIn = 0;
writeOut = 0;
* 启动线程
public void start() {
write = new WriteRunnable();
write.isRunnung = true;
(new Thread(write)).start();
read = new ReadRunnable();
read.isRunnung = true;
(new Thread(read)).start();
* 停止线程
public void stop() {
if (write != null) {
write.isRunnung = false;
synchronized (writeBuffer) {
writeBuffer.notifyAll();
if (read != null)
read.isRunnung = false;
* 写入数据。这个数据会先写入到一个缓存里,然后再通知输出线程将数据发送出去。
* @param data 数据
* @param offset 有效偏移量
* @param size 数据大小
* @return 成功返回true,否则返回false
public boolean write(byte[] data, int offset, int size) {
if (data == null)
throw new NullPointerException("data = null");
if (offset + size > data.length)
throw new IndexOutOfBoundsException("offset + size > data.length");
synchronized (writeBuffer) {
if (size > 1024 - writeIn)
return false;
System.arraycopy(data, offset, writeBuffer, writeIn, size);
writeIn += size;
writeBuffer.notifyAll();
return true;
* 设置输入流。这个输入流应是串口输入流。监听线程就是监听这个输入流。
* @param inputStream 串口输入流。
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
* 设置输出流。这个输出流应是串口输出流。发送线程会将数据从这个输出流发凌空出去。
* @param outputStream 串口输出流。
public void setOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
* 设置接收监听器。当从输入流收到数据后会通过这个监听器通知设置监听器的类。
* @param listener
public void setListener(ReceiverListener listener) {
this.listener = listener;
* 数据输出线程主体。
* @author Hong
private class WriteRunnable implements Runnable {
private boolean isRunnung = false;
@Override
public void run() {
System.out.println("Start WriteRunnable!");
if (outputStream == null)
isRunnung = false;
try {
while (isRunnung) {
synchronized (writeBuffer) {
// 当缓存为空时会阻塞
while (isRunnung && (writeIn == writeOut))
writeBuffer.wait();
if (!isRunnung)
break;
// 将数据写入输出流
int size = writeIn - writeOut;
outputStream.write(writeBuffer, writeOut, size);
writeOut += size;
if (writeIn == writeOut) {
writeIn = 0;
writeOut = 0;
} catch (Exception e) {
e.printStackTrace();
isRunnung = false;
System.out.println("finish WriteRunnable!");
* 监听输入线程主体。
* @author Hong
private class ReadRunnable implements Runnable {
private boolean isRunnung = false;
@Override
public void run() {
System.out.println("Start ReadRunnable!");
if (inputStream == null)
isRunnung = false;
try {
while (isRunnung) {
// 从输入流读取数据。
// int oneByte = inputStream.read();
// if(oneByte < 0) {
// Thread.sleep(5);
// continue;
// }
// // System.out.println("oneByte=" + oneByte);
// if(listener != null)
// listener.onReceive((byte) (oneByte & 0xFF));
byte[] buffer = new byte[64];
int size = inputStream.read(buffer);
if (size > 0) {
for (int i = 0; i < size; i++) {
//System.out.println("oneByte=" + buffer[i]);
if (listener != null)
listener.onReceive((byte) (buffer[i] & 0xFF));
} catch (Exception e) {
e.printStackTrace();
isRunnung = false;
System.out.println("finish ReadRunnable!");
* 接收监听器。
* @author Hong
public interface ReceiverListener {
void onReceive(byte oneByte);
3.主界面进行串口设置,包括路径和波特率,注意,这个波特率要和硬件的路径&波特率保持一致,判断硬件和软件是否已经用串口通信的方法,就是发送一条数据,看看能不能接收,如果数据收发正确,则表示串口已连接,本例子有一点bug,串口的路径和波特率,在本地写死了一个路径和波特率配置,这个才是真正起作用的,但是最好选择的和写死的要保持一致
package com.example.administrator.testz;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import com.aill.androidserialport.SerialPort;
import com.aill.androidserialport.SerialPortFinder;
* 主界面类。在这个界面里主要是一些操作有:选择串口端口,选择串口波特率,
* 显示接收到的串口数据,输入需要发送的数据。
* @author Hong
@SuppressLint("HandlerLeak")
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener,
OnClickListener, OnCheckedChangeListener, SerialTransceiver.ReceiverListener {
private static final String[] BAUDRATE = {"9600", "19200",
"38400", "57600", "115200"};
private SharedPreferences sp;
private SerialPort serial;
private SerialTransceiver transceiver;
private Spinner spinnerDevice;
private Spinner spinnerBaud;
private EditText editIn;
private EditText editOut;
private String deviceName;
private String baudRate;
private boolean isSerialOpen;
private boolean isHEX;
* 创建界面函数。在这里会初始化所有的控件。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sp = getSharedPreferences("com.nrisc.serialport_Preferences", 0);
deviceName = sp.getString("deviceName", "");
baudRate = sp.getString("baudRate", "");
isHEX = sp.getBoolean("HEX", false);
System.out.println(isHEX);
editIn = (EditText) findViewById(R.id.editText_in);
editOut = (EditText) findViewById(R.id.editText_out);
spinnerDevice = (Spinner) findViewById(R.id.spinner_device);
spinnerBaud = (Spinner) findViewById(R.id.spinner_baud);
initSpinner();
Button buttonSerial = (Button) findViewById(R.id.button_serial);
Button buttonSend = (Button) findViewById(R.id.button_send);
Button buttonClear = (Button) findViewById(R.id.button_clear);
buttonSerial.setOnClickListener(this);
buttonSend.setOnClickListener(this);
buttonClear.setOnClickListener(this);
CheckBox check = (CheckBox) findViewById(R.id.checkBox);
check.setOnCheckedChangeListener(this);
check.setChecked(isHEX);
// 初始化串口相关的类,并设置监听器。
// deviceName baudRate
try {
serial = new SerialPort(new File("/dev/ttyS1"), 115200, 0);
//serial = new SerialPort(new File(),);
transceiver = new SerialTransceiver();
transceiver.setListener(this);
} catch (IOException e) {
e.printStackTrace();
* 界面销毁函数。在这里 会先去关闭串口再保存一些设置。
@Override
protected void onDestroy() {
buttonClose();
Editor editor = sp.edit();
editor.putString("deviceName", deviceName);
editor.putString("baudRate", baudRate);
editor.putBoolean("HEX", isHEX);
editor.commit();
super.onDestroy();
* 按键事件响应函数
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_serial: // 打开和关闭串口
if (isSerialOpen) {
buttonClose();
((Button) v).setText(R.string.button_open);
} else {
if (buttonOpen())
((Button) v).setText(R.string.button_close);
break;
case R.id.button_send: // 发送数据
buttonSend();
break;
case R.id.button_clear: // 清除接收到的数据。
editIn.getText().clear();
break;
* Spinner选择事件响应函数。Spinners主要是选择串口端口与波特率。
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
switch (arg0.getId()) {
case R.id.spinner_device: //选择串口设备
deviceName = (String) arg0.getAdapter().getItem(arg2);
break;
case R.id.spinner_baud: //选择串口波特率.
baudRate = BAUDRATE[arg2];
break;
@Override
public void onNothingSelected(AdapterView<?> arg0) {
* 复选框状态改变事件响应函数。复选框是用于是否是16进制模式。
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
isHEX = isChecked;
editOut.getText().clear();
String textOut = editOut.getText().toString();
if(!textOut.equals("")) {
if(isHEX) {
byte[] data = textOut.getBytes();
String newTextOut = byteToHexString(data);
if(newTextOut != null)
editOut.setText(newTextOut);
editOut.setText("");
else {
byte[] data = hexStringToByte(textOut);
if(data != null) {
String newTextOut = "";
for(int i = 0; i < data.length; i++)
newTextOut += (char) data[i];
editOut.setText(newTextOut);
editOut.setText("");
* 串口数据接收监听器响应处。当串口接收到数据时就会调用这个函数。
* 在这个函数里我们得到串口数据后再将数据交到Handler,再由Handler让文本框显示。
* 因为只有在主线程才可以修改界面的内容。这个函数是在串口接收线程响应的。而Handler
* 是在主线程里运行的。
@Override
public void onReceive(byte oneByte) {
String str = "";
// 判断是否是16进制模式,并疳数据修改成不同模式内容。
if (isHEX) {
int h = (oneByte >>> 4) & 0xF;
int l = oneByte & 0xF;
char ch = (char) ((h < 10) ? ('0' + h) : ('A' + h - 10));
char cl = (char) ((l < 10) ? ('0' + l) : ('A' + l - 10));
str = " ";
str += ch;
str += cl;
} else {
str += (char) oneByte;
// 交给Handler。
Message msg = new Message();
msg.what = 0;
Bundle data = new Bundle();
data.putString("text", str);
msg.setData(data);
handler.sendMessage(msg);
* 初始化Spinner
private void initSpinner() {
List<String> deviceList = new LinkedList<String>();
ArrayAdapter<String> deviceNameAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, deviceList);
deviceNameAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
spinnerDevice.setAdapter(deviceNameAdapter);
spinnerDevice.setOnItemSelectedListener(this);
SerialPortFinder finder = new SerialPortFinder();
String[] allDevices = finder.getAllDevicesPath();
for (String device : allDevices)
deviceNameAdapter.add(device);
int index = deviceList.indexOf(deviceName);
if (index != -1)
spinnerDevice.setSelection(index);
ArrayAdapter<String> baudRateAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, BAUDRATE);
baudRateAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
spinnerBaud.setAdapter(baudRateAdapter);
spinnerBaud.setOnItemSelectedListener(this);
for (int i = 0; i < BAUDRATE.length; i++) {
if (BAUDRATE[i].equals(baudRate)) {
spinnerBaud.setSelection(i);
break;
* 打开串口
* @return 成功返回true,否则返回false
private boolean buttonOpen() {
if (isSerialOpen)
return true;
if (!checkDevice())
return false;
try {
int rate = Integer.parseInt(baudRate);
// if (serial.openDevice(deviceName, rate)) {
transceiver.setInputStream(serial.getInputStream());
transceiver.setOutputStream(serial.getOutputStream());
transceiver.start();
isSerialOpen = true;
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
* 关闭串口
private void buttonClose() {
if (!isSerialOpen)
return;
transceiver.stop();
serial.close();
isSerialOpen = false;
* 发送串口数据
private void buttonSend() {
if (!isSerialOpen)
Toast.makeText(this, R.string.msg_close,
Toast.LENGTH_SHORT).show();
byte[] data;
String textOut = editOut.getText().toString();
if (textOut.equals("")) {
Toast.makeText(this, R.string.msg_not_empty,
Toast.LENGTH_SHORT).show();
return;
if (isHEX) {
data = hexStringToByte(textOut);
if (data == null) {
Toast.makeText(this, R.string.msg_error_send,
Toast.LENGTH_SHORT).show();
return;
} else {
data = textOut.getBytes();
transceiver.write(data, 0, data.length);
* 判断串口设备是否有读写权限。
* @return 有则返回true,没有则返回false
private boolean checkDevice() {
File file = new File(deviceName);
if (file.exists()) {
if (!file.canRead() || !file.canWrite()) {
Toast.makeText(this, R.string.msg_no_permission,
Toast.LENGTH_SHORT).show();
return false;
return true;
} else {
Toast.makeText(this, R.string.msg_no_device, Toast.LENGTH_SHORT).show();
return false;
private String byteToHexString(byte[] data) {
if(data == null)
return null;
String str = "";
for(int i = 0; i < data.length; i++) {
int h = (data[i] >>> 4) & 0xF;
int l = data[i] & 0xF;
char ch = (char) ((h < 10)? ('0' + h) : ('A' + h - 10));
char cl = (char) ((l < 10)? ('0' + l) : ('A' + l - 10));
str += ch;
str += cl;
str += " ";
return str;
* 16进制字符串转换成字节数组。
* @param hex 16进制字符串
* @return 字节数组
private byte[] hexStringToByte(String hex) {
if (hex == null || hex.equals(""))
return null;
String[] arry = hex.split(" ");
byte[] data = new byte[arry.length];
try {
for (int i = 0; i < arry.length; i++) {
if (arry[i].length() > 2)
return null;
int d = Integer.parseInt(arry[i], 16);
data[i] = (byte) (d & 0xff);
} catch (Exception e) {
return null;
return data;
* 一个Handler实例
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
// 让文本框显示接收到的数据内容。
String text = msg.getData().getString("text");
editIn.append(text);
break;
super.handleMessage(msg);
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_name"
android:textSize="18sp" />
<Spinner
android:id="@+id/spinner_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/baud_rate"
android:textSize="18sp" />
<Spinner
android:id="@+id/spinner_baud"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/button_serial"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_open" />
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/check_HEX"
android:textSize="18sp" />
</LinearLayout>
<EditText
android:id="@+id/editText_in"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:ems="10"
android:focusableInTouchMode="false"
android:gravity="top"
android:inputType="textMultiLine"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/editText_out"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="text"
android:singleLine="true" >
<requestFocus />
</EditText>
<Button
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_send" />
<Button
android:id="@+id/button_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_clear" />
</LinearLayout>
</LinearLayout>
string资源:
<string name="app_name">Serial Port</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="device_name">串口:</string>
<string name="baud_rate">波特率:</string>
<string name="button_open">打开</string>
<string name="button_close">关闭</string>
<string name="button_send">发送</string>
<string name="button_clear">清空显示</string>
<string name="check_HEX">HEX</string>
<string name="msg_no_device">没有此设备!</string>
<string name="msg_no_permission">没有权限打开设备!</string>
<string name="msg_open_succeed">设备打开成功!</string>
<string name="msg_close">设备尚未打开!</string>
<string name="msg_not_empty">发送内容不能为空!</string>
<string name="msg_error_send">发送内容有误!</string>
serialport是谷歌的一个关于串口通信的框架,它封装了方法,帮助我们更好的使用串口,进行软件和硬件之前的通信效果图:手机效果图是大屏android,无截图按钮,图片可能和结果稍有出处1.在app下的builder.gradle下面添加依赖: //gogle serialPort implementation 'com.aill:AndroidSerialPort:...
NWJS使用chrome api连接/接收/发送串口数据
参考http://www.oschina.net/code/snippet_1379244_55248,本文对其代码进行注释和发表一些自己的见解
1.本机可以使用串口虚拟串口程序(vspd自行下载)
2.所需要环境node.js并且安装模块:iconv-lite
3.nwjs所用的环境为nwjs13.*sdk版本
请查看有关Web Serial API的博客文章,以了解有关它的更多信息: :
TL; DR
将./web_serial_onboard_led.ino的代码上传到您的Arduino设备
通过访问chrome:// flags /#enable-experimental-web-platform-features启用Chrome的实验性Web平台功能
在本地运行演示应用
使用Web应用程序的“连接到串行端口”连接到正确的端口
发送1 tu打开LED,或发送0关闭LED
最近做了一个项目,上位机向单片机要205个字节的报文。每次上位机接收的数据总是分成好几段,不能一次接收205个字节。所以对数据处理造成影响。因此就想着怎么能实现一次接收205字节数据,一次进行处理。试了很多办法,最后终于解决了。
C#中,使用的是serialPort.DataReceived来接收数据。一开始,上位机向单片机发送轮询指令后,单片机按照modbus协议上传205个字节数据。但是每次
该组件支持任何的浏览器,支持当前的IE、谷歌、火狐、360等主流的浏览器。
该组件可以直接与支持Modbus RTU协议的设备进行数据交互(电脑端需安装RS232转RS485的转换器或安装USB转RS485的转换器)。
同时该组件支持二次开发,可以自定义串口通信协议,完美解决任何串口网页版通信问题。
本地使用免费版