第4章 MATLAB 7.0基本编程
MATLAB作为一种广泛应用于科学计算的工具软件,不仅具有强大的数值计算、符号计算、矩阵运算的能力和丰富的画图功能,还可以像C语言、FORTRAN等计算机高级语言一样进行程序设计,编写扩展名为.m的M文件,实现各种复杂的运算,这使得MATLAB在科研中的应用更加深入,常常作为系统仿真的工具应用。MATLAB提供文件编辑器和编译器,这为用户带来了方便,事实上,MATLAB自带的许多函数就是M文件函数,用户也可利用M文件来生成和扩充自己的函数库。
所谓M文件,简单来说就是用户把要实现的命令写在一个以.m作为扩展名的文件中,然后由MATLAB系统进行解释,最后运行出结果。由此可见MATLAB具有强大的可开发性和可扩展性。另外,由于MATLAB是由C语言开发而成的,因此M文件的语法规则与C语言几乎一样,简单易学。
本章将讨论MATLAB中编程的规则和注意事项,并给出很多典型的例程帮助读者尽快熟悉。
4.1 脚本和函数
M文件有函数(Functions)和脚本(Scripts)两种格式。二者相同之处在于它们都是以m作为扩展名的文本文件,不进入命令窗口,而是由文本编辑器来创建外部文本文件。但是两者在语法和使用上略有区别。
4.1.1 函数
MATLAB中许多常用的函数(如sqrt、inv和abs等)都是函数式M文件,使用时,MATLAB获取传递给它的变量,利用操作系统所给的输入,运算得到要求的结果,然后返回这些结果。函数文件类似于一个黑箱,由函数执行的命令以及这些命令所创建的中间变量都是隐含的。运算过程中的中间变量都是局部变量(除特别声明外),存放在函数本身的工作空间内,不会和MATLAB基本工作空间(Base workspace)的变量相互覆盖,对用户来说,可见的只是输入和输出,因此易于使程序模块化,特别适合于大型程序代码。
下面通过一个函数的例子来说明其结构。
此函数的第一行为函数定义行,以function语句作为引导,定义了函数名称(function1,需要注意的是函数名和文件名必须相同)、输入自变量(value,调用此函数时指定此变量的值,类似于C语言的形式参数)和输出自变量(average,函数执行完毕返回的结果)。function为关键词,说明此M文件为函数。第二行则为函数主体,规范函数的运算过程,并指出输出自变量的值。若调用此函数,可输入以下命令:
此外,还可在函数定义行下加入注解,以%开头,即函数的在线帮助,若在MATLAB输入“help函数主文件名”,即可看到这些帮助,需要注意的是,在线帮助和M函数定义行之间可以有空行,但是在线帮助的各行之间不应有空行。示例代码如下:
如果函数中有注释,代码设置如下:
则在线帮助只有第一行:
以上的例子只是为了说明函数的性质和用法,有关编程的方法、具体问题以及典型例子会在后面的章节介绍。
4.1.2 脚本
脚本是一个扩展名为.m的文件,其中包含了MATLAB的各种命令,与批处理文件很类似,在MATLAB命令窗口下直接输入此文件的主文件名,MATLAB可逐一执行在此文件内的所有命令,和在命令窗口逐行输入这些命令一样。脚本式M文件运行产生的所有变量都是全局变量,运行脚本后,所产生的所有变量都驻留在MATLAB基本工作空间内,只要用户不使用clear命令加以清除,且MATLAB指令窗口不关闭,这些变量将一直保存。基本空间随MATLAB的启动而产生,在关闭MATLAB软件时该基本空间被删除。
例如,假设当前目录下有一个脚本M文件,可用type命令显示其内容如下:
在上面的示例代码中以%开头的行是注释,在命令窗口执行solver命令,即可得到方程组的解,具体代码如下:
结合上例,下面对M文件必须遵循的规则及两种类型的异同做简要介绍。
(1)从函数名必须与文件名相同。
(2)脚本式M文件没有输入参数或输出参数,而函数式M文件有输入参数和输出参数。
(3)函数可以有零个或多个输入和输出变量。函数nargin和nargout包含输入和输出变量的个数。在运行时,可以按少于M文件中规定的输入和输出变量的个数进行函数调用,但不能多于这个标称值。
从运行上看,与脚本文件不同的是,函数文件被调用时,MATLAB会专门为它开辟一个临时工作空间,称为函数工作空间(Function workspace),用来存放中间变量,当执行完函数文件的最后一条命令或者遇到return时就结束该函数文件的运行,同时该临时函数空间及其所有的中间变量将被清除。函数工作空间相对于基本空间是临时的、独立的,在MATLAB运行期间,可以产生任意多个临时函数空间。
(4)在M文件中,包括脚本和函数,到第一个非注释行为止的注释行是帮助文本,当需要帮助时,返回该文本,通常用来说明文件的功能和用法。
(5)函数M文件中的所有变量除特殊声明外都是局部变量,而脚本中的变量都是全局变量。
(6)变量的命名可以包括字母、数字和下划线,但必须是以字母开头。并且在M文件设计中是区分大小写的。变量的长度不能超过系统函数namelengthmax所规定的值。
(7)假设在函数文件中发生对某脚本文件的调用,那么该脚本文件运行产生的所有变量都存放于此函数空间中,而不是存在基本工作空间中。
通常M文件是文本文件,所以可使用一般的文本编辑器编辑M文件,存储时以文本模式存储。此外,MATLAB内部自带了M文件编辑器与编译器,可选择MATLAB命令窗口上的file/new子菜单,然后选择下一级子菜单中的M-file子菜单,就进入了M文件编辑/编译器,如图4-1所示。它是一个集编辑与调试两种功能于一体的工具环境。进行代码编辑时,它可以用不同的颜色来显示注解、关键词、字符串和一般程序代码,使用非常方便。在书写完M文件后,也可以像一般的程序设计语言一样,对M文件进行调试、运行。
图4-1 M文件编辑器和编译器
4.1.3 子函数与私有目录
一个M文件可以包含一个以上的函数,其中有一个主函数,其他为子函数。这些子函数只能被同一文件中的函数(主函数或其他子函数)调用,但是不能被其他文件的函数调用。在一个M文件中,主函数必须出现在最上方,其后可接上任意数目的子函数,而且子函数的次序可随意。同一文件的主函数、子函数的工作空间都是彼此独立的,各函数间的信息可通过输入输出宗量、全局变量或跨空间指令传递。
此外,可以在某一目录中建立一个自己命名的私有目录来存放相关的函数,例如在work目录下建立一个rscode目录,则work中的M文件(无论是脚本还是函数)即可调用rscode下的任何函数,而不必再定义其他搜寻路径。在rscode下的函数,只能被其父目录的函数所调用而不能被其他目录下的函数调用。
当M文件中需要调用某一个函数时,MATLAB是按照以下顺序来搜寻的:
· 检查此函数是否是子函数;
· 检查此函数是否为私有目录的函数;
· 从所设定的搜寻路径搜索此函数。
搜索过程中,只要找到与第一个文件名相符的函数就会立即取用而停止搜索。
4.1.4 P码文件
P码是伪代码Psedocode的简写,一个M文件首次被调用时,MATLAB将首先对该M文件进行语法分析,并把生成的相应内部伪代码(P码)存放在内存中。此后当再次调用该M文件时,将直接运行该文件在内存中的P码文件而不会对原码文件重复进行语法分析。P码文件和原码文件具有相同的文件名,但其扩展名为“.p”。并且其运行速度要高于原码文件,但是对于规模不大的文件,用户一般察觉不到这种速度上的优势。
尤其在MATLAB环境中,假如存在同名的P码和原码文件,则当该文件名被调用时,被执行的肯定是P码文件。
P码文件不是只有当M文件被调用时才可产生,也可被预先生成。这种功能可以用作代码保护的手段,生成P码文件后,其他用户可以使用该代码,但是无法看到代码的内容。具体操作如下:
如果要在内存中对P码文件进行操作,可键入以下命令:
4.2 MATLAB中的变量和语句
MATLAB能识别一般常用的加、减、乘、除和幂等运算,对于简单的计算可以在命令窗口中输入表达式后,按Enter键即可完成。MATLAB会将运算结果存入默认变量ans中,并显示其结果(如果表达式后加上分号“;”,则不会显示),当然用户也可设定自己的变量。与C语言不同的是,MATLAB中的变量是不需要事先定义的。
MATLAB的主要功能虽然是数值运算,但是它也是一个完整的程序语言,有各种语句格式和语法规则。下面就进行详细介绍。
4.2.1 变量类型
与C语言的区别是,在用M语言编写程序的过程中,变量不需要事先定义。但MATLAB中的变量也有自己的命名规则,即必须以字母开头,之后可以是任意字母、数字或下划线,不能有空格;变量名区分大小写;在MATLAB 7.0中,变量名不能超过63个字符,第63个字符之后的部分将被忽略。
除了上述命名规则外,MATLAB还包括一些特殊的变量,如表4-1所示。
表4-1 MATLAB中的特殊变量
用户定义的变量有局部变量和全局变量两种类型。每一个函数在运行时,均占用单独的一块内存,此工作空间独立于MATLAB的基本工作空间和其他函数的工作空间,因此不同工作空间的变量完全独立不会相互影响,这些变量称为局部变量。有时为了减少变量的传递,可使用全局变量,它是通过global指令定义的,格式为:
通过上述指令,可以使MATLAB允许几个不同的函数空间以及基本工作空间共享同一个变量。每个希望共享全局变量的函数或MATLAB基本工作空间必须逐个对具体变量加以专门定义,没有采用global定义的函数或基本空间将无权使用全局变量。
如果某个函数的运行使得全局变量发生了变化,则其他函数空间及基本工作空间内的同名变量随之变化。只要与全局变量相联系的工作空间有一个存在,则全局变量存在。
在使用全局变量中需要注意以下几个方面。
· 在使用之前必须首先定义,建议将定义放在函数体的首行位置。
· 虽然对全局变量的名称并没有特别的限制,但是为了提高程序的可读性,建议采用大写字符命名全局变量。
· 全局变量会损坏函数的独立性,使程序的书写和维护变得困难,尤其是在大型程序中,不利于模块化,这里不推荐使用。
例如,在函数sum2(y)中声明了一个全局变量,内容如下:
要测试此函数,可在命令窗口进行如下操作:
除命名规则外,变量命名时还需要注意以下两个方面:
(1)不要把函数名用作变量,否则在没有从内存中清除该变量的情况下将不能调用该函数,要测试变量名是否已用作函数名,可键入which-all<name>进行确认;
(2)MATLAB预留了一些关键字并且不允许重载它们,定义变量时要避开这些关键字,(用iskeyword可列出所有的预留关键字)否则系统会显示类似于缺少操作数之类的错误信息。
4.2.2 M文件的流控制语句
一般来讲,决定程序结构的语句可分为顺序语句、循环语句和分支语句3种,每种语句有各自的流控制机制,相互配合使用可以实现功能强大的程序。
1.顺序语句
顺序语句就是依次顺序执行程序的各条语句,批处理文件就是典型的顺序语句的文件,这种语句不需要任何特殊的流控制。示例代码如下:
2.循环语句
循环语句一般用于有规律的重复计算。被重复执行的语句称为循环体,控制循环语句走向的语句称为循环条件。MATLAB中有for循环和while循环两种语句。
(1)for循环
for循环的语法结构如下:
可以看出,这种语句与其他语言中的for循环结构是相同的,循环体的执行次数是确定的,它是由数组的列数决定。此外,for循环是可以多重嵌套的,请看下面的示例代码:
注意:不能在for循环体内重新对循环变量赋值来终止循环的执行,有专门的命令break可以完成这一功能,后面会具体介绍。为得到高效代码,应尽可能提高代码的向量化程度,采用矩阵运算,而避免使用循环结构,如果使用的话,在循环指令之前尽量对数组进行预定义。
(2)while循环
while循环的语法结构如下:
while循环的次数是不固定的,只要表达式的值为真,循环体就会被执行。通常表达式给出的是一个标量值,但也可以是数组或者矩阵,如果是后者,则要求所有的元素都必须为真。示例代码如下:
3.条件语句
在程序中如果需要根据一定条件来执行不同的操作时就需要用到条件语句了,MATLAB中有if-else-end语句和switch-case-otherwise语句两种条件语句。
(1)if-else-end语句
其语法结构如下:
也可有更简化的结构:
有多个条件式的复杂结构:
如果在条件式中使用矩阵,则必须矩阵元素都不为0时,条件式才算成立。
下面是一个简单的分支语句的例程,代码设置如下:
(2)switch-case-otherwise语句
此语句与C语言中的选择语句是相同功能的,它通常用于条件较多而且较单一的情况,类似于一个数控的多路开关。其语法结构如下:
expression是一个标量或者字符串,将expression的值依次和各个case指令后面的检测值进行比较,当比较结果为真时,MATLAB执行后面的一组命令,然后跳出该switch结构。如果所有的比较结果都为假,则执行otherwise后的命令。当然otherwise指令也可以不存在。
下面是MATLAB工具箱中的itemsmsg函数的示例,其中运用了switch结构,代码设置如下:
4.其他流控制语句
在许多程序设计中会碰到需要提前终止循环、跳出子程序、显示出错信息等情况,因此还需要其他的流控制语句来实现这些功能。主要有continue、break、return、echo、error、try…catch等。下面对各语句分别进行介绍。
(1)continue
此命令的作用是结束本次循环,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判断。以下是MATLAB自带的M文件magic.m中运用continue的示例,代码设置如下:
(2)break
此命令的作用是终止本次循环,跳出最内层循环,即不必等到循环的结束而是根据条件退出循环,它的用法和continue类似,常常和if语句合用来强制终止循环。示例代码如下:
需要注意的是,当break命令碰到空行时,将退出while循环。
(3)return
此命令可使正在运行的函数正常退出,并返回调用它的函数继续运行,经常用于函数的末尾以正常结束函数的运行,当然也可用在某条件满足时强行结束执行该函数。例如在showopcevents.m函数中,就运用了这一控制命令。
(4)echo
通常执行M文件时,在命令窗口是看不到执行过程的,但在特殊情况下比如需要作演示,要求M文件的每条命令都要显示出来,可以用echo命令实现这样的操作。
对于脚本式M文件和函数式M文件,echo命令有所不同,对于脚本式M文件,echo命令可以用以下方式来实现:
对于函数式M文件,echo命令可以用以下方式来实现:
(5)error
此指令是用来指示出错信息并终止当前函数的运行。语法格式如下:
类似的还有warning指令,二者区别在于warning指示警告信息后程序仍继续运行。
(6)try…catch
其功能与error类似,用于对异常情况进行处理,其语法结构如下:
组命令1总会被执行,当执行出现错误时,catch控制块就可捕获它,执行组命令2,针对不同的错误类型进行不同的处理。可调用lasterr函数查询出错原因。示例代码如下:
运行结果如下:
注意try和catch控制块中的语句之间用逗号隔开。
(7)input
此命令用来提示用户从键盘输入数据、字符串或表达式,并接收输入值。语法格式如下:
下面是一个等待用户确认显示的M文件的示例,代码设置如下:
用户运行这个文件时,命令窗口将出现以下信息:
(8)keyboard
此命令被放置在M文件中,将停止文件的执行并将控制权交给键盘。通过在提示符前显示K来表示一种特殊状态。在M文件中使用该命令,对程序的调试和在程序运行中修改变量都很方便。
如果在上例中某个位置加入keyboard命令,则执行到这句话时,MATLAB的命令窗口将显示如下代码:
(9)pause
此命令用于暂时中止程序的运行,等待用户按任意键继续进行。该命令在程序的调试过程和用户需要查询中间结果时使用很方便。该命令的语法格式如下:
4.3 程序的调试(Debug)
对于编程者来说,程序运行时出现bug在所难免,尤其是在大规模、多人共同参与的情况下,因此掌握程序调试的方法和技巧对提高工作效率很重要。一般来说,错误可分为两种,即语法错误(Syntax Errors)和逻辑错误(Logic Errors)。语法错误一般是指变量名与函数名的误写、标点符号的缺漏和end的漏写等,对于这类错误,MATLAB在运行或P码编译时一般都能发现,终止执行并报错,用户很容易发现并改正。而逻辑错误可能是程序本身的算法问题,也可能是用户对MATLAB的指令使用不当,导致最终获得的结果与预期值偏离,这种错误发生在运行过程中,影响因素比较多,而这时函数的工作空间已被删除,调试起来比较困难。
下面针对上述的两种错误推荐两种调试方法,即直接调试法和工具调试法。
4.3.1 直接调试法
MATLAB本身的运算能力强,指令系统比较简单,因此程序一般都显得比较简洁,对于简单的程序采用直接调试法往往还是很有效的。通常采取的措施如下。
(1)通过分析后,将重点怀疑语句后的分号删掉,将结果显示出来,然后与预期值进行比较。
(2)单独调试一个函数时,将第一行的函数声明注释掉,并定义输入变量的值,然后以脚本方式执行此M文件,这样就可保存下原来的中间变量了,可以对这些结果进行分析,找出错误。
(3)可以在适当的位置添加输出变量值的语句。
(4)在程序中的适当位置添加keyboard指令。当MATLAB执行至此处时将暂停,并显示k>>提示符,用户可以查看或改变各个工作空间中存放的变量,在提示符后键入return指令可以继续执行原文件。
但是对于文件规模大,相互调用关系复杂的程序,直接调试是很困难的,这时可以借助于MATLAB的专门工具调试器(Debugger)进行,即工具调试法。
4.3.2 工具调试法
MATLAB自身包括调试程序的工具,利用这些工具可以提高编程的效率,包括一些命令行形式的调试函数和图形界面形式的菜单命令。实际工作中,可以根据个人需要进行操作。本节主要介绍一些基本方法,在这些方法的基础上还需要读者不断实践,总结经验,才能做到熟练运用,高效编程。
1.以命令行为主的程序调试
以命令行为主的程序调试手段具有通用性,可以适用于各种不同的平台,它主要是应用MATLAB提供的调试命令。在命令窗口输入help debug可以看到一个对于这些命令的简单描述,下面分别进行介绍。
(1)设置断点
这是其中一个最重要的部分,可以利用它来指定程序代码的断点,使得MATLAB可在断点前停止执行,从而可以检查各个局部变量的值。函数格式有以下几种:
· dbstop in mfile
在文件名为mfile的M文件的第一个可执行语句前设置断点,执行该命令后,当程序运行到mfile的第一个可执行语句时,可暂时中止M文件的执行,并进入MATLAB的调试模式。M文件必须处在MATLAB搜索路径或当前目录内。如果用户已经激活了图形调试模式,则MATLAB调试器将打开该M文件,并在第一个可执行语句前设置断点。
· dbstop in mfile at lineno
在文件名为mfile的M文件的第lineno行设置断点,执行过程与上一命令类似。如果行号为lineno的语句为非执行语句,则停止执行的同时,在该行号的下一个可执行语句前设置断点。M文件必须处在MATLAB搜索路径或当前目录内。此时,用户可以使用各种调试工具、查看工作空间变量、公布任何有效的MATLAB函数。
· dbstop in mfile at subfun
执行该命令后,当程序执行到子程序subfun时,暂时中止文件的执行并使MATLAB处于调试模式,其他要求和操作与上面的函数类似。
· dbstop if error
执行该命令后,可在运行M文件遇到错误时,终止M文件的执行,并使MATLAB处于调试状态,运行停止在产生错误的行。这里的错误不包括try…catch语句中检测到的错误,用户不能在错误后重新开始程序的运行。
· dbstop if all error
与上一命令类似,但是在执行该命令时遇到任何类型的运行错误时均停止,包括在try…catch语句中检测到的错误。
· dbstop if warning
执行该命令后,在运行M文件遇到警告时,终止M文件的执行,并使MATLAB处于调试状态,运行将在产生警告的行暂停,程序可以恢复运行。
· dbstop if caught error
执行该命令后,当try…catch检测到运行时间错误时,停止M文件的执行,用户可以恢复程序的运行。
· dbstop if naninf或dbstop if infnan
执行该命令后,当遇到无穷值或者非数值时,终止M文件的执行。
下面给出一个在命令窗口设置断点的示例。
运行的M文件内容如下:
在命令窗口输入以下内容:
在打开的M文件窗口中设置断点的情况如图4-2所示,在第一行设置了一个断点。
图4-2 断点图示
test1 函数中输入只能为向量,如果输入矩阵,会产生错误。因此可以根据错误进入MATLAB调试状态,在命令窗口输入如下:
此时,M文件执行到最后一行,如图4-3所示。
图4-3 文件执行情况图示
由上例可以看到,程序停止执行后,MATLAB进入调试模式,命令行上出现K>>的提示号,代表此时可以接受键盘输入。
(2)清除断点
· dbclear all
清除所有M文件中的所有断点。
· dbclear all in mfile
清除文件名为mfile的M文件中的所有断点。
· dbclear in mfile
清除mfile中第一个可执行语句前的断点。
· dbclear in mfile at lineno
清除mfile中行号为lineno的语句前的断点。
· dbclear in mfile at subfun
清除mfile中子函数subfun行前的断点。
· dbclear if error
清除由dbstop if error设置的暂停断点。
· dbclear if warning
清除由dbstop if warning设置的暂停断点。
· dbclear if naninf
清除由dbstop if naninf设置的暂停断点。
· dbclear if infnan
清除由dbstop if infnan设置的暂停断点。
(3)恢复执行
· dbcont
从断点处恢复程序的执行,直到遇到程序的另一个断点或错误后返回MATLAB基本工作空间。
(4)调用堆栈
· dbstack
此命令显示M文件名和断点产生的行号,调用此M文件的名称和行号等,直到最高级M文件函数,即列出了函数调用的堆栈。
使用此命令时,有如下格式:
通过M×1的结构体ST形式返回堆栈信息。ST的形式有以下几种。
file——函数出现的文件名,如果没有则为空;
name——文件中的函数名;
line——函数行号。
I是当前的工作空间的索引。
· dbstack(N)
此命令省略了显示中的前N个帧。
· dbstack('-completenames')
此命令输出堆栈中的每个函数的全名,即函数文件的名称和在堆栈中函数包含的关系。
(5)列出所有断点
· dbstatus
此命令列出所有的断点,包括错误、警告、nan和inf等。
s=dbstatus将通过一个M×1的结构体来返回断点信息,结构体中有以下字段。
name——函数名;
line——断点行号向量;
expression——与line中相对应的断点条件表达字符串;
cond——条件字符串,如error、caught error、warning或naninf;
identifier——当条件字符串是error、caught error或warning时,该字段是MATLAB的信息指示字符串。
· dbstatus mfile
此命令列出指定的M文件中的所有断点设置,mfile必须是M文件函数的名称或者是MATLAB有效的路径名。
(6)执行1行或多行语句
· dbstep
执行当前M文件下一个可执行语句。
· dbstep nlines
执行下nlines行可执行语句。
· dbstep in
当执行下一个可执行语句时,如果其中包含对另外一个函数的调用,此命令将从被调用的函数文件的第一个可执行语句执行。
· dbstep out
此命令将执行函数剩余的部分,在离开函数时停止。
这4种形式的语句执行完后,都返回调试模式,如果在执行过程中遇到断点,程序将中止。
(7)列出文件内容
· dbtype mfile
列出mfile文件的内容,并在每行语句前面加上标号以方便使用者设定断点。
· dbtype mfile start:end
列出mfile文件中指定行号范围的部分。
在UNIX和VMS调试模式下,并不显示MATLAB的调试器,此时必须使用dbtype来显示源程序代码。
(8)切换工组空间
· dbdown
遇到断点时,将当前工作空间切换到被调用的M文件的空间。
· dbup
将当前工作空间(断点处)切换到调用M文件的工作空间。两个命令常常配合使用。
(9)退出调试模式
· dbquit
立即结束调试器并返回到基本工作空间,所有断点仍有效。
2.以图形界面为主的程序调试
MATLAB自带的M文件编辑器同时也是程序的编译器,用户可以在编辑完程序后直接进行调试,更加方便和直观。
新建一个M文件,即可打开编译器,选择主菜单中“Debug”选项,打开下拉菜单,有各种调试命令,如图4-4所示。
图4-4 打开的MATLAB调试器图形界面
下拉菜单中的命令有一部分在工具栏中有图标相对应,其功能与上一节介绍的调试命令是相同的,下面只对各命令做简单介绍。
· Step
单步执行,快捷键为F10,与调试命令中的dbstep相对应。
· Step in
深入被调函数,快捷键为F11,与调试命令中的dbstep in相对应。
· Step out
跳出被调函数,快捷键为Shift+F11,与调试命令中的dbstep out相对应。
· run/continue
连续执行,快捷键为F5,与调试命令中的dbcont相对应。
· go until cursor
运行到鼠标所在的行,与dbstop in mfile at lineno相对应。
· set/clear breakpoint
设置或清除断点,快捷键为F12,与dbstop和dbclear相对应。
· set/modify conditional breakpoint…
设置或者修改条件断点,单击此菜单项时,会弹出如图4-5所示的对话框,要求用户对断点的条件作出设置,设置前光标在哪一行,则设置的断点就在这一行前。
图4-5 set/modify conditional breakpoint对话框
· enable/disable breakpoint
允许或者禁止断点的功用。
· clear breakpoints in all files
清除所有断点,与dbclear all相对应。
· stop if errors/warnings
与dbstop if error、dbstop if all error、dbstop if warning、dbstop if caught error、dbstop if naninf和dbstop if infnan等命令等价,单击此菜单项时,将弹出一个对话框,如图4-6所示。
图4-6 “Stop if Errors/Warnings for All Files”对话框
可以看到,对话框的每一栏都和一个调试命令相对应,用户可以在调试前根据自己的要求设定,然后运行程序。
· exit debug mode
退出调试模式,与dbquit相对应。
只有当文件进入调试状态时,上述命令才会全部处于使能态。需要注意的是,在调试器的工具栏的右侧,还有一个如图4-7所示的堆栈下拉菜单。在调试中,可以通过改变它的内容来观察和操作不同工作空间中的量,类似于调试命令中的dbdown和dbup。
图4-7 工作空间切换项
下面通过一个实例来说明一下这些菜单项的用法。
【例4.1】计算向量的标准差。
函数文件名为strd v.m,具体代码如下:
其中调用的子函数代码如下:
在命令窗口输入如下代码:
由上述语句得到如下结果:
而MATLAB提供的同样功能的函数std计算结果如下:
上面的示例说明函数的逻辑有问题,需要调试,具体操作步骤如下。
在文件strd v.m最后一行前设置一个断点,编译器用一个大红点标记,如图4-8所示,程序运行时,将在断点处暂停。
图4-8 设置断点
运行程序,检查变量,代码设置如下:
当运行到断点处时,在断点和文本之间将会出现一个绿色箭头,表示程序运行至此停止,如图4-9所示。
图4-9 设置断点后程序的运行
在K>>后检查变量l,s,y,t的数值,发现t有错误,结果如下:
检查变量也可通过变量浏览器进行,如图4-10所示。双击变量,即可打开数组编辑器,可以查看和修改变量内容,如图4-11所示。由此可以确定断点以前的部分是正确的,出错的是strd fun子程序,需对子程序进行调试。
图4-10 变量浏览器
图4-11 数组编辑器
切换工作空间到基本工作空间,即将图4-7中的“Stack”项选为“Base”。清除前面设置的断点,可以选择“Debug”下拉菜单中的“set/clear breakpoints”项,也可直接在大红标记上单击一下鼠标左键。此时,标记将会消除,绿色箭头变为白色。为了去除白色箭头,需单击“continue”按钮使程序继续运行。
然后调试子函数。打开strd_fun.m文件,在第5行设置一个断点,采用前面的向量v再一次调用函数strd_v,这时单击函数文件strd_v.m的标签或者在stack下拉列表中选择strd_v,则在文件的第5行前增加了一个白色的箭头,如图4-12所示,表示在主程序中的断点位置。查看变量值为:
图4-12 主程序的断点设置情况
可见,错误出在t的计算式上,观察发现t=t+(x-y).^2)中的x应改为x(i)。
退出调试后修改程序,清除断点,重新运行后得到正确的结果,代码设置如下:
本例只是为了说明一些命令的用法,实际中遇到的问题可能会更复杂,要求调试者必须要有耐心和细心。
4.4 函数的设计和实现
前面介绍了MATLAB的语法规则和使用方法,本节将通过一个示例具体来讲述如何用MATLAB来解决问题。
【例4.2】用MATLAB对基带数字通信系统进行仿真。通信系统结构如图4-13所示,整个系统模拟了发送和接收的全过程。其中调制部分采用16QAM,并且采用基带传输,不必调制到载频上(在实际操作中通信系统应该对基带信号进行载波调制,可以减少传输中的损耗,并且提高信道频带的利用率)。该系统采用了高斯信道,在接收端进行解调并与发送信号比较得到误码率。图4-13中还要求得到各个模块的输出结果图示。此外,在发送端的低通滤波后,有一个调制到载波的步骤,主要是为了观察实际系统中的波形,这是相对独立的一部分。
图4-13 通信系统仿真框图
4.4.1 建立数学模型
建立模型前先对要解决的问题作细致的分析,弄清楚系统的输入/输出和工作过程,然后逐步建立数学模型。在本例中需要对通信系统中的各个部分进行分析和建模。首先要产生随机数,可以用MATLAB自带的函数实现,然后根据格雷码的编码原则实现基带的QAM调制。
插值(过采样)是为了后面经过低通时更好的成形滤波的需要,相当于滤波后采样点数增加,从而使波形更平滑。实际的通信系统中有两倍的、四倍的和八倍的过采样方式,本系统中采样八倍过采样,虽然倍数越高得到的波形越平滑,系统的性能越好,但是这样会增加运算量。在设备条件一定的情况下,可能会影响到通信的实时性,尤其是载波调制时,这个问题更为明显,本系统采用8倍过采样是一种折中的做法。
实际信道中传输的都是模拟信号,所以插值之后需要进行滤波,实现D/A转换,在MATLAB中所有的计算都是离散的,这样经过数字滤波器后仍是一个离散序列。程序中采用平方根升余弦滚降低通滤波器来进行发送端的低通滤波和接收端的匹配滤波,滚降系数取0.5,由于滤波器的响应出现了一个群延迟,所以在作图的时候需要通过将输入信号延迟一下做补偿,补偿值为delay*fd。
信号在信道中传输时会受到噪声的影响,本系统中用叠加高斯白噪声的方法来模拟这个现象,程序中用函数(awgn)来实现。
经过信道的传输信号到达接收端,首先要经过匹配滤波器,去掉噪声的影响。对于平方根升余弦滚降低通滤波器来讲,匹配滤波器仍是它本身。
由于过采样的过程中加入了多余的点,而实际发送的信号并不包括这些信号,所以接收的时候应该通过采样的方式将实际发送的信号点提取出来。采样时应该注意与发送信号的同步,这里在滤波的过程中前后分别加了24个冗余的点,采样时应该把这些点排除在外。解调部分根据欧氏距离最小的原则对采样后的信号进行判决,然后按照调制时的规则进行逆变换,每一个符号分成两个比特,然后可以计算实际的误比特率ber等于接收的错误比特数除以总比特数。
对于加载波部分,程序中采用10个点表示一个正弦波形。因为要求载波频率是信号频率(指的是I路和Q路分离后的频率)的10倍,随机序列经过I路和Q路分离后,频率为1Hz,映射后变为0.5Hz,插点后频率扩大八倍变为4Hz,而载波频率为16Hz,所以两个点之间应该有4个周期的三角函数波形,每两个点之间对应有载波的40个点。为了做到矩阵运算的匹配需要将信号数组作40倍扩展,反映到实际电路中应该加保持电路,而这样会增加很多高频成分,往往加低通滤波对波形进行平滑,本系统中不做考虑。
4.4.2 编写代码
数学模型已经基本讨论完毕,下面介绍实现的代码。整个系统是通过一个主函数和若干个子函数实现的,这样便于调试,也增强了可读性。这些子函数包括QAM调制函数(Qam_modulation.m)、绘制星座图(plot_astrology.m)、插值(insert_value.m)、绘制正交信号图(plot_2way.m)、绘制滤波后的信号图(stem_2way.m)、升余弦滤波(rise_cos.m)、调制到载波(modulate_to_high.m)、叠加高斯噪声(generate_noise.m)、采样(pick_sig.m)、解调(demodulate_sig.m)和误码率曲线作图(plot_snr.m)。下面将具体介绍这些函数。
1.主函数:project.m
2.QAM调制函数Qam_modulation.m
3.绘制星座图plot_astrology.m
4.插值insert_value.m
5.绘制正交信号图plot_2way.m
6.绘制滤波后的信号图stem_2way.m
7.升余弦滤波rise_cos.m
8.调制到载波modulate_to_high.m
9.叠加高斯噪声generate_noise.m
10.采样pick_sig.m
11.解调demodulate_sig.m
12.误码率曲线作图plot_snr.m
4.4.3 运行程序
运行程序,即在命令行中输入project(40000,0.5),此数值可以根据需要进行选择。如果有错误则进行调试。正确的运行结果如图4-14~图4-21所示。
图4-14 QAM星座图
图4-1 经过插值后的两路信号波形图
图4-16 通过低通滤波后的两路信号波形图
图4-17 载波调制信号图
图4-18 加入高斯白噪声的两路信号波形
图4-19 经过匹配滤波器后的波形
图4-20 信噪比为10dB时的星座图
由图4-20可见,当信噪比为10dB时,解调出来的信号大部分是正确的,基本上集中在发送信号的周围。
图4-21中的横坐标是信噪比,信噪比越大,误码率越低,这一点符合通信系统的规律。(其中上面一条是实际的误码率曲线,下面一条是理论曲线,二者基本吻合,说明程序是正确的。)
图4-21 误码率曲线
从本例中可以看出,MATLAB系统仿真的功能是十分强大的,重要的是建立正确的数学模型。在编写程序时,应尽量将重复用到的部分编写成独立的函数。同时,为了程序的可扩展性,应该尽量避免使用全局变量。各个函数可单独调试,调试正确后再统一运行,如果仍不正确,则需要认真考虑算法是否合理。