• 15380阅读
  • 7回复

基于嵌入式可视通讯系统:模块_摄像头采集显示程序:出现的问题,不知道有谁知道 [复制链接]

上一主题 下一主题
离线keisuo
 

只看楼主 倒序阅读 楼主  发表于: 2007-05-11
— 本帖被 XChinux 执行加亮操作(2008-07-18) —
摄像头采集显示程序:出现的问题,不知道有谁知道
1.在采集图片while (1)循环中,先内存映射得到buffer地址,然后调用重绘制函数repaint时,只有当前应用程序窗口,被其他窗口覆盖后,再把应用程序窗口点击到顶层窗口后,才回重绘制窗口.
2.当调用pixmap.loadFromData((const char*)buffer,(unsigned int )size);
出现错误:aggregate value used where an integer wea expected.
3.当调用pixmap.loadFromData((const char*)buffer,size);
出现错误:unknown second  参数
[ 此贴被XChinux在2008-07-18 16:41重新编辑 ]
离线keisuo

只看该作者 1楼 发表于: 2007-05-11
如何显示内存中的图片
我有一段内存,连续存放图片的数据,想要显示在label上,应该怎么做啊?我试了几种方法,都不行,请大家帮帮忙。谢谢
离线keisuo

只看该作者 2楼 发表于: 2007-05-11
发表于: 2007-02-08 17:09    发表主题: 请教:QT图片循环显示,闪烁如何优化?谢谢 

--------------------------------------------------------------------------------

我刚刚接触QT,本应该从头慢慢学起的,但目前有一个问题急于解决,还望各位高手不吝赐教一下。

为了使几副图片循环显示,在paintEvent里加了几副图片的显示调用painter.drawPixmap();并在主窗口里加了一个Timer,timeout触发repaint();

虽然可以实现,但效果不理想,刷新时,感觉图片会有闪烁。

查了资料http://doc.trolltech.com/qq/qq06-flicker-free.html
可以利用double buffer缓存方法解决,但我却看不明白,能否指导一下,谢谢。

double buffer是不是指开一个新的空间,提前将所要显示的内容存储进去,然后再进行显示?因为我所要显示的是透明背景的图片,是否需要将背景和图片先进行叠加,然后再进行显示?如果需要叠加,那应该调用什么函数?
盼复,谢谢。


大致的代码,可以缩减为如下:

#include "t1111dialog.h"
#include <qtimer.h>
#include <qpixmap.h>

t1111Dialog::t1111Dialog( QWidget* parent, const char* name, bool modal, WFlags f )
: t1111DialogBase( parent, name, modal, f )
{
num = 0;
// Add your code
QTimer *movieTimer = new QTimer( this ); // create internal timer
connect( movieTimer, SIGNAL(timeout()), SLOT(timeout()) );
movieTimer->start( 500 ); // emit signal every 5 seconds
}


void t1111Dialog::timeout()
{
repaint();
}


void t1111Dialog::paintEvent( QPaintEvent *evt )
{
QPainter painter;
QPixmap image1("PICTURE-01.png");
QPixmap image2("PICTURE-02.png");
QPixmap image3("PICTURE-03.png");
QPixmap image4("PICTURE-04.png");

painter.begin(this);


switch(num){
case 0:
painter.drawPixmap(0,0,image1);
break;
case 1:
painter.drawPixmap(0,0,image2);
break;
case 2:
painter.drawPixmap(0,0,image3);
break;
case 3:
painter.drawPixmap(0,0,image4);
break;
default:
break;
}
painter.end();
num++;
if( num >= 4 )
num = 0;


}

double buffer建议你看看C++ Programming with Qt 3/4
里面都有专门的小节讲这个

你是想做动画?
想不闪烁建议用update
不要用repaint
这部分看看文档吧


谢谢站长的回复.

我去找下C++ Programming with Qt 3/4看一下.

但另外的你所指的文档,是指?QT 类的说明吗?

或者是否可以提供个网址给我,谢谢了.

我是想做动画,显示的图片是透明背景的,因为刚接触QT,所以调用的函数也显得很局限.

我原先一开始就是用update(),后来改用repaint()感觉没太大区别,呵呵.

另外,我用的是QDialog,这个跟用QWidget在调用double buffer处理闪烁问题应该没什么区别吧?

话罗嗦了点,烦请指教.

好好看看QWidget中有关paint(), update(), repaint()的介绍吧
离线keisuo

只看该作者 3楼 发表于: 2007-05-11
Double Buffering 


Flicker闪烁 can be completely avoided if there is no delay延迟 between painting the background and painting the content. This can be achieved完成 by rendering 着色 the background into a pixmap 象素映射, then painting the content onto that pixmap, and finally painting the pixmap onto the widget. This technique is known as double buffering.

The quick and easy way to do this is to create a pixmap that's the same size as the rectangle that needs to be repainted. Then paint everything into the pixmap and finally paint the pixmap onto the widget. While doing this is completely flicker-free, it means paying a price in memory consumption and processing. For example, you would need to allocate and deallocate a pixmap of width × height × color-depth bits in every paint event, and this is a lot of memory if the widget gets maximized on a 32-bpp (bits per pixel) 1280 × 1024 screen. It is possible to reuse the same pixmap to save reallocations, but it would need to be the maximum possible size of the widget, so it will still consume a big chunk大块 of memory.

A neat灵巧的 solution is to combine double buffering with stripes条纹. Instead of storing the whole widget's rectangle in a pixmap, we can store one line instead. We then paint the background and content of a single line at a time and copy the pixmap back to the widget for each line that's painted. This requires a pixmap that's only line-width × line-height × color-depth bits in size; a lot less than the whole widget. We can also reuse the same pixmap, resizing it to be larger if necessary.

Here's another version of the paintEvent() we wrote earlier, this time using double buffering, reusing the pixmap, and painting in stripes.

    void MyWidget::paintEvent(QPaintEvent *evt)
    {
        // any initialization
        QPainter painter(this);
        QRegion unpainted(evt->clipRegion());
        static QPixmap *doubleBuffer = 0;
        if (!doubleBuffer)
            doubleBuffer = new QPixmap;
        QPainter dbPainter(doubleBuffer);

    for (int i = 0; i < lines.count(); i++) {
            Line *line = lines;
            doubleBuffer->resize(QMAX(doubleBuffer->width(), line->boundingRect().width()),
                 QMAX(doubleBuffer->height(), line->boundingRect().height()));
            doubleBuffer->fill(backgroundColor);
            line->draw(&dbPainter);
            painter.drawPixmap(0, line->boundingRect().y(), *doubleBuffer, 0, 0,
                              line->boundingRect().width(), line->boundingRect().height());
            unpainted -= line->boundingRect();
        }
        painter.setClipRegion(unpainted);
        painter.fillRect(unpainted.boundingRect(), backgroundColor);
    }
    We reuse the pixmap by declaring it as a static pointer and allocating it if it is 0 (i.e. the first time we paint). Instead of painting directly on the widget, we paint on the pixmap. We must ensure that the pixmap is large enough each time we use it, resizing it if necessary. The painting code is almost the same as before because Qt provides a device-independent painting API. We just have to copy the pixmap to the widget after painting, using drawPixmap(). (We could have used bitBlt(), but drawPixmap() is faster if the painter is already open.)

This technique completely eliminates flicker, without consuming too much memory. Qt uses this approach in many standard widgets, including QTextEdit, QListView and QMenuBar. In fact, Qt applies a further memory optimization. Instead of allocating a double buffer pixmap for every single widget, Qt allocates a global pixmap for every type of widget, no matter how many instances of that widget are used.
离线keisuo

只看该作者 4楼 发表于: 2007-05-11
  char* devicename="/dev/video0";
  char* buffer;
  typedef struct _v4ldevice device;
  int width = 640;
  int height = 480;
  int frame = 0;
  //v4lopen("/dev/video0",&device);    打开设备
  int i;
  if((vd->fd = open(name,O_RDWR)) < 0)
  {
      return -1;
  }
  if(v4lgetcapability(vd))
      return -1;
  //v4lgrabinit(&device,width,height); 初始化设备,定义获取的影像的大小
  vd->mmap.width = width;
  vd->mmap.height = height;
  vd->mmap.format = vd->picture.palette;
  vd->frame = 0;
  vd->framestat[0] = 0;
  vd->framestat[1] = 0;
  //v4lmmap(&device);                  内存映射
  if(v4lgetmbuf(vd)<0)
      return -1;
  if((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0)
  {
      return -1;
  }
  return 0;
  //v4lgrabstart(&device,frame);      开始获取影像
  vd->mmap.frame = frame;
  if(ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0)
  {
      return -1;
  }
  vd->framestat[frame] = 1;
  return 0;
  /////////////////////////////////////////////////////
  while(1)
  {
  //v4lsync(&device,frame);          等待传完一帧
    if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
    {
      return -1;
    }
    vd->framestat[frame] = 0;
    return 0;
  /////////////////////////////
    frame = (frame+1)%2;              //下一帧的frame
    //v4lcapture(&device,frame);        获取下一帧
    vd->mmap.frame = frame;
    if(ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0)
    {
      return -1;
    }
    vd->framestat[frame] = 1;
    return 0;
    ////////////////////////////
    buffer = (char*)v4lgetaddress(&device);//得到这一帧的地址
    //buffer给出了图像的首地址,你可以选择将图像显示或保存......
    //图像的大小为 width*height*3
  ..........................
  }
/////////////
  int v4lgetcapability(v4ldevice *vd)
  {
    if(ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0)
     {
        v4lperror("v4lopen:VIDIOCGCAP");
        return -1;
     }
    return 0;
  }
/////////////
  int v4lgetmbuf(v4ldevice *vd)
  {
      if(ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))<0)
     {
        v4lperror("v4lgetmbuf:VIDIOCGMBUF");
        return -1;
     }
      return 0;
  }
////////////
  unsigned char *v4lgetaddress(v4ldevice *vd)
  {
    return (vd->map + vd->mbuf.offsets[vd->frame]);
  }
离线keisuo

只看该作者 5楼 发表于: 2007-05-11
嵌入式远程视频采集系统的设计与实现
来源:CE china / 作者:张永强,赵永勇,李崇德 点击:1240 添加时间:2006-6-5 9:11:42     

            多媒体通信技术的发展为信息的获取和传输提供了丰富的手段,视频采集是其中不可缺少的重要组成部分,该系统基于S3C2410的ARM9芯片和嵌入式Linux操作系统,采用USB摄像头捕捉视频,经MPEG-4算法压缩编码,系统直接与网络相连,用户使用标准的网络浏览器和流媒体播放程序即可查看远程视频影像。 
      1 硬件系统 
      系统硬件平台选用北京博创公司的UP-NETARM2410开发板,该系统基于ARM9架构嵌入式芯片S3C2410,稳定工作在202MHz主频,板载64MB SDRAM 64MB FLASH,主板资源包括:主USB口、从USB口、10M/100M以太网口,触摸屏、彩色LCD、键盘、8个用户自定义LED数码管, A/D,RTC电路,2个串口、1个JTAG通用接口,音频模块,支持MPEG4,MP3编解码,3个168PIN的扩展插座,32位的数据总线,保留充分扩展空间。 
      其中标配模块包括:IC卡+PS2模块、IDE硬盘+CF卡模块、PCMCIA+SD/MMC模块。另外可选配模块有:GPS模块,GPRS模块,FPGA模块,CAN+AD+DA模块、红外模块、蓝牙模块、摄像头模块。 



      2 软件系统 
      2.1 内核配置与USB摄像头驱动 
      假定已经搭建好嵌入式Linux的开发环境,下面第一步工作就是USB摄像头的安装与驱动。首先检查Linux Kernel中是否已经添加了USB模块的支持,并且加入Video4Linux支持。 
      Multimedia devices→<M>Video For Linux 
      Video For Linux→[*]V4L information in proc filesystem 
      在主菜单的USB Support下还有各种摄像头的驱动,选中将要使用的摄像头芯片类型。 
      <>USB IBM (Xirlink)C-it Camera support<*>USB OV511 Camera support<>USB Philips Cameras 
      <>USB SE401 Camera support<>USB STV680(Pencam)Camera support<>USB 3com HomeConnect(akavicam)support 
      在USB摄像头选购时,优先考虑Linux内核公开支持的摄像头芯片,不然要额外编写相应的USB摄像头驱动程序,然后进行编译、安装。在此选用网眼公司的V3000产品,他采用了OV511的芯片。 
确定USB摄像头被正常驱动后,下一步就是使用Video4Linux提供的API函数集来编写视频采集程序。 
      2.2 基于V4L设计的视频采集模块 
      在Linux下,所有外设都被看成是一种特殊的文件,称为设备文件。系统调用是内核和应用程序之间的接口,而设备驱动程序则是内核和外设之间的接口。他完成设备的初始化和释放、对设备文件的各种操作和中断处理等功能,为应用程序屏蔽了外设硬件的细节,使得应用程序可以像普通文件一样对外设进行操作。
    Linux系统中的视频子系统Video4Linux为视频应用程序提供了一套统一的API,视频应用程序通过标准的系统调用即可操作各种不同的视频捕获设备。Video4Linux向虚拟文件系统注册视频设备文件,应用程序通过操作视频设备文件实现对视频设备的访问。Linux下与Video4Linux相关设备及用途如表1所示。 


 
      这里主要针对设备文件/dev/video进行视频捕捉方面的程序设计。Linux下视频采集流程如图2所示。 


 
其中用到的主要函数有: 
      Camera_open():用来开启视频设备文件,使用前需要首先声明一个video_device类型的设备文件。 
      Camera_get_capability():通过调用ioctl()函数取得设备文件的相关信息,并存放到video_capability结构里。 
      Camera_get_picture():通过调用ioctl()函数取得图像的相关信息,并且存放到video_picture结构里。 
      Camera_close():用来关闭设备文件。 
      Camera_grab_image():用来抓取图像,采用mmap方式,直接将设备文件/dev/video0映射到内存,
      加速文件I/O操作,还可以使多个线程共享数据。剩下的还有设备初始化、参数设备等相关函数,不再详述。 
        2.3 视频压缩编码模块 
      获取图像数据后,可以直接输出到FrameBuffer进行显示,由于本系统要将采集到的视频影响通过网络传输出去,所以在传输之前要对原始的图像数据进行压缩编码,在此选用MPEG-4视频编解码方案。和其他标准相比,MPEG-4压缩比更高,节省存储空间,图像质量更好,特别适合在低带宽条件下传输视频,并能保持图像的质量。MPEG-4中基于对象的视频编码过程可以分为3步进行:
      (1)从原始视频流中分割视频对象。 
      (2)对视频对象进行编码,对不同视频对象的运动信息、形状信息、纹理信息分配不同的码字。对输入的任意形状的VOP序列,用基于块的混合编码技术编码,处理顺序是先IVOP后PVOP,BVOP。在对VOP的形状信息编码后,取得任意形状VOP的采样,每个VOP划分为不相交的宏块,每个宏块含有4个8×8象素块进行运动补偿以及纹理编码,已编码的VOP帧保存在帧存中,在当前VOP帧和已编码VOP帧之间的计算运动矢量;对将编码的块和宏块,计算他们的运动补偿预测误差;运动补偿预测后的IVOP及误差用8×8块DCT变换,并进行DCT系数的量化,然后是游程编码和熵编码。 
      (3)对各个视频对象的码流进行复合,每个视频对象的形状、运动纹理信息复合成VOL比特流,各视频对象视频流复合成统一的码流输出。对视频流进行压缩编码以后,接下来就要实现网络传输部分的功能。 
      2.4 JRTPLIB网络传输模块 
    流媒体指的是在网络中使用流技术传输的连续时基媒体,RTP是目前解决流媒体实时传输问题的好办法,JRTPLIB是一个面向对象的RTP库,他完全遵循RFC1889设计,下面讲述如何在Linux平台上运用RTP协议进行实时流媒体编程。 
      2.4.1 初始化 
      在使用JRTPLIB进行实时流媒体数据传输之前,首先应该生成RTPSession类的一个实例来表示此次RTP会话,然后调用Create()方法来对其进行初始化操作。RTPSession类的Create()方法只有一个参数,用来指明此次RTP会话所采用的端口号。 
      2.4.2 数据发送 
      当RTP会话成功建立起来之后,接下来就可以开始进行流媒体数据的实时传输了。首先需要设置好数据发送的目标地址,RTP协议允许同一会话存在多个目标地址,这可以通过调用RTPSession类的AddDestination()、DeleteDestination()和ClearDestinations()方法来完成。目标地址全部指定之后,接着就可以调用RTPSession类的SendPacket()方法,向所有的目标地址发送流媒体数据。 
      2.4.3 数据接收 
      对于流媒体数据的接收端,首先需要调用PollData()方法来接收发送过来的RTP或者RTCP数据报。由于同一个RTP会话中允许有多个参与者(源),因此既可以通过调用GotoFirstSource()和GotoNextSource()方法来遍历所有的源,也可以通过调用GotoFisstSourceWithDat()和GotoNextSourceWithData()方法来遍历那些携带有数据的源。在从RTP会话中检测出有效的数据源之后,接下去就可以调用RTPSession类的GetNextPacket()方法从中抽取RTP数据报,当接收到的RTP数据报处理完之后,要及时释放。 
      JRTPLIB为RTP数据报定义了3种接收模块,通过调用RTPSession类的SetReceiveMode()方法可以设置下列这些接收模式:RECEIVEMODE_ALL:缺省的接收模式,所有到达的RTP数据报都将被接受; 
RECEIVEMODE_IGNORESOME:除了某些特定的发送者之外,所有到达的RTP数据报都将被接受,而被拒绝的发送者列表可以通过调用AddToIgnoreList(),DeleteFromIgnoreList()和ClearIgnoreList()方法来进行设置;RECEIVEMODE_ACCEPTSOME:除了某些特定的发送者之外,所有到达的RTP数据报都将被拒绝,而被接受的发送者列表可以通过调用AddToAcceptList(),DeleteFromAcceptList和ClearAcceptList()方法来进行设置。
      2.4.4 控制信息 
      JRTPLIB是一个高度封装后的RTP库,只要PollData()或者SendPacket()方法被成功调用,JRTPLIB就能够自动对达到的RTCP数据报进行处理,并且还会需在要的时候发送RTCP数据报,从而能够确保整个RTP会话过程的正确性。
      在本系统中,使用RTPSession JRTPLIB类库提供的方法来实现底层的RTP/RTCP操作,并且把他封装在CrtpTransmitter类中,该类从Media Sink类继承而来,接收到相应的媒体帧数据,使用RTPSession类库的操作把数据发送到网络上。 
      3 结语 
      本系统基于S3C2410平台和Linux操作系统,利用Video4Linux设计采集程序,使用MPEG-4压缩编码算法,通过实时流媒体传输技术实现了网络传输,整个系统具有稳定可靠、安装简便、成本低廉等特点,可扩展应用在工业控制、视频会议系统、可视电话、远程监控系统等诸多领域
离线keisuo

只看该作者 6楼 发表于: 2007-05-11
用GNU工具开发基于ARM的嵌入式系统
来源:internet 点击:5643 添加时间:2003-12-19 17:01:11     

        作 者: 摩托罗拉苏州技术中心 许庆丰

摘 要: 介绍如何利用GNU的工具开发基于ARM的嵌入式系统,以及使用编译器、连接器和调试工具的具体方法,为广大嵌入式系统开发人员提供一种低成本的开发手段。

关键词: ARM GNU MC928MX1 gcc gdb gdbserver

  当前,ARM公司的32位RISC处理器,以其内核耗电少、成本低、功能强、特有16/32位双指令集,已成为移动通信、手持计算、多媒体数字消费等嵌入式解决方案的RISC标准,市场占有率超过了75 %。多家公司都推出了自己的基于ARM内核的处理器产品,越来越多的开发人员开始了针对ARM平台的开发。通常开发人员需要购买芯片厂商或第三方提供的开发板,还需要购买开发软件,如C编译器或者集成了实时操作系统的开发环境。开发板的价格从数百到上千美元,而编译器、实时操作系统价格更是动辄数千到数万美元。这样,在开发初期,软硬件上的投资就需要上万美元,对于国内大多数开发人员来说,无疑是太贵了。 

  庆幸的是,GNU所倡导的自由软件给开发者带来了福音。1984 年,旨在开发一个类似 Unix 的,并且是完全免费的完整操作系统和配套工具:GNU 系统(发音为"guh-NEW")。GNU的操作系统和开发工具都是免费的,遵循GNU 通用公共许可证 (GPL)协议,任何人都可以从网上获取全部的源代码。关于GNU和公共许可证协议的详细资料,读者可参看GNU网站的中文介绍:http://www.gnu.org/home.cn.html

  除了大家熟知的Linux操作系统外,GNU的软件还包括编译器(gcc,g++)、二进制转换工具(objdump,objcopy)、调试工具(gdb,gdbserver,kgdb)和基于不同硬件平台的开发库。GNU开发工具的主要缺点是采用命令行方式,用户掌握和使用比较困难,不如基于Windows系统的开发工具好用。但是,GNU工具的复杂性是由于它更贴近编译器和操作系统的底层,并提供了更大的灵活性。一旦学习和掌握了相关工具,也就了解了系统设计的基础知识,为今后的开发工作打下基础。GNU的开发工具都是免费的,遵循GPL协议,任何人都可以从网上获取。笔者参与了一个基于ARM平台的嵌入式Linux系统开发,采用的是摩托罗拉龙珠系列的MC928MX1。从测试代码、引导程序、嵌入式Linux移植、应用程序、图形界面都可以用GNU工具进行开发,不需要在开发工具上做额外的投入。本文所介绍的开发方法同样适用于其它公司的基于ARM的产品。

1 硬件平台

  MC928MX1(以下简称MX1)是摩托罗拉公司基于ARM核心的第一款MCU,主要面向高端嵌入式应用。内部采用ARM920T内核,并集成了SDRAM/Flash、LCD、USB、蓝牙(bluetooth)、多媒体闪存卡(MMC)、CMOS摄像头等控制器。关于MX1的详细资料,感兴趣的读者可以参考http://www.motorola.com.cn/semiconductors/。作为应用开发的最小系统必须包括RAM(程序运行空间)、Flash(存放目标代码)和串行接口(用于调试和下载程序)。MX1提供了6个片选端(CS0~CS5),内置了SDRAM控制器,数据宽度32位。在笔者的系统中采用了2片8M×16位的SDRAM和2片4M×16位的同步Flash存储器,分别接入数据线的低16位和高16位,如图1所示。

  图1中SDRAM接片选端CS2,Flash接片选端CS3,其余为SDRAM/Flash的控制信号。最小系统还包括至少1个串行接口,可以采用MX1内置的UART控制器,图略。



图 1 (小图点击放大)

2 自举模式

  目前,许多嵌入式处理器都提供了自举模式(Bootstrap),供用户写入引导代码。自举模式利用了固化在芯片内部的一段引导程序,当处理器复位时,如果在特定引脚上加信号,则处理器将在复位后执行固化ROM中的程序。例如,MX1提供了4条复位引脚,复位时引脚不同的电平组合可以从不同的片选端启动系统。自举ROM中的程序完成串口的初始化,然后等待用户从串口写入用户代码。自举模式所能接受的是一种专门格式的文本文件,包括数据和要写入/读出的地址。关于自举模式的代码格式,可参考相关芯片的手册。在摩托罗拉的网站还提供了许多小工具,帮助开发者将其它格式的文件转换成为自举模式格式。通过自举模式下载的通常是一段和上位机软件(如超级终端)通信的程序,完成接收数据并写入Flash的操作。写入的数据可以是用户自己的应用程序、数据或者操作系统的内核。通过自举模式下载的引导程序同样可以用GNU工具开发。

3 GNU的编译器和开发工具

  GNU提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm -linux-ld 和arm-linux-objcopy。GNU的所有开发工具都可以从www.gnu.org上下载,基于ARM的工具可以从www.uclinux.org获得。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文件(elf格式),用二进制转换工具生成可下载的二进制代码。GNU工具都运行在Linux下,开发者需要1台运行Linux的PC作为上位机。由于篇幅所限,不能完整地介绍整个嵌入式操作系统的开发过程,将以第二节中提到的通过自举模式下载的引导程序为例,说明开发的过程。对于像Linux这样的大系统,基本的开发流程是一样的。

  引导程序将通过自举模式下载到MX1的片内RAM,从地址0x00300000开始并执行。完成串口和SDRAM的初始化后,引导程序将等待接收应用程序或操作系统内核,将接收到的数据放在SDRAM中。数据接收完毕后,引导程序将SDRAM中的数据写入Flash,下一次就可以从Flash中直接引导系统了。由于操作系统的内核比较大,如Linux有1 MB以上,下载过程必须考虑纠错。因此,接收部分采用Xmode协议,可以用Windows下超级终端的Xmode发送方式发送文件。

(1)编写C、C++语言或汇编源程序

  通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(www.gnu.org)上下载有关规范。汇编程序的缺省入口是start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。

(2)用gcc或g++生成目标文件

  如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件) xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。
分别用如下命令生成目标文件:
arm-linux-gcc-c-O2-o init.o init.s
arm-linux-gcc-c-O2-o xmrecever.o xmrecever.c
arm-linux-gcc-c-O2-o flash.o flash.c
其中-c命令表示只生成目标代码,不进行连接;-o 命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。

(3)编写连接脚本文件

  gcc等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和.init(构造函数代码)等。有关elf文件格式,读者可自行参考相关资料。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址开始放置这些段。例如笔者的引导程序连接文件link.lds为:

ENTRY(begin)
SECTION
{ .=0x00300000;
.text : { *(.text) }
.data: { *(.data) }
.bss: { *(.bss) }
}

  其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x00300000,这一段地址为MX1的片内RAM;.text : { *(.text) }表示从0x00300000开始放置所有目标文件的代码段,随后的.data: { *(.data) }表示数据段从代码段的末尾开始,再后是.bss段。

(4)用连接器生成最终目标文件

  有了连接脚本文件,如下命令可生成最终的目标文件:
arm-linux-ld-nostadlib-o bootstrap.elf-T link.lds init.o xmrecever.o flash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件;最后是需要连接的目标文件列表。

(5)生成二进制代码

  连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy-O binary bootstrap.elf bootstrap.bin
其中-Obinary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-O srec。如果想将生成的目标代码反汇编,还可以用objdump工具:
arm-linux-objdump-D bootstrap.elf 

  至此,所生成的目标文件就可以直接写入Flash中运行了。如果要通过自举模式下载,还需要转换为自举模式的文件格式,相关转换工具可以在摩托罗拉的网站上找到。

  掌握了GNU工具后,开发者就可以开发或移植C或C++代码的程序。用户可以不需要操作系统,直接开发简单应用程序。但对于更复杂的应用来说,操作系统必不可少。目前流行的源代码公开的操作系统如Linux、μC/OS都可以用GNU工具编译。ARM的Linux已有很多成熟的版本,可以支持ARM720、ARM920、ARM1020等多种处理器,读者可从www.uclinux.orgwww.armdevzone.com上获取最新信息。Linux移植过程中和处理器相关的代码都放在arch/arm目录下。对于内核,用户需要做的是设定自己系统的内存映像,RAM起始地址,I/O地址空间和虚拟I/O地址空间,参看arch/arm/mach-integrator/arch.c文件。除了内核外,用户还需要为自己的系统编制各种各样的驱动程序。

4 调试工具

  Linux下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux下应用程序的远程调试。gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入-target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从ftp://ftp.gnu.org/pub/gnu/gdb/上下载。

  对于Linux内核的调试,可以采用kgdb工具,同样需要通过串口与上位机上的gdb通信,对目标板的Linux内核进行调试。由于篇幅所限,感兴趣的读者可以从http://oss.sgi.com/projects/kgdb/上了解具体的使用方法。

结束语

  本文以一个具体的实例为例,对GNU工具中的常用功能作了介绍。其实GNU工具的功能还远不止这些,更进一步的操作有:针对不同处理器,不同算法的软件优化、高效的内嵌汇编、大型项目管理功能等。相信GNU能成为越来越多开发人员的选择。

参考资料

1 摩托罗拉公司. MC928MX1 user manual
离线keisuo

只看该作者 7楼 发表于: 2007-05-28
有谁用过 paintEvent
快速回复
限100 字节
 
上一个 下一个