在前面Android ADK 编程简介介绍过使用Android NDK可以通过Arduino Board 实现一个自动控制系统,今天和同事一起设计了一个“生产线”(具体用途这里就不说了),先上个图片。
中间是从IKEA买的圆等和餐桌上用的转盘:-)。经过几个小时的忙活,这个系统原型基本达到设计要求。
在前面Android ADK 编程简介介绍过使用Android NDK可以通过Arduino Board 实现一个自动控制系统,今天和同事一起设计了一个“生产线”(具体用途这里就不说了),先上个图片。
中间是从IKEA买的圆等和餐桌上用的转盘:-)。经过几个小时的忙活,这个系统原型基本达到设计要求。
这个例子是使用OpenGL ES绘图最简单的例子,说明如何使用GLSerfaceView,前面的例子Android OpenGL ES 开发教程(6):GLSurfaceView 已经详细说明了,本篇不再具体描述。几何图形顶点定义参见Android OpenGL ES 开发教程(8):基本几何图形定义,颜色定义参见Android OpenGL ES 开发教程(20):颜色Color。
本例对应的类文件为Cube.java ,CubeRenderer.java ,GLSurfaceViewActivity.java 。
Cube 类定义了一个立方体,要注意的是本例使用GL_FIXED,而非GL_FLOAT, GL_FIXED 表示16.16 定点浮点数,GL_FIXED 的0x10000 相当于GL_FLOAT 的1.0
对应的代码如下:
int one = 0x10000; int vertices[] = { -one, -one, -one, one, -one, -one, one, one, -one, -one, one, -one, -one, -one, one, one, -one, one, one, one, one, -one, one, one, }; int colors[] = { 0, 0, 0, one, one, 0, 0, one, one, one, 0, one, 0, one, 0, one, 0, 0, one, one, one, 0, one, one, one, one, one, one, 0, one, one, one, }; byte indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2 };
CubeRenderer 通过坐标变换的方式绘制两个立方体,
gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glTranslatef(0, 0, -3.0f); gl.glRotatef(mAngle, 0, 1, 0); gl.glRotatef(mAngle*0.25f, 1, 0, 0); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY); mCube.draw(gl); gl.glRotatef(mAngle*2.0f, 0, 1, 1); gl.glTranslatef(0.5f, 0.5f, 0.5f); mCube.draw(gl);
下图为当mAngle=45 度时显示结果:
Cube Map,中文可以翻译成立方环境映射,下面是摘自维基百科的说明:
立方反射映射是用立方映射使得物体看起来如同在反射周围环境的一项技术。通常,这通常使用户外渲染中使用的 skybox 完成。尽管由于反射物周围的物体无法在结果中看到,所以这并不是一个真正的反射,但是通常仍然可以达到所期望的效果。
通过确定观察物体的向量就可以进行立方映射反射,照相机光线在照相机向量与物体相交的位置按照曲面法线方向进行反射,这样传到立方图(cube map)取得纹素(texel)的反射光线在照相机看来好像位于物体表面,这样就得到了物体的反射效果。
简单的讲,就是你把一个具有金属反射特性的茶壶放在一个房间中,茶壶的金属表面会反射房间的场景,Cube Map就是解决如何将场景(环境)的内容显示在茶壶的表面,如下图所示:
本例使用环面(Torus)做为反射的表面,在OpenGL ES中任何3D物体,最终都是通过三角形来构造的,本例代码generateTorusGrid 和Grid对象用来构造环面的顶点坐标。具体算法有兴趣的可以自行研究(需要有立体几何的知识,这里不详细解释)。
Cube map技术说到底就是用一个虚拟的立方体(cube)包围住物体,眼睛到物体某处的向量eyevec经过反射(以该处的法线为对称轴),反射向量reflectvec射到立方体上,就在该立方体上获得一个纹素了(见下图)。明显,我们需要一个类似天空盒般的6张纹理贴在这个虚拟的立方体上。按CUBE MAPPING原意,就是一种enviroment map,因此把周围场景渲染到这6张纹理里是“正统”的。也就是每次渲染时,都作一次离线渲染,分别在每个矩形中心放置相机“拍下”场景,用FBO渲染到纹理,然后把这张纹理作为一个cube map对象的六纹理之一。这样即使是动态之物也能被映射到物体表面了(虽然缺点是不能映射物体自身的任何部分)。
本例使用的六张图为res/raw 目录下的 skycubemap0 — skycubemap5 ,如下图所示
使用Cube Map,首先要检测设备是否支持Cube Map 材质,本例使用以下代码检测设备是否支持Cube Map。
private boolean checkIfContextSupportsCubeMap(GL10 gl) { return checkIfContextSupportsExtension(gl, "GL_OES_texture_cube_map"); } /** * This is not the fastest way to check for an extension, but fine if * we are only checking for a few extensions each time a context is created. * @param gl * @param extension * @return true if the extension is present in the current context. */ private boolean checkIfContextSupportsExtension(GL10 gl, String extension) { String extensions = " " + gl.glGetString(GL10.GL_EXTENSIONS) + " "; // The extensions string is padded with spaces between extensions, but not // necessarily at the beginning or end. For simplicity, add spaces at the // beginning and end of the extensions string and the extension string. // This means we can avoid special-case checks for the first or last // extension, as well as avoid special-case checks when an extension name // is the same as the first part of another extension name. return extensions.indexOf(" " + extension + " ") >= 0; }
Cube Map (使用6张图),处调用设置Cube Map外,其基本使用步骤类似于普通材质的使用。本例使用资源,其设置Cube Map的基本步骤如下:
1. 调入图像资源
if (mContextSupportsCubeMap) { int[] cubeMapResourceIds = new int[]{ R.raw.skycubemap0, R.raw.skycubemap1, R.raw.skycubemap2, R.raw.skycubemap3, R.raw.skycubemap4, R.raw.skycubemap5}; mCubeMapTextureID = generateCubeMap(gl, cubeMapResourceIds); } .... private int generateCubeMap(GL10 gl, int[] resourceIds) { checkGLError(gl); int[] ids = new int[1]; gl.glGenTextures(1, ids, 0); int cubeMapTextureId = ids[0]; gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, cubeMapTextureId); gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); for (int face = 0; face < 6; face++) { InputStream is = getResources().openRawResource(resourceIds[face]); Bitmap bitmap; try { bitmap = BitmapFactory.decodeStream(is); } finally { try { is.close(); } catch(IOException e) { Log.e("CubeMap", "Could not decode texture for face " + Integer.toString(face)); } } GLUtils.texImage2D(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, bitmap, 0); bitmap.recycle(); } checkGLError(gl); return cubeMapTextureId; }
2. 绑定材质
函数generateCubeMap返回一个Texture的ID,在OpenGL ES中使用材质时,需要绑定材质
gl.glActiveTexture(GL10.GL_TEXTURE0); checkGLError(gl); gl.glEnable(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP); checkGLError(gl); gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, mCubeMapTextureID); checkGLError(gl); GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl; gl11ep.glTexGeni(GL11ExtensionPack.GL_TEXTURE_GEN_STR, GL11ExtensionPack.GL_TEXTURE_GEN_MODE, GL11ExtensionPack.GL_REFLECTION_MAP); checkGLError(gl); gl.glEnable(GL11ExtensionPack.GL_TEXTURE_GEN_STR); checkGLError(gl); gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_DECAL); ... mGrid.draw(gl); ... gl.glDisable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);
这样就给环面物体添加了环境材质,显示结果如下:
前面已经说过Android SDK ,NDK, APK, 现在有来了个ADK, 这么多K:-). 那么什么是ADK,ADK是Android Open Accessory Development Kit 的缩写。使用ADK使得不支持USB Host功能的Android设备也可以和其它USB设备交互。比如使用Android手机来控制步进电机,条码扫描仪,机器人等。
Android 设备支持各种各样的USB设备,即可以以USB Host模式工作,也可以以USB Accessory 模式工作:
从Android 3.1 (API Level 12) 开始Andriod平台开始支持USB Accessory 和 Host 工作模式,Google也通过附加库的方式中Android 2.3.4 (API Level 10) 支持USB Accessory 和Host 工作模式。
注意:对USB Host或Accessory 模式的支持最终取决于设备硬件,和平台OS的版本无关,比如Sumsung Galaxy Nexus 同时支持USB Host或Accessory 模式而Sumsung Nexus S 只支持USB Accessory 模式,尽管两种手机都采用了ICS 4.0.4平台。
本例介绍如何使用ADK 通过Arduino 连接QRCode Scanner 通过ADK 和Android应用通信。
其硬件连接图如下:
其中Arduino 控制板采用的Freetronics的UsbDroid控制板(和Arduino兼容),QRCode Scanner(工作中USB HID模式)。
由于手机的USB需要和Usb Hub 连接,因此本例需要Android 不通过USB数据线调试的方法。
本例没有采用http://developer.android.com/guide/topics/usb/adk.html 中介绍的软件和USB_Host_Sheild 库,是以为这个库不支持Usb Hub,需要将上图中连接Usb Hub的线直接连到手机,这样就无法再连接QR Code Scanner了。不过基本步骤是一致的。
1. 下载Ardunio软件和对应的USB库。
目前Ardunio的版本为1.0,如果你想使用Google 网站上的例子,你需要使用023版本。为方便起见,你可以从本站下载软件和USB库(支持USB Hub功能),其中USB库为Circuits @Home 提供。
2. 下载 对应的Android实例应用,本例使用Arduino terminal
3. 将firmware 安装到Arduino 控制板上。
本例使用adk 中adk-barcode 示例。其代码如下,可以参见
/**/ /* A sketch demonstrating data exchange between two USB devices - a HID barcode scanner and ADK-compatible Android phone */ /**/ #include <avrpins.h> #include <max3421e.h> #include <usbhost.h> #include <usb_ch9.h> #include <Usb.h> #include <usbhub.h> #include <avr/pgmspace.h> #include <address.h> #include <adk.h> #include <hidboot.h> USB Usb; USBHub Hub1(&Usb); USBHub Hub2(&Usb); HIDBoot<HID_PROTOCOL_KEYBOARD> Keyboard(&Usb); ADK adk(&Usb,"Circuits@Home, ltd.", "USB Host Shield", "Arduino Terminal for Android", "1.0", "http://www.circuitsathome.com", "0000000000000001"); class KbdRptParser : public KeyboardReportParser { protected: virtual void OnKeyDown (uint8_t mod, uint8_t key); virtual void OnKeyPressed(uint8_t key); }; void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key) { uint8_t c = OemToAscii(mod, key); if (c) OnKeyPressed(c); } /* what to do when symbol arrives */ void KbdRptParser::OnKeyPressed(uint8_t key) { const char* new_line = "\n"; uint8_t rcode; uint8_t keylcl; if( adk.isReady() == false ) { return; } keylcl = key; if( keylcl == 0x13 ) { rcode = adk.SndData( strlen( new_line ), (uint8_t *)new_line ); } else { rcode = adk.SndData( 1, &keylcl ); } Serial.print((char) keylcl ); Serial.print(" : "); Serial.println( keylcl, HEX ); }; KbdRptParser Prs; void setup() { Serial.begin(115200); Serial.println("\r\nADK demo start"); if (Usb.Init() == -1) { Serial.println("OSCOKIRQ failed to assert"); while(1); //halt }//if (Usb.Init() == -1... Keyboard.SetReportParser(0, (HIDReportParser*)&Prs); delay( 200 ); } void loop() { Usb.Task(); }
这里的ADK与Google提供的示例稍有不同,但大同小异。
4. 在手机上运行Arduino terminal (这不是必须的),当将USB线连接到手机时,会自动触发这个应用。这是因为:
Firmware中使用ADK定义
ADK adk(&Usb,”Circuits@Home, ltd.”,
“USB Host Shield“,
“Arduino Terminal for Android”,
“1.0″,
“http://www.circuitsathome.com”,
“0000000000000001”);
和Arduino terminal 的broadcast receiver 定义对应:
<activity android:name=”.StartServiceActivity” android:label=”@string/starter_app_name”
android:launchMode=”singleInstance” android:theme=”@android:style/Theme.NoDisplay”
android:excludeFromRecents=”true”>
<intent-filter>
<action android:name=”android.hardware.usb.action.USB_ACCESSORY_ATTACHED” />
</intent-filter>
<meta-data android:name=”android.hardware.usb.action.USB_ACCESSORY_ATTACHED”
android:resource=”@xml/accessory_filter” />
</activity>
xml/accessory_filter.xml 的定义如下:
<resources>
<usb-accessory manufacturer=”Circuits@Home, ltd.” model=”USB Host Shield” version=”1.0″ />
</resources>
对于你自己的例子,你可以使用你自己定义的名称,只有保证这几个值在firmware 和Android应用中定义的值一致即可。
运行结果如下:
Arduino 编程并不复杂,只要会C语言即可,可以参见arduino-1.0 中的reference文档。Usb_host C库可以参考libraries/felis_USB_Host_Shield 的头文件。
注:通过这种连接方法,可以连接USB照相机,GPS, 步进电机等各种外设,可以实现使用Android来自动控制等。