从0开始,步步实现Android串口编程(一)

前言

最近在做手机跟外设交互,因为之前没有涉猎过这方面,做起来真的是头大。幸好有万能的百度和无所不能的google,以及程序员的小帮手github,多方查询资料,咨询同事,以及万能的群友帮助,终于顺利实现了第一款串口编程的App。不得不说现在的手机越来越强大,都可以通过USB接口,直接读取其它外设的数据了。写这篇博客一是为了记录一下这次开发的经验,二是给后来的同学提供一些经验。

基本常识

串口通信:指串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以使用一根线发送数据的同时接收数据。

在串口通信中,常用的协议包括RS232、RS-422和RS-485.

我这次工作中对接的是RS232,当然具体是哪种协议和你选择的硬件有关,将你的硬件插到对应的协议的串口即可。

开发前准备

1.检查你的硬件装备
正确连接你的设备,向你的硬件提供商索要开发资料,或者说明书。基本的资料包括硬件的通讯命令格式。类似下图,

在这里插入图片描述
在这里插入图片描述
看上面的通讯协议,能看出正确的命令格式,正确的接收数据格式,以及错误的数据格式

命令帧:

例:AA 75 02(命令字) 00(数据块长度) 00(数据块长度) 00(备用)xx(数据块)DD(校验字)
说明:数据块长度,是由后面的数据块xx决定的,如果不传数据块即参数,数据块长度为0,校验字,是由AA–>xx整个数据计算出来的,通常用的是BCC校验字,计算方法可以去这里看从0开始,步步实现Android串口编程(二),也可以用BCC校验(异或校验)在线计算在线计算。

再啰嗦一句,发送命令帧的时候,()不要带,所以合法的命令帧是:
AA7502000000DD

应答帧:

例:55 7A 02(命令字) 00(数据块长度) 06(数据块长度) 00(备用) 12 08 01 08 47 31(数据块) 4E(校验字)
这里的应答帧,需要满足的条件,1,格式满足,2,数据块跟数据块长度对应,保证应答帧的完整性,3,校验字正确,保证数据未篡改,

因此,接收到数据的时候,需要首先判断是否合法的应答帧,然后在处理数据。
我们接到的原始数据是这样的:
557A020006001208010847314E

为什么串口调试助手上的数据,有空格,那是因为我程序里面对数据进行了加空格处理,主要是为了美观,好看

2.正确的连接,测试你的硬件与系统
串口电脑调试助手
Android USB串口调试助手
下载一个串口助手,按照资料输入命令。测试是否能够成功的启动设备,收到对应的返回数据。

我的硬件设备连接线是DB9的USB-RS-232转接线,再配一个转接头,即可
在这里插入图片描述

开发阶段

整体的开发流程如下:打开串口–>开启接收线程(ReadThread)–>发送串口数据–>接收数据,处理返回信息–>关闭接收数据线程–>关闭串口。

获取权限

1.要使用手机的USB接口首先要获取相关的权限

    <!--USB权限-->
    <uses-feature android:name="android.hardware.usb.host" />
    <!---->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!--这里是为了硬件调试时,保存日志,申请的权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2.在需要打开串口的activity,添加如下数据

 <activity android:name=".MainActivity" android:windowSoftInputMode="stateHidden">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <!--添加 1-->
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
             <!--添加 2-->
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>

3.在res目录下新建xml目录,新建device_filter.xml文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resource xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 要进行通信的USB设备的供应商ID(VID)和产品识别码(PID)
    (如果这个不对,需要你跟你的硬件供应商索要这个参数)-->
    <usb-device vendor-id="1027" product-id="24577" /> <!-- FT232RL -->
</resource>

导入jar包

这里是我自己的独家jar包,简单明了,好用,易上手,里面已经封装好了,不需要自己进行繁琐的JNI操作,特别是对JNI不熟悉的同学。这个独家秘笈我就不免费放出来了,如果你用这个手机调试助手能正常调试,再去这里下载助手源码

USBSerialPortUtils

下面看重点,我封装到utils中的方法

  1. 初始化基本参数
   /**
     * oncreate
     * 为了准确提示,使用位置,故定义成了onCreate
     * 1个停止位,8个数据位,奇校验,16进制,波特率
     * flowControl None
     * @param context
     */
    public void onCreate(Context context) {
        try {
            ftD2xx = D2xxManager.getInstance(context);
        } catch (D2xxManager.D2xxException e) {
            Log.e("FTDI_HT", "getInstance fail!!");
        }
        //这里为了从子线程切换到主线程
        handler = new MyHandler(context);
        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "My Lock");
        global_context = context;
        // init modem variables
        modemReceiveDataBytes = new int[1];
        modemReceiveDataBytes[0] = 0;
        /* allocate buffer */
        writeBuffer = new byte[512];
        readBuffer = new byte[UI_READ_BUFFER_SIZE];
        readDataBuffer = new byte[MAX_NUM_BYTES];
        actualNumBytes = 0;
        // start main text area read thread
        HandlerThread handlerThread = new HandlerThread(handler);
        handlerThread.start();
        baudRate = Integer.parseInt(PublicCache.getBaudRate());
        stopBit = 1;
        dataBit = 8;
        /**
         *
         None parity = 0;
         Odd parity = 1;
         Even parity = 2;
         Mark parity = 3;
         Space parity = 4;
         */
        parity = 1;
        /**
         None flowControl = 0;
         CTS/RTS flowControl = 1;
         DTR/DSR flowControl = 2;
         XOFF/XON flowControl = 3;
         */
        flowControl = 0;
        portIndex = 0;
        //16进制HEX
        bFormatHex = true;
        configParams();
    }
    
  


2.打开串口,配置基本参数

 /**
     * 打開串口
     */
    private void connectFunction() {
        if (portIndex + 1 > DevCount) {
            portIndex = 0;
        }

        if (currentPortIndex == portIndex && ftDev != null && ftDev.isOpen()) {
            //Toast.makeText(global_context,"Port("+portIndex+") is already opened.", Toast.LENGTH_SHORT).show();
            return;
        }

        if (bReadTheadEnable) {
            bReadTheadEnable = false;
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        ftDev = ftD2xx.openByIndex(global_context, portIndex);
        uart_configured = false;

        if (ftDev == null) {
            if (!isMainThread()) {
                Looper.prepare();
                Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
                Looper.loop();
            } else
                Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
            if (onReceivedListener != null)
                onReceivedListener.openFailure();
            return;
        }

        if (ftDev.isOpen()) {
            currentPortIndex = portIndex;
            if (!isMainThread()) {
                Looper.prepare();
                Toast.makeText(global_context, "open device port(" + portIndex + ") OK", Toast.LENGTH_SHORT).show();
                Looper.loop();
            } else {
                Toast.makeText(global_context, "open device port(" + portIndex + ") OK", Toast.LENGTH_SHORT).show();
            }


            if (onReceivedListener != null)
                onReceivedListener.openSuccess();

            if (!bReadTheadEnable) {
                ReadThread readThread = new ReadThread(handler);
                readThread.start();
            }
        } else {
            if (!isMainThread()) {
                Looper.prepare();
                Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
                Looper.loop();
            } else {
                Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
            }
            if (onReceivedListener != null)
                onReceivedListener.openFailure();
        }
    }

   /***
     * 配置参数
     */
    private void configParams() {
        createDeviceList();
        if (DevCount > 0) {
            connectFunction();
        }

        if (DeviceStatus.DEV_NOT_CONNECT == checkDevice()) {
            return;
        }

        setConfig(baudRate, dataBit, stopBit, parity, flowControl);

        uart_configured = true;

    }
    
    
    
    
     /**
     * 配置数据位,校验位,停止位
     * @param baud
     * @param dataBits
     * @param stopBits
     * @param parity
     * @param flowControl
     */
    private void setConfig(int baud, byte dataBits, byte stopBits, byte parity, byte flowControl) {
        // configure port
        // reset to UART mode for 232 devices
        ftDev.setBitMode((byte) 0, D2xxManager.FT_BITMODE_RESET);

        ftDev.setBaudRate(baud);

        switch (dataBits) {
            case 7:
                dataBits = D2xxManager.FT_DATA_BITS_7;
                break;
            case 8:
                dataBits = D2xxManager.FT_DATA_BITS_8;
                break;
            default:
                dataBits = D2xxManager.FT_DATA_BITS_8;
                break;
        }

        switch (stopBits) {
            case 1:
                stopBits = D2xxManager.FT_STOP_BITS_1;
                break;
            case 2:
                stopBits = D2xxManager.FT_STOP_BITS_2;
                break;
            default:
                stopBits = D2xxManager.FT_STOP_BITS_1;
                break;
        }

        switch (parity) {
            case 0:
                parity = D2xxManager.FT_PARITY_NONE;
                break;
            case 1:
                parity = D2xxManager.FT_PARITY_ODD;
                break;
            case 2:
                parity = D2xxManager.FT_PARITY_EVEN;
                break;
            case 3:
                parity = D2xxManager.FT_PARITY_MARK;
                break;
            case 4:
                parity = D2xxManager.FT_PARITY_SPACE;
                break;
            default:
                parity = D2xxManager.FT_PARITY_NONE;
                break;
        }

        ftDev.setDataCharacteristics(dataBits, stopBits, parity);

        short flowCtrlSetting;
        switch (flowControl) {
            case 0:
                flowCtrlSetting = D2xxManager.FT_FLOW_NONE;
                break;
            case 1:
                flowCtrlSetting = D2xxManager.FT_FLOW_RTS_CTS;
                break;
            case 2:
                flowCtrlSetting = D2xxManager.FT_FLOW_DTR_DSR;
                break;
            case 3:
                flowCtrlSetting = D2xxManager.FT_FLOW_XON_XOFF;
                break;
            default:
                flowCtrlSetting = D2xxManager.FT_FLOW_NONE;
                break;
        }

        ftDev.setFlowControl(flowCtrlSetting, XON, XOFF);

        uart_configured = true;
    }
    

3.开启接收数据线程

 //开启接收线程
    
     class ReadThread extends Thread {
        final int USB_DATA_BUFFER = 8192;

        Handler mHandler;

        ReadThread(Handler h) {
            mHandler = h;
            this.setPriority(MAX_PRIORITY);
        }

        @Override
        public void run() {
            byte[] usbdata = new byte[USB_DATA_BUFFER];
            int readcount = 0;
            int iWriteIndex = 0;
            bReadTheadEnable = true;

            while (bReadTheadEnable) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                while (iTotalBytes > (MAX_NUM_BYTES - (USB_DATA_BUFFER + 1))) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                readcount = ftDev.getQueueStatus();
                //Log.e(">>@@","iavailable:" + iavailable);
                if (readcount > 0) {
                    if (readcount > USB_DATA_BUFFER) {
                        readcount = USB_DATA_BUFFER;
                    }
                    ftDev.read(usbdata, readcount);

                    if ((MODE_X_MODEM_CHECKSUM_SEND == transferMode)
                            || (MODE_X_MODEM_CRC_SEND == transferMode)
                            || (MODE_X_MODEM_1K_CRC_SEND == transferMode)) {

                    } else {
                        //DLog.e(TT,"totalReceiveDataBytes:"+totalReceiveDataBytes);

                        //DLog.e(TT,"readcount:"+readcount);
                        for (int count = 0; count < readcount; count++) {
                            readDataBuffer[iWriteIndex] = usbdata[count];
                            iWriteIndex++;
                            iWriteIndex %= MAX_NUM_BYTES;
                        }

                        if (iWriteIndex >= iReadIndex) {
                            iTotalBytes = iWriteIndex - iReadIndex;
                        } else {
                            iTotalBytes = (MAX_NUM_BYTES - iReadIndex) + iWriteIndex;
                        }

                        //DLog.e(TT,"iTotalBytes:"+iTotalBytes);
                        if ((MODE_X_MODEM_CHECKSUM_RECEIVE == transferMode)
                                || (MODE_X_MODEM_CRC_RECEIVE == transferMode)
                                || (MODE_X_MODEM_1K_CRC_RECEIVE == transferMode)
                                || (MODE_Y_MODEM_1K_CRC_RECEIVE == transferMode)
                                || (MODE_Z_MODEM_RECEIVE == transferMode)
                                || (MODE_Z_MODEM_SEND == transferMode)) {
                            modemReceiveDataBytes[0] += readcount;
                        }
                    }
                }
            }

        }
    }

 // Update UI content,发送消息到handler,切换到主线程处理数据
    class HandlerThread extends Thread {
        Handler mHandler;

        HandlerThread(Handler h) {
            mHandler = h;
        }

        public void run() {
            byte status;
            Message msg;

            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (bContentFormatHex) // consume input data at hex content format
                {
                    status = readData(UI_READ_BUFFER_SIZE, readBuffer);
                } else if (MODE_GENERAL_UART == transferMode) {
                    status = readData(UI_READ_BUFFER_SIZE, readBuffer);

                    if (0x00 == status) {
                        if (!WriteFileThread_start) {
                            checkZMStartingZRQINIT();
                        }

                        // save data to file
                        if (WriteFileThread_start && buf_save != null) {
                            try {
                                buf_save.write(readBuffer, 0, actualNumBytes);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                        msg = mHandler.obtainMessage(UPDATE_TEXT_VIEW_CONTENT);
                        mHandler.sendMessage(msg);
                    }
                }
            }
        }
    }

4.发送数据:

  
    /**
     * 写数据
     * @param command
     * @param dataBlock
     */
    public void writeData(String command, String dataBlock) {

        String hexStr = FormatUtils.createCommand(command, dataBlock);

        if (DeviceStatus.DEV_CONFIG != checkDevice()) {
            return;
        }

        // check whether there is some data
        if (hexStr.length() != 0x00) {
            // hexadecimal format 16进制hex显示

            if (hexStr.length() % 2 != 0) {
                if (!isMainThread()) {
                    Looper.prepare();
                    Toast.makeText(global_context, "输入格式错误,16进制hex不能有空格", Toast.LENGTH_SHORT).show();
                    Looper.loop();
                } else {
                    Toast.makeText(global_context, "输入格式错误,16进制hex不能有空格", Toast.LENGTH_SHORT).show();
                }
                return;
            }


            try {
                String atemp = FormatUtils.hexToAscii(hexStr);

                byte numBytes = (byte) atemp.length();
                for (int i = 0; i < numBytes; i++) {
                    writeBuffer[i] = (byte) atemp.charAt(i);
                }

                sendData(numBytes, writeBuffer);
            } catch (IllegalArgumentException e) {
                if (!isMainThread()) {
                    Looper.prepare();
                    Toast.makeText(global_context, "Incorrect input for HEX format."
                            + "\nAllowed charater: 0~9, a~f and A~F", Toast.LENGTH_SHORT).show();
                    Looper.loop();
                } else
                    Toast.makeText(global_context, "Incorrect input for HEX format."
                            + "\nAllowed charater: 0~9, a~f and A~F", Toast.LENGTH_SHORT).show();
                return;
            }

            hexStr = hexStr.replaceAll(".{2}(?!$)", "$0 ");        //字符串中每2个插入空格

            String tmp = hexStr.replace("\\n", "\n");
            bSendHexData = true;
            appendData(tmp);

        }
    }



5.关闭连接,停止接收数据线程

 /**
     * 关闭连接,停止接收数据线程
     */
    private void disconnectFunction() {
        DevCount = -1;
        currentPortIndex = -1;
        bReadTheadEnable = false;
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (ftDev != null) {
            if (ftDev.isOpen()) {
                ftDev.close();
            }
        }
    }

基本流程就是这样,好了看一下,实物图:
在这里插入图片描述

总结一下

基本的串口通信到此结束,到了实际生产,更多的要解决多线程上的逻辑问题,设备的各种状态以及突发状况的处理等等。所以串口通信成功只是一个小小的开始,更多的问题还在后面,如果有什么问题,可以留言交流,水平有限,敬请大佬轻喷。

感谢

万分感谢(qq:495385332)谷歌的塔利班,这位大佬的帮助,没有大佬的解惑,可能我不会那么快上手这个串口编程,非常感谢!!!

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值