关于作者

用户名:bjtuwei
笔名:咸零蛋
地区: 北京-北京
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



matlab论坛

访问统计:
文章个数:33
评论个数:14
留言条数:5




Powered by BlogDriver 2.1

ZeroEgg 实验室

 

欢迎访问ZeroEgg 实验室,这里是讲究科学,崇尚科学,记录我学习上的问题,收集网上的资料!…… 特别注明:大部分资料是从网上收集到的,为自己使用的,我会尽量注明来源的。

文章

好久不来更新了
好久不来更新了,自从上了研究生以后,一直忙上课,学习东西等等,都没有时间来写点东西呢,太对不起了.

- 作者: 咸零蛋 2006年04月5日, 星期三 22:56  回复(0) |  引用(0) 加入博采

为什么要在operator=中返回"*this"的引用
[问题的提出]:
在很多书籍和文章中,很多次提到在对赋值操作符(=)进行重载的时候,要返回对目的(调用)对象实例(*this)的引用。其中不免有这样的论断:一定要返回对调用对象的引用;返回对调用实例对象的引用是为了实现链式连续赋值。
这里说明两个问题:第一,是否重载赋值操作符必须返回对调用对象的引用,第二,是否这样就可以实现链式赋值,而不这样就不行。
首先,必须承认,返回对"*this"的引用是标准的二目操作符重载的格式,效率很高。这样做有很多优点:如实现链式赋值、避免临时对象的产生(调用拷贝构造函数)、销毁(调用析构函数),但不是非这样做不可,下面通过对比来论述返回对"*this"的引用的优点及其他做法的缺点,同时也能清楚第二个问题,我们从例子着手。
// a.h
class A  
{
public:
A();
	A(int nTest);
	A(const A& a);
	virtual ~A();
	A operator=(const A& a);
	// A& operator=(const A& a);

private:
	int m_nTest;
	
public:
	void printit();
};
}

// a.cpp
A::A(int nTest)
{
	m_nTest = nTest;
	cout << "constructor A Value is executed now!" << endl;
}

A::A(const A& a)
{
	this->m_nTest = a.m_nTest;
	cout << "Copy constructor A is executed now!" << endl;	
}

A::A()
{
	cout << "constructor A Default is executed now!" << endl;
}

A::~A()
{
	cout << "Destructor A is executed now!" << endl;
}
A A::operator=(const A& a)
// A& A::operator=(const A& a)
{
	if (this==&a)  
	return *this;
	this->m_nTest = a.m_nTest;
	cout << "Assignment A is  
executed now!" << endl;
	return *this;
}
在main()函数中调用
A a(100),b(99),c(98);
a = b = c;
a.printit();
b.printit();
c.printit();
结果为:
constructor A Value is executed now!
constructor A Value is executed now!
constructor A Value is executed now!
Assignment A is executed now!
Copy constructor A is executed now!
Assignment A is executed now!
Copy constructor A is executed now!
Destructor A is executed now!
Destructor A is executed now!
99
99
98
Destructor A is executed now!
Destructor A is executed now!
Destructor A is executed now!

如果将 A operator=(const A& a) 改为 A& operator=(const A& a)
则结果为:
constructor A Value is executed now!
constructor A Value is executed now!
constructor A Value is executed now!
Assignment A is executed now!
Assignment A is executed now!
98
98
98
Destructor A is executed now!
Destructor A is executed now!
Destructor A is executed now!

两者的不同为前者比后者多执行了两次构造(拷贝构造函数)和析构函数,可见在执行过程充产生了两个临时对象。

[1]在赋值函数为:A operator=(const A& a)的情况下
对于a=b=c; 实际为a.operator=(b.operator=(c))
在执行A operator=(const A& a) 后返回 *this 给一个临时对象,所以生成和销毁这个临时对象的时候分别要调用构造和析构函数,而构造时是用一个已经存在的实例出初始化同类型的实例,所以调用的拷贝初始化函数。析构时,先析构前面一个(a.operator=)产生的临时对象,后析构"b.operator="产生的临时对象.

[2] 在赋值函数为:A& operator=(const A& a)的情况下
不同的是没有临时对象的产生,因为operator=返回的是对当前对象的引用,而引用只是别名,而不是构造新对象的。这点可以通过如下函数调用来理解:
void fun(A& temp)
{
temp ...
}
A a;

执行fun(a)函数调用时,没有产生临时对象。

可见,重载"="操作符,不一定要返回对赋值目的对象的引用,但返回引用是很好的做法,建议您这样使用。

最后提出几个问题,大家可以思考一下:
[1] 若将a=b=c; 改为(a=b)=c后分别调用A operator=(const A& a) 和A&operator=(const A& a)结果会有什么不同?
[2] 能否将A&operator=(const A& a)改为const A&operator=(const A& a)?
[3] 能否将A&operator=(const A& a)中的return *this;改为return a?
[4] A a, b;
a = b;
与 A a;
A b = a; 有什么不同?

水平有限,欢迎大家批评指正!

- 作者: 咸零蛋 2006年01月18日, 星期三 10:52  回复(0) |  引用(0) 加入博采

makefile文件的写法

概述
——

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些 Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile 了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。

因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的 make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。

在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。



关于程序的编译和链接
——————————

在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

好,言归正传,GNU的make有许多的内容,闲言少叙,还是让我们开始吧。



Makefile 介绍
———————

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。


一、Makefile的规则

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

target ... : prerequisites ...
command
...
...

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于 prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比 target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

说到底,Makefile的东西就是这样一点,好像我的这篇文档也该结束了。呵呵。还不尽然,这是Makefile的主线和核心,但要写好一个Makefile还不够,我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。:)


二、一个示例

正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。

edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

反斜杠(\)是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。

在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab 键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的 lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make 命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译关的命令,比如程序的打包,程序的备份,等等。


Makefile 总述
———————

一、Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个 Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


二、Makefile的文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、 “makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的 “makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”, “Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如: make -f Make.Linux或make --file Make.AIX。


三、引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫 foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2、如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:

-include
其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。


四、环境变量 MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的 Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make 时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。


五、make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么, make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。

 

三、make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如 file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。

而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。


四、makefile中使用变量

在上面的例子中,先让我们看看edit的规则:

edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit man.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果 makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)


于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。


五、让make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make 找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。


objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
rm edit $(objects)

这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。


六、另类风格的makefile

即然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。

objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
rm edit $(objects)

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。


七、清空目标文件的规则

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

clean:
rm edit $(objects)

更为稳健的做法是:

.PHONY : clean
clean :
-rm edit $(objects)

前面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。


上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?准备好了就来。

- 作者: 咸零蛋 2005年11月25日, 星期五 19:09  回复(6) |  引用(0) 加入博采

寂寞不是出轨的理由(转载)
女人带着孩子出门旅游去了,留下了男人一个人在家,女人不在家,男人喝着啤酒,不停地换着电视频道。这时,女孩的电话打来了,她说,我闲着没事,到你家做做吧,男人说,这不行,我正要出去。女孩其实已经在男人的楼下了。
        女孩是男人的部下,女孩很多次对他表示出了好感觉,男人都巧妙地拒绝了。男人知道,年轻女孩的心是一张空白的纸,他没有资格在上面留下痕迹。
        女孩手里提着很多东西,还有一瓶红酒,站在了男人的家门口。男人说,那我下厨吧。女孩说,不用,便在厨房里忙碌起来,男人忙不迭地收拾房子,他偶然看见女孩忙碌的背影,突然有一种感动。就那么一会儿,他立即将这种片刻的感觉压在了心底。
        在另一间房子了,他开始打电话约熟悉的朋友来家里吃饭,可是朋友们都不在。过了一会儿,女孩已经在喊他了,他到厨房猛的愣了,女孩端给他的是一盘热腾腾的饺子。他最爱吃饺子了,可是,平时他和女人都太忙,没有时间包饺子。
        两盘饺子,几蹀小菜,一瓶红酒,女孩的脸上柔柔的笑,搅动了他的心。说不清为什么,他在女孩不注意的时候,关掉了手机,拉上了阳台的帘。他能听到自己心跳的声音。
        一瓶红酒喝完了,女孩说头晕,就软绵绵地倒在了男人的怀里。男人承认女孩是美丽的,他紧紧地把她抱在怀里,也就在那一刻,他才感觉到女孩的身体是那样的弱小,在他宽阔的肩膀里像个孩子似的睡着,像他的女儿,他的心猛的一颤。
      &bsp; 女孩在他的床上睡去了,他轻轻地带上了门。这时客厅的电话响了,是女人和孩子打来的。
        男人仍然喝着啤酒,不停地换着频道,,他分明听到了女孩轻微的呼吸,但是,他努力地让自己的心冷静,再冷静。
        女孩醒来的时候已经是第二天早上。男人一夜未眠。男人为女孩准备了早餐。吃饭的时候,女孩问,你不喜欢我吗?男人说,喜欢。那你不寂寞吗?有点,可是——怕我纠缠你?女孩的嘴像蹦豆似的发问。
        男人认真地说,生活就像是一种责任,就像这碗稀饭和煎蛋,尽管老吃觉得没有什么味道,可是你每天还得做,还得吃,有时甚至觉得它难吃,可是不吃心里空荡荡的。
        女孩沉默了。送走了女孩,男人觉得从未有过的轻松。
        爱是一种诚信,是需要付出代价的,如果不爱,或无法承受,那么就别轻易地将自己的心打开。诱惑和寂寞,本不是爱的理由。

- 作者: 咸零蛋 2005年11月24日, 星期四 22:35  回复(1) |  引用(0) 加入博采

嵌入式人才的发展方向
嵌入式人才的发展方向!

嵌入式系统无疑是当前最热门最有发展前途的IT应用领域之一。嵌入式系统用在一些特定专用设备上,通常这些设备的硬件资源(如处理器、存储器等)非常有限,并且对成本很敏感,有时对实时响应要求很高等。特别是随着消费家电的智能化,嵌入式更显重要。像我们平常常见到的手机、PDA、电子字典、可视电话、VCD/DVD/MP3 Player、数字相机(DC)、数字摄像机(DV)、U-Disk、机顶盒(Set Top Box)、高清电视(HDTV)、游戏机、智能玩具、交换机、路由器、数控设备或仪表、汽车电子、家电控制系统、医疗仪器、航天航空设备等等都是典型的嵌入式系统。

嵌入式系统是软硬结合的东西,搞嵌入式开发的人有两类。

一类是学电子工程、通信工程等偏硬件专业出身的人,他们主要是搞硬件设计,有时要开发一些与硬件关系最密切的最底层软件,如BootLoader、Board Support
Package(像PC的BIOS一样,往下驱动硬件,往上支持操作系统),最初级的硬件驱动程序等。他们的优势是对硬件原理非常清楚,不足是他们更擅长定义各种硬件接口,但对复杂软件系统往往力不从心(例如嵌入式操作系统原理和复杂应用软件等)。

另一类是学软件、计算机专业出身的人,主要从事嵌入式操作系统和应用软件的开发。如果我们学软件的人对硬件原理和接口有较好的掌握,我们完全也可写BSP和硬件驱动程序。嵌入式硬件设计完后,各种功能就全靠软件来实现了,嵌入式设备的增值很大程度上取决于嵌入式软件,这占了嵌入式系统的最主要工作(目前有很多公司将硬件设计包给了专门的硬件公司,稍复杂的硬件都交给台湾或国外公司设计,国内的硬件设计力量很弱,很多嵌入式公司自己只负责开发软件,因为公司都知道,嵌入式产品的差异很大程度在软件上,在软件方面是最有“花头“可做的),所以我们搞软件的人完全不用担心我们在嵌入式市场上的用武之地,越是智能设备越是复杂系统,软件越起关键作用,而且这是目前的趋势。

从事嵌入式软件开发的好处是:
(1)
目前国内外这方面的人都很稀缺。一方面,是因为这一领域入门门槛较高,不仅要懂较底层软件(例如操作系统级、驱动程序级软件),对软件专业水平要求较高(嵌入式系统对软件设计的时间和空间效率要求较高),而且必须懂得硬件的工作原理,所以非专业IT人员很难切入这一领域;另一方面,是因为这一领域较新,目前发展太快,很多软硬件技术出现时间不长或正在出现(如ARM处理器、嵌入式操作系统、MPEG技术、无线通信协议等),掌握这些新技术的人当然很找。嵌入式人才稀缺,身价自然就高,越有经验价格就越高。其实嵌入式人才稀少,根本原因可能是大多数人无条件接触,这需要相应的嵌入式开发板和软件,另外需要有经验的人进行指导开发流程。
(2)
与企业计算等应用软件不同,嵌入式领域人才的工作强度通常低一些(但收入不低)。搞企业应用软件的IT企业,这个用户的系统搞完了,又得去搞下一个用户的,而且每个用户的需求和完成时间都得按客户要求改变,往往疲于奔命,重复劳动。相比而言,搞嵌入式系统的公司,都有自己的产品计划,按自己的节奏行事。所开发的产品通常是通用的,不会因客户的不同而修改。一个产品型号开发完了,往往有较长一段空闲时间(或只是对软件进行一些小修补),有时间进行充电和休整。另外,从事嵌入式软件的每个人工作范围相对狭窄,所涉及的专业技术范围就是那些(ARM、RTOS、MPEG、802.11等),时间长了这些东西会越搞越有经验,卖卖老本,几句指导也够让那些初入道者琢磨半年的。若搞应用软件,可能下一个客户要换成一个完全不同的软件开发平台,那就苦了。
(3)
哪天若想创业,搞自已的产品,那么嵌入式是一个不错的主意,这可不像应用软件那样容易被盗版。土木学院有一个叫启明星的公司开发出一个好象叫“工程e”的掌上PDA(南校区门口有广告),施工技术人员用该PDA可当场进行土木概预算和其它土木计算,据说销路特好。我认识的某大学老师,他开发的饭馆用的点菜PDA(WinCE平台,可无线连网和上网),据他说销路不错,饭馆点点PDA让客户点菜,多显派头档次。我记得00级2+2班当年有一组同学在学Windows程序设计课程时用VC++设计了一个功能很强的点菜系统做为课程项目,当时真想建议他们将这个软件做成PDA,估计会有些销路(上海火车站南广场的Macdonald便使用很漂亮的PDA给用户点食品,像摸像样的)。这些PDA的硬件设计一般都是请其它公司给订做(这叫“贴牌”:OEM),都是通用的硬件,我们只管设计软件就变成自己的产品了。

从事嵌入式软件开发的缺点是:
(1)
入门起点较高,所用到的技术往往都有一定难度,若软硬件基础不好,特别是操作系统级软件功底不深,则可能不适于此行。
(2)
这方面的企业数量要远少于企业计算类企业。特别是从事嵌入式的小企业数量较多(小企业要搞自己的产品创业),知名大公司较少(搞嵌入式的大公司主要有Intel、Motorola、TI、Philip、Samsung、Sony、Futjtum、Bell-Alcatel、意法半导体、Microtek、研华、华为、中兴通信、上广电等制造类企业)。这些企业的习惯思维方式是到电子、通信等偏硬专业找人。由于我院以前毕业生以企业计算为主,所以我院与这些企业联系相对较少。我院正积极努力,目前已与其中部分公司建立了联系,争取今后能有我院同学到这些企业中实习或就业。
(3)有少数公司经常要硕士以上的人搞嵌入式,主要是基于嵌入式的难度。但大多数公司也并无此要求,只要有经验即可。

我院同学若学习嵌入式,显然应偏重于嵌入式软件,特别是嵌入式操作系统方面,应是我们的强项。对于搞嵌入式软件的人,最重要募际跸匀皇牵ㄊ导噬虾芏喙镜恼衅腹愀嫔暇褪钦庋吹模?BR>(1) 掌握主流嵌入式微处理器的结构与原理
(2) 必须掌握一个嵌入式操作系统
(3) 必须熟悉嵌入式软件开发流程并至少做过一个嵌入式软件项目。

嵌入式软件方面最重要的课程包括:
(1)
嵌入式微处理器结构与应用:这是一门嵌入式硬件基础课程,我院用这门课取代了传统的“微机原理与接口”课程(目前国内已有少部分高校IT专业这样做了,因为讲x86微机原理与接口很难找到实际用处,只为教学而已)。我们说过,嵌入式是软硬件结合的技术,搞嵌入式软件的人应对ARM处理器工作原理和接口技术有充分了解,包括ARM的汇编指令系统。若不了解处理器原理,怎么能控制硬件工作,怎么能写出节省内存又运行高速的最优代码(嵌入式软件设计特别讲究时空效率),怎么能写出驱动程序(驱动程序都是与硬件打交道的)?很多公司招聘嵌入式软件人员时都要求熟悉ARM处理器,将来若同学到公司中从事嵌入式软件开发,公司都会给你一本该设备的硬件规格说明书
(xxx
Specification),您必须能看懂其中的内存分布和端口使用等最基本的说明(就像x86汇编一样),否则怎么设计软件。有些同学觉得嵌入式处理器课程较枯燥,这主要是硬件课程都较抽象的原因,等我的嵌入式实验室10月份建好后,您做了一些实验后就会觉得看得见摸得着。还有同学对ARM汇编不感兴趣,以为嵌入式开发用C语言就足够了。其实不应仅是将汇编语言当成一个程序设计语言,学汇编主要是为了掌握处理器工作原理的。一个不熟悉汇编语言的人,怎么能在该处理器写出最优的C语言代码。在嵌入式开发的一些关键部分,有时还必须写汇编,如Bootloader等(可能还包括BSP)。特别是在对速度有极高要求的场合(如DSP处理器的高速图像采集和图像解压缩),目前主要还要靠汇编写程序(我看到过很多公司是这样做的)。当您在一个嵌入式公司工作时,在查看描述原理的手册时,可能很多都是用汇编描述的(我就遇到过),这是因为很多硬件设计人员只会写或者喜欢用汇编描述,此时您就必须看懂汇编程序,否则软硬件人员可能就无法交流。很多嵌入式职位招聘时都要求熟悉汇编。

(2) 嵌入式操作系统类课程
除了WinCE的实时性稍差外,大多数嵌入式操作系统的实时性都很强,所以也可称为实时操作系统Real Time
Operating
System.从事嵌入式的人至少须掌握一个嵌入式操作系统(当然掌握两个更好),这在嵌入式的所有技术中是最为关键的了。目前最重要的RTOS主要包括:

第一类、传统的经典RTOS:最主要的便是Vxworks操作系统,以及其Tornado开发平台。Vxworks因出现稍早,实时性很强(据说可在1ms内响应外部事件请求),并且内核可极微(据说最小可8K),可靠性较高等,所以在北美,Vxworks占据了嵌入式系统的多半疆山。特别是在通信设备等实时性要求较高的系统中,几乎非Vxworks莫属。Vxworks的很多概念和技术都和Linux很类似,主要是C语言开发。像Bell-alcatel、Lucent、华为等通信企业在开发产品时,Vxworks用得很多。但Vxworks因价格很高,所以一些小公司或小产品中往往用不起。目前很多公司都在往嵌入式Linux转(听说华为目前正在这样转)。但无论如何,Vxworks在一段长时间内仍是不可动摇的。与Vxworks类似的稍有名的实时操作系统还有pSOS、QNX、Nucleus等RTOS。

第二类、嵌入式Linux操作系统:Linux的前途除作为服务器操作系统外,最成功的便是在嵌入式领域的应用,原因当然是免费、开源、支持软件多、呼拥者众,这样嵌入式产品成本会低。Linux本身不是一个为嵌入式设计的操作系统,不是微内核的,并且实时性不强。目前应用在嵌入式领域的Linux系统主要有两类:一类是专为嵌入式设计的已被裁减过的Linux系统,最常用的是uClinux(不带MMU功能),目前占较大应用份额,可在ARM7上跑;另一类是跑在ARM
9上的,一般是将Linux
2.4.18内核移植在其上,可使用更多的Linux功能(当然uClinux更可跑在ARM
9上)。很多人预测,嵌入式Linux预计将占嵌入式操作系统的50%以上份额,非常重要。缺点是熟悉Linux的人太少,开发难度稍大。另外,目前我们能发现很多教材和很多大学都以ucOS/II为教学用实时操作系统,这主要是由于ucOS/II较简单,且开源,非常适合入门者学习实时操作系统原理,但由于ucOS/II功能有限,实用用得较少,所以我院不将其作为教学重点,要学习就应学直接实用的,比如
uClinux就很实用。况且熟悉了Linux开发,不仅在嵌入式领域有用,对开发Linux应用软件,对加深操作系统的认识也有帮助,可谓一举多得。据我所知,目前Intel、Philip都在大搞ARM+LINUX的嵌入式开发,Fujitum则是在自己的处理器上大搞Linux开发。目前在嵌入式Linux领域,以下几个方面的人特别难找,一是能将Linux移植到某个新型号的开发版上;二是能写Linux驱动程序的人;三是熟悉Linux内核裁减和优化的人。我院在该嵌入式Linux方面的课程系列是:本科生操作系统必修课,然后是Linux程序设计选修课,最后是嵌入式Linux系统选修课。我院在Linux方面目前已有较强力量,魏老师和张老师熟悉Linux开发,金老师和唐老师熟悉Linux系统管理。

第三类、 Windows
CE嵌入式操作系统:Microsoft也看准了嵌入式的巨大市场,MS永远是最厉害的,WinCE出来只有几年时间,但目前已占据了很大市场份额,特别是在PDA、手机、显示仪表等界面要求较高或者要求快速开发的场合,WinCE目前已很流行(据说有一家卖工控机的公司板子卖得太好,以至来不及为客户裁减WinCE)。WinCE目前主要为4.2版(.NET),开发平台主要为WinCE Platform Builder,有时也用EVC环境开发一些较上层的应用,由于WinCE开发都是大家熟悉的VC++环境,所以我院学过Windows程序设计课程的同学都不会有多大难度,这也是WinCE容易被人们接受的原因,开发环境方便快速,微软的强大技术支持,WinCE开发难度远低于嵌入式Linux。对于急于完成,不想拿嵌入式Linux冒险的开发场合,WinCE是最合适了(找嵌入式Linux的人可没那么好找的),毕竟公司不能像学生学习那样试试看,保证开发成功更重要。根据不同的侧重点 ,WinCE还有两个特殊版本,一个是MS PocketPC操作系统专用于PDA上(掌上电脑),另一个是MS SmartPhone操作系统用于智能手机上(带PDA功能的手机),两者也都属于WinCE平台。在PDA和手机市场上,除WinCE外,著名的PDA嵌入式操作系统还有Palm OS(因出现很早,很有名)、Symbian等,但在WinCE的强劲冲击下,Palm和Symbian来日还能有多长?我院可能是全国高校中唯一一家开设专门的“Windows CE嵌入式操作系统“课程的学校,这主要是基于以下原因:我院本身前面便有Windows程序设计课程,同学学过VC++后再学WinCE,非常方便自然,通过学习WinCE同样也可了解嵌入式软件的一般开发过程,对Linux有惧怕心理的同学也很合适。很显然,嵌入式Linux永远不可能替代WinCE,而且将来谁占份额大还很难讲,毕竟很多人更愿意接受MS的平台,就像各国政府都在大力推LINUX已好长时间,但您能看到几个在PC机上真正使用LINUX的用户?据我观察,目前在嵌入式平台上,LINUX是叫得最响,但还是WinCE实际用得更多.嵌入式LINUX可能更多地是一些有长远产品计划的公司,为降低成本而进行长远考虑;
二是微软亚洲研究院对我院WinCE课程的支持计划,我们也很希望将来我院能有同学通过微软的面试去实习。WinCE和多媒体(如MPEG技术)是微软亚洲工程院目前做得较多的项目领域之一,他们很需要精通WinCE的人。

总结关于嵌入式操作系统类课程,若您觉得自己功底较深且能钻研下去,则可去学嵌入式Linux;若您觉得自己VC++功底较好且想短平快地学嵌入式开发,则我院的WinCE课程是最好的选择。

(3) 嵌入式开发的其它相关软件课程

搞嵌入式若能熟悉嵌入式应用的一些主要领域,这样的人更受企业欢迎。主要的相关领域包括:
A、数字图像压缩技术:这是嵌入式最重要最热门的应用领域之一,主要是应掌握MPEG编解码算法和技术,如DVD、MP3、PDA、高精电视、机顶盒等都涉及MPEG高速解码问题。为此,我院已预订了一位能开设数字图像处理课程的博士。
B、通信协议及编程技术:这包括传统的TCP/IP协议和热门的无线通信协议。首先,大多数嵌入式设备都要连入局域网或Internet,所以首先应掌握TCP/IP协议及其编程,这是需首要掌握的基本技术;其次,无线通信是目前的大趋势,所以掌握无线通信协议及编程也是是很重要的。无结通信协议包括无线局域网通信协议802.11系列,Bluetooth,以及移动通信(如GPRS、GSM、CDMA等)。
C、网络与信息安全技术:如加密技术,数字证书CA等。我院有这方面的选修课。
D、DSP技术:DSP是Digital Signal
Process数字信号处理的意思,DSP处理器通过硬件实现数字信号处理算法,如高速数据采集、压缩、解压缩、通信等。数字信号处理是电子、通信等硬件专业的课程,对于搞软件的人若能了解一下最好。目前DSP人才较缺。如果有信号与系统、数字信号处理等课程基础,对于学习MPEG编解码原理会有很大帮助。

(4)嵌入式开发的相关硬件基础

对于软件工程专业的学生,从事嵌入式软件开发,像数字电路、计算机组成原理、嵌入式微处理器结构等硬件课程是较重要的。另外,汇编语言、C/C++、数据结构和算法、特别是操作系统等软件基础课也是十分重要的。我们的主要目地是能看懂硬件工作原理,但重点应是在嵌入式软件,特别操作系统级软件,那将是我们的优势。
我们的研究生里有些是学电子、通信类专业过来的,有较好的模拟电路和单片机基础,学嵌入式非常合适。嵌入式本身就是从单片机发展过来的,只是单片机不带OS,而现在很多嵌入式应用越来越复杂,以至不得不引入嵌入式操作系统。另外,为追求更高速的信号处理速度,现在在一些速度要求较高的场合,有不少公司是将一些DSP算法,如MPEG压缩解压缩算法等用硬件来实现,这就涉及到HDL数字电路设计技术及其FPGA/IP核实现技术,这方面的人目前市场上也很缺。

题外话
另外,能写驱动程序的人目前是非常紧缺的(驱动程序也可归于嵌入式范畴),包括桌面Windows中的DDK开发环境和WDM驱动程序。公司每时每刻都要推出新产品,每一个新产品出来了,要能被操作系统所使用,是必须写驱动程序的。写驱动程序就必须掌握操作系统(如Windows或Linux)的内部工作原理,还涉及到少量硬件知识,难度较大,所以这方面的人很难找。想成为高手的同学,也可从驱动程序方面获得突破。我可说一下自己的经历,三年前我曾短暂地在一家公司写过WinCE驱动程序(正是因为知道这方面的人紧缺,所以才要做这方面的事),尽管那以前从未做过驱动程序,应聘那个职位时正是看准了公司是很难招聘到这方面的人,既然都找不到人,驱动还得有人做,这正是可能有机会切入这一领域的大好机会。面试时大讲自己写过多少万行汇编程序,对计算机工作原理如何清楚,简历中又写着我曾阅读完两本关于Windows Driver Model的两本英文原版书,写过几个小型的驱动程序练习程序(其实根本没写过,我们的同学将来千万不要像我这样,早练就些过硬功夫,就不至于沦落到我这等地步,就不用像我那样去“欺骗”公司了,我这是一个典型的反面教材),居然一切都PASS(当然最重要的是笔试和面试问题还说得过去),这只能说明这一领域找人的困难程度。公司本就未指望找到搞过驱动的人,找个有相关基础的人就算不错了。做了以后,发现也并不是怎样难的。实搞驱动程序的工作是很舒服的,搞完一个版本就会空一段时间,只有等公司新的芯片推出或新的OS出现后,才需要再去开发新一版驱动,那时有将近一个月时间空闲着在等WinCE .NET Beta版推出,准备将驱动程序升级到CE .NET上,现在在软件学院工作整日忙,无限怀念那段悠闲时光。

很巧合,最近本人无意中再次体会到了嵌入式的迷人之处。上周我那用了3年的手机终于不能WORK了。此次更新,除要求有手机常见功能外,最好有MP3功能(现在很多英语听力都有MP3文件),最好有英汉词典,最好还能读WORD文档。最后选了个满足以上条件的最便宜的手机DOPOD 515(斩了我2.2K,但想想这也算自己对嵌入式事业的支持,这样便也想开了),算得上最低档的智能手机了。回来一查,手机的about显示,本手机Processor是ARM,其OS是MS Smartphone(即WinCE .NET 4.2),这么巧合,简直可做为学习嵌入式课程的产品案例了(等我们的WinCE课程开得有声有色后,希望能从微软研究院搞些Smartphone来开发开发)。有OS的手机果然了得,金山词霸、WORD、EXCEL、REGEDIT等居然都有smartphone版的,PC上的MP3、DOC等居然在download时都可被自动转换成smartphone格式,真是爽。完全可用Windows CE自己开发一些需要的程序download到自己的手机上。现在市面销售PDA智能手机火爆,MS总是财源滚滚。但我已发现国产的ARM+LINUX手机出现在市面上,价格只1.2K。

在GOOGLE网上能搜索太多的关于嵌入式系统的讨论了,我刚发现一个http://www.embyte.com非常不错,有很多有经验者谈自己的体会,投入到其中的论坛中,你会切身感到嵌入式学习的热潮。

要么走ARM+WinCE,要么走ARM+LINUX,要么走ARM+VXWORKS。每个搞嵌入式的人都可选一条路,条条大路通罗马。

- 作者: 咸零蛋 2005年11月12日, 星期六 21:20  回复(0) |  引用(0) 加入博采

控制论思想

1、控制论击毁驱逐舰

1982
4月,英国和阿根廷在南大西洋的马尔维纳斯(福克兰)群岛附近,展开了第二次世界大战以来规模最大的海空战,也是世界上第一场动用核潜艇和空对舰导弹以及复杂电子系统的大战。这次海战,双方共出动了数十艘战舰和几百架飞机,尤其是使用了几十种现代化导弹,在水面、水下、空中和岛岸进行了封锁与反封锁、空袭与反空袭、登陆与反登陆的殊死较量。52日,英征服者号核潜艇在水下发射两枚配有先进制导系统的虎鱼式鱼雷,击沉了阿根廷唯一的一艘3千多吨的巡洋舰。54日,阿根廷使用法国制造的超级军旗式战斗机,在距英舰央48公里左右处,发射一枚飞鱼AM39”型空对舰导弹,一举击沉了英国现代化程度很高、价值约2亿美元的谢菲尔德号导弹驱逐舰。525日,阿方再次使用超级军旗式飞机发射两枚飞鱼式导弹,又击沉了一艘排水量为1.8万吨的英国大西洋运送者号运兵船。

阿方空军用一枚价值20万美元的飞鱼导弹,击沉了英国一艘价值2亿美元的导弹驱逐舰,使世界各国军事人员目瞪口呆,惊呼导弹在未来的海战中将起到关键作用。战斗结果表明,配有精密制导系统的武器彻底改变了传统海战方式,双方用不着面对面地舰炮对射,在几十公里乃至几百公里外就可用导弹发动攻击了。

我们介绍这一战例的目的在于说明这样一个事实:现代导弹装配的精密制导系统正是自动控制技术在军事上的一个重要应用。

控制论是什么?

控制论研究的对象是包括人在内的生物系统和各种非生物系统(如工程系统、化工系统、通讯系统、经济系统等)。所谓系统,是由相互制约的各个部件组成的具有一定功能的整体。

恒温箱就是一个简单的系统。如图所示,构成系统的基本部件是:电源、开关、加热电阻丝、箱体和执行动作的人。因为我们的目标是要保持恒温箱内的温度不变,所以这就是一个控制系统,受控对象是箱内温度,控制方式是接通或断开电源开关。

如果我们希望箱内的温度保持在20(假定环境温度低于20),那么当操作员观察到温度计的数值低于20时,就合上开关,接通电源加热,使箱内的温度升高。等升到20,操作员又将开关断开了一段时间后,箱内温度就会因为散热而下降,这时操作员又得合上开关为箱子加热。重复上述过程,操作员眼睛观察温度计,手控制电源开关,就能基本上保证箱内的温度维持在20

如果我们采用自动控制技术来代替人的劳动,就成为自动恒温系统。它与人工恒温系统不同的是减少了操作员,但增加了一根弹簧、一个继电器和有关线路。温度计底部及20处,各有一根导线连到电源上,这些增加部件的功能就是代替人工操作。当继电器回路的电源接通时,就会产生吸力,将开关拉开(断开),因此加热电阻不工作(停止加热)。如果继电器回路中电源断开,继电器不产生吸力,那么开关受弹簧拉力的作用而闭合,接通加热器回路的电源,加热器开始加热。

这个系统是怎样实现自动控制的呢?原理很简单。因为温度计内的水银是可以导电的,所以当箱内温度低于规定值20时,继电器回路是断开的,不产生吸力,弹簧把开关拉紧(闭合),加热器回路有电,电阻丝发热,箱内温度持续升高。一旦温度升到20时,水银使继电器回路接通,因为继电器产生的吸力大于弹簧的拉力,所以将开关断开,加热器停止工作。由此可见,在自动恒温系统中,弹簧与继电器接替了操作员双手的工作——合上或断开开关;而温度计上的接线相当于人双眼的观察,它能依据箱内是否达到规定值而使继电器动作,从而实现了自动恒温的目的。

从上面这个简单例子可以归纳出自动控制系统的一个突出特点,即系统自动控制过程就是信息传递和变换的过程。但是一旦系统中信息传递受阻,又会发生什么情况呢?我们再以一个美国独立战争时期发生的一件事来说明。

当时,英国殖民主义者为了巩固它在美国的殖民统治地位和有效镇压美国人民的起义,曾组织了一次重要的战役。英军指挥者设想一支军队从加拿大出发,同另一支从纽约出发的军队,在一个名叫萨拉托加的地方汇合,对那里的起义队伍形成两面夹击的进攻阵势,企图一举消灭起义军队。但是战役的结果却并不像英军指挥者设想的那样。当其中一支部队到达指定地点时,却始终不见另一支英军前来汇合,结果形成了孤军奋战的被动局面,惨遭失败。事后才查明,原因是由于疏忽,行动命令只发给了一支部队,另一支部队根本就没有接到命令。

显而易见,英军失败的主要原因是信息受阻。其下属根本没有收到上级的任何指示,因为英军的信息传递只有自上而下(命令),而没有自下往上的信息反馈。所谓反馈(Feed-back),是指当指挥者控制系统发出的指令信息(也叫系统输入)输入后,通过系统内部变换后又将信息作用的结果(也叫系统输出)返回到系统输出端,并根据系统输出与系统输入(规定值)是否吻合,再对系统施加作用的过程。这也正是控制论创始人维纳所提出的双向通讯的慨念,既有从系统输入到系统输出的正向信息传递和变换,也有从系统输出端返回输入端的反馈信息。从控制论的观点来看,系统的自动控制过程正是通过双向通讯的信息反馈联系而实现的。信息在系统中的这种循环往返过程中,不断变换形式,最终实现控制目标。这就是控制论所揭示的自动控制系统的反馈机制,它是自动控制系统的第二个特点。

反馈机制

在山村野地,一群小鸡在嘁嘁喳喳地寻找食物,时而翻动草屑,时而啄食幼虫,怡然自得。一只饿鹰从远处飞来,发现了猎物,急速俯冲下来,吓得小鸡四处逃窜,于是演出了一幕追踪逃逸的活戏剧。在这个追踪逃逸系统中,对老鹰来说,目标是小鸡,控制机构是鹰脑(发出指令),执行动作的机构是鹰翅、鹰爪和嘴。在整个追踪过程中,鹰借助眼睛不断地获得反馈信息(即小鸡的位置、速度和方向变化),据此及时调整自己的动作,直到抓住目标。

从这场鹰鸡殊死之战的过程中,我们可以看出信息反馈和反馈控制的重要性。

其实反馈作为一种技术手段自动控制目标,早在古代就开始了,只不过那时人们尚未从理论上加以升华。相传早在2千多年前,我国和古希腊都曾发明过水钟(铜壶滴漏)。这种简单的装置中就包含了深奥的反馈控制原理。水钟的基本要求是控制水流的速度恒定以达到准确记时的目的。控制方式如图所示。

反馈控制早期应用的另一个实例是离心式调速器如图所示。大家都知道,1768年,英国工人瓦特(J.Watt17361819年)发明了蒸汽机,从而导致了西方国家的第1次产业革命。据说瓦特小时候家里很穷,没有上过学,可是他十分爱学习,特别爱动脑子。一天,小瓦特在厨房里看奶奶做饭,正巧炉子上的一壶水开了,壶盖啪啪地直响,还不断的跳动。小瓦特看了半天,感到很奇怪,就问奶奶:什么东西使壶盖跳动不停呀?奶奶说:水开了,壶盖就动呗!瓦特进一步问道:为什么水烧开了,壶盖就会动呢?是不是有什么东西在里面推动它呢?奶奶看瓦特老是问个不停,就说:我不知道,你自己去看吧!为了弄清壶盖为什么会跳动,瓦特常常坐在炉旁边仔细观察

为了进一步解决蒸汽机所推动的机械装置的速度控制问题,1788年瓦特在系统中采用了离心式自动调速器。据估计,在19世纪中,仅英国就有7.5万台装有瓦特调速器的蒸汽机装置。我们又一次看到了反馈控制的神奇魅力。

反馈控制的概念还可以应用到更为广泛的领域,如教师讲课时,在认真讲授书中内容的同时,还密切观察同学们的反应,并随时提问,课后批改作业。这后面的三种方式就是为了获取反馈信息,以检查同学们掌握教学内容的程度,并根据这些信息调整讲授方法和进度,确保教学质量。

反馈机制对于人们的各种社会实践活动也具有十分重要的意义。就拿企业管理来说吧,管理也是一种深奥的控制活动,必须紧紧抓住信息反馈这个关键环节。管理没有信息反馈,只有上情下达,而无下情上达,就必然会脱离实际而出乱子,企业也会弄得一塌糊涂。同样,对经营决策者来说,市场信息的反馈也是至关重要的,不注意市场需求变化而关起门来盲目生产的决策者,必然会导致企业亏损,甚至倒闭。

从上面的讨论可以看出,信息概念和反馈思想是控制论的两个基本出发点。

陀螺与航行自动控制

大家小时候可能都玩过陀螺,当你掌握了正确方法用鞭子抽打它几下以后,它就会尖顶朝下竖起来,并绕其轴线旋转而不倾倒。

可别小看陀螺这小玩意儿,人们正是根据其自旋不倒的原理而设计制造出了五花八门的精密陀螺仪,为各种飞行器(如飞机)、导弹、人造卫星等)的飞行自动控制奠定了基础。尽管陀螺仪的外表看起来与常见的陀螺不大一样,其大小也不尽相同(如用在飞行仪器上的陀螺仪最轻者只有几十克重,而一个稳定核潜艇的陀螺仪却重达55吨),但是基本原理却并无二致。

陀螺仪对于现代飞行控制系统来说可谓举足轻重。它不仅对整个系统的工作起着决定性作用,而且它的精度高低、可靠性程度和使用寿命长短等指标,对飞行器的稳定性和精确性都有着至关重要的影响。

陀螺仪的最早应用领域是航海事业。19世纪人们广泛利用陀螺仪标定航向,在漫长的航海史上写下了新的一页。从20世纪40年代开始,陀螺仪便在导弹武器及航空航天事业上得到广泛应用,其稳定性和工作精度也随着科学技术的进步和工艺水平的提高而迅速提高。目前陀螺仪已有滚珠轴承、气浮、液浮、挠性、激光等类型。

陀螺仪在高速旋转时,能够抗拒任何外力和干扰的影响,保持其自转轴相对于惯性空间方向上稳定不变。当飞行器的飞行姿态偏离了预定正确方向,陀螺仪在转轴与飞行方向之间的夹角便发生了变化,飞行器上的检测元件立刻就可测量出来,并同时发出控制信号,通过执行机构的作用使飞行器的状态恢复正常。因此,这种自动控制系统也叫做姿态稳定系统

陀螺自转轴方向不变的原理除应用于导弹的制导和飞机姿态控制以外,在宇航技术中也同样得到广泛运用。例如陀螺仪用在人造卫星上,可以保证人造卫星不受外界干扰而稳定运行在预定轨道上。不论人造卫星绕地球转到哪个位置或受其他什么外界干扰,卫星上的陀螺仪始终是指向空间某一预定方向。

小小的玩具陀螺和精度日益提高的陀螺仪竟具有完全相同的原理,初看起来似乎觉得有点不可思议,其实这正是大千世界中存在的客观规律。许多表面上看来非常简单的东西,却蕴藏着深奥的科学道理,关键在于我们锨迤浔局使媛桑缁嵯蚯胺?
英国科学巨人牛顿从苹果落地而不是飞向空中这样一个司空见惯的现象中,发现了苹果从树上落到地上与其他行星绕太阳运行都遵循着同样的法则——万有引力定律,并且计算出了太阳系中各行星绕太阳旋转的轨道和周期,一下子把几百年来争论不休的是日心学说正确还是地心学说正确的问题解释得一清二楚。科技发展史表明,科学上许多原理和理论,常常是经过一段模糊时期后突然为人们所认识。细心的读者是否会从中得到什么启迪呢?

自动控制与电子战争

现代科学技术的发展,使电子技术在军事上的应用日益普及。现代化的武器装备,如大炮、坦克、飞机、军舰、导弹等,都配有相应的雷达、通讯设备及红外线或激光装置。然而历史的发展规律总是有矛就有盾。有了电子技术的应用,就会有电子技术上的斗争,这样现代战争中就出现了一个崭新的竞争领域——电子对抗和电子干扰,或叫做电子战。

所谓电子对抗,是指敌对双方利用电子设备和能够反射、吸收电磁波的器材的电子斗争。电子对抗的历史可以追溯到本世纪初。当无线电刚开始在军事通信中应用时,以截获和破译敌方情报为特征的简单的无线电通信对抗就萌芽了,并揭开了电子对抗的序幕。二次大战以来,炮瞄雷达、导弹制导雷达和飞机截击雷达相继问世,大大提高了武器命中率。与此同时,围绕着兵器控制与反控制展开了更激烈的电子对抗。

我们知道,雷达和无线电通信是现代社会当之无愧的千里眼顺风耳。而电子干扰却能把敌人的眼睛耳朵封住,使它们成为瞎子聋子,或者巧施妙计,使敌人上当受骗。比如,利用雷达干扰发射机作为干扰源,可以发射或转发某种电磁波来压制敌方的电子设备,使它们无法正常工作。我们平时收看电视都有体会,如果附近有电子干扰(如汽车发动机或电焊机等工作时所产生的干扰),电视屏幕的画面就会发生畸变,干扰越强,画面畸变越厉害。同样,当飞机或舰艇上的雷达接收机受到干扰压制后,在雷达距离显示器屏幕上会出现参差不齐的茅草。干扰电波的能量越大,茅草长得越高,把本应能够发现的目标信号给掩盖住了,因为此时目标信号完全淹没在干扰信号中了。而且,无线电通信设备受到强烈的电磁干扰后,耳机中充满了杂乱刺耳的噪声,也无法进行正常的通信联络。

另外一种电子对抗手段是欺骗性干扰。其原理是用干扰发射机或无线电台巧妙地模仿敌方信号使敌人上当受骗。对雷达的欺骗性干扰,可使雷达在测定目标、方位、速度时产生错误,破坏雷达跟踪或制导,使敌方火炮、导弹击不中目标。无线电通信欺骗干扰还可以冒充敌台通报、通话,搅乱敌方通信,达到以假乱真的目的。

电子干扰技术在第二次世界大战中为盟军19446月在法国诺曼底登陆,并最终消灭德国法西斯军队立下了汗马功劳。当时盟军的具体作法是:

1)将计就计,实施欺骗。纳粹德军统帅部曾武断地认为联军将在加莱地区登陆。英美联军将计就计,在多佛尔设置了一个假司令部电台群,不断发出内容适当的电报,故意泄密,造成联军将在加莱登陆的假象,使希特勒陷入圈套。

2)严密侦察,挖睛扫障。英美联军对德军部署在法国沿海一带的雷达站、干扰站、警报台和电台进行严密侦察,并做到了如指掌。在登陆前夕,又派出轰炸机和战斗机进行大规模袭击,摧毁了德军所有的干扰台和80%以上的雷达站,挖掉了德军的眼睛,并保证了联军雷达和电台的正常工作。

3)巧布疑阵,声东击西。登陆前夜,英美联军用一群群小船装着角反射器,拖着涂铝汽球等用于干扰的物体驶向加莱地区,使残存无几的德军雷达误将小船队视为大批飞机掩护下的大型进攻舰队,牵制住了加莱地区的大量德军,减少了联军在诺曼底登陆战中的阻力。

4)施放干扰,出奇制胜。登陆开始时,英美联军出动320架干扰飞机迷惑德军残存的雷达,掩护了飞向战区的大批战机。庞大的突击舰队始终隐蔽前进,只是在距登陆地点10海里时,因发动机响声才被德军发现,然而联军5个师20万人的突击部队的登陆已是势不可挡了。

60
年代以来,还出现了一种反雷达导弹,是专门用来对付敌方雷达的导弹。其原理是利用敌方雷达发射的电磁波作引导,跟踪其信号直捣老巢,最后摧毁敌方雷达站。反雷达导弹第一次出现在战争舞台上是1965年,当时美国利用百舌鸟反雷达导弹,攻击越南的高炮阵地装备百舌鸟导弹的飞机,一般先在地面防空高炮火力有效射程外盘旋,引诱敌方雷达开机搜索,然后捕捉其信号,再发射导弹予以摧毁。这种武器曾严重破坏了越南北方地面雷达系统。其后,在中东战争、英阿马岛战争、两伊战争中,反雷达导弹都显示了巨大的威力。

最优化思想

在日常生活中,我们都有这样的经验,无论干什么事都希望以最小的代价获得最大的成功。例如上街购买东西时,我们总是挑那些质量好、外形最美观、价格也便宜的商品;在学习上,我们喜欢掌握最好的学习方法,以便在最短的时间内取得最好的学习成绩;在工作时,我们更愿意用最轻松愉快的方式来取得最满意的工作效果。这些看似平常的日常现象,其中包含了现代控制理论中的最优化思想。将上述这种最优化的观点应用于工程实践,便产生了在社会生活各个方面得到广泛应用的最优控制技术。

最优控制理论的发展是伴随着最优化概念的提出而开始的。在第二次大战期间及以后的一段时间内,应战争和军事防御上的需要,以提高大炮发射命中率为主要目标的自动控制系统(通常叫做伺服系统)的技术日臻完善。但是,随着社会的发展,简单的反馈控制已经难以满足工程实践的要求,传统的系统设计方法也无法实现日渐增高的性能指标。在这种情况下,科学家们通过大量的研究,于50年代初提出了最优化的概念,并试图对控制对象施加最优控制。但由于理论上尚不完善故未能真正实现。直到1960年前后,由于在控制理论中引入一系列新的研究方法和数学成果,推出了最优控制所必须满足的必要的充分条件后,才使最优控制的应用逐渐普及,并成为60年代自动控制领域的热门课题。特别是空间技术的迅猛发展,更进一步推动了最优控制理论向前迈进。举个例子来说,为了使宇宙飞船登月舱能以最小的燃料在月球表面准确、平稳地实现软着陆,即落到月球表面时的速度恰好为零,以避免与月球表面发生碰撞而损坏舱内设备,必然选择合适的控制方式来改变火箭发动机的推力。这就是所谓的月球软着陆问题,也叫做燃料最省控制问题。

再举一个例子:坐电梯。开关一按,哧溜一下就到了几十层的大楼顶上。电梯省时省力,是现代科学和文明的产物。不过,应当怎样来控制电梯的运动,使它能以最短的时间到达顶楼(或从楼上下到地面)地面呢?也许有人会说,这还不简单,让电梯始终以最快的速度直上(或直下)不就行了么!其实仔细想一下就会发现这种控制方式是不行的。因为当电梯以最大的速度冲向楼顶(或地面)时,必然会发生剧烈的碰撞而造成设置损坏甚至人员伤亡。因此必须运用科学分析的方法,制定合理可行的控制方案,既要保证电梯上升(或下降)的时间最短,又要让它到达楼顶或地面时速度恰好为零。这也是一个最优控制问题,我们称之为时间最优控制问题

为了解决各种各样的最优控制问题,人们找到了许多方法,其中有两种最有成效。一种是美国学者贝尔曼于19531957年间研究提出的动态规划;另一种是前苏联学者庞特里亚金于19561958年间创立的极大值原理

变色蜥蜴的启示

变色龙,也叫变色蜥蜴,它能够自动适应周围环境的变化,随时把皮肤颜色变成与它所附着的物体相同的颜色(俗称保护色)。变色龙这种难能可贵的变色本领具有极好的伪装效果,通常不会为凶猛野兽识别,从而达到保护自己免受其天敌袭击或吞食的目的。

我们人体本身也同样具有适应外界环境变化的巨大能力。如人的体温,无论酷暑严寒,总能保持在一个相对恒定的水平上。

人们从生物体具有自动适应外界环境变化的能力这种自然现象中受到了很大的启发。如果人们设计的自动控制系统也能够在外界条件发生变化时,仍然保持最优运行,岂不是美事一桩吗?正是在这种思想支配下,人们提出了自适应控制(Adaptive Control)的概念。

前面我们已经介绍了,反馈控制的基本思想是利用系统输入(受控量)与希望值之间的偏差来控制系统的行为,使误差趋近于零。但实际上,由于多数受控制对象的特性很难准确掌握,内部参数也随环境而变化(如电阻会随温度变化),外界条件会随时波动(如电压波动),而且这些变化通常是无法预测的,所以,人们在对原系统进行控制的过程中,该系统的特性实际上已经发生了不同程度的变化。事先确定的最优控制在内部参数和外部环境变化后,可能已不再是最优方案了,因此只有设计一种随内部参数和外部环境变化而自动调整系统特性的控制方式,才能保证控制系统始终处于或接近最优运行状态,这种系统就是自适应控制系统,具有自适应能力的控制器叫做自适应控制器。

自适应控制的设想,最先是由考德威尔(W.1.Caldwell)于1950年提出来的。1958年美国麻省理工学院的怀特克(H.P.Whitaker)教授首先应用自适应控制方法设计了飞机自适应自动驾驶仪。

自适应控制系统的两个基本功能是:能够自动检测和分析受控对象的特性以及系统所处环境的变化;能够根据从环境和系统内部检测到的信息得出决策,适当改变系统的结构或参数以及控制策略,以保护系统在任何情况下都能稳定和最优运行。要实现这两种功能,显然必须进行大量的复杂计算和推断,所以自适应控制系统不开现代社会的天之骄子”——电子计算机的帮助。可以说,没有电子计算机的参与,要实现系统的自适应控制是不可能的,正如巧妇难为无米之炊

如前所述,飞行器的控制是较早应用自适应控制技术的。大家知道,飞行器飞行的高度和速度会随着高空中云层、气流等环境的改变而发生剧烈变化,飞行器的动力学参数也会产生较大波动,依靠常规的反馈控制往往难以获得令人满意的控制精度。现在,采用带电脑的自适应控制系统可以实现良好的飞行。此外,大型船舶的自动驾驶仪是自适应控制技术成功应用的典型范例。

海上航行,环境复杂,气候多变,随时会出现一些意想不到的情况,如海浪、海潮、台风等。采用船舶自适应驾驶仪后,则可以克服风、流、浪、水域深度、船舶装载重量及其他不可预见的因素对船舶操纵性能的影响,确保船舶在各种环境条件下能量消耗最小,并安全准确地航行。目前,瑞典、日本和英美等国已生产出许多性能良好的产品投放市场。由于采用这种自适应驾驶仪后,航速可提高1%,估计每条远洋轮船可节省燃油3%,因此具有明显的经济效益和社会效益。

在医院,当有重病患者需急诊抢救时,往往要对患者进行长时间的输液治疗,这对医护人员是一个很重的负担。日夜值班守护,一时疏忽就可能酿成重大事故。但如果采用自适应监护系统,就可以日夜不间断地监测病人的脉搏和心电图,及时获得病情信息,并根据病人病情变化自动调整输液量。这样不但减轻医护人员的工作负担,还可明显提高治疗效果。

此外,自适应控制技术还广泛应用于工业、农业、石油勘探与开发、资源分配、宏观经济调控等各个部门。

自适应控制系统的进一步发展,将走向所谓自学习自组织智能控制系统。这些系统除具备一般自适应功能外,还能够自动记忆本系统过去的经验和教训,回忆过去曾经发生的情况,并基于这些信息改进系统的自适应功能。或许在不远的将来,通过读者朋友们的辛勤劳动和创造,在自动控制领域内将产生更加惊人的突破。

- 作者: 咸零蛋 2005年11月11日, 星期五 19:04  回复(0) |  引用(0) 加入博采

汉字动态编码

汉字动态编码
zhenglixin
发表于 2005-5-14 14:23:00

因为汉字本身的特点,显示汉字始终是计算机在我国应用普及的一个障碍。最初,为了能在PC机上显示、处理汉字,国人发明了一种硬件设备"汉卡",后来各种各样的采用纯软件技术的中文DOS逐渐成熟,其中、西文软件的运行速度和性能还是有明显的差距。最终在软件进入支持UNICODE、真正实现国际化的WIN95以后,硬件跨入"奔腾"时代,才实现了汉字与西文的统一显示,但是这一切是以硬件资源的飞速发展为前提的。以国际GB2312为例,一、二级汉字库共收录了6000多个汉字,每个字按16×16点阵计算,字模需要占用32字节的存储空间,整个字库的规模在200k字节以上,高点阵(24点阵以上)和矢量字库以及Windows用的TrueType字体的字库规模都是几兆字节大小,这在早期的386时代是难以想象的。单片机因为使用灵活、结构简单、体积小、成本低而在工业和生活中得到广泛应用,也正是因此,它的硬件资源很有 限,寻址和计算机能力都远低于PC机,显示汉字更受限制。人们不满足单片机系统采用LED数码管的简单显示,根据单片机的特点,开发出了很多种汉字显示方法。

1
几种常用单片机显示汉字方法

(1)
采用标准字[1]

这种方法仿器中文DOS的办法,将一个标准的汉字库装入ROM存储器,再根据汉字的机内码在字库中寻址,找到对应的字模,提取后送到显示器显示。因为采用了和PC机相同的编码(机内码),软件的开发和维护非常简单,基本上与写PC机软件差不多。而对单片机系统自身的要求则相对高多了,16×16点阵的字库需要256K字节,但是一般8位单片机的寻址能力只有64K字节,要进行存储器扩充,除增加很大一部分硬件成本外,还因为要进行存储器分页管理、地址切换,显示速度明显受影响,而且只能显示一种点阵字体。
因为汉字本身的特点,显示汉字始终是计算机在我国应用普及的一个障碍。最初,为了能在PC机上显示、处理汉字,国人发明了一种硬件设备"汉卡",后来各种各样的采用纯软件技术的中文DOS逐渐成熟,其中、西文软件的运行速度和性能还是有明显的差距。最终在软件进入支持UNICODE、真正实现国际化的WIN95以后,硬件跨入"奔腾"时代,才实现了汉字与西文的统一显示,但是这一切是以硬件资源的飞速发展为前提的。以国际GB2312为例,一、二级汉字库共收录了6000多个汉字,每个字按16×16点阵计算,字模需要占用32字节的存储空间,整个字库的规模在200k字节以上,高点阵(24点阵以上)和矢量字库以及Windows用的TrueType字体的字库规模都是几兆字节大小,这在早期的386时代是难以想象的。单片机因为使用灵活、结构简单、体积小、成本低而在工业和生活中得到广泛应用,也正是因此,它的硬件资源很有 限,寻址和计算机能力都远低于PC机,显示汉字更受限制。人们不满足单片机系统采用LED数码管的简单显示,根据单片机的特点,开发出了很多种汉字显示方法。

1
几种常用单片机显示汉字方法

(1)
采用标准字[1]

这种方法仿器中文DOS的办法,将一个标准的汉字库装入ROM存储器,再根据汉字的机内码在字库中寻址,找到对应的字模,提取后送到显示器显示。因为采用了和PC机相同的编码(机内码),软件的开发和维护非常简单,基本上与写PC机软件差不多。而对单片机系统自身的要求则相对高多了,16×16点阵的字库需要256K字节,但是一般8位单片机的寻址能力只有64K字节,要进行存储器扩充,除增加很大一部分硬件成本外,还因为要进行存储器分页管理、地址切换,显示速度明显受影响,而且只能显示一种点阵字体。

2)直接固化显示字模[2]

将要显示的语句中全部汉字的字模数据依次提取出来,顺序存放在存储器中,当显示时,直接取出字模数据送至显示器即可。这种方法占用空间少,程序实现简单,显示速度快;但是字模数据的提取和存储安排是一件委有繁琐的事件,要想大量显示汉字或进行程序修改几乎是不可能的,软件的可维护性很差。

(3)
建立带索引的小字库[3]

将全部要显示的汉字统一建成一个小字库,字库分为2部分:索引素和字模表。索引表由若干定长记录组成,记录的内容为:汉字机内码、地址码、识别码。其中地址码是该汉字字模在字模表中的位置,识别码标志该汉字的点阵形式或字体等。字模表中按素引存放汉字字模。显示汉字时先根据待显汉字的机内码在索引表中寻找,找到对应索引记录后,读出地址码和识别码,再根据此从字模表中读出字模,送显即可。这种方法可根据实际使用对字库进行裁剪,硬件开销较小,但是要进行复杂的查询运算,字多了平均寻找时间就会变长,效率降低。

2
汉字动态编码

综上所述,我们发现:在方法1中,程序员工作量最少,但单片要机的软、硬件开销最大;方法2中,单片机的开销较少,但是编写和维护软件极为困难;方法3,介于二者之间。显然,存储空间、显示速度、软件开发维护件间存在着矛盾。受各种PC机模拟软件的启发,我们提出一种基于PC机预处理的汉字显示方法--汉字动态编码,在实际应用中较好地解决了这一问题。其基本原理如下:建立一种新的编码机制,这个汉字编码是动态的;一个编码不与某个汉字具体相联系,而仅代表某个汉字在字库中的位置(这个位置也是动态的);用该码代替程序里字符串(C语言)或数据段(汇编语言)内汉字的机内码,单处机显示程序可根据这个新的编码直接在专门建立的动态小字库中找到字模,不用进行复杂的寻址、查找等运算,如图1所示。

实现汉字动态编码的过程就是先进行汉字识别,然后建立编码字典、提取字模、建立动态字库、改写机内码。首先扫描一遍程序文件,识别其中的汉字,将它们按出现先后顺序或机内码的大小排序,重复出现的剔除,建立了一个编码字典;根据汉字在编码字典的位置(序号),可以对汉字按区码、位码进行编码,也可以采用其它的方法编码,总之序号与它的动态编码存在一一对应关系;根据字典中每个汉字的机内码依次从PC机的汉字点阵字库中取字模,顺序存储,建立一个小规模的动态字库,这样每个汉字的字模在字库中的位置就与其在编码字典中的序号、动态编码一一对应了。最后,再扫描一遍程序文件,按照编码字典将每个汉字的机内码改写为对应的动态编码。因为程序文件中的汉字随时会增减,编码随之而变,字库的大小也随时在变。所以称之为动态编码和动态字库。

考虑一般应用场合,1000个左右的汉字即可满足要求,按照汉字动态编码方法所需的字库仅为32K字节大小,只需要127256即可,几乎不用增加什么硬件。这样,字库的大小可由汉字的多少控制,程序的编写和维护可以沿用中文系统下的习惯,仅需要编写好的单片机程序用PC机进行一次预处理,程序员从繁杂的汉字处理工作中解放出来,有效地降低了软件和硬件开发成本。

3
汉字动态编码的具体实现

实现汉字动态编码的关键是建立编码字典和改写机内码。下面以是显示1行汉字"天上有个太阳,水中有个月亮"为例,说明动态编码的实现过程。

1)汉字识别

汉字在PC机内的存储和处理是用机内码来实现的。每个汉字的机内码是唯一的,由2个字节组成,分区码和位码,为了和西文的ASCII码有区别,汉字机内码的区码和位码的取值都大于0A0H。我们要处理的源程序文件都是文本文件,存储的都是西文字符、控制符的ASCII码和中文字符的机内码,当扫描到文件中大于0A0H的字节内容时,即可判断该字节是汉字机内码的1个字节,而且肯定是成对出现,第1个字节是区别,第2个字节是位码,都大于0A0H,否则出错。

C和汇编程序中表示字符的方式有所不同,但最终字符在文件中的存储格式是一样的。显示上面那行汉字,用C语言可以表示为:

char OneSent[]="
天上有个太阳,水中有个月亮"

printfhz
OneSent;/*printfhz()显示函数*/

用十六进制编辑器(我们用的是UEdit32)察看文件中C语言字符串定义语句为:

63 68 61 72 20 20 4F 6E 65 53 65 6E 74 5B 5D 20 3D 20 22 CC EC C9 CF D3 D0 B8 F6 CC AB D1 F4 A3 AC CB AE D6 D0 D3 D0 B8 F6 D4 C2 C1 C1 22 20 3B 0D 0A

用汇编语言可以表示为:

ONESENT:DB '
天上有个太阳,水中有个月亮'00H

MOV DPTR,ONESENT

LCALL DISPLAY
DISPLAY是显示子程序

用十六进制编辑器察看上面用汇编语言定义字符串的那一条语句为:

4F 4E 45 53 45 4E 54 3A 44 42 20 27 CC EC C9 CF D3 D0 B8 F6 CC AB D1 F4 A3 AC CB AE D6 D0 D3 D0 B8 F6 D4 C2 C1 C1 27 2C 30 30 48 0D 0A

由此可以观察到情况确如前所述。

2)建立编码字典

编码字典是在扫描的同时逐步建立起来的,每扫描到一个汉字(包括全角符号),即与字典中已有的字符进行比较,如没有重复,是新的字符就顺序存入字典,否则继续扫描,直至文件结属。由于每个字符都是从尾部添加的,它们的序号也是依次递增的,根据序号就可以进行动态编码了。由于显示的汉字一般都得在256个以上,即使进行动态编码,也需要用2字节编码来实现。以MCS51系列单片机和16×16点阵汉字做一优化编码示例:8051的地址指针DPTR16位指针,由高、低2字节指针DPHDPL组合而成,如果将存储器按0FFH256)字节分布,修改DPH即可直接寻址到任一页,修改DPL可寻址该页的任一字节。一个16×16点阵汉字的字模是32字节大小,每页存储器正好能容纳8个汉字字模。可以优化设计动态编码的高字节指向字模的页地址(DPH),低字节指向字模在该页的首地址(DPL)。考虑地址空间的有效分配,将字库的地址放在0A000H以后(程序或数据存储器均可),动态编码的高字节要加上地址有效分配,将字库的地址放在0A000H以后(程序或数据存储器均可),动态编码的高字节要加上地址的页偏移量(大于等于0A0H);考虑汉字与西文字符的区别,动态编码的低字节也需要加上一个大于或等于0A0H的偏移量。设某汉字在编码字典中的序号为Num,则该汉字的动态编码为:

动态编码高字节=页偏移量+Num/8

动态编码低字节=偏移量+(Num%8)×32    (1)

偏移量一般可设为0A0H。当单片机显示某个汉字时,只需将其动态编码的高字节送DPH,低字节减0A0H后送DPL,即可得到对应字模的地址指针。

3)提取字模、建立动态字库

汉字机内码与点阵字库的详细关系可参考有关资料,它们存在如下联系:

字模首地址=((机内码高字节-1×94+(机内码低字节-1))×N    2

注:N为一个汉字点阵字模的字节数。

按照编码字典内容,根据字模首地址,依次取出汉字字模,顺序写入一个二进制文件,即建成动态字库(其它方法略),用烧录器写入EPROM,就可以使用了。

4)编码改写

机内码是PC机识别处理汉字用的,单片机只能处理我们建立起来的动态编码,还得把程序中汉字的仅机码根据编码字典改成对应的动态编码才行。由于在编写源程序的文本编辑器中看到的是经过系统处理过的字节,看不到汉字的机内码,也无法对其进行改写。根据"汉字识别"一节所述,不经过文本编辑器,直接将动态编码(十六进制数)定改磁盘文件对应位置即可,但是处理过后的汉字在文本编辑器里会显示出乱码。

5)汉字显示

在明白了动态编码与动态字库中字模的关系后,可以完成按照PC机下汉字显示原理进行单片机下的程序设计,编写前面的函数printhz()或子程序的DISPLAY,可参考相关资料[4]

4 MCS51
汉字显示例程

根据上述汉字动态编码方法,我们利用Borland C++编写了PC机预处理程序,将ASM51C51源程序用PC机预处理后,建立了动态字库和改写了机内码,并且用ASM51写了一个针对MCS51进行优化的子程序DIS_CHAR。它显示一个西文或中文字符,实现过程如图2所示。

西文字符码的显示与流字显示基本相同,将西文字库(仅数字和字符部分)装入程序存储器中,根据ASCII码的值计算出字模首地址,将字符字模依次读出,再送显示即可。

此方案不但可用于单片机系统中,还可应用于任何无中文系统支持的嵌入式系统中。根据这个思路还可设计出同字体、点阵混合的字库,支持包含2万多个字符的新国标编码,甚至矢量字体在单片机系统中的应用也成为可能。由于技术水平有限,此方案还存在一些不足之处,如改写编码后源程序中汉字显示为乱码,不知道改码处理是否正确,操作比较繁琐。如果能采用插件技术实现此方案,编辑器中能正常显示汉字,而输出已经是改码后的程序文件,则能很好地解决上述不足。在这里,我们抛码引玉,希望有兴趣的朋友一起合作,实现单片机中文显示的广义开发平台。

- 作者: 咸零蛋 2005年11月11日, 星期五 19:03  回复(0) |  引用(0) 加入博采

美国日本雇“写手”占领中文BBS !警惕!
发信人: daozi (小刀), 信区: Military
标  题: 美国日本雇“写手”占领中文BBS !警惕!
发信站: 两全其美网 (Fri Nov  4 19:49:14 2005), 本站(lqqm.net)


美国日本雇“写手”占领中文BBS !警惕!

美国日本雇“写手”占领中文BBS !警惕!

很偶然的看到这个帖子,不知以前有没有人贴过。
贴出这个帖子,并不是要针对某一个人,
只是觉得,凡是中国人都要时刻保持高度警惕!



本帖子转自加拿大中文网

据总部设在北美的一家中文BBS网站对经常在网上发表文章的人的背景调查,美国CIA,日本都雇佣了一批人专门在网上张贴诬蔑攻击中国(注意不仅是新浪,而且是整个中国)的文章和真假消息。他们的手段包括:
1,极力打击中国人的爱国热情,对发表爱国文章的人进行恶意辱骂和贬低,称他们是"爱国贼”,“粪青”,“太子党”,“共狗”等等不一而足。 而且往往以不同笔名
发多个跟贴,给人以人多势众的感觉,仿佛中国爱国的人已经人数廖廖,多数都是象他们一样对中国恨之入骨。

2,极力诋毁中国取得的任何成绩,或者根本不提中国还有成绩。如果有人提了,就群起攻击,说他们是共产党的爪牙,太监,是无耻。

3,大肆宣扬中国的坏消息。对中国确实发生的坏事,不但连篇类牍地张贴,还添油加醋尽可能补充不实的内容,牵强附会地发掘所谓内幕,总之是中国罪有应得。

4,大肆散布关于中国的谣言,假消息。这些假消息当然都是坏的,如****太子党如何在海外过奢华的生活,XXX的女儿如何在美国,中国导弹击毁台湾华航客机等等,试图唤起中国人对自己的政府和国家的仇恨。

5,对中国人进行种族攻击。大力宣扬中国人如何丑陋,如何只配当劣等民族,如何吃狗肉不人道,如何在加拿大,澳大利亚及全世界污染白人社区等等。

6,极力打击中国的英雄人物。据他们考证,岳飞根本不是忠臣,秦桧倒是受到了不公待遇,应该平反。领导中国人民站起来的毛泽东因为在朝鲜战场*和美国人使美国人丢了面子而成了十恶不赦的恶棍,暴君,流氓。他发动的十年文革更是罄竹难书,饿死几千万。其实这些人对文革根本一无所知,反正一口咬死先黑了再说。

7,故意挑动中国不同地区的人们进行斗争。河南人做假,湖南人最具反抗精神因而该死,新疆人该造反,西藏人该独立等等。总之,中国越乱越好。他们知道堡垒最容易从内部攻破,如果中国人万众一心那还得了。

8,逢中必反,逢外必捧。美国炸了中国大使馆是该炸,撞了中国飞机是中国飞机太好斗,美国人还在媒体公开幸灾乐祸是人家幽默。美国在911被炸是恐怖分子对*****的攻击,中国人个个应该如丧栲妣,如有人敢笑则是卑鄙,冷血,该杀。

9,宣杨人权高于主权。中国人被美国人,日本倭寇杀了也是有人权的,因为至少他们还肯动手。做美国日本的奴才和狗当然也可以有点“奴才权”,“狗权”。反之,如果敢反抗则是对人权的肆意践踏,甚至对奴才和狗也应该尊重,否则就是大不敬,违反人道。中国人只能对美国人,日本人俯首贴耳,根本不能不配有主权。

以上只是列了几条,其实还有更多,不过这些打手的目的只有一个,搞垮中国。

被雇佣的打手大多是本来是中国人但后来“痛改前非”而“规化了”的“美国佬”,
“日本小鬼子”,“台毒”,也有一些冒充的中国人(如本论坛的JAPANESE BOY-
**OY),当然也有为了骗点钱的XXXX人士,***教徒。可惜的是还有一些被蒙蔽了的中国人也跟风跟雨,不知明辩是非。

打手的面目很容易识别,诸君睁大眼睛请仔细瞧瞧。

其实请大家包括是中国人的打手们好好想想,同为炎黄子孙,只有祖国强大了,每个中国人才能不受歧视。即使一定要做汉奸和狗,也要盼望中国强大,否则兔死狗烹,恐怕狗也做不成了。


本人在海外网站与美日台反华网特较量过一番,摸出了一些反华网特的门道。写
出来给大家做个参考。

1.编造中国人吃婴儿的假新闻,编造吃人历史。
2.借宣传基督教,否定唯物主义。
3.曲解毛泽东的只言片语,编造虚假历史材料,诋毁毛泽东,编造绯闻侮辱毛泽
东。以及侮辱毛泽东亲属和原身边工作人员。
4.否定抗美援朝战争,否定抗美援越战争。
5.丑化北朝鲜及北朝鲜领导人,制造假舆论,为美国军事打击北朝鲜作理论铺垫

6.夸大叁年自然灾害,鼓吹“大饥荒”“饿死叁千万”或者“饿死两千万”谎言

7.夸大文革中右派死亡人数。
8.通过曲解、丑化文革,对立党群关系,否定共产党存在的合理性。
9.否定建国初期叁十年的建设成就。
10.否定票证制度的历史贡献。夸大农副产品、轻工业产品紧缺历史。
11.编造“亲身经历”“现身说法”攻击计划生育政策。
12.利用个案,攻击中国户籍管理制度、暂住证制度。
13.宣传“人权高于主权”“条件爱国论”“唯武器论”。
14.借宣传“民主”推行资产阶级独裁价值观。
15.替历史上着名汉奸翻案。宣扬“爱国贼”理论。
16.利用“托”的办法,故作恍然大悟状,吸引思想免疫力弱的人上当。或者干
脆直接互相吹捧。

反华网特并非凡人,不但有深厚的理论功底,而且深谙心理战法。以上这些任何
一条,在他们那里都可以变成推翻中华人民共和国的理论依据。

他们是拿工资的网特,你驳他、骂他,他都不会走。凡是跟贴吹捧的笔名,都有
可能是他们的同伙,或者根本就是一个人。他们的理论已经在强国论坛深入讨论
区,和其它各大中文论坛被驳倒无数次了,但还是厚着脸皮贴,贴,贴,一方面
寻找思想松动者,另一方面谎言说一千遍就是真理。值得注意的是,不少跟贴是
在中国的夜间上的,也就是说他们有时差,他们不在国内。请版主和广大网友注
意观察。

- 作者: 咸零蛋 2005年11月5日, 星期六 16:29  回复(4) |  引用(0) 加入博采

源码公开的TCP/IP协议栈在远程监测中的应用


作 者:■ 上海大学 张懿慧 陈泉林


摘 要:介绍一个适用于8/16位单片机的嵌入式TCP/IP协议栈(uIP)在发电机远程监测系统中的应用。重点阐述uIP的功能特性、体系结构和相关接口,并详细介绍如何在该协议栈上实现一个嵌入式Web服务器。目前uIP已成功地移植到51单片机上。

关键词:TCP/IP协议栈 uIP 嵌入式Web服务器 远程监测


引 言

  目前,随着互联网的发展,越来越多的工业测控设备已经将网络接入功能作为其默认配置,以实现设备的远程监控和信息分布式处理。笔者曾参与某发电机射频监测仪的开发,该设备主要用于诊断和预警发电机早期故障,并通过RS232接口定时输出电平和状态数据,现场专门设一台PC作接收、显示及存储。每年都要有专家到各发电厂对以往数据作检查和诊断,不胜其烦。因此有必要设计一个RS232到Internet的数据传输模块,以便对发电机的运行状况作远程监测。设计该模块的关键在于如何实现一个嵌入式TCP/IP协议栈,根据以往的经验,自己设计一个协议栈的难度很可能超过应用本身,而采用商业的协议栈似乎又无必要(功能过于复杂),最后笔者选用一种功能简易的免费TCP/IP协议栈uIP 0.9作为设计核心。

1 嵌入式TCP/IP协议栈

  目前,市面上几乎所有的嵌入式TCP/IP协议栈都是根据BSD版的TCP/IP协议栈改写的。在商业嵌入式TCP/IP协议栈大都相当昂贵的情况下,很多人转而使用一些源代码公开的免费协议栈,并加以改造应用。目前较为著名的免费协议栈有:

  lwIP(Light weight TCP/IP Stack)——支持的协议比较完整,一般需要多任务环境支持,代码占用ROM>40KB,不适合8位机系统,没有完整的应用文档;

  uC/IP (TCP/IP stack for uC/OS)——基于uC/OS的任务管理,接口较复杂,没有说明文档。

  笔者采用的协议栈系瑞典计算机科学研究所Adam Dunkels开发的uIP0.9 。其功能特性总结如下:
◇完整的说明文档和公开的源代码(全部用C语言编写,并附有详细注释);
◇极少的代码占用量和RAM资源要求,尤其适用于8/16位单片机(见表1);
◇高度可配置性,以适应不同资源条件和应用场合;
◇支持ARP、IP、ICMP、TCP、UDP(可选)等必要的功能特性;
◇支持多个主动连接和被动连接并发,支持连接的动态分配和释放;
◇简易的应用层接口和设备驱动层接口;
◇完善的示例程序和应用协议实现范例。
        
  正是由于uIP所具有的显著特点,自从0.6版本以来就被移植到多种处理器上,包括MSP430、AVR和Z80等。笔者使用的uIP0.9是2003年11月发布的版本。目前,笔者已将它成功移植到MCS-51 上了。

2 uIP0.9的体系结构

  uIP0.9是一个适用于8/16位机上的小型嵌入式TCP/IP协议栈,简单易用,资源占用少是它的设计特点。它去掉了许多全功能协议栈中不常用的功能,而保留网络通信所必要的协议机制。 其设计重点放在IP、ICMP和TCP协议的实现上,将这三个模块合为一个有机的整体,而将UDP和ARP协议实现作为可选模块。uIP0.9的体系结构如图1所示。
         
  uIP0.9处于网络通信的中间层,其上层协议在这里被称之为应用程序,而下层硬件或固件被称之为网络设备驱动。显然,uIP0.9并不是仅仅针对以太网设计的,它具有媒体无关性。

为了节省资源占用, 简化应用接口, uIP0.9在内部实现上作了特殊的处理。
① 注意各模块的融合,减少处理函数的个数和调用次数,提高代码复用率,以减少ROM占用。
② 基于单一全局数组的收发数据缓冲区,不支持内存动态分配, 由应用负责处理收发的数据。
③ 基于事件驱动的应用程序接口,各并发连接采用轮循处理,仅当网络事件发生时,由uIP内核唤起应用程序处理。这样,uIP用户只须关注特定应用就可以了。传统的TCP/IP实现一般要基于多任务处理环境,而大多数8位机系统不具备这个条件。
④ 应用程序主动参与部分协议栈功能的实现(如TCP的重发机制,数据包分段和流量控制),由uIP内核设置重发事件,应用程序重新生成数据提交发送,免去了大量内部缓存的占用。基于事件驱动的应用接口使得这些实现较为简单。

3 uIP的设备驱动程序接口

  uIP内核中有两个函数直接需要底层设备驱动程序的支持。

  一是uip_input()。当设备驱动程序从网络层收到一个数据包时要调用这个函数,设备驱动程序必须事先将数据包存放到uip_buf[]中,包长放到uip_len,然后交由uip_input()处理。当函数返回时,如果uip_len不为0,则表明有带外数据(如SYN,ACK等)要发送。当需要ARP支持时,还需要考虑更新ARP表或发出ARP请求和回应,示例如下。

#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
uip_len = ethernet_devicedriver_poll(); //接收以太网数据包
//(设备驱动程序)
if(uip_len>0){ //收到数据
if(BUF->type = = HTONS(UIP_ETHTYPE_IP)) { //是IP包吗?
uip_arp_ipin(); //去除以太网头结
//构,更新ARP表
uip_input(); //IP包处理
if(uip_len>0){ //有带外回应数据
uip_arp_out(); //加以太网头结构,在主动连接时可能要
//构造ARP请求
ethernet_devicedriver_send(); //发送数据到以太网
//(设备驱动程序)
}
}else if (BUF->type = = HTONS(UIP_ETHTYPE_ARP)) {
//是ARP请求包
uip_arp_arpin(); //如是是ARP回应,更新ARP表;如果是
//请求,构造回应数据包
if(uip_len>0) { //是ARP请求,要发送回应
ethernet_devicedriver_send(); //发ARP回应到以太网上
}
}
另一个需要驱动程序支持的函数是uip_periodic(conn)。这个函数用于uIP内核对各连接的定时轮循,因此需要一个硬件支持的定时程序周期性地用它轮循各连接,一般用于检查主机是否有数据要发送,如有,则构造IP包。使用示例如下。
for(i=0 ; iuip_periodic(i);
if(uip_len > 0){
uip_arp_out();
ethernet_devicedriver_send();
}
}

  从本质上来说, uip_input()和uip_periodic()在内部是一个函数,即uip_process (u8t flag), UIP的设计者将uip_process(UIP_DATA)定义成uip_input(),而将uip_process(UIP_TIMER)定义成uip_periodic(),因此从代码实现上来说是完全复用的。

4 uIP的应用程序接口

  为了将用户的应用程序挂接到uIP中,必须将宏UIP_APPCALL()定义成实际的应用程序函数名, 这样每当某个uIP事件发生时,内核就会调用该应用程序进行处理。如果要加入应用程序状态的话,必须将宏UIP_APPSTATE_SIZE定义成应用程序状态结构体的长度。在应用程序函数中,依靠uIP事件检测函数来决定处理的方法,另外可以通过判断当前连接的端口号来区分处理不同的连接。下面的示例程序是笔者实现的一个Web服务器应用的框架。

#define UIP_APPCALL uip51_appcall
#define UIP_APPSTATE_SIZE sizeof(struct uip51app_state)
struct uip51app_state{
unsigned char *dataptr;
unsigned int dataleft;
};

void uip51_initapp{ //设置主机地址
u16_t ipaddr[2];
uip_ipaddr(ipaddr, 202 ,120,127,192 );
uip_sethostaddr(ipaddr);
uip_listen(HTTP_PORT); //HTTP WEB PORT(80);
}

void uip51_appcall(void){
struct uip51app_state *s;
s = (struct uip51app_state *)uip_conn->appstate;
//获取当前连接状态指针
if(uip_connected()) {
… //有一个客户机连上
}
if(uip_newdata()||uip_rexmit()) { //收到新数据或需要重发
if(uip_datalen()>0){
if(uip_conn->lport = = 80) { //收到GET HTTP请求
update_table_data(); //根据电平状态数据表动态
//生成网页
s->dataptr=newpage;
s->dataleft=2653;
uip_send(s->dataptr,s->dataleft);
//发送长度为2653 B的网页
}
}
}
if(uip_acked()) { //收到客户机的ACK
if(s->dataleft>uip_mss()&&uip_conn->lport = = 80){
//发送长度>最大段长时
s->dataptr+=uip_conn->len; //继续发送剩下的数据
s->dataleft-=uip_conn->len;
uip_send(s->dataptr,s->dataleft);
}
return;
}
if(uip_poll())
{ … //将串口缓存的数据复制到
//电平状态数据表
return;
}
if(uip_timedout()|| //重发确认超时
uip_closed()|| //客户机关闭了连接
uip_aborted()){ //客户机中断连接
return; }
}

5 uIP0.9在发电机远程监测系统中的应用

  笔者设计了一个嵌入式Web模块UIPWEB51,用于将发电机射频监测仪串口输出的数据上网,以实现对发电机工作状态的远程监测,目前已取得初步成功。该模块的硬件框图如图2所示。
       
  单片机采用的是Atmel的AT89C55WD,它内置20KB 程序Flash,512字节RAM,3个定时器/计数器,工作在22.1184MHz时具有约2MIPS的处理速度。 网卡芯片同样采用的是低成本的RTL8019AS, 是一款NE2000兼容的网卡芯片。系统外扩了32KB的SRAM,用于串口数据和网络数据的缓冲,另外还存放了uIP的许多全局变量。

  UIPWEB51的主程序采用中断加轮循的方式,用中断触发的方式接收发电机射频监测仪发出的数据,并设置了一个接收队列暂存这些数据。在程序中轮循有无网络数据包输入,如有则调用uIP的相关处理函数(如上uip_input()使用示例);如无则检测定时轮循中断是否发生。这里将T2设为uIP的定时轮循计数器, 在T2中断中设置轮循标志,一旦主程序检测到这一标志就调用uip_periodic()轮循各连接(如上uip_periodic()使用示例)。

  UIPWeb51的应用程序(如uIP的应用程序接口示例),这个Web服务器首先打开80端口的监听,一旦有客户机要求连上,uIP内部会给它分配一个连接项, 接着等收到客户机IE浏览器发出的“GET HTTP…”请求后, 将发电机电平与状态数据队列中的数据填入网页模板,生成一幅新的网页发给客户机。因为这幅网页的大小已经超过uIP的最大段长(MSS), 因此在uIP内核第一次实际只发出了MSS个字节, 在等到下一次轮循到该连接并且收到上次数据包的ACK时,发送剩下的网页数据。在连接处于空闲的时候(uip_poll()),应用程序可以从串口队列中读出原始数据,经格式处理后再存到发电机电平与状态数据队列中,而在这个队列中保存着当前1min的设备工作数据,以便下次更新网页时使用。在网页中添加了更新按钮,一旦浏览器用户点击了按钮, 浏览器会自动发出CGI请求, UIPWEB51收到后,立即发送包含最新数据的网页。如果uIP接收ACK超时,它会自动设置重发标志,应用程序中可以用uip_rexmit()来检测这个标志,重新生成网页并发送。一旦用户关闭了浏览器,uIP也会自动检测到这一事件(应用程序中可以用uip_closed()来检测),并且释放掉这个连接项。
图3是UIPWEB51的总体程序结构图。
        
6 测试结果

  将uIP0.9配置成允许4个并发连接,1个监听端口, 10个ARP表项,去掉UDP支持,UIP_BUFSIZE=1500和其它优化选项。用KEIL C编译,整个uIP0.9内核模块代码量小于8KB(含Web应用程序),内核对RAM的占用小于2KB(不含网页)。整个系统程序的代码量小于12KB,占用的RAM小于10KB。另外,在公网上测试了该模块的传输速度,大于20Kbps,对于此项应用已达到要求。目前,该模块正准备应用于新一代的发电机射频监测系统中。

                 参考文献
1 RTL8019AS Realtek Full-Duplex Ethernet Controller with Plug and Play Function Specification, 2002
2 ATMEL AT89C55WD datasheet, 2001
3 Adam Dunkels . uIP 0.9 reference manual, 2003
4 Adam Dunkels. uIP - A free Small TCP/IP Stack, 2002
5 Douglas E.Comer. 用TCP/IP进行网际互联(卷1). 林瑶等译. 北京:电子工业出版社, 2001
6 王罡,林立志编著. 基于Windows的TCP/IP编程. 北京: 清华大学出版社, 2000

- 作者: 咸零蛋 2005年11月5日, 星期六 13:25  回复(0) |  引用(0) 加入博采

源码公开的TCP/IP协议栈在远程监测中的应用


作 者:■ 上海大学 张懿慧 陈泉林


摘 要:介绍一个适用于8/16位单片机的嵌入式TCP/IP协议栈(uIP)在发电机远程监测系统中的应用。重点阐述uIP的功能特性、体系结构和相关接口,并详细介绍如何在该协议栈上实现一个嵌入式Web服务器。目前uIP已成功地移植到51单片机上。

关键词:TCP/IP协议栈 uIP 嵌入式Web服务器 远程监测


引 言

  目前,随着互联网的发展,越来越多的工业测控设备已经将网络接入功能作为其默认配置,以实现设备的远程监控和信息分布式处理。笔者曾参与某发电机射频监测仪的开发,该设备主要用于诊断和预警发电机早期故障,并通过RS232接口定时输出电平和状态数据,现场专门设一台PC作接收、显示及存储。每年都要有专家到各发电厂对以往数据作检查和诊断,不胜其烦。因此有必要设计一个RS232到Internet的数据传输模块,以便对发电机的运行状况作远程监测。设计该模块的关键在于如何实现一个嵌入式TCP/IP协议栈,根据以往的经验,自己设计一个协议栈的难度很可能超过应用本身,而采用商业的协议栈似乎又无必要(功能过于复杂),最后笔者选用一种功能简易的免费TCP/IP协议栈uIP 0.9作为设计核心。

1 嵌入式TCP/IP协议栈

  目前,市面上几乎所有的嵌入式TCP/IP协议栈都是根据BSD版的TCP/IP协议栈改写的。在商业嵌入式TCP/IP协议栈大都相当昂贵的情况下,很多人转而使用一些源代码公开的免费协议栈,并加以改造应用。目前较为著名的免费协议栈有:

  lwIP(Light weight TCP/IP Stack)——支持的协议比较完整,一般需要多任务环境支持,代码占用ROM>40KB,不适合8位机系统,没有完整的应用文档;

  uC/IP (TCP/IP stack for uC/OS)——基于uC/OS的任务管理,接口较复杂,没有说明文档。

  笔者采用的协议栈系瑞典计算机科学研究所Adam Dunkels开发的uIP0.9 。其功能特性总结如下:
◇完整的说明文档和公开的源代码(全部用C语言编写,并附有详细注释);
◇极少的代码占用量和RAM资源要求,尤其适用于8/16位单片机(见表1);
◇高度可配置性,以适应不同资源条件和应用场合;
◇支持ARP、IP、ICMP、TCP、UDP(可选)等必要的功能特性;
◇支持多个主动连接和被动连接并发,支持连接的动态分配和释放;
◇简易的应用层接口和设备驱动层接口;
◇完善的示例程序和应用协议实现范例。
        
  正是由于uIP所具有的显著特点,自从0.6版本以来就被移植到多种处理器上,包括MSP430、AVR和Z80等。笔者使用的uIP0.9是2003年11月发布的版本。目前,笔者已将它成功移植到MCS-51 上了。

2 uIP0.的体系结构

  uIP0.9是一个适用于8/16位机上的小型嵌入式TCP/IP协议栈,简单易用,资源占用少是它的设计特点。它去掉了许多全功能协议栈中不常用的功能,而保留网络通信所必要的协议机制。 其设计重点放在IP、ICMP和TCP协议的实现上,将这三个模块合为一个有机的整体,而将UDP和ARP协议实现作为可选模块。uIP0.9的体系结构如图1所示。
         
  uIP0.9处于网络通信的中间层,其上层协议在这里被称之为应用程序,而下层硬件或固件被称之为网络设备驱动。显然,uIP0.9并不是仅仅针对以太网设计的,它具有媒体无关性。

为了节省资源占用, 简化应用接口, uIP0.9在内部实现上作了特殊的处理。
① 注意各模块的融合,减少处理函数的个数和调用次数,提高代码复用率,以减少ROM占用。
② 基于单一全局数组的收发数据缓冲区,不支持内存动态分配, 由应用负责处理收发的数据。
③ 基于事件驱动的应用程序接口,各并发连接采用轮循处理,仅当网络事件发生时,由uIP内核唤起应用程序处理。这样,uIP用户只须关注特定应用就可以了。传统的TCP/IP实现一般要基于多任务处理环境,而大多数8位机系统不具备这个条件。
④ 应用程序主动参与部分协议栈功能的实现(如TCP的重发机制,数据包分段和流量控制),由uIP内核设置重发事件,应用程序重新生成数据提交发送,免去了大量内部缓存的占用。基于事件驱动的应用接口使得这些实现较为简单。

3 uIP的设备驱动程序接口

  uIP内核中有两个函数直接需要底层设备驱动程序的支持。

  一是uip_input()。当设备驱动程序从网络层收到一个数据包时要调用这个函数,设备驱动程序必须事先将数据包存放到uip_buf[]中,包长放到uip_len,然后交由uip_input()处理。当函数返回时,如果uip_len不为0,则表明有带外数据(如SYN,ACK等)要发送。当需要ARP支持时,还需要考虑更新ARP表或发出ARP请求和回应,示例如下。

#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
uip_len = ethernet_devicedriver_poll(); //接收以太网数据包
//(设备驱动程序)
if(uip_len>0){ //收到数据
if(BUF->type = = HTONS(UIP_ETHTYPE_IP)) { //是IP包吗?
uip_arp_ipin(); //去除以太网头结
//构,更新ARP表
uip_input(); //IP包处理
if(uip_len>0){ //有带外回应数据
uip_arp_out(); //加以太网头结构,在主动连接时可能要
//构造ARP请求
ethernet_devicedriver_send(); //发送数据到以太网
//(设备驱动程序)
}
}else if (BUF->type = = HTONS(UIP_ETHTYPE_ARP)) {
//是ARP请求包
uip_arp_arpin(); //如是是ARP回应,更新ARP表;如果是
//请求,构造回应数据包
if(uip_len>0) { //是ARP请求,要发送回应
ethernet_devicedriver_send(); //发ARP回应到以太网上
}
}
另一个需要驱动程序支持的函数是uip_periodic(conn)。这个函数用于uIP内核对各连接的定时轮循,因此需要一个硬件支持的定时程序周期性地用它轮循各连接,一般用于检查主机是否有数据要发送,如有,则构造IP包。使用示例如下。
for(i=0 ; i<UIP_CONNS; i++){
uip_periodic(i);
if(uip_len > 0){
uip_arp_out();
ethernet_devicedriver_send();
}
}

  从本质上来说, uip_input()和uip_periodic()在内部是一个函数,即uip_process (u8t flag), UIP的设计者将uip_process(UIP_DATA)定义成uip_input(),而将uip_process(UIP_TIMER)定义成uip_periodic(),因此从代码实现上来说是完全复用的。

4 uIP的应用程序接口

  为了将用户的应用程序挂接到uIP中,必须将宏UIP_APPCALL()定义成实际的应用程序函数名, 这样每当某个uIP事件发生时,内核就会调用该应用程序进行处理。如果要加入应用程序状态的话,必须将宏UIP_APPSTATE_SIZE定义成应用程序状态结构体的长度。在应用程序函数中,依靠uIP事件检测函数来决定处理的方法,另外可以通过判断当前连接的端口号来区分处理不同的连接。下面的示例程序是笔者实现的一个Web服务器应用的框架。

#define UIP_APPCALL uip51_appcall
#define UIP_APPSTATE_SIZE sizeof(struct uip51app_state)
struct uip51app_state{
unsigned char *dataptr;
unsigned int dataleft;
};

void uip51_initapp{ //设置主机地址
u16_t ipaddr[2];
uip_ipaddr(ipaddr, 202 ,120,127,192 );
uip_sethostaddr(ipaddr);
uip_listen(HTTP_PORT); //HTTP WEB PORT(80);
}

void uip51_appcall(void){
struct uip51app_state *s;
s = (struct uip51app_state *)uip_conn->appstate;
//获取当前连接状态指针
if(uip_connected()) {
… //有一个客户机连上
}
if(uip_newdata()||uip_rexmit()) { //收到新数据或需要重发
if(uip_datalen()>0){
if(uip_conn->lport = = 80) { //收到GET HTTP请求
update_table_data(); //根据电平状态数据表动态
//生成网页
s->dataptr=newpage;
s->dataleft=2653;
uip_send(s->dataptr,s->dataleft);
//发送长度为2653 B的网页
}
}
}
if(uip_acked()) { //收到客户机的ACK
if(s->dataleft>uip_mss()&&uip_conn->lport = = 80){
//发送长度>最大段长时
s->dataptr+=uip_conn->len; //继续发送剩下的数据
s->dataleft-=uip_conn->len;
uip_send(s->dataptr,s->dataleft);
}
return;
}
if(uip_poll())
{ … //将串口缓存的数据复制到
//电平状态数据表
return;
}
if(uip_timedout()|| //重发确认超时
uip_closed()|| //客户机关闭了连接
uip_aborted()){ //客户机中断连接
return; }
}

5 uIP0.9在发电机远程监测系统中的应用

  笔者设计了一个嵌入式Web模块UIPWEB51,用于将发电机射频监测仪串口输出的数据上网,以实现对发电机工作状态的远程监测,目前已取得初步成功。该模块的硬件框图如图2所示。
       
  单片机采用的是Atmel的AT89C55WD,它内置20KB 程序Flash,512字节RAM,3个定时器/计数器,工作在22.1184MHz时具有约2MIPS的处理速度。 网卡芯片同样采用的是低成本的RTL8019AS, 是一款NE2000兼容的网卡芯片。系统外扩了32KB的SRAM,用于串口数据和网络数据的缓冲,另外还存放了uIP的许多全局变量。

  UIPWEB51的主程序采用中断加轮循的方式,用中断触发的方式接收发电机射频监测仪发出的数据,并设置了一个接收队列暂存这些数据。在程序中轮循有无网络数据包输入,如有则调用uIP的相关处理函数(如上uip_input()使用示例);如无则检测定时轮循中断是否发生。这里将T2设为uIP的定时轮循计数器, 在T2中断中设置轮循标志,一旦主程序检测到这一标志就调用uip_periodic()轮循各连接(如上uip_periodic()使用示例)。

  UIPWeb51的应用程序(如uIP的应用程序接口示例),这个Web服务器首先打开80端口的监听,一旦有客户机要求连上,uIP内部会给它分配一个连接项, 接着等收到客户机IE浏览器发出的“GET HTTP…”请求后, 将发电机电平与状态数据队列中的数据填入网页模板,生成一幅新的网页发给客户机。因为这幅网页的大小已经超过uIP的最大段长(MSS), 因此在uIP内核第一次实际只发出了MSS个字节, 在等到下一次轮循到该连接并且收到上次数据包的ACK时,发送剩下的网页数据。在连接处于空闲的时候(uip_poll()),应用程序可以从串口队列中读出原始数据,经格式处理后再存到发电机电平与状态数据队列中,而在这个队列中保存着当前1min的设备工作数据,以便下次更新网页时使用。在网页中添加了更新按钮,一旦浏览器用户点击了按钮, 浏览器会自动发出CGI请求, UIPWEB51收到后,立即发送包含最新数据的网页。如果uIP接收ACK超时,它会自动设置重发标志,应用程序中可以用uip_rexmit()来检测这个标志,重新生成网页并发送。一旦用户关闭了浏览器,uIP也会自动检测到这一事件(应用程序中可以用uip_closed()来检测),并且释放掉这个连接项。
图3是UIPWEB51的总体程序结构图。
        
6 测试结果

  将uIP0.9配置成允许4个并发连接,1个监听端口, 10个ARP表项,去掉UDP支持,UIP_BUFSIZE=1500和其它优化选项。用KEIL C编译,整个uIP0.9内核模块代码量小于8KB(含Web应用程序),内核对RAM的占用小于2KB(不含网页)。整个系统程序的代码量小于12KB,占用的RAM小于10KB。另外,在公网上测试了该模块的传输速度,大于20Kbps,对于此项应用已达到要求。目前,该模块正准备应用于新一代的发电机射频监测系统中。

                 参考文献
1 RTL8019AS Realtek Full-Duplex Ethernet Controller with Plug and Play Function Specification, 2002
2 ATMEL AT89C55WD datasheet, 2001
3 Adam Dunkels . uIP 0.9 reference manual, 2003
4 Adam Dunkels. uIP - A free Small TCP/IP Stack, 2002
5 Douglas E.Comer. 用TCP/IP进行网际互联(卷1). 林瑶等译. 北京:电子工业出版社, 2001
6 王罡,林立志编著. 基于Windows的TCP/IP编程. 北京: 清华大学出版, 2000

- 作者: 咸零蛋 2005年11月5日, 星期六 13:25  回复(0) |  引用(0) 加入博采