2009年2月26日星期四

Nucleus学习

作者:Sam (甄峰) sam_code@hotmail.com

公司想MTK解决方案中添加手柄概念进去。MTK使用Nucleus 操作系统,它是由ATI开发的RTOS.

MTK使用了nucleus实时操作系统,在其上做了个内核抽象层的封装(Kal) ,以适应多种实时操作系统,如oscar、ThreadX、nucleus。整个软件系统包括nucleus操作系统、平台设备驱动、协议栈、文件系统、WGUI、MMI、J2ME等。在这里MMI部分几乎包括了操作系统内核、协议栈、文件系统之上的所有部分,其中WGUI也在其中。

Nucleus是单一地址空间操作系统的一种,作为商业化的嵌入式操作系统产品,曾被广泛使用。在MIPS架构中,其操作系统和用户程序完全工作在内核模式,且只占用和访问0x80000000以上的线性地址空间。因此,在Nucleus中,操作系统和用户程序工作在线性地址空间中,且用户程序与内核服务之间没有明显的区分,进入内核服务更像是调用API(Application Program Interface)而不需要上下文切换。其优点是限制少,编程方便,但系统健壮性差。

MTK手机软件系统的构建使用了GNU的make,使得整个工程的构建可以自动进行,且可以灵活控制。整个构建过程由Make.bat、make2.pl、MoDIS.dsw、Gsm2.mak、Option.mak、_.mak等文件控制。


在MTK手机软件系统的根目录下有一个批处理文件Make.bat,这个批处理文件启动了整个工程的构建过程。在windows的命令行下,在该系统的根目录下输入命令make和相应的参数即可开始工程的构建,该批处理文件的使用方法如下所示。
Usage: make ["customer""mt62xx"] "project" "action" ["modules"]



增加模块的配置实例
若我们需要加入zlib这么一个模块(zlib包含了很多程序都用到的压缩和解压函数),我们可以按如下步骤进行。
1. 把zlib的源代码包拷贝到你的MTK软件系统根目录下,这样所有的zlib代码都在zlib目录或zlib-1.2.3目录下(用哪个目录名由你的喜好来确定),在此为简单起见就放在zlib目录。
2. 在make目录下新增一个zlib的目录(最好和根目录下的目录名一样),增加四个文件,分别是zlib.def、zlib.inc、zlib.lis、zlib.pth。zlib.def文件只要加上APCS_INTWORK就可以了,其他三个文件中加上源文件及其目录、头文件目录即可。
3. 在REL_CR_MMI_.mak这个文件的合适位置加上如下语句。CUS_REL_SRC_COMP += zlib
4. 把这个工程remake一下,若没有错误,zlib模块就成功加上去了。其他模块就可以调用zlib提供的压缩和解压缩函数了。

Nucleus内核的主要目的是管理实时任务的调度运行,共享处理器资源,为应用提供各种便利,快速响应外部事件,实现实时性。利用Nucleus开发平台,用户只需要编写任务代码(Task)和中断服务程序代码(ISR),二者利用系统调用实现与Nucleus的交互,由Nucleus内核来调度多个任务并行执行,实现处理器共享。

2009年2月23日星期一

嵌入式系统中的usleep延迟

作者:Sam(甄峰) sam_code@hotmail.com

Sam在作嵌入式系统中,发现类似usleep这样的函数在不同平台上延迟并不相同。所以如果想要延迟一段时间,尤其是比较精确的延迟,应慎重使用类似函数。

举例而言:
例1:
usleep(5000); //延迟5ms.

在PC-linux中,延迟时间大约在5020-5030微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在8010微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。

当然,这不排除Sam使用gettimeofday() 函数所用时间,但可以肯定的是,在不同CPU速度下,usleep() 延迟造成的效果应该差别很大。


例2:
usleep(50); //延迟50微秒
在PC-linux中,延迟时间大约在59-60微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在4000微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。


例3:
usleep(50000); //延迟50000微秒
在PC-linux中,延迟时间大约在50076微秒左右。
在Canmore(800M CPU)中,延迟则会大很多, 在56047微秒左右。
在X5平台(300M CPU)中, 延迟到60000微秒左右。


从上面的数据看,在嵌入式系统中,最好不用时用usleep这类函数,因为他们特别不准确。当延迟比较大时,稍微准确一些,但延迟小到一定程度时,延迟会非常不准确。好像有一个延迟最低点。低于这个数据后,不管你想要延迟多少,系统都会延迟这么久。例如X5平台的20000微秒。

2009年2月19日星期四

高速数据交换时的lock问题

作者:Sam(甄峰) sam_code@hotmail.com

Sam在作Bluetooth Remote Control程序时,遇到一个问题。
手柄每5ms发送一个数据包。
而Sam程序是这样设计的:
在手柄连接时,BTRC创建Read thread. 在Read thread中会读取手柄数据并解析包。解析结果放到一个buffer(结构体)中。
另外一边,应用程序会使用BTRC的read接口去读取这个buffer的内容。这个应用程序应该会用一个thread去不断的调用BTRC的read接口去读取。


方案一:
Sam在解析包部分向buffer中写东西时会加锁,写完会解锁。BTRC的read接口读取buffer时加锁,读完会解锁。
但这样有个后果:
1. 如果 双方都是全速运行,也就是说:如果双方都不添加sleep.则会出现:程序先进入BTRC的read接口读取buffer很多次(20次)左右。这个时候,BTRC的使用BTX去读取并解析然后写入buffer的程序的lock无法进入。
然后,在某一时刻,BTRC使用BTX区读取并解析然后写入buffer的程序又集中运行很多次(15-20次)。
这就造成上层程序使用BTRC的读取接口读到的解析后的数据完全不正确。

2. 如果上层程序调用BTRC的读取接口时作一个延迟,比如4ms.
这样的话,因为程序使用BTRC 的读取接口有一个延迟,所以BTRC是用BTX去读取并解析然后写入buffer的程序有更大机会去写入。但会造成写入多个才能够读取一次的情况。例如:A包过来了,BTRC使用BTX去读取,解析后写入buffer. A+1包又过来了,然后BTRC使用BTX读取时,BTRC的读接口来读buffer,但buffer已经被lock了。所以只能等下一次去读取了。这造成只能读取第二个包。所以还是会造成丢包。

3. 如果去掉lock,并全速运行:
现象与情况一相同。


方案二:
将BTRC的读接口改造,每次不是再去读当前的一份数据,而是如下去做:
用返回值表示从上次读取到此次读取这段时间内,buffer中新的数据包个数。参数为指针的指针。指向数据头。





注[1]:Sam有个感觉,在不同速度的CPU上,尤其是一些嵌入式平台中,thread的schedule速度好像比较慢,所以在不同thread之前的切换速度比较慢。像在X5平台上,BTRC的Read接口去read buffer, 竟然只能读到30%-40%的包。Canmore可以读到70%的包。而PC上则可以读到100%的包,还有重复读到的。





2009年2月18日星期三

Hi3110 EV 100平台上Bluetooth的移植

作者:Sam(甄峰) sam_code@hotmail.com


Sam想把手柄库移植到海思 Hi3100 EV 100平台上去。先进行评估如下:



1. Hi3100 EV 100为ARM平台。
2. Hi3100 EV 100使用uclibc。
这两个差异决定了需要从bluetooth 底层库开始移植。


一:
Sam从linux kernel中编译出了bluetooth.ko, hci_usb.ko, l2cap.ko, hidp.ko。

二:
下载新版bluez.
http://www.bluez.org/


三:
交叉编译bluez:
交叉编译时发现,新版bluez要基于D-BUS等库。所以Sam暂时使用bluez 1.0版本。
#cd bluez-libs-2.12
# CC=/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc ./configure --host=arm-linux
#make clean;make
#cd bluez-libs-2.12/src/.libs
看到libbluetooth.so就在这里。


四:
交叉编译BTX:
在BTX Makefile中,
CC := /opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc
CFLAGS := -D_SHOW -D_DEBUGTIMEOUT -Wall -I../include -I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/
LFLAGS := -L./ -L../resource -L/opt/hisilicon/toolchains/sysroot-uclibc-soft/usr/lib
编译libBTX.so和BTX_Test成功。
其中libBTX.so编译选项如下:
$(CC) $(CFLAGS) $(LFLAGS) -fPIC -shared BTX.o -o libBTX.so -lbluetooth

但奇怪的是,在Hi3110 EV100平台上,运行BTX_Test会出现如下信息:
Can't modify .//libBTX.so's text section. Use GCC option -fPIC for shared objects, please.

Sam只好把BTX并入程序中。


五:
编译Bluetooth_remote:
CC := /opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc CFLAGS := -D_SHOW -D_DEBUGTIMEOUT -Wall -I../include -I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/ LFLAGS := -L./ -L../resource -L/opt/hisilicon/toolchains/sysroot-uclibc-soft/usr/lib
并将BTX.c编译进去。
编译成功。
但运行时会有问题,反应比较慢。Sam还在查。

2009年2月15日星期日

FCEU 分析

作者: Sam(甄峰) sam_code@hotmail.com

Sam下载了fceu_0.96.tar.gz.
首先察看 Makefile.

这个Makefile包含makefile.base。将所有东西编译进去。因为是Linux版本,所以底层使用SDL.
所以Makefile将drivers/cli 中的sdl有关的内容全部编译进去。

主程序从 drivers/cli/sdl.c中开始。

大致看来,FCEU底层显示和声音基本都是使用SDL实现的。

Makefile中,有一项-DUNIXDSP。如果使用这一项,则直接使用OSS driver建立的device--/dev/dsp来播放声音。如果不使用这一项,则通过SDL播放。 SDL底层则可以适配到不同的音频设备上,如OSS, ALSA等。


代码解析:
在Fceu代码目录中,drivers/cli为Linux下实现Fceu的代码段。因为Fceu基于SDL实现,所以底层和SDL有很大关系。
代码起点放在: drivers/cli/sdl.c中。

SetDefaults(); 用来初始化一些设置,这些设置放在全局变量Settings中。

然后进入Fceu的主体:CLImain()

1.FCEUI_Initialize()
NES(FC)的分辨率为:256x224(NTSC) 或 256x239(PAL)

1.1 分配video buffer.
每个scanline有256+15 byte.
一共有最多240个scanline.
另外还有8 byte的alignment。 8byte的catch overflow
所以分配了 (256+16)x 240 + 8 + 8 字节的buffer,并设为128。

1.2 建立目录。
在 "HOME"目录下创建.fceultra 目录,并在其中创建5个目录。
例如: /home/sam/.fceultra/

1.3 load config 或建立config file.
查/home/sam/.fceultra/fceu.cfg文件,如果有,则提取其config,如果没有,则建立。


2009年2月5日星期四

SDL的移植

作者:Sam(甄峰) sam_code@hotmail.com

Sam有一个计划,将SDL video driver底层适配到dfb和framebuffer上。之前做过一些工作,虽然编译通过了,但SDL例子程序跑起来却会有问题。这次决定重新作一次。


SDL简介:
SDL(Simple DirectMedia Layer),一个简单的游戏开发库。SDL是一个跨平台的多媒体函数库,被用来设计成在低层访问音频,键盘,鼠标,游戏杆,基于OPENGL(一个3D图象开发函数库)的3D 硬件,和2D图象帧缓冲区。Sam:这个图像帧缓冲区是否就是framebuffer??


1.下载SDL:
http://www.libsdl.org/download-1.2.php


2. SDL代码结构粗浅理解:
Sam看过SDL/src目录后,对代码结构有个粗浅的理解。
以下以audio和video目录为例谈一谈。

2.1 audio目录:
audio目录中包括一些.c文件和一些目录,如alsa,dsp,dummy等。
Sam的理解是:audio目录中的.c文件是上层程序使用audio的统一接口。而这些目录如alsa,dsp,dummy等中的代码则是SDL对具体使用何种audio device的适配。alsa就是audio统一接口对alsa的适配,dsp则为对OSS的适配等等。

2.2 video目录:
同上video目录下的.c文件为上层程序显示的统一接口。目录内的程序则为各种显示方法的适配,
如fbcon为framebuffer. directfb为dfb。


3.编译尝试:
3.1: SDL提供了一个最小化的Makefile:Makefile.minimal,所有的实现都是dummy,就是一个空的实现,编译能通过,但运行时什么都不能做.
我们先研究一下这个Makefile:

INCLUDE = -I./include
CFLAGS = -g -O2 $(INCLUDE)
AR = ar
RANLIB = ranlib
CONFIG_H = include/SDL_config.h

TARGET = libSDL.a
SOURCES = \
src/*.c \
src/audio/*.c \
src/cdrom/*.c \
src/cpuinfo/*.c \
src/events/*.c \
src/file/*.c \
src/joystick/*.c \
src/stdlib/*.c \
src/thread/*.c \
src/timer/*.c \
src/video/*.c \
src/audio/dummy/*.c \
src/video/dummy/*.c \
src/joystick/dummy/*.c \
src/cdrom/dummy/*.c \
src/thread/generic/*.c \
src/timer/dummy/*.c \
src/loadso/dummy/*.c \
OBJECTS = $(shell echo $(SOURCES) sed -e 's,\.c,\.o,g')

all: $(TARGET)

$(TARGET): $(CONFIG_H) $(OBJECTS)
$(AR) crv $@ $^ $(RANLIB) $@
$(CONFIG_H):

cp $(CONFIG_H).default $(CONFIG_H)
clean:

rm -f $(TARGET) $(OBJECTS)

这个Makefile很简单:
#make all 会生成libSDL.a
libSDL.a是所有.o ar成的一个库。

注意:OBJECTS= SOURCES的.c 变.o

#make clean 会删除libSDL.a和所有.o


3.2 正式编译
那怎样把想要的实现加上去呢?
比如:
3.2.1
Sam想在Video上选择framebuffer,在audio上选择alsa. 该怎么做呢?

则可以在Makefile中添加:
src/video/fbcon/*.c
src/audio/alsa/*.c

直接编译,发现编译成功。生成了libSDL.a

3.2.2
Sam想要把底层适配为如下模式:
Video: directfb
audio: alsa
则Sam如下添加:
src/video/directfb/*.c
src/audio/alsa/*.c

编译后会报错。显示缺乏directfb头文件。

于是Sam在Makefile中添加了:
-I/home/sam/work/current/Intel_CE_3110/Intel_CE3110_Dev/Canmore-1.1078/i686-linux-elf/usr/local/include/directfb
但发现为跨平台准备的类型定义重定义了。
这其实是因为SDL和Directfb都为跨平台考虑,tyedef了类型。
但directfb中是:typedef unsigned long uintptr_t;
SDL中为:typedef unsigned int uintptr_t;
所以Sam修改了SDL中的typedef

则可以编译成功。


注意:虽然编译成功,但其实bootstrap中并未加入fbcon或者directfb.
所以Sam又在include/SDL_config_minimal.h中添加了:
/* Sam add it for fb*/
#define SDL_VIDEO_DRIVER_FBCON 1
则fb被加入了。



SDL Video架构:
基于SDL的程序初始化时,会调用 SDL_Init()。
SDL_Init(flags)通过调用SDL_InitSubSystem(flags)来初始化各个子系统。具体哪个子系统,通过flags指定。
如果flags指定为video. 则调用SDL_VideoInit()

在SDL_VideoInit() 中,通过bootstrap去分别指向video底层的各个实现。
SDL设计的很精巧,bootstrap中只有2个接口:available,create。

其中available用来查询设备是否可用。 create用来对上层提供所有接口。
所以,如果有人想要在嵌入式系统中移植SDL,则要么支持fb或dfb.要么提供一套新的接口。并利用available和create将他们提供给上层。


SDL中有个结构:
SDL_PixelFormat:
这个结构是用来表示每个Pixel的格式的。因为SDL适配于多种显示设备上。所以用这个结构体来抽象每个Pixel。

这个结构中类似BitsPerPixel这样的含义很简单。
还有一下这些项目:

//每个色彩的精确度损失
Uint8 Rloss;
Uint8 Gloss;
Uint8 Bloss;
Uint8 Aloss;

//每个piexl中各个颜色的偏移
Uint8 Rshift;
Uint8 Gshift;
Uint8 Bshift;
Uint8 Ashift;

//每个piexl中对应色彩的Mask。
Uint32 Rmask;
Uint32 Gmask;
Uint32 Bmask;
Uint32 Amask;
这些数值可以和framebuffer中的fb_bitfield 结构对应起来看。
例如:
从Framebuffer中,读到的fb_var_screeninfo 中:
red:offset=10,length=5
Blue: offset=5, length=5
green:offset=0, length=5
transp:offset=15,length=1
这说明一个pixel结构如下:
GBRA
5551 =16bit
那么:
Rshift=Red.offset=10
Bshift = Blue.offset=5
Gshift = Green。offset=0
Ashift=transp.offset=15


Rmask=111110000000000B
Bmask=000001111100000B
Gmask=000000000111110B
Amask=00000000000001B




2009年2月4日星期三

GDB学习及嵌入式下如何使用Debug

作者: Sam(甄峰) sam_code@hotmail.com

前段时间和朋友聊天,谈到工作中使用什么调试工具,Sam很不好意思地讲,我只用printf打印信息调试,被狠狠地嘲笑了一通。Sam一直作嵌入式程序或kernel层程序,在这个领域内,用调试工具的人好像还真不算多。但Sam很认同朋友的话,调试工具能够极大的提高效率。所以决定借当前的移植工作把GDB用起来。


GDB(GNU symbolic debugger)
GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量,寄存器,内存及堆栈。更进一步你可以修改变量及内存值。


Sam首先用BT_remote来做GDB试验。
1. 编译时需要加入选项 -g

2. 使用GDB 启动程序。
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l //相当于list,列出代码
166 int main(int argc, char** argv)
167 {
168 BOOL bDongle_Ready = FALSE;
169 BTRC_Rel BTRC_Rel_all;
170 int iRet_pthread_create = 0;
171 pthread_t ntid;
172 UINT8 ID;
173 UINT16 SetAudioFile_rel;
174 int i = 0;
175 BT_CONNECTION_STATUS status;
176
177
178
179 BTRC_Rel_all = BluetoothDongleInit();
180 if(BTRC_Rel_all != BTRC_SUCCESS)
181 {
182 printf("\nBluetooth Dongle Init fail. Error Code:[%d]\n", BTRC_Rel_all);
183 return -1;
184 }


(gdb) break 169 //在168行设置断点
(gdb) break BluetoothDongleInit //在函数BluetoothDongleInit处加断点
(gdb) info break //察看断点
Num Type Disp Enb Address What
1 breakpoint keep y BluetoothDongleInit
2 breakpoint keep y 0x0804925c in main at main.c:169

(gdb)clear169 //清除Line 169断点



(gdb)watch iBluetooth_Rel_all //将变量设为观察点,当此变量有变化时,程序立刻停住
(gdb)info watchpoints //显示观察点
(gdb) r //run,开始run程序
到了第一个断点,则停下来,可以用 l 命令看其前后代码,也可以用p 来看某个变量的值

(gdb) p bDongle_Ready
$1 = FALSE

(gdb) n //next
(gdb) c //继续执行



主题二:
嵌入式下如何使用GDB:

1. 下载GDB.
http://ftp.gnu.org/gnu/gdb/

2. 编译:
(注意:因为此gdbserver是为Canmore使用的,所以不需要类似:CC=arm-linux-gcc ./configure --host=.... --target==... 这样的东西,如果是其它ARM平台,则需要这样做)
2.1 编译GDB
#./configure
#make clean;make
2.2 编译gdbserver
#cd gdb/gdbserver
#./configure
#make clean;make
将生成的gdbserver放到嵌入式板子上去。

3. copy 所需库:
将libthread_db copy到嵌入式平台。

4. 使用
4.1在嵌入式平台:
#gdbserver 172.16.1.61:1234 BT_remote
Process BT_remote created; pid = 716
Listening on port 1234
这个意思是:debug BT_remote这个程序。对方是172.16.1.61. 端口为:1234。
4.2在Host 端:
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)Copyright (C) 2006 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) target remote 172.16.1.31:1234
Undefined target command: "172.16.1.31:1234 ". Try "help target".(gdb) target remote 172.16.1.31:1234`system-supplied DSO at 0x110000' has disappeared; keeping its symbols.Remote debugging using 172.16.1.31:1234[New Thread 716]
warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug0xb7f17810 in _start () from /lib/ld-linux.so.2

此时发现嵌入式板子上有反应:
Remote debugging from host 172.16.1.61

则表明嵌入式板子和Host Debug链接成功,则可以在Host上调试嵌入式板子上的程序了。

但请注意,在Host上,不能使用 r 来运行,而应该使用 c来运行,因为这个程序已经在运行了。

则可以正常使用了。



嵌入式平台上GDB调试实例:
Sam想在X5平台上使用GDB来调试程序。
分析:
1. 需要gdbserver
2. 客户端需要gdb程序。
3. 需要重新编译,加入 -g.

1.编译gdbserver:
#cd gdb-6.8/gdb/gdbserver
#CC=/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc ./configure --host=arm-linux
#make clean;make all
则gdbserver生成成功。Sam把它copy到板子的/bin目录中

2. 客户端gdb程序。
直接使用交叉编译器中的arm-uclibc-linux-gdb

3.重新编译程序。
Sam是想调试SDL例子程序,所以在SDL库生成过程和test生成过程中均加入 -g.




实际操作:
1. 嵌入式板子中:
#gdbserver 172.16.1.61:1234 testsprite

2. Host
2.1
#arm-uclibc-linux-gdb testsprite
2.2
(gdb) target remote 172.16.1.45:1234
2.3
(gdb) c


之后和本地调试类似。