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

2021-03-07  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读