背景
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實現一遍。