美文网首页
02.基于Android的蓝牙通讯的OpenBlt-STM32烧

02.基于Android的蓝牙通讯的OpenBlt-STM32烧

作者: 小小秤 | 来源:发表于2021-03-07 11:35 被阅读0次

    0.背景

    工作中遇到这样一种情况:安卓系统显示器通过蓝牙与控制器进行通讯,控制器的硬件是STM32芯片。目前控制器STM32的芯片进行升级是用PC端升级软件,升级的程序是利用开源库OpenBLT Bootloader(https://www.feaser.com/openblt/doku.php?id=homepage)。下载OpenBLT源码,源码提供两种烧写方式:
    1.BootCommander.exe 命令行方式 :C开发环境
    2.MicroBoot.exe 带UI界面方式:Lazarus IDE开发环境
    不管哪种方式都用到源码中的/Host/Source/LibOpenBLT
    由于之前对Lazarus IDE没有接触过,加上本来的思路就是对命令行的方式进行安卓的NDK改写,因此本文是基于对命令行的源码进行解读。
    OpenBLT源码的目录结构如下图1所示

    图1.OpenBlt命令行方式目录结构

    1.OpenBlt命令行源码阅读

    程序的入口在BootCommander的main.c,看main.c的源码及注释很容易得到烧写的步骤:
    (1)固件(将要烧写的软件文件)加载;
    (2)Session会话初始化;
    (3)擦除
    (4)烧写程序
    (5)结束Session会话,清除内存

    根据以上步骤进行源码的解析,以uart通讯方式为例:
    (1)对于烧写过程的运用层来说,所有的操作都是封装在openblt.h和openblt.c中,烧写的运用层任何操作都要调用openblt的方法。对于AndroidNDK来说就是要利用JNI调用openblt的方法。
    (2)所谓的Session会话的意思就是在实际的通讯方式与openblt的xcp协议之间建立一个可以通讯的场景。
    (3)openblt.c中的BltSessionInit(...)作用就是根据命令行参数赋值xcploader.h的设置结构体tXcpLoaderSettings,这个结构体比较重要的参数是传输设置及传输结构体( xcpLoaderSettings.transport,xcpLoaderSettings.transportSettings),这两个参数决定了在传输层采用哪种传输方式。以uart方式为例子最后就调用xcptpuart.c中的XcpTpUartGetTransport()方法,返回的tXcpTransport结构体包括初始化、中止、连接、断开连接、发生数据包方法。
    这里tXcpLoaderSettings,tXcpTransport 都是与xcploader相关的。
    (4)BltSessionInit(...)最后调用的是session.c中SessionInit(...)方法。这个方法里有两个参数一个是tSessionProtocol结构体通过XcpLoaderGetProtocol获得,一个是tXcpLoaderSettings。tSessionProtocol封装了初始化、中止、开始、停止、清除内存、写数据、读数据函数。
    其实SessionInit最后调用 xcploader中的XcpLoaderInit()方法,该方法中会调用tXcpLoaderSettings的传输层初始化。也就是xcptpuart.c中的XcpTpUartInit。
    以上步骤:应用层-->openblt-->session-->loader-->uart
    (5)最后通讯的收发集中在xcploader中-->调用具体传输方式的SendPacket

    2.Android端开发思路

    思路一:直接通过对命令行main.c直接修改,形成NDK方法直接提供给运用层使用,这种方式遇到的困难是不知道安卓蓝牙底层的端口号(有待研究)
    思路二:自己写openblt传输层的xcptpble.c,xcptpble.h给session openblt xploader调用
    最后采用思路二,由于蓝牙的通讯在运用中使用Java代码实现,因此进行NDK开发不仅涉及到Java调用C也涉及到C调用Java。

    3.开发关键点

    (1)蓝牙通讯的实现
    (2)NDK开发,c调用java的蓝牙传输
    (3)烧写openblt库c代码的修改
    (4)具体烧写java层的异步任务

    4.应用截图

    图2.应用截图

    5.关键代码

    图3.系统和烧写相关的JNI

    (1)系统有关(获取系统时间/延时/蓝牙收发)
    在jni_sysutils.c中的主要方法

    /***********关于系统时间和延迟的--Begin**************/
    jclass SysTimeUtils;
    jobject mSysTimeUtils;
    jclass SysTimeUtils_temp;
    jobject mSysTimeUtils_temp;
    jmethodID getJavaSysTimeMs;
    jmethodID setJavaSysDelay;
    int GetSysTimeUtilsInstance(jclass obj_class);
    int InitSysTimeUtils(){...}
    int GetSysTimeUtilsInstance(jclass obj_class){...}
    /***********关于系统时间和延迟的--End**************/
    
    /***********关于蓝牙发送--Begin**************/
    uint8_t *gReceiveData;
    jsize gReceiveLength;
    jclass XUploadApplication;
    jclass XUploadApplication_temp;
    jmethodID sendBleMsgId;
    int InitXUploadApplication(){...}
    /***********关于蓝牙发送--End**************/
    /******************************************************/
    /************具体的C调用JAVA接口函数******************/
    /*****************************************************/
    long getSysTimeMs(){...}
    void setTimeDelay(int delayMs){...}
    bool sendBleMsg(uint8_t const * data,uint32_t length){...}
    //供C内部调用的蓝牙获取代码
    //其实也就是分析接收到的数据
    bool receiveBleMsg2rxPacket(uint8_t  * data,uint32_t length,uint8_t type){...}
    void clearReceiveData(){...}
    //Application中初始化JNIEnv
    JNIEXPORT void JNICALL Java_com_xsl_xupload_XUploadApplication_initJNIEnv(JNIEnv *env, jclass clazz){...}
    //Application中的接收函数
    JNIEXPORT void JNICALL Java_com_xsl_xupload_XUploadApplication_readBleMsg(JNIEnv *env, jclass clazz,jbyteArray array){...}
    

    java中的

    public class XUploadApplication extends Application {
        static {
            try {
                System.loadLibrary("OpenBLT");
            }catch (Exception e){
    
            }
        }
        //蓝牙发送相关
        public static boolean sendBleMsg(byte[] data){
            Log.i("jni_xsl","send_data:"+ ByteUtils.byteArrayToHex(data));
            boolean result = true;
            if (!StringUtils.isTrimEmpty(bleAddress) && bleRWState){
                LeProxy.getInstance().send(bleAddress,data);
            }else {
                result=false;
            }
            return result;
        }
    //省略
    
        /********************************************/
        /**************JNI 函数开始******************/
        /*******************************************/
        public static native void initJNIEnv();
        public static native boolean readBleMsg(byte[] data);
    
    }
    

    系统时间相关java

    public class SysTimeUtils {
        public static long getTimeMs(){
            return System.currentTimeMillis();
        }
    
        public void delayMs(int ms){
            try {
                Thread.sleep(ms);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    (2)openblt中传输层相关的


    图4.蓝牙传输层

    主要体现在xcptpble.c的XcpTpBleSendPacket方法

    static bool XcpTpBleSendPacket(tXcpTransportPacket const * txPacket,
                                   tXcpTransportPacket * rxPacket, uint16_t timeout){
        bool result = false;
        uint16_t byteIdx;
        static uint8_t  bleBuffer[XCPLOADER_PACKET_SIZE_MAX + 1];
        uint32_t responseTimeoutTime = 0;
        bool packetReceptionComplete;
    
        assert(txPacket != NULL);
        assert(rxPacket != NULL);
    
        if ((txPacket!=NULL) && (rxPacket!=NULL)){
            result = true;
            //发送包处理
            bleBuffer[0] = txPacket->len;
            for (byteIdx=0; byteIdx<txPacket->len; byteIdx++){
                bleBuffer[byteIdx + 1] = txPacket->data[byteIdx];
            }
            //发送数据包
            if(!sendBleMsg(bleBuffer,txPacket->len+1)){
                result = false;
            }
            //接收到的数据赋值给rxPacket;
            if(result){
                //定义接收超时时间
                responseTimeoutTime = getSysTimeMs()+timeout;
                rxPacket->len = 0;
                while (getSysTimeMs()<responseTimeoutTime){
                    if(receiveBleMsg2rxPacket(&(rxPacket->len),1,1)){
                        break;
                    }
                }
                if(rxPacket->len==0){
                   result = false;
                } 
            }
    
            if(result){
                byteIdx = 0;
                packetReceptionComplete = false;
                while (getSysTimeMs()<responseTimeoutTime){
                    receiveBleMsg2rxPacket(&rxPacket->data[byteIdx],byteIdx,2);
                    if ((byteIdx+1)==rxPacket->len) {
                        packetReceptionComplete = true;
                        break;
                    }
                    byteIdx++;
                }
                if(packetReceptionComplete){
                    clearReceiveData();
                }
                if(!packetReceptionComplete){
                    result = false;
                }
            }
    
        }
        return result;
    }
    

    (3)运用层调用代码
    java

    public class SysUploadUtils {
        //jni测试
        public static native void test();
        //固件加载
        public static native int startFirmwareLoading(String path);
        //会话初始化即开始
        public static native int startSessionInit();
        //擦除操作
        public static native int eraseOperation();
        //烧写更新操作
        public static native int updateOperation();
        //停止会话
        public static native int stopSessionAndCleanUp();
    
    }
    
    图5.应用层具体烧写C代码

    异步任务代码

        private class FireUpdateTask extends AsyncTask<UpdateFileInfo,Integer, UploadResult>{
            private UpdateFileInfo info;
            public FireUpdateTask() {
            }
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
            @Override
            protected UploadResult doInBackground(UpdateFileInfo... updateFileInfos) {
                boolean result = false;
                int stepResult = -1;
                UploadResult uploadResult = new UploadResult(false,stepResult);
                try {
                    info = updateFileInfos[0];
    
                    mHandler.sendEmptyMessage(Step_Firmware);
                    stepResult = SysUploadUtils.startFirmwareLoading(info.getPath());
                    if(stepResult!=0){
                        uploadResult.setErrorCode(1);
                        return uploadResult;
                    }
    
                    mHandler.sendEmptyMessage(Step_Session);
                    stepResult = SysUploadUtils.startSessionInit();
                    if(stepResult!=0){
                        uploadResult.setErrorCode(2);
                        return uploadResult;
                    }
    
                    mHandler.sendEmptyMessage(Step_Erase);
                    stepResult = SysUploadUtils.eraseOperation();
                    if(stepResult!=0){
                        uploadResult.setErrorCode(3);
                        return uploadResult;
                    }
    
                    mHandler.sendEmptyMessage(Step_Update);
                    stepResult = SysUploadUtils.updateOperation();
                    if(stepResult!=0){
                        uploadResult.setErrorCode(4);
                        return uploadResult;
                    }
    
                    mHandler.sendEmptyMessage(Step_EndClean);
                    SysUploadUtils.stopSessionAndCleanUp();
                    uploadResult.setResult(true);
                    uploadResult.setErrorCode(0);
                    return uploadResult;
                }catch (Exception e){
                    Log.i("xsl","e="+e.toString());
                    e.printStackTrace();
                }
                return uploadResult;
            }
            @Override
            protected void onPostExecute(UploadResult result) {
                super.onPostExecute(result);
                if(result.isResult()){
                    mHandler.sendEmptyMessage(0);
                }else {
                    mHandler.sendEmptyMessage(result.getErrorCode());
                }
            }
    
    
        }
    

    6.解释及说明

    由于用在实际项目中只能写部分代码,本人也是刚接触NDK开发因此以下公布几个开发中作者用到的知识点链接:
    (1)NDK开发环境搭建:https://blog.csdn.net/android_cai_niao/article/details/106474705
    (2)Android NDK开发扫盲及最新CMake的编译使用:
    https://www.jianshu.com/p/6332418b12b1
    (3)Android JNI开发系列之Java与C相互调用:
    https://www.jianshu.com/p/01a298112a89
    (5)NDK开发中获取java方法的签名方法:
    https://blog.csdn.net/JQ_AK47/article/details/53436355
    (6)Android studio配置jni打印https://blog.csdn.net/LoveTheCoding/article/details/103692959?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control&dist_request_id=1328576.10858.16146559653182207&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control
    (7)C通过JNI 层调用Java的静态和非静态方法:
    https://blog.csdn.net/iteye_661/article/details/82301395
    (8)NDK开发之错误集锦:
    https://www.jianshu.com/p/94f764c0111f

    相关文章

      网友评论

          本文标题:02.基于Android的蓝牙通讯的OpenBlt-STM32烧

          本文链接:https://www.haomeiwen.com/subject/wqveqltx.html