EDN首页   博客首页

最新日志

发表于:2008-4-26 16:56:02
标签:c  

2

有限状态机

有限状态机(finite state machine)是一个数学概念,如果把它运用于程序中,可以发挥很大的作用。它是一种协议,用于有限数量的子程序("状态")的发展变化。每个子程序进行一些处理并选择下一种状态(通常取决于下一段输入)。

有限状态机(FSM)可以用作程序的控制结构。FSM对于那些基于输入的在几个不同的可选动作中进行循环的程序尤其合适。投币售货机就是FSM的一个好例子。另外一个你可以想到的复杂的例子就是你正在用的东西,想到了吗?没错,就是操作系统。在投币售货机的例子中,输入是硬币,输出是待售商品,售货机有"接受硬币","选择商品","发送商品"和"找零钱"等几种状态。

它的基本思路是用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常在当前状态和下一次输入字符的基础上,另外再经过一次表查询)下一个应该进入的状态。你从一个"初始状态"开始。在这一过程中,翻译表可能告诉你进入了一个错误状态,直到到达结束状态。

在C语言中,有好几种方法可以用来表达FSM,但它们绝大多数都是基于函数指针数组。一个函数指针数组可以像下面这样声明:

void (*state[MAX_STATES]) ();

如果知道了函数名,就可以像下面这样对数组进行初始化。

extern int a(),b(),c(),d();

int (*state[]) ()={a,b,c,c};

可以通过数组中的指针来调用函数:

(*state[i]) ();

所有函数必须接受同样的参数,并返回同种类型的返回值(除非你把数组元素做成一个联合)。函数指针是很有趣的。注意,我们可以去掉指针形式,把上面的调用写成:

state[i] ();

甚至

(******state[i]) ();

这是一个在ANSI C中流行的不良方法:调用函数和通过指针调用函数(或任意层次的指针间接引用)可以使用同一种语法。

如果你想干得漂亮一点,可以让状态函数返回一个指向通用后续函数的指针,并把它转换为适当的类型。这样,就不需要全局变量了。如果你不想搞得太花哨,可以使用一个switch语句作为一种简朴的状态机,方法是赋值给控制变量并把switch语句放在循环内部。关于FSM还有最后一点需要说明:如果你的状态函数看上去需要多个不同的参数,可以考虑使用一个参数计数器和一个字符串指针数组,就像main函数的参数一样。我们熟悉的int argc,char *argv[]机制是非常普遍的,可以成功地应用在你所定义的函数中。 

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(1) | 阅读(433)
发表于:2008-4-26 16:32:45
标签:c  单片机  

2

环形缓冲区

环形缓冲区

在通信程序中,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。

1、环形缓冲区的实现原理

环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写人。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

1、图2和图3是一个环形缓冲区的运行示意图。图1是环形缓冲区的初始状态,可以看到读指针和写指针都指向第一个缓冲区处;图2是向环形缓冲区中添加了一个数据后的情况,可以看到写指针已经移动到数据块2的位置,而读指针没有移动;图3是环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。

点击看大图

2、实例:环形缓冲区的实现

环形缓冲区是数据通信程序中使用最为广泛的数据结构之一,下面的代码,实现了一个环形缓冲区:

/*ringbuf .c*/

#i nclude<stdio. h>

    #i nclude<ctype. h>

#define NMAX 8

int iput = 0; /* 环形缓冲区的当前放人位置 */

int iget = 0; /* 缓冲区的当前取出位置 */

int n = 0; /* 环形缓冲区中的元素总数量 */

double buffer[NMAX];

/*  环形缓冲区的地址编号计算函数,,如果到达唤醒缓冲区的尾部,将绕回到头部。

环形缓冲区的有效地址编号为:0(NMAX-1)

*/

int addring (int i)

{

        return (i+1) == NMAX ? 0 : i+1;

}

/* 从环形缓冲区中取一个元素 */

double get{void}

{

cnt pos;

if (n>0){

           Pos = iget;

           iget = addring(iget);

           n--;

           return buffer[pos];

}

else {

printf(“Buffer is empty\n”);

return 0.0;

}

/* 向环形缓冲区中放人一个元素*/

void put(double z)

{

if (n<NMAX){

           buffer[iput]=z;

           iput = addring(iput);

           n++;

}

else

printf(“Buffer is full\n”);

}

 

int main{void)

{

chat opera[5];

double z;

do {

printf(“Please input p|g|e?”);

scanf(“%s”, &opera);

               switch(tolower(opera[0])){

               case ‘p’: /* put */

                  printf(“Please input a float number?”);

                  scanf(“%lf”, &z);

                  put(z);

                  break;

case ‘g’: /* get */

                  z = get();

printf(“%8.2f from Buffer\n”, z);

break;

case ‘e’:

                  printf(“End\n”);

                  break;

default:

                  printf(“%s - Operation command error! \n”, opera);

}/* end switch */

}while(opera[0] != ’e’);

    return 0;

}

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(1) | 阅读(401)
发表于:2008-4-26 12:00:46
标签:单片机  通讯  

2

基于51串口通讯编程软件架构剖析(请大家就本文相关内容发表评论,来探讨一个高效可靠的通讯架构)

 

前言:
串口通讯对于所有的嵌入式工程师十分常见,对于一个与外界交互的系统必须依赖一些手段,比如串口、USB、红外、GPRS之类的数据通讯传输方式。而串口作为一种廉价的短距离可靠的通讯方式得到了广泛应用。
废话少说了,就此打住,进入正题。
本文主要从软件结构上讲解如何在资源比较缺乏的系统上实现通讯协议的串口通讯编程,以及如何优化程序效率,从而使系统更快、更稳定运行。

正文:
      我们以51单片机为例。51中一般针对串口通讯编程,通常采取中断接受查询发送的方式。中断函数在接受数据到达时被重复调用,其实是个重复入栈的过程,所以不宜将函数写的太长,函数太长一般会导致栈太深占用系统资源,二是处理时间过长,可能导致通讯出错。为了防止在处理数据过程中不受干扰,通常在处理接受数据前关闭中断,处理完后再开。
通常的的编程方式如下:
static void UartInterruptService(void) interrupt 4
{
    ES = 0;
    RI = 0;
    uart_process(SBUF);
    ES="1";
}
下面重点介绍数据处理函数 uart_process(SBUF);
其实很多时候,对于通讯传输的数据处理才是关键,尤其对于设计通讯协议而言。笔者在刚刚做的一个系统上就碰到这样的问题,当系统庞大了,资源十分有限的情况下,数据处理一旦占用资源太多,效率太低将导致系统崩溃而无法运行。
到了这里,很多工程师可能会考虑开个大的缓冲区FIFO将接收到的数据保存在缓冲区,然后对其进行解析、判断进行下一步程序编写,当然这在系统资源比较丰富的情况下是没有问题的,ARM上采取的就是这样的方式。但如何系统庞大呢,留给的资源缺乏则不行。这样做的一个很大缺点必须是将数据帧接收完了才能够判断,降低了效率和运行速度。
其实还有另外的方式,可以采取在每接收一个字节就对其解析,解析完判断转到下一个状态,并将其中的有用数据存储在相应的数据结构中去,可以采取状态机实现。

将状态机设计为两个控制状态,一是串口状态——uart_state ,一是命令类型状态——cmd_state .
(1)状态机开始状态:串口状态为CMD_NO
(2)接受到STX_CMD,状态变为CMD_START.
(3)接下来将自动进入接受命令帧的状态,再开启命令状态的状态机,对发送来的有用数据进行解析,保存,校验等。处理完毕后将uart_state设为CMD_END状态进行下一步的接受完毕判断,将cmd_state设置为初始的NO_CMD状态。
(4)最后进行ETX_CMD判断,判断数据接收是否完毕。


void uart_process(U8 u8)
{
     if(uart_state == CMD_NO)
     {
    if(u8 == STX_CMD)
      {
        uart_state = CMD_START;
      }
       
       }
    else if(uart_state == CMD_START)
    {
        switch(cmd_state)
        {
           case NO_CMD:
              cmd_state = u8;
              break;
               
           case COST_CMD:
                    //解析存储有用数据到相应数据结构中
                    //进行CRC校验
                    ……
                       uart_state = CMD_END;
                       cmd_state = NO_CMD;
                       CRC = 0;
                        break;
                        ……
                   }
              ……
            }
        else if(uart_state == CMD_END)
         {
            uart_state = CMD_NO;
            if(u8 == ETX_CMD)
            {
              //接受完毕
              //可以考虑抛出一个消息main函数循环中进行响应处理。
             }
        
           }
}
                         

接下来我们要讨论解析后我们数据存储的问题,其实在资源比较足够的情况下或者能够挤出data区的情况下可以考虑用结构体,我们构造好相应结构体,将接收到的数据存储进去,要应用的时候就十分方便。但这也有个矛盾,一般c51定义的结构体都被存储在data区,一般通讯的字节量大空间必然不够,存在一个矛盾,可以采用联合体union进行存储效果会好一点。当然也可以在保存数据时采用定义在xdata区(片外)的buffer来存储。这样在一定程序上优化了程序的执行效率,在程序处理立即抛出消息处理,提高了通讯数据的处理速度。对于通常资源比较丰富的系统,比如ARM上一般采取的做法是这样的,将数据存在缓冲区,接收完一帧数据后再转换成相应的数据结构,再进行分析、校验。   

总体来说,这种采取状态机实时解析串口通讯数据的方式在一定程序提高了程序运行效率,使软件架构清晰明了,程序可扩展性大,有利于后续开发。以上是笔者的一点愚见,欢迎指教。       

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(3) | 阅读(463)
发表于:2008-4-8 14:55:17
标签:无标签

1

C运行时库详解

 
        运行时库是程序在运行时所需要的库文件,通常运行时库是以LIB或DLL形式提供的。C运行时库诞生于20世纪70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于新观念。所以这个时期的C运行时库都是单线程的。

  随着操作系统多线程技术的发展,最初的C运行时库无法满足程序的需求,出现了严重的问题。C运行时库使用了多个全局变量(例如errno)和静态变量,这可能在多线程程序中引起冲突。假设两个线程都同时设置errno,其结果是后设置的errno会将先前的覆盖,用户得不到正确的错误信息。

  因此,Visual C++提供了两种版本的C运行时库。一个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库有两个重大差别:

  (1)类似errno的全局变量,每个线程单独设置一个;

  这样从每个线程中可以获取正确的错误信息。

  (2)多线程库中的数据结构以同步机制加以保护。

  这样可以避免访问时候的冲突。

  Visual C++提供的多线程运行时库又分为静态链接库和动态链接库两类,而每一类运行时库又可再分为debug版和release版,因此Visual C++共提供了6个运行时库。如下表:

C运行时库 库文件
Single thread(static link)   libc.lib
Debug single thread(static link)   libcd.lib
MultiThread(static link)   libcmt.lib
Debug multiThread(static link) libcmtd.lib
MultiThread(dynamic link) msvert.lib
Debug multiThread(dynamic link) msvertd.lib  

  2.C运行时库的作用

  C运行时库除了给我们提供必要的库函数调用(如memcpy、printf、malloc等)之外,它提供的另一个最重要的功能是为应用程序添加启动函数。

  C运行时库启动函数的主要功能为进行程序的初始化,对全局变量进行赋初值,加载用户程序的入口函数。

  不采用宽字符集的控制台程序的入口点为mainCRTStartup(void)。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在crt0.c中被定义,下列的代码经过了笔者的整理和简化:

void mainCRTStartup(void)
{
 int mainret;
 /*获得WIN32完整的版本信息*/
 _osver = GetVersion();
 _winminor = (_osver >> 8) & 0x00FF ;
 _winmajor = _osver & 0x00FF ;
 _winver = (_winmajor << 8) + _winminor;
 _osver = (_osver >> 16) & 0x00FFFF ;

 _ioinit(); /* initialize lowio */

 /* 获得命令行信息 */
 _acmdln = (char *) GetCommandLineA();

 /* 获得环境信息 */
 _aenvptr = (char *) __crtGetEnvironmentStringsA();

 _setargv(); /* 设置命令行参数 */
 _setenvp(); /* 设置环境参数 */

 _cinit(); /* C数据初始化:全局变量初始化,就在这里!*/

 __initenv = _environ;
 mainret = main( __argc, __argv, _environ ); /*调用main函数*/

 exit( mainret );
}


  从以上代码可知,运行库在调用用户程序的main或WinMain函数之前,进行了一些初始化工作。初始化完成后,接着才调用了我们编写的main或WinMain函数。只有这样,我们的C语言运行时库和应用程序才能正常地工作起来。

  除了crt0.c外,C运行时库中还包含wcrt0.c、 wincrt0.c、wwincrt0.c三个文件用来提供初始化函数。wcrt0.c是crt0.c的宽字符集版,wincrt0.c中包含windows应用程序的入口函数,而wwincrt0.c则是wincrt0.c的宽字符集版。

  Visual C++的运行时库源代码缺省情况下不被安装。如果您想查看其源代码,则需要重装Visual C++,并在重装在时选中安装运行库源代码选项。

  3.各种C运行时库的区别

  (1)静态链接的单线程库

  静态链接的单线程库只能用于单线程的应用程序,C运行时库的目标代码最终被编译在应用程序的二进制文件中。通过/ML编译选项可以设置Visual C++使用静态链接的单线程库。

  (2)静态链接的多线程库

  静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过/MD编译选项可以设置Visual C++使用静态链接的单线程库。

  (3)动态链接的运行时库

  动态链接的运行时库将所有的C库函数保存在一个单独的动态链接库MSVCRTxx.DLL中,MSVCRTxx.DLL处理了多线程问题。使用/ML编译选项可以设置Visual C++使用动态链接的运行时库。

  /MDd、 /MLd 或 /MTd 选项使用 Debug runtime library(调试版本的运行时刻函数库),与/MD、 /ML 或 /MT分别对应。Debug版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上Release版本。

  下面看一个未正确使用C运行时库的控制台程序:

#include <stdio.h>
#include <afx.h>
int main()
{
 CFile file;
 CString str("I love you");
 TRY
 {
  file.Open("file.dat",CFile::modeWrite | CFile::modeCreate);
 }
 CATCH( CFileException, e )
 {
  #ifdef _DEBUG
  afxDump << "File could not be opened " << e->m_cause << "\n";
  #endif
 }
 END_CATCH

 file.Write(str,str.GetLength());
 file.Close();
}

  我们在"rebuild all"的时候发生了link错误:

nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
main.exe : fatal error LNK1120: 2 unresolved externals
Error executing cl.exe.

  发生错误的原因在于Visual C++对控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程。我们只需要在Visual C++6.0中依次点选Project->Settings->C/C++菜单和选项,在Project Options里修改编译选项即可。

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(220)
发表于:2008-4-3 8:59:32
标签:无标签

1

函数的定义,声明与原型

         对函数的“定义”和“声明”不是一回事。“定义”是指对函数功能的确立,包括指定函数名,函数值类型、形参类型、函数体等,它是一个完整的、独立的函数单位。而“声明”的作用则是把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。从程序中可以看到对函数的声明与函数定义中的函数首部基本上是相同的。因此可以简单地照写已定义的函数的首部,再加一个分号,就成为了对函数的“声明”。在函数声明中也可以不写形参名,而只写形参的类型。 在C语言中,函数声明称为函数原型(function prototype)。使用函数原型是ANSI C的一个重要特点。它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。

说明:

<1> 以前的C版本的函数声明方式不是采用函数原型,而只是声明函数名和函数类型。
如:float add(); 不包括参数类型和参数个数。系统不检查参数类型和参数个数。新版本也兼容这种用法,但不提倡这种用法,因为它未进行全面的检查。

<2> 实际上,如果在函数调用前,没有对函数作声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int型。如一个max函数,调用之前没有进行函数声明,编译时首先遇到的函数形式是函数调用"max(a, b)",由于对原型的处理是不考虑参数名的,因此系统将max()加上int作为函数声明,即int max(); 因此不少教材说,如果函数类型为整型,可以在函数调用前不必作函数声明。但是使用这种方法时,系统无法对参数的类型做检查。或调用函数时参数使用不当,在编译时也不会报错。因此,为了程序清晰和安全,建议都加以声明为好。

<3> 如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义的函数类型,会根据函数首部提供的信息对函数的调用作正确性检查。

<4> 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调用函数中不必对所调用的函数再作声明。

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(414)
发表于:2008-3-27 16:16:49
标签:无标签

1

最支持台独的厂商,支持台独艺人

        昨天的台湾《联合报》发了一篇报道“大陆拒挺扁两台商入境…台湾的第一金融控股公司发言人蔡哲三昨天证实,该公司董事长谢寿夫、总经理蔡哲雄原定本月初赴北京,与中国银行签订策略联盟合约,但因大陆拒绝发台胞证,行程被迫取消,连带是项策略联盟合作也无限期延后。…谢寿夫等人不获核发台胞证,与银行公会在三二○大选前,以「全国金融界挺扁后援会」的名义,刊登题为「改革金融相信台湾」的挺扁广告有关,在该则广告上签字的金融界负责人已被列入不受欢迎黑名单。

        另一名也在广告上签名的台湾民营银行负责人最近赴大陆,据说,行前曾向对方解释是支持金融改革而非支持陈 水扁,才获准赴大陆。”,无独有偶,国台办发言人张铭清在前天的记者招待会上明确指出“我们不欢迎到大陆赚钱却又支持台独的台商”,无论这两者是否是巧合或者是既定,都提醒我们必须注意一个问题,就是应该对在台湾疯狂支持台独,又在大陆招摇过市、大赚其钱的经济台独、娱乐界台独分子予以坚决的限制。 台独势力不是陈水 扁一个人能够形成的,台独是政治,政治的基础是经济,政治的工具之一是文化娱乐,陈水 扁的背后正是有那么一批经济头面人物提供物质基础,有那么一批娱乐界人物摇旗呐喊,才会形成声势,才会形成气候,而奇怪的是,一批台独经济巨头,一批台独的热心宣传者在,同时在大陆的经济文化舞台成为座上宾,成为获利者。 比如,奇美集团董事长许文龙,此公每逢台湾“选举”,都要站出来大声“挺扁”,甚至在其他台湾经济巨头出于各种利益不出声或者含糊出声的情况下,此公都要旗帜鲜明地来一番台独秀。许文龙还有台独分子所特有的日本“皇民”情结,曾经公开诬蔑台湾“慰安妇”是“自愿”为日本军队“服务”的。而许文龙顽固的台独立场,却不妨碍他在大陆的生意兴隆,许文龙的奇美集团,在江苏地区有数十个企业。另外,死硬支持陈水 扁台独理念的台湾经济巨头还有如:长荣集团 张荣发、宏基Acer 施振荣等,都是在大陆很体面的主,有的还上过中央电视台。 台湾商人不是散财童子,不是慈善家,他们来大陆的目的是赚钱,是利用大陆的廉价劳动力,他们在大陆开办的企业,根本没有核心技术,大都是劳动密集型的装配工序,所谓的“根(研发阶段)留台湾”,然后在大陆销售牟利。许文龙就多次说过,“大陆劳动力便宜,为什么不去。”“到大陆都是投资价值不高的产业。”等等,他们很狡猾,他们每年从大陆获得数百亿美元的好处,客观上起了为台独势力输血的作用,特别是令台独势力有大批、长期购买美国高技术武器,以武拒统的底气。 我们还别小看了张惠妹之类的娱乐界台独的作用,他们在疯狂的商业包装之下,对青少年潜移默化的影响,是非常有效果的,对台湾青少年有影响,对大陆青少年也有影响。涉世不深的青少年会随着其“追星”的惯性,自然而然地接受这些人的台独立场,比如,陈水 扁每次的竞选,都有张惠妹参加的大张旗鼓的“造势”活动,在一阵阵狂野的嚎叫声中,台独势力需要的效果就出来了。类似的人物,还有我们大陆青少年如雷贯耳的苏有朋、任贤齐、周杰伦之类。这些面孔在我们的各种舞台、电视屏幕上,已经泛滥有时了。 张惠妹、苏有朋、任贤齐、周杰伦、F4一干人物,绝对不是出于对中华民族的认同感,处于真心与大陆青少年交朋友的目的来大陆“发展”的,他们的目的只有一个,就是钱,就是年少无邪的大陆青少年口袋里的人民币。诸君可曾见过他们讲过一句哪怕认同一个民族的话,相反,张惠妹在多个场合表达她与中国人三字无关,这两天还把姚明称为“大家都是亚洲人”。 这些经济娱乐界台独人物在大陆大行其道,在于我们对他们的过于宽厚,太把他们当回事,使他们利令智昏,头脑膨胀,以为大陆的经济、娱乐离了他们就玩不转了。也暴露了我们的某些地方政府,单纯追求招商引资成果,忽视反台独大局,我们的某些演出经纪公司,单纯追求演出利益,不辨香臭美丑。这种沾满铜臭,不讲政治的现象必须纠正,你不讲政治,台独势力要讲,这是回避不了的。

最支持台独的厂商
长荣集团 张荣发
奇美集团 许文龙
宏基Acer 施振荣
义美食品 高志明
玉山银行 林钟雄
高铁集团 殷琪
BENQ 康师傅
台独支持艺人名单
支持绿营(民进党)阿扁的艺人: 江霞,陈昭荣,苏有朋, 林瑞阳(现在上海开房地产公司),张庭,任贤齐,S.H.E.,于美人,文章,陈美凤,安迪,罗时丰,江惠,周杰伦,江淑娜, 李威, 林依晨,澎恰恰,林凤英,江宏恩,秦杨,蔡振南,猪头皮,水蜜桃姐妹 F4 张惠妹 伍百

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(184)
发表于:2008-3-27 11:03:12
标签:软件开发  c  c++  

1

林锐《高质量cc++编程》

点击下载经典著作,帮你走上高手之路!

点击此处查看原文 >>

系统分类: 软件开发   |    用户分类:    |    来源: 转贴

评论(1) | 阅读(289)
发表于:2008-3-25 16:35:29
标签:c语言  

0

C 语言嵌入式系统编程之:软件架构篇

 

模块划分

模块划分的""是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。

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

 

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

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

 

这样如果模块123 操作a 的话,对应的是同一片内存单元。

 

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

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

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

多任务还是单任务

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

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

究竟选择多任务还是单任务方式,依赖于软件的体系是否庞大。例如,绝大多数手机程序都是多任务的,但也