打印

C语言嵌入式系统编程(转)

[复制链接]
2327|21
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
hsbjb|  楼主 | 2011-8-28 10:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
模块划分

  模块划分的"划"是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。C语言作为一种结构化的程序设计语言,在模块的划分上主要依据功能(依功能进行划分在面向对象设计中成为一个错误,牛顿定律碰到了>相对论),C语言模块化程序设计需理解如下概念:

  (1) 模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明;

  (2) 某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern要害字声明;

  (3) 模块内的函数和全局变量需在.c文件开头冠以static要害字声明;

  (4) 永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。如:

http://www.armforum.cn

/*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文件 */
ARM


  以上程序的结果是在模块1、2、3中都定义了整型变量a,a在不同的模块中对应不同的地址单元,这个世界上从来不需要这样的程序。正确的做法是:

字串4

/*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文件 */
字串4  这样假如模块1、2、3操作a的话,对应的是同一片内存单元。

  一个嵌入式系统通常包括两类模块:

  (1)硬件驱动模块,一种特定硬件对应一个模块;

  (2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。

  多任务还是单任务

  所谓"单任务系统"是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地"同时"执行多个任务。

  多任务的并发执行通常依靠于一个多任务操作系统(OS),多任务OS的核心是系统调度器,它使用任务控制块(TCB)来治理任务调度功能。TCB包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时,要用到这些信息。此外,TCB还被用来存放任务的"上下文"(context)。任务的上下文就是当一个执行中的任务被停止时,所要保存的所有信息。通常,上下文就是计算机当前的状态,也即各个寄存器的内容。当发生任务切换时,当前运行的任务的上下文被存入TCB,并将要被执行的任务的上下文从它的TCB中取出,放入各个寄存器中。 字串8


  嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物,我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核,作者正预备进行此项工作,希望能将心得贡献给大家。

  究竟选择多任务还是单任务方式,依靠于软件的体系是否庞大。例如,绝大多数手机程序都是多任务的,但也有一些小灵通的协议栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模拟多任务环境。
单任务程序典型架构

  (1)从CPU复位时的指定地址开始执行;

  (2)跳转至汇编代码startup处执行;

  (3)跳转至用户主程序main执行,在main中完成:

  a.初试化各硬件设备;

  b.初始化各软件模块;

  c.进入死循环(无限循环),调用各模块的处理函数

  用户主程序和各模块的处理函数都以C语言完成。用户主程序最后都进入了一个死循环,其首选方案是:

M开**坛

while(1)
{
}
字串4  有的程序员这样写:

字串8
for(;;)
{
}
字串5


  这个语法没有确切表达代码的含义,我们从for(;;)看不出什么,只有弄明白for(;;)在C语言中意味着无条件循环才明白其意。

  下面是几个"闻名"的死循环:

  (1)操作系统是死循环;

  (2)WIN32程序是死循环;

  (3)嵌入式系统软件是死循环;

  (4)多线程程序的线程处理函数是死循环。

  你可能会辩驳,大声说:"凡事都不是绝对的,2、3、4都可以不是死循环"。Yes,you are right,但是你得不到鲜花和掌声。实际上,这是一个没有太大意义的牛角尖,因为这个世界从来不需要一个处理完几个消息就喊着要OS杀死它的WIN32 程序,不需要一个刚开始RUN就自行了断的嵌入式系统,不需要莫名其妙启动一个做一点事就干掉自己的线程。有时候,过于严谨制造的不是便利而是麻烦。君不见,五层的TCP/IP协议栈超越严谨的ISO/OSI七层协议栈大行其道成为事实上的标准?

  经常有网友讨论:

M开**坛
printf("%d,%d", i,i ); /* 输出是什么?*/
c = a b; /* c=? */
M开**坛


  等类似问题。面对这些问题,我们只能发出由衷的感慨:世界上还有很多有意义的事情等着我们去消化摄入的食物。

  实际上,嵌入式系统要运行到世界末日。

  中断服务程序

  中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持,提供新的要害字用于标示中断服务程序 (ISR),类似于__interrupt、#program interrupt等。当一个函数被定义为ISR的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。

  中断服务程序需要满足如下要求:

  (1)不能返回值;

  (2)不能向ISR传递参数;

  (3) ISR应该尽可能的短小精悍;

  (4) printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。

  在某项目的开发中,我们设计了一个队列,在中断服务程序中,只是将中断类型添加入该队列中,在主程序的死循环中不断扫描中断队列是否有中断,有则取出队列中的第一个中断类型,进行相应处理。

ARM
/* 存放中断的队列 */
typedef struct tagIntQueue
{
 int intType; /* 中断类型 */
 struct tagIntQueue *next;
}IntQueue;

IntQueue lpIntQueueHead;

__interrupt ISRexample ()
{
 int intType;
 intType = GetSystemType();
 QueueAddTail(lpIntQueueHead, intType);/* 在队列尾加入新的中断 */
}
http://www.armforum.cn  在主程序循环中判定是否有中断:

字串8

While(1)
{
 If( !IsIntQueueEmpty() )
 {
  intType = GetFirstInt();
  switch(intType) /* 是不是很象WIN32程序的消息解析函数? */
  {
   /* 对,我们的中断类型解析很类似于消息驱动 */
   case xxx: /* 我们称其为"中断驱动"吧? */
    …
    break;
   case xxx:
    …
    break;
   …
  }
 }
}
字串8


  按上述方法设计的中断服务程序很小,实际的工作都交由主程序执行了。
硬件驱动模块

  一个硬件驱动模块通常应包括如下函数:

  (1)中断服务程序ISR

  (2)硬件初始化

  a.修改寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率等);

  b.将中断服务程序入口地址写入中断向量表:

ARM
/* 设置中断向量表 */
m_myPtr = make_far_pointer(0l); /* 返回void far型指针void far * */
m_myPtr = ITYPE_UART; /* ITYPE_UART: uart中断服务程序 */
/* 相对于中断向量表首地址的偏移 */
*m_myPtr = &UART _Isr; /* UART _Isr:UART的中断服务程序 */
M开**坛  (3)设置CPU针对该硬件的控制线

  a.假如控制线可作PIO(可编程I/O)和控制信号用,则设置CPU内部对应寄存器使其作为控制信号;

  b.设置CPU内部的针对该设备的中断屏蔽位,设置中断方式(电平触发还是边缘触发)。

  (4)提供一系列针对该设备的操作接口函数。例如,对于LCD,其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数;而对于实时钟,其驱动模块则需提供获取时间、设置时间等函数。

  C的面向对象化

  在面向对象的语言里面,出现了类的概念。类是对特定数据的特定操作的集合体。类包含了两个范畴:数据和操作。而C语言中的struct仅仅是数据的集合,我们可以利用函数指针将struct模拟为一个包含数据和操作的"类"。下面的C程序模拟了一个最简单的"类":

http://www.armforum.cn

#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;
};
字串8

  我们可以利用C语言模拟出面向对象的三个特性:封装、继续和多态,但是更多的时候,我们只是需要将数据与行为封装以解决软件结构混乱的问题。C模拟面向对象思想的目的不在于模拟行为本身,而在于解决某些情况下使用C语言编程时程序整体框架结构分散、数据和函数脱节的问题。我们在后续章节会看到这样的例子。

  总结

  请记住:软件结构是软件的灵魂!结构混乱的程序面目可憎,调试、测试、维护、升级都极度困难。本篇介绍了嵌入式系统编程软件架构方面的知识,主要包括模块划分、多任务还是单任务选取、单任务程序典型架构、中断服务程序、硬件驱动模块设计等,从宏观上给出了一个嵌入式系统软件所包含的主要元素。

相关帖子

沙发
即时生效| | 2011-8-29 17:06 | 只看该作者
很多东西看不到啊,楼主重新编辑下吧

使用特权

评论回复
板凳
huzixian| | 2011-8-29 21:25 | 只看该作者
收藏起来了

使用特权

评论回复
地板
lhchen922| | 2013-11-4 18:54 | 只看该作者

使用特权

评论回复
5
comeon201208| | 2013-11-10 20:16 | 只看该作者
模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明;

使用特权

评论回复
6
comeon201208| | 2013-11-10 20:17 | 只看该作者
某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern要害字声明;  这个好理解的啊

使用特权

评论回复
7
comeon201208| | 2013-11-10 20:20 | 只看该作者
模块内的函数和全局变量需在.c文件开头冠以static要害字声明;
这个不好理解的啊,我实际调试程序的时候,在模块内部的局部变量若要看到实时的数据值变化的,就要加上static要害字的,若不想查看的话,不加也是可以的,这是为什么呢?不是很明白的:L

使用特权

评论回复
8
comeon201208| | 2013-11-10 20:21 | 只看该作者
永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。

这个看来我还是需要好好的注意了啊。。

使用特权

评论回复
9
comeon201208| | 2013-11-10 20:28 | 只看该作者
看完这个的deque感触很深可的,在一个.h文件中定义一个变量a,有多少个.c文件的要用到该变量a的,就的为该变量分配多少个内存空间的,,,造成了空间的浪费的

使用特权

评论回复
10
comeon201208| | 2013-11-10 20:29 | 只看该作者
所谓"单任务系统"是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地"同时"执行多个任务。

使用特权

评论回复
11
comeon201208| | 2013-11-10 20:29 | 只看该作者
多任务的并发执行通常依靠于一个多任务操作系统(OS),多任务OS的核心是系统调度器,它使用任务控制块(TCB)来治理任务调度功能。TCB包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。

使用特权

评论回复
12
comeon201208| | 2013-11-10 20:32 | 只看该作者
for(;;)在C语言中意味着无条件循环??还是不大明白的。。。是死循环还是?

使用特权

评论回复
13
smilingangel| | 2013-11-18 20:20 | 只看该作者
模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明;

使用特权

评论回复
14
pkat| | 2013-11-26 18:58 | 只看该作者
分析的很好

使用特权

评论回复
15
shenmu2012| | 2013-11-26 19:06 | 只看该作者
永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。

使用特权

评论回复
16
shenmu2012| | 2013-11-26 19:08 | 只看该作者
这个说的非常好,的却,在.h中定义变量,有多少不同的.c文件去调用该.h文件的,就相当于对该变量定义了多少遍的,这也就是说在给该同一个变量开辟了很多的空间的

使用特权

评论回复
17
shenmu2012| | 2013-11-26 19:09 | 只看该作者
这样就造成了cpu资源的浪费的

使用特权

评论回复
18
shenmu2012| | 2013-11-26 19:13 | 只看该作者
按照楼主的意见的是非常好的,实际的应用当中,对同一个变量a,当我在.c文件1中定义了int a =0,在.c文件2、3。。。中调用的时候,我往往对该变量重新定义,只是对该变量a定义时,在定义前边加上extern

使用特权

评论回复
19
shenmu2012| | 2013-11-26 19:14 | 只看该作者
即定义:extern int a,不能赋值的,这样也是可以的,这样编译运行都是可以的,不知道各位大侠有何一见的啊?

使用特权

评论回复
20
shenmu2012| | 2013-11-26 19:15 | 只看该作者
我的描述应该很清楚了,不知道有呵建议的啊,各位?先谢谢啦

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

193

主题

2354

帖子

0

粉丝