大家好,又见面了,我是你们的朋友全栈君。
要在Cubieboard2上开发四轴飞行器的控制模块,需要编写远程控制的接收端和底层控制模块。换言之需要编写用户层client软件和driver,本人负责单片机模块,此文是跟踪笔记,权当参考和提醒。值得声明的是,由于嵌入式平台的平台相关性很大,相关操作不一定可以完全再现。
学习资料主要参考论坛
———————2013年12月23号14点先记———————
需要什么:
- 一个合适的Linux发行版
- Java运行环境以完成网络通信。Java主要搭构client端的程序框架。安装上jdk最佳。
- Timer驱动完成飞行器控制。Cubieboard2应有较好性能运行arm-linux-gcc,可以尝试安装。
- JNI黏合,琐碎
关键点(只是开发思路和前期准备):
Cubieboard2有原生的安卓4.2OS,但常年类月的安卓使用经验告诉我这个操作系统或许不能满足实时性要求,因此要更换成传统的Linux。而其中以原生搭载CPU负担最小的模块为最佳。然而需要编写驱动,因此需要有相应的内核代码和编译链。如果该发行版没有明确给出工具,第三点需求就会死绝。
浏览下来,初步决定采用 Cubian,如开发过程不顺利,有可能改变环境,日志会后续跟进
当前PC运行环境如下:
代码语言:javascript复制hu@forhu-debian:~$ uname -a
Linux forhu-debian 3.2.0-4-686-pae #1 SMP Debian 3.2.51-1 i686 GNU/Linux
代码语言:javascript复制hu@forhu-debian:~$ java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (IcedTea 2.3.10) (7u25-2.3.10-1~deb7u1)
OpenJDK Client VM (build 23.7-b01, mixed mode, sharing)
代码语言:javascript复制hu@forhu-debian:~$ ~/jdk1.7.0_45/bin/java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) Client VM (build 24.45-b08, mixed mode)
当前我有两个jdk环境,这是为了测试java运行的有效性,我原来安装的OpenJDK6,实际使用时出现了一些问题,展示如下:
代码语言:javascript复制hu@forhu-debian:~$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.6) (6b27-1.12.6-1~deb7u1)
OpenJDK Client VM (build 20.0-b12, mixed mode, sharing)
测试例程是简单的hello world程序,不赘贴出。.java文件的编译和运行操作有:
代码语言:javascript复制hu@forhu-debian:~$ vi hw.java
hu@forhu-debian:~$ ~/jdk1.7.0_45/bin/javac hw.java
hu@forhu-debian:~$ java hw
Exception in thread "main" java.lang.UnsupportedClassVersionError: hw : Unsupported major.minor version 51.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:277)
at java.net.URLClassLoader.access$000(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:212)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
Could not find the main class: hw. Program will exit.
也就是说,我用JDK7编译出来的.class程序并不能被OpenJDK6的jre运行,这比较头疼。我用Java的初衷在于消除嵌入式平台的平台相关性,JVM应该可以达成这个目标,以使得我在Windows下完成开发的无本地代码都能够正常运行。显然我现在安装的jre是不能满足要求的。因此卸载了OpenJDK6,换用OpenJDK7,运行后证实是可以的:
代码语言:javascript复制hu@forhu-debian:~$ java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (IcedTea 2.3.10) (7u25-2.3.10-1~deb7u1)
OpenJDK Client VM (build 23.7-b01, mixed mode, sharing)
hu@forhu-debian:~$ java hw
hello world!
由于OpenJDK7是有源码的,我只要下载下来,并且编译一遍即可,这一块估计问题不大。
驱动的编写需要的环境由两个模块构建:内核代码及其.o文件和gcc。这里Cubian都有给出相关链接:内核 和编译链 。 这里的编译链应该是x86的,我的目标是在Cubieboard2上构建arm对arm的编译链,这个难度应该不高,取决于gcc和glib的版本。只要知道这两点都可以解决。但是最坏情况至少得构建在PC上的交叉编译链,否则驱动无从写起。这应该是构建完OS以后实验的第一要项。
JNI的环境构建完全可以取决于上面环境的成果,相当琐碎。在最理想的情况下,可以完全通过Cubieboard2进行开发而不需要PC的介入。
——————今日追加——————
惊觉oracle已经推出了armv6/v7下的JDK,可以省去第二项时间开销
———————2013年12月24号11点日记———————
TF卡启动刷进了两个操作系统,分别是Cubian和Lubuntu(因为我只有两张空闲的TF卡)。由于Lubuntu的镜像太大(3.xG呵呵),因此还是优先选择使用Cubian。
下一步是配置无线连接。这在上面没有提到,但是对工具的下载和板子的调试都是绕不过去的。
我用的无线网卡是Mercury 150US,用wext驱动即可。先查询无线信号:
代码语言:javascript复制cubie@Cubian:~$ iwlist wlan0 scan
扫描无线信号。这时候会列出很多搜索到的信号,如果没有搜到信号,那么这应该是OS里面没有驱动。要不就去找一个匹配的驱动,要不就换一张无线网卡。这里先记下目标信号的相关信息,尤其是pairwise、group之类的,可通过重导向至文件避免重复输入命令。然后通过设置/etc/wpa_supplicant.conf 来连接上wifi。这一步前先知道psk:
代码语言:javascript复制wpa_passphrase essid password >> /etc/wpa_supplicant.conf
然后再按照 教程 所述修改即可。
下面是安装JDK的过程。如上所述,采用的是JDK1.7。经实验用hf的包以加速。建立在前人成功的基础上,用的这个包。
步骤大致如下:下载->拷贝->解压->移动至/usr/java->设定环境变量->建立符号链接
即完成了JDK的建立。
值得提醒的是,为了鉴定JDK能不能在板子上运行,可以首先在解压后打开相应的jdk文件夹下的bin,并运行:
代码语言:javascript复制./java -version
看看会不会打印出相关信息。如果没有,那就基本可以给这个包判死刑了。
环境建立以后的结果:
代码语言:javascript复制cubie@Cubian:~$ java -version
java version "1.7.0_40"
Java(TM) SE Runtime Environment (build 1.7.0_40-b40)
Java HotSpot(TM) Client VM (build 24.0-b55, mixed mode)
下面是测试运行环境,比较琐碎,不做赘述
———————2013年12月25号14点日记———————
今天安装交叉编译链和建立跨平台环境,工作环境是Debian。昨天晚上下载完gcc和内核,今天直接上。这里值得提醒的是,要注意两个东西的版本号是否匹配。在安装完Cubian(或者其他Linux发行版)后,如不确定,可以查看dmesg的打印信息。输出的信息比较长,但是对体系结构的了解帮助很大,如果有时间可以稍作浏览。我这里需要的信息摘取如下:
代码语言:javascript复制[ 0.000000] Linux version 3.4.67 (root@ubtu) (gcc version 4.8.2 20130603 (prerelease) (crosstool-NG linaro-1.13.1-4.8-2013.06 - Linaro GCC 2013.06) ) #4 SMP PREEMPT Wed Nov 6 08:31:09 CST 2013
上面显示内核版本是3.4.67而gcc版本是4.8.2 20130603,因此我上面给出的两个链接对于我现在所用的版本的Cubian是匹配的。对于嵌入式开发的新手容易忽略这个问题导致出现一些无法理解的错误,导致系统崩溃,这里先提醒一下,免做无用功。
在下载完gcc以后,注意到的一点是它的前缀很奇怪:arm-linux-gnueabihf-,arm-linux-gnu为止都应该知道是什么意思,后几个缩写是指代什么呢?下面稍作回答:
eabi是embedded application binary interface的意思,也就是嵌入式系统的ABI,和PC平台的ABI相似
而hf是ARM的一种体系结构。这个在构建JDK的时候就应该碰到过。简单来说,ARM的CPU分成两种,一种是带FPU的,一种没有。在没有FPU的体系结构下,浮点数运算是依靠函数来完成的,因此对于armhf或armel的库来说,这种体系结构下搞的事都是错的。
带FPU的又分成两种,armel和armhf,分别用两种方式来完成硬件浮点数运算。armel是ARM EABI Little-endian的意思。它通过CPU的寄存器(整型的)来完成参数传递。而armhf则是通过FPU的寄存器来完成的。因此这两种包仅针对浮点数的操作编译出来的机器码有所不同。因此我估计对于内核模块,这两个体系结构应在机器码级别上不做区分。
继续编译链的构建。解压完以后随便放哪儿,不过放在/usr下似乎更符合POSIX规范。然后再在.bashrc下更改环境变量,指向bin文件夹即可。然后下面就是花样hello world测试时间。不必上代码了。
然后要编写驱动的重要一步就是编译内核。按照文档设置应该不会出大问题。我的步骤如下:
代码语言:javascript复制hu@forhu-debian:~/sunxi-3.4$ cd sunxi-3.4/
hu@forhu-debian:~/sunxi-3.4$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- sun7i_defconfig
hu@forhu-debian:~/sunxi-3.4$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
hu@forhu-debian:~/sunxi-3.4$ make -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- uImage modules
注意我的是cubieboard2,不同的板子具体设置不一样。相信编译过内核的人都大致读得懂,menuconfig是进入图形化配置界面,由于第二行已经有默认的配置选项生成,这里不过是进去瞄两眼,不是十分重要。第四行开始编译,但我这里的网络方面的模块好像编译失败。具体原因待查。今天先写到这里,明天继续。
———————2013年12月27号14点双日记———————
开发遇到了困境。首先是Java方面。原来在PC上调试好的代码现在不能使用。主要的原因是用的开源模块的JNI的动态链接库没法加载,具体原因不明,但是我自己编写的动态链接库测试出来是可以的。因此不知道这里出现问题的原因只能归结成开源包没有测试好。于是想从另外一个角度来解决,就是直接通过重新编译来解决这个问题。但是让我疑惑的地方就是这个开源包用到了一个依赖库,其用的版本比官网上放出来的版本还要新,因此想要重新编译也没有办法,最后只能再找其他方法来实现同样的功能。
更加严重的问题是我前天编译内核失败引起的。通过insmod等加载内核模块的时候,需要查询这个模块的版本号,而这个版本号是在编译的时候通过读取内核源代码目录下的Module.symvers加进去的。而由于我内核编译失败,所以这个文件也没有生成,导致我一个很简单的内核模块也没办法加载进去。另外,现在最新的《Linux设备驱动程序》上讲的知识都是针对2.6.x版本的内核,不知道3.4版本的会不会引入差异,这一点相当值得关注。
因此,要成功运行到内核模块,必须先成功编译完内核(关键的一步是make modules的一步)。逛了一天的论坛,总结下来导致内核编译失败的原因大致就是:由于github只提供zip包,而zip包是没法处理符号链接的,因此解压下来的文件是错误的,在编译的时候会显示无法找到文件。因此,针对这个问题,解决方案也有几个,各有优缺点:
- git clone -b /dev/sunxi-3.4 https://github.com/mmplayer/linux-sunxi.git ~/dst 来从github上下载完整的包,这个最稳健,但是国情原因这个速度太慢。
- 重新为zip打补丁,并编译点击打开链接
- 依然是zip包,但是通过7z解压,这一步最简单,我此时正在尝试
我采用的是7z解压方式,但是在解压过程中竟然报文件重复了。我想没有比这个更加蛋疼的事。我的方针是先到先得,天晓得能不能覆盖。现在编译的过程中好像还没有error(呃…),但是有很多warnings(呃呃……,代码是有多不严谨,我还看到数组越界了艹艹艹艹艹),因此我现在处于心惊肉跳的阶段,要是这个都不成功,那只能通宵开机git clone了。
假设内核编译成功,那驱动该怎么写呢?最好的方法当然是找到范例照着写,由于源码就在手上,这个并不困难。但由于要用到硬件资源,我们必须先弄懂我们要用的资源有没有和操作系统占用的冲突,然后再编写相应的驱动模块。因此,我们得先知道对应的寄存器的值,再比对器件手册查看相应的比特位来确定用那个寄存器组。
现在的进度卡在了内核编译上,是死是活就看它了。
———————2013年12月31号15点连日记———————
已经好久没更新了。为什么不呢?只是因为实在做得太爽,抽不出时间来。今天趁着过年,好好地总结一下这几天做的工作,并给出细节步骤和建议。
首先是内核编译。因为和驱动相关,所以内核编译必须得通过。通过7z解压的方式是可以编译通过内核的。但是这里会迎来另外一个问题,就是编译完的内核的版本号和cubian的版本号存在差异:一个是3.4.67,一个是3.4.67 。对,没错,就是这个差异,这么蛋疼。这个细节很重要,我通过询问cubian的开发者得知,这个 号是因为代码通过git仓库clone到本地的时候,由于某个脚本来进行判断的。但是通过浏览器下载zip包又有其自在的优势(稍微快一些,下载的体积小,不容易断线),因此我这里还是建议大家采用浏览器下载的方式下载zip包内核然后通过7z解压。
那 号引起的版本不匹配导致的内核模块无法加载的问题怎么解决呢?这个好办,我们只要uname这个命令的返回结果到底是以什么文件为依据就可以了。这个文件是include/linux/vermagic.h。打开这个文件如下:
代码语言:javascript复制#include <generated/utsrelease.h>
/* Simply sanity version stamp for modules. */
#ifdef CONFIG_SMP
#define MODULE_VERMAGIC_SMP "SMP "
#else
#define MODULE_VERMAGIC_SMP ""
#endif
...//omit following code
这里发现还include了另外一个文件。当我去找这个文件的时候发现没找到(应该是从来没编译过,或者make clean后就找不到)。从文件夹名字其实就知道这个文件夹里面的文件都是在编译的时候产生出来的。因此,我们要做的并不是去手动修改这个文件(因为会在一次新的编译启动的时候被覆盖掉),而是去修改内核编译的config文件。当然,有更好的方式就是menuconfig选项。只要在某个位置输入 号,编译时编号后面就会以用户定义字串的方式把 号增加上去。
General setup->Local version – append to kernel release
这里要提醒的是,如果是通过git来下载源码包,那么上述操作都是多余的。git下来的包原本就有 号,因而可以直接开始编译。
当内核编译通过以后,内核目录下就会生成Module.symvers文件,这个文件对内核模块的版本号提供至关重要,决定了模块是否能被成功加载。如果内核编译通过,并且注意到版本号问题,那么这个时候就可以开始动手写驱动。这里的驱动需要的是要产生一个PPM波(Pulse Position Modulation)。虽然Cubieboard2有PWM的驱动,但是由于没法(或者不知道)向里面注册中断处理程序,因此,无法满足时间要求,因而要自己编写特定的驱动程序。
这里编写驱动程序要分两步走:
- 要先确定哪些资源能用,哪些资源不能
- 确定下来后,寄存器怎么配置,驱动程序怎么写
其实第一步就是要读取寄存器,相当于要求编写一个特定的驱动程序了。当然,这个驱动程序并没有任何难点,只要iomap一下就可以了。我这里核心要用到timer中断,因此只需要看timer的基址后一段距离的寄存器状态,就可以根据这个状态来判断是不是已经被操作系统占用了。实际的运行结果如下:
代码语言:javascript复制root@Cubian:/home/cubie/kermod# insmod check_reg.ko
base addr: 0x1c20c00:
offset value
0x0: 0x3
0x4: 0x0
0x8: 0x0
0xc: 0x0
0x10: 0x44
0x14: 0x3a98
0x18: 0x3a8d
0x1c: 0x0
0x20: 0x4
0x24: 0x3a980
0x28: 0x3720a
0x2c: 0x0
0x30: 0x4
0x34: 0x0
0x38: 0x0
0x3c: 0x0
0x40: 0x0
0x44: 0x0
0x48: 0x0
0x4c: 0x0
0x50: 0x4
0x54: 0x0
0x58: 0x0
0x5c: 0x0
0x60: 0x4
0x64: 0x0
0x68: 0x0
0x6c: 0x0
0x70: 0x0
0x74: 0x0
0x78: 0x0
0x7c: 0x0
和 用户手册结合,就可以知道寄存器的使用情况。
得知可用timer4资源。因此后面就是编写timer4相关的驱动程序。等我这个项目做完会发布相关源码,而且这个驱动程序也很简单,因此今天不再作赘述。
———————完结补记———————
临近考试周,所以我就断了两个星期备考,另加上这个东西都要做不完了,所以现在才来补上。
首先之前我已经确认内核模块和Java环境两点,因此下面的步骤就是上机连调。
上机以后发现,直接用Timer产生PPM波会有10 us的误差因而导致整个想法崩盘,最后使用的方式是MSP430 ARM GPIO控制的方式实现NAZA飞控控制。至于430程序怎么编写这点实在是琐碎,而且也不必要,任何一个简单的单片机都能胜任的工作,只是手头上有小体积的430并且熟悉,可以短时间完成开发,而GPIO的控制驱动就更加简单了,因此这一部分的开发就没什么难度。
然后就是用户层接口。就是简单的open,close和ioctl的wrapper罢了,不予赘述。
这个项目的难点,对于我来说其实就是前面的环境构建部分,由于我混底层还是有点经验了,因此后面的驱动、单片机开发基本就不会出现什么意料以外的事情,比较平淡地就过去了。最后上几张飞起来的图。
最后会有总结型的日志以报告形式给出,这篇日志还是略显繁琐。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/163034.html原文链接:https://javaforall.cn