背景
DynamixelSDK是ROBOTIS公司为其Dynamixel电机系列开发的SDK,面向x86平台的各种OS(Windows/Linux)和语言(C/C++/Java/Python)。因为我们的机器人要全面转向安卓,所以需要将该SDK移植到安卓平台,前后花了2天时间,算是基本完成,记录一下。
准备工作
- Ubuntu 14.4 LTS
- Android NDK bundle
创建源码目录结构
- 下载SDK源码
- 创建一个名叫Dynamixel-android的空目录,再在下面创建jni子目录,然后将
SDK_dir/c/
目录下的src
和include
子目录拷贝到jni
子目录下面, - 创建Android.mk文件和Application.mk文件
- 最终形成如下目录树结构
编写mk文件
Android.mk内容
LOCAL_PATH := $(call my-dir)
#################################################
include $(CLEAR_VARS)
LOCAL_MODULE := dxl
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
src/dynamixel_sdk/group_bulk_read.c \
src/dynamixel_sdk/group_bulk_write.c \
src/dynamixel_sdk/group_sync_read.c \
src/dynamixel_sdk/group_sync_write.c \
src/dynamixel_sdk/packet_handler.c \
src/dynamixel_sdk/port_handler.c \
src/dynamixel_sdk/protocol1_packet_handler.c \
src/dynamixel_sdk/protocol2_packet_handler.c \
src/dynamixel_sdk_linux/port_handler_linux.c
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_STATIC_LIBRARIES := cpufeatures
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/cpufeatures)
Application.mk内容
APP_ABI = armeabi
APP_PLATFORM = android-21
这里着重提示下,Application.mk是告诉ndk-build
程序整个so模块
1. 想生成哪些ABI(我选的armeabi
,你可以选armeabi-v7a
或arm64-v8a
,或都选)
2. 想针对哪些Android平台版本(很重要,不设置该选项会出现下面错误,可能是不同平台SYSROOT
里的内容不一样)
[armeabi] Compile thumb : dxl <= port_handler_linux.c
/home/haipeng/projetcs/Dynamixel-android/jni/src/dynamixel_sdk_linux/port_handler_linux.c:42:10: fatal error: 'linux/serial.h' file not found
#include <linux/serial.h>
^
1 error generated.
make: *** [/home/haipeng/projetcs/Dynamixel-android/obj/local/armeabi/objs/dxl/src/dynamixel_sdk_linux/port_handler_linux.o] Error 1
编译
- 指定环境变量
NDK_PROJECT_PATH
的值为你刚才创建的目录export NDK_PROJECT_PATH=~/projetcs/Dynamixel-android
- 在任意目录运行
ndk-build
命令 - 在
$(NDK_PROJECT_PATH)/libs
目录可以找到libdxl.so文件
集成JNA
DynamixelSDK为了方便Java用户,提供了JNA的适配代码,但是JNA因为历史久远的原因,对Android的支持并不好,我们要解决它
编译支持Android的JNA
根据JNA官网的这篇文章先编译出支持Android的JNA,编译前记得 安装autoconf 软件包,否则ant -Dos.prefix=android-arm dist
运行过程中会报错:autoreconf not found
将JNA集成到Android工程
ant
编译输出在JNA_dir/dist/
目录下,有jna.jar和jna-min.jar两个文件,二者的区别是前者包括各平台(Linux、Windows、Android等)的库文件(.a/.dll/.so),后者没有(只有class文件)。
因为Android的apk制作工具在编译jar包时会跳过里面的so文件,所以选用后者,同时将前者里面android-arm
架构的libjnidispatch.so拷贝到Android工程libs/armeabi
目录下,并在build.gradle
里添加相应语句,将其打包到apk里去
在Android工程里使用JNA
在用到JNA的类里(比如MainActivity.java)添加
static {
System.loadLibrary("jnidispatch");
}
集成
修改java/dynamixel_functions_java/x86/Dynamixel.java
文件,将
LibFunction libFunction = (LibFunction)Native.loadLibrary(“dxl_x86_c”, LibFunction.class);
替换成
LibFunction libFunction = (LibFunction)Native.loadLibrary(“dxl”, LibFunction.class);
并将修改后的Dynamixel.java拷贝到demo工程src
下适当目录
最终Android dem应用工程里至少包含5个关键文件:
+ DynamixelSDK的so文件、JNA适配文件(Dynamixel.java)
+ JNA的so文件、jna-min.jar
+ 根据Java版DynamixelSDK样例程序编写的demo类
推倒重来
集成完了运行,程序报错说打不开串口设备,想起来Android对USB设备(包括USB串口)做了保护,必须通过Android USB Host API访问才行,不能通过直接读写/dev/
目录下的设备文件来实现,所以DynamixelSDK里大量的串口相关底层代码完全无用,必须使用github上的usb-serial-for-android库才行。
这里涉及一个考量,是用usb-serial-for-android迁就Dynamixel的上层逻辑呢?还是Dynamixel迁就usb-serial-for-android的底层逻辑?考虑到Dynamixel的底层代码与上层耦合很紧,而上层代码的复杂度并没有那么高,所以决定抛弃Dynamixel的所有代码,根据其开发手册中描述的UART帧格式完全用Java实现一遍。