limingth 发表于 2005-6-14 00:21:25

Learn lumit Step 17 : 综合实验二

Learn lumit Step 17 : 综合实验二
++++++++++++++++++++++++++++++++++++++++++++++++++++++

    这个综合实验相对前面的综合实验一来说,有了很大的改变。首先从底层设备的支持
上来说,我们除了使用 led, seg7, dip4 之外,又多了 beep, int0, timer 新的输入和
输出方式,同时还为系统引入了中断处理机制,可以使得程序的执行以异步的方式,可以
应用于更多会有人为介入的场合中。

    更重要的是,我们从代码组织结构上重写了系统的 Makefile,引入了各级子目录的
Makefile 和 Rules.make 文件,这也是模仿了 linux 内核编译中的递归调用的机制。
通过上层的 Makefile 来调用子目录的 Makefile,完成一个自顶向下的代码管理和编译。
同时,我们采用了每一级子目录生成一个库文件 .a 的方式,来参与最终的代码生成,
这样做的好处是每个子目录的功能比较独立,如果代码完善后,就可以以“库”的方式
发布,对于上层写应用程序的开发者来说,他们不关心底层的实现,只需要了解能用的
接口就可以了。

    这里我们简要的将 Makefile 中比较重要的部分拿出来分析:
# Makefile

PRJ = demo_all_2

DRIVER_DIR = led seg7 dip4 beep int0 timer irq

INC_DIR = -Iled -Iseg7 -Idip4 -Ibeep -Iint0 -Itimer -Iirq

LIBS = $(DEBUG_DIR)\led.a $(DEBUG_DIR)\seg7.a $(DEBUG_DIR)\dip4.a \
        $(DEBUG_DIR)\beep.a $(DEBUG_DIR)\int0.a $(DEBUG_DIR)\timer.a \
       $(DEBUG_DIR)\irq.a        

OBJS = $(DEBUG_DIR)\startup.o $(DEBUG_DIR)\main.o

DEBUG_DIR = ..\debug

    可以看出, LIBS 主要定义了所有 DRIVER_DIR 下需要生成的库文件名称,基本上
库文件都是采用了和所在目录同名的命名规则。除了需要调用的库文件外,还需要编译
startup.s 和 main.c 两个主程序。最终生成的所有目标文件、中间文件以及可执行的
二进制文件等都统一放到 DEBUG_DIR 目录下,和源码分开存放以避免误删。

sub_dir:        
        @for %d in ($(DRIVER_DIR)) do cd %d & make & cd ..
        @echo ------------------------------------------------------

    这个目标 sub_dir 就完成了类似 linux Makefile 中 make -C 的功能,可以执行
子目录下的 Makefile ,但由于 NMake.exe 不支持递归读取 Makefile ,因此我们采用
了一种类似的中间办法,就是直接进入 driver 目录,然后执行 make ,执行完毕后再
通过 cd .. 退出该目录。在 windows 下的 Makefile 实现这个功能主要是采用了 for
这个命令辅助多命令执行 & 连接符。

    Rules.make 是下级各个 driver 子目录都需要包含的一个规则定义文件。打开看一
下就知道,它主要是用来告诉 Make 是通过什么编译命令从 .c 文件得到 .o 的目标,
如何再从 .o 目标文件得到一个打包的库文件 .a 。同时这个文件里面还定义了如何删除
临时文件和所有中间目标文件等的规则。

$(O_TARGET): $(O_OBJS)
        @echo + archiving o file ...$(O_TARGET)
        @armar -r $(O_TARGET) $(O_OBJS)
        @echo ------------------------------------------------------

.c{$(DEBUG_DIR)}.o:
        @echo + compiling c file ... $<       
        @armcc -O1 -g+ -c $(INC_DIR) $< -o $@
        @echo ------------------------------------------------------

c clean:
        @echo + deleting all files ...        
        -del $(O_OBJS) $(O_TARGET)       
        @echo ------------------------------------------------------
       
cleanbak:
        @echo + deleting all bak files ...        
        @-if exist *.bakdel *.bak
        @echo ------------------------------------------------------       
       

    对于每个 driver 子目录,都存在一个子 Makefile 与之相对应,定义了与这个目录
密切相关的几个变量和文件,具体我们以 led 为例来看看是如何实现的?

SUBDIR = led

TARGET = $(SUBDIR).a
OBJS = $(SUBDIR)_driver.o $(SUBDIR)_api.o

DEBUG_DIR = ..\..\debug

O_TARGET = $(DEBUG_DIR)\$(TARGET)
O_OBJS = $(DEBUG_DIR)\$(SUBDIR)_driver.o$(DEBUG_DIR)\$(SUBDIR)_api.o
       
# this include file must be at the first position above any other rules
include ..\Rules.make

$(DEBUG_DIR)\$(SUBDIR)_api.o:                $(SUBDIR)_api.h       
$(SUBDIR)_api.h:                        $(SUBDIR)_driver.h       
$(DEBUG_DIR)\$(SUBDIR)_driver.o:        $(SUBDIR)_driver.h       

    这上面的 O_TARGET 和 O_OBJS 是代表着 make 过程中实际生成的库文件和目标文件。
这个命名方法也是来自 linux 源码树中的一些相关定义。文件的最后表明了目标文件和
源文件/头文件的一些依赖关系。

    整个主程序的框架和代码反而是很简单的,主要是一个 main 函数和两个 irq_hooker,
分别是 int0_irq_hooker 和 timer_irq_hooker ,其中前者负责在 int0 中断发生后,
执行 beep_music ;后者负责在 timer 0 中断发生后,执行 beep_note 函数。

void int0_hooker( void )
{       
        // beep music
        beep_music( laohu );
}

void timer_hooker( void )
{       
        // beep note
        beep_note( dip4_value%8, 4, 8 );
}

int main( void )
{
        install_irq_handler( irq_handler );
       
        led_init();
        seg7_init();
        dip4_init();
        beep_init();
       
        // init should be after hooker installation
        timer_install_irq_hooker( timer_hooker );               
        timer_init();
        timer_set_counter( 0x01effff0 * 2 );
       
        // init should be after hooker installation
        int0_install_irq_hooker( int0_hooker );               
        int0_init();
       
        while( 1 )
        {
                dip4_value = dip4_get_value();
                led_set_value( dip4_value );
                seg7_display_num( dip4_value );
        }
       
        return 0;
}
   
    整个程序实现的功能就是,主程序流程读取 dip4 的拨码状态,把这个状态通过
led 和 seg7 来显示出来,同时 timer 定时产生一个中断,中断例程中会对当前拨码
所代表的音符通过 beep 发声出来。 如果用户按下 int0 会触发一个新的中断,这个
中断里面,将会播放《两只老虎》的音乐曲子。

    其他源码中的相关接口和结构和前面几个小节的基本一样,这里就不赘述了。只有
Makefile 的组织方式和实现有了比较大的变化,因此特别地在这个小节里面做一说明,
其最根本的灵感还是来自 linux 。

zhaoic 发表于 2005-6-18 17:35:23

李明文档写的真是不错,受益匪浅啊,呵呵,我也要学习编写规范文档
页: [1]
查看完整版本: Learn lumit Step 17 : 综合实验二