02.基于Android的蓝牙通讯的OpenBlt-STM32烧
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命令行源码阅读
程序的入口在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