C语言嵌入式系统编程修炼之软件架构篇
副标题#e#
模块分别
模块分另外"划"是筹划的意思,意指奈何公道的将一个很大的软件分别为一系列成果独立的部门相助完成系统的需求。C语言作为一种布局化的措施设计语言,在模块的分别上主要依据成果(依成果举办分别在面向工具设计中成为一个错误,牛顿定律碰着了相对论),C语言模块化措施设计需领略如下观念:
(1) 模块等于一个.c文件和一个.h文件的团结,头文件(.h)中是对付该模块接口的声明;
(2) 某模块提供应其它模块挪用的外部函数及数据需在.h中文件中冠以extern要害字声明;
(3) 模块内的函数和全局变量需在.c文件开头冠以static要害字声明;
(4) 永远不要在.h文件中界说变量!界说变量和声明变量的区别在于界说会发生内存分派的操纵,是汇编阶段的观念;而声明则只是汇报包括该声明的模块在毗连阶段从其它模块寻找外部函数和变量。如:
/*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文件 */
以上措施的功效是在模块1、2、3中都界说了整型变量a,a在差异的模块中对应差异的地点单位,这个世界上从来不需要这样的措施。正确的做法是:
/*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内核,作者正筹备举办此项事情,但愿能将心得孝敬给各人。
毕竟选择多任务照旧单任务方法,依赖于软件的体系是否复杂。譬喻,绝大大都手机措施都是多任务的,但也有一些小灵通的协议栈是单任务的,没有操纵系统,它们的主措施轮番挪用各个软件模块的处理惩罚措施,模仿多任务情况。
#p#副标题#e#
单任务措施典范架构
(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=? */
等雷同问题。面临这些问题,我们只能发出由衷的感应:世界上尚有许多有意义的工作等着我们去消化摄入的食物。
实际上,嵌入式系统要运行到世界末日。
间断处事措施
#p#分页标题#e#
间断是嵌入式系统中重要的构成部门,可是在尺度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的间断处事措施 */
(3)配置CPU针对该硬件的节制线
a.假如节制线可作PIO(可编程I/O)和节制信号用,则配置CPU内部对应寄存器使其作为节制信号;
b.配置CPU内部的针对该设备的间断屏蔽位,配置间断方法(电平触发照旧边沿触发)。
(4)提供一系列针对该设备的操纵接口函数。譬喻,对付LCD,其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数;而对付及时钟,其驱动模块则需提供获取时间、配置时间等函数。
C的面向工具化
在面向工具的语言内里,呈现了类的观念。类是对特定命据的特定操纵的荟萃体。类包括了两个领域:数据和操纵。而C语言中的struct仅仅是数据的荟萃,我们可以操作函数指针将struct模仿为一个包括数据和操纵的"类"。下面的C措施模仿了一个最简朴的"类":
#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;
};
我们可以操作C语言模仿出头向工具的三个特性:封装、担任和多态,可是更多的时候,我们只是需要将数据与行为封装以办理软件布局杂乱的问题。C模仿面向工具思想的目标不在于模仿行为自己,而在于办理某些环境下利用C语言编程时措施整体框架布局分手、数据和函数脱节的问题。我们在后续章节会看到这样的例子。
总结
#p#分页标题#e#
本篇先容了嵌入式系统编程软件架构方面的常识,主要包罗模块分别、多任务照旧单任务选取、单任务措施典范架构、间断处事措施、硬件驱动模块设计等,从宏观上给出了一个嵌入式系统软件所包括的主要元素。
请记着:软件布局是软件的魂灵!布局杂乱的措施脸孔可憎,调试、测试、维护、进级都非常坚苦。