/*module1.h*/ int a = 5; /* 在模块1的.h文件中定义int a */ /*module1 .c*/ #include "module1.h" /* 在模块1中包含模块1的.h文件 */ /*module2 .c*/ #include "module1.h" /* 在模块2中包含模块1的.h文件 */ /*module3 .c*/ #include "module1.h" /* 在模块3中包含模块1的.h文件 */ |
/*module1.h*/ extern int a; /* 在模块1的.h文件中声明int a */ /*module1 .c*/ #include "module1.h" /* 在模块1中包含模块1的.h文件 */ int a = 5; /* 在模块1的.c文件中定义int a */ /*module2 .c*/ #include "module1.h" /* 在模块2中包含模块1的.h文件 */ /*module3 .c*/ #include "module1.h" /* 在模块3中包含模块1的.h文件 */ |
这样如果模块1、2、3操作a的话,对应的是同一片内存单元。
一个嵌入式系统通常包括两类模块:
(1)硬件驱动模块,一种特定硬件对应一个模块;
(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。
多任务还是单任务
所谓"单任务系统"是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地"同时"执行多个任务。
多任务的并发执行通常依赖于一个多任务操作系统(OS),多任务OS的核心是系统调度器,它使用任务控制块(TCB)来管理任务调度功能。TCB包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时,要用到这些信息。此外,TCB还被用来存放任务的"上下文"(context)。任务的上下文就是当一个执行中的任务被停止时,所要保存的所有信息。通常,上下文就是计算机当前的状态,也即各个寄存器的内容。当发生任务切换时,当前运行的任务的上下文被存入TCB,并将要被执行的任务的上下文从它的TCB中取出,放入各个寄存器中。
嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物,我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核,作者正准备进行此项工作,希望能将心得贡献给大家。
究竟选择多任务还是单任务方式,依赖于软件的体系是否庞大。例如,绝大多数手机程序都是多任务的,但也有一些小灵通的协议栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模拟多任务环境。
单任务程序典型架构
(1)从CPU复位时的指定地址开始执行;
(2)跳转至汇编代码startup处执行;
(3)跳转至用户主程序main执行,在main中完成:
a.初试化各硬件设备;
b.初始化各软件模块;
c.进入死循环(无限循环),调用各模块的处理函数
用户主程序和各模块的处理函数都以C语言完成。用户主程序最后都进入了一个死循环,其首选方案是:
while(1) |
有的程序员这样写:
for(;;) |
这个语法没有确切表达代码的含义,我们从for(;;)看不出什么,只有弄明白for(;;)在C语言中意味着无条件循环才明白其意。
下面是几个"著名"的死循环:
(1)操作系统是死循环;
(2)WIN32程序是死循环;
(3)嵌入式系统软件是死循环;
(4)多线程程序的线程处理函数是死循环。
你可能会辩驳,大声说:"凡事都不是绝对的,2、3、4都可以不是死循环"。Yes,you are right,但是你得不到鲜花和掌声。实际上,这是一个没有太大意义的牛角尖,因为这个世界从来不需要一个处理完几个消息就喊着要OS杀死它的WIN32程序,不需要一个刚开始RUN就自行了断的嵌入式系统,不需要莫名其妙启动一个做一点事就干掉自己的线程。有时候,过于严谨制造的不是便利而是麻烦。君不见,五层的TCP/IP协议栈超越严谨的ISO/OSI七层协议栈大行其道成为事实上的标准?
经常有网友讨论:
printf("%d,%d",++i,i++); /* 输出是什么?*/ c = a+++b; /* c=? */ |
等类似问题。面对这些问题,我们只能发出由衷的感慨:世界上还有很多有意义的事情等着我们去消化摄入的食物。
实际上,嵌入式系统要运行到世界末日。
中断服务程序
中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持,提供新的关键字用于标示中断服务程序(ISR),类似于__interrupt、#program interrupt等。当一个函数被定义为ISR的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。
中断服务程序需要满足如下要求:
(1)不能返回值;
(2)不能向ISR传递参数;
(3) ISR应该尽可能的短小精悍;
(4) printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
在某项目的开发中,我们设计了一个队列,在中断服务程序中,只是将中断类型添加入该队列中,在主程序的死循环中不断扫描中断队列是否有中断,有则取出队列中的第一个中断类型,进行相应处理。
/* 存放中断的队列 */ typedef struct tagIntQueue { int intType; /* 中断类型 */ struct tagIntQueue *next; }IntQueue; IntQueue lpIntQueueHead; __interrupt ISRexample () { int intType; intType = GetSystemType(); QueueAddTail(lpIntQueueHead, intType);/* 在队列尾加入新的中断 */ } |
在主程序循环中判断是否有中断:
While(1) { If( !IsIntQueueEmpty() ) { intType = GetFirstInt(); switch(intType) /* 是不是很象WIN32程序的消息解析函数? */ { /* 对,我们的中断类型解析很类似于消息驱动 */ case xxx: /* 我们称其为"中断驱动"吧? */ … break; case xxx: … break; … } } } |
按上述方法设计的中断服务程序很小,实际的工作都交由主程序执行了。
硬件驱动模块
一个硬件驱动模块通常应包括如下函数:
(1)中断服务程序ISR
(2)硬件初始化
a.修改寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率等);
b.将中断服务程序入口地址写入中断向量表:
/* 设置中断向量表 */ m_myPtr = make_far_pointer(0l); /* 返回void far型指针void far * */ m_myPtr += ITYPE_UART; /* ITYPE_UART: uart中断服务程序 */ /* 相对于中断向量表首地址的偏移 */ *m_myPtr = &UART _Isr; /* UART _Isr:UART的中断服务程序 */ |
#ifndef C_Class #define C_Class struct #endif C_Class A { C_Class A *A_this; /* this指针 */ void (*Foo)(C_Class A *A_this); /* 行为:函数指针 */ int a; /* 数据 */ int b; }; |
用户105409 2009-2-27 21:28