添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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的转换器)。 同时该组件支持二次开发,可以自定义串口通信协议,完美解决任何串口网页版通信问题。 本地使用免费版