China Open source community
站内导航:
站内排行前50热点文章

精华文章  GDB调试精粹及使用实例
普通文章  STL中map用法详解
精华文章  负载均衡软件比较(Hapr...
普通文章  头文件的重复引用
普通文章  递归函数的调用过程
普通文章  TCP三次握手/四次挥手详解
普通文章  贪心策略的理论基础——...
普通文章  BMH算法原理与实现(模...
普通文章  排列组合与回溯算法
普通文章  DP动态规划
精华文章  Android线程模型
普通文章  Linux socket编程之套接字
普通文章  Linux内核中的红黑树
精华文章  linux下使用minicom的几...
普通文章  Java开源Html解析类库
精华文章  enum类型的本质
普通文章  memcached server LRU ...
普通文章  linux设置环境变量的方法
普通文章  android核心模块及相关...
普通文章  linux源代码包(.tar.g...
普通文章  L.A.M.P配置过程
普通文章  在ubuntu9.10下安装QT4...
普通文章  C/C++程序员常见面试题...
普通文章  gcc编译过程概述
普通文章  python的memcache和jso...
普通文章  应用程序二进制接口---ABI
普通文章  linux内核编译问题
普通文章  Java多线程实现简单实例
普通文章  Python程序员常用的IDE...
普通文章  brk和sbrk详述
普通文章  优化C语言代码(程序员必...
普通文章  python非贪婪,多行匹配...
普通文章  函数指针传递和全局指针...
普通文章  Unix操作系统的历史演变
普通文章  网络编程之C10K问题
普通文章  发行版发布:CentOS 5.4
普通文章  在windows中构建gtk开发...
普通文章  i++循环与i--循环的执行...
普通文章  关于Qvariant类--万能的...
普通文章  Debian sudo 设置
普通文章  busybox1.15.x 交叉编译
普通文章  关于僵死进程zombie
普通文章  递归思想的妙用
普通文章  判断链表是否存在环并找...
普通文章  Android Porting Exper...
普通文章  关于/etc/bashrc和$HOM...
普通文章  [翻译]Django初窥
普通文章  Python list的排序
普通文章  Django实现大数据量分页...
普通文章  Debug方式取代printf满...

 
 
 
当前位置: 首页 >> 程序设计 >> C/C++编程 >> C语言嵌入式系统编程修炼
 
 

C语言嵌入式系统编程修炼

作者:      来源:zz     发表时间:2007-05-09     浏览次数:      字号:    

这几篇连载是在写的太好了。我不得不摘抄笔记。

模块划分

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

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

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

  (4 永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。 防止重复包含头文件造成重定义。但是可以通过#ifdef 来解决重定义问题。

单任务程序典型架构

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

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

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

  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;
   
  }
 }
}


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

硬件驱动模块
硬件初始化

  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++后的结果是p= 0xF000FF01,若p指向int,即:

int *p = (int *)0xF000FF00;


  p++(++p)的结果等同于:p = p+sizeof(int),而p-(-p)的结果是p = p-sizeof(int)
记住:CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。

函数指针

2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPUPC寄存器;

  (3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?请往下看:

  请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186 CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF00xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:

typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 */
/*
函数指针类型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /*
定义一个函数指针,指向*/
/* CPU
启动后所执行第一条指令的位置 */
lpReset(); /*
调用函数 */


  在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset(),它实际上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。

  记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!


 

数组vs.动态申请
 

给出原则:

  (1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);

  (2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且mallocfree应成对出现!

 

关键字const

const int a;
int const a;
const int *a;
int * const a;
int const * a const;


  (1 关键字const的作用是为给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于"输入参数"。在有多个形参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的辨别哪些是输入参数,哪些是可能的输出参数。

  (2)合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。

 

关键字volatile

  C语言编译器会对用户书写的代码进行优化,譬如如下代码:

int a,b,c;
a = inWord(0x100); /*
读取I/O空间0x100端口的内容存入a变量*/
b = a;
a = inWord (0x100); /*
再次读取I/O空间0x100端口的内容存入a变量*/
c = a;


  很可能被编译器优化为:

int a,b,c;
a = inWord(0x100); /*
读取I/O空间0x100端口的内容存入a变量*/
b = a;
c = a;


  但是这样的优化结果可能导致错误,如果I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,bc的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类似优化,正确的做法是:

volatile int a


  volatile变量可能用于如下几种情况:

  (1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);

  (2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量)

  (3) 多线程应用中被几个任务共享的变量。

 

[1] [2]

编辑 webmaster

 
 
 
 
中国源码网 - www.YuanMa.org - 中国 开放源代码+编程 社区