任务4 高级主题——用数组建立复杂运动
到目前为止,你已经试过3种不同的编程方法来使机器人向前走,左转,右转和向后走。每种方法都有它的优点,但是如果要让机器人执行一个更长、更复杂的动作时用这些方法都很麻烦。下面要介绍的两个例子将用子函数来实现每个简单的动作,将复杂的运动存储在数组中,然后在程序执行过程中读出并解码。避免了重复调用一长串子函数。这里,要用到C语言的一种新的数据类型——数组。
前面,只用到了C语言的基本数据类型之一的整型数据,以int作为类型说明符。另外一种基本数据类型是字符型,以char作为类型说明符。
字符型数据
字符常量
字符常量是指用一对单引号括起来的一个字符,如'a'、'9'、'!'。字符常量中的单引号只起到定界作用并不表示字符本身。单引号中的字符不能是单引号(')和反斜杠(\),它们特有的表示法将在转义字符中介绍。
在C语言中,字符是按其所对应的ASCII码值来存储的,一个字符占一个字节,见表3-1。
表3-1 字符与其所对应的ASCII码值
ASCII码
ASCII是美国标准信息交换码(American Standard Code for Information Interchange)的缩写,用来制订计算机中每个符号对应的代码,也叫做计算机的内码(code)。
每个ASCII码以1个字节(Byte)储存,从0到数字127代表不同的常用符号,例如大写A的ASCII码是65,小写a则是97。这套内码加上了许多外文和表格等特殊符号,成为目前常用的内码。
注意字符'9'和数字9的区别,前者是字符常量,后者是整型常量,它们的含义和在计算机中的存储方式都截然不同。
由于C语言中字符常量是按整数存储的,所以字符常量可以像整数一样在程序中参与相关的运算,如:
'a'-32; //执行结果97-32=65 'A'+32; //执行结果65+32=97 '9'-9; //执行结果57-9=48
转义字符
转义字符是一种特殊的字符常量,以反斜杠“\”开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。例如,前面各例题printf函数中用到的“\n”就是一个转义字符,其意义是“回车换行”。
通常使用转义字符表示用一般字符不便于表示的控制代码,如用于表示字符常量的单引号(')、用于表示字符串常量的双引号(")和反斜杠(\)等。
表3-2给出了C语言中常用的转义字符。
表3-2 C语言中常用的转义字符
广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表中的\ddd和\xhh正是为此而提出的。ddd和hh分别为八进制和十六进制的ASCII代码。如\101表示字母“A”,\102表示字母“B”,\134表示反斜线,\XOA表示换行等。
字符变量
字符变量用来存放字符常量,注意只能存放一个字符。
字符变量的定义形式如下:
char c1,c2;
它表示c1和c2为字符变量,各放入一个字符。因此可以用下面语句对c1、c2赋值:
c1='a';c2='A';
数组
在程序设计中,为了处理方便,可以把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。一个数组可以分解为多个数组元素,根据数组元素数据类型的不同,数组可以分为多种不同类型。数组又分为一维数组、二维数组甚至三维数组。本节只用到一维数组。一维数组的定义方式为:
类型说明符 数组名[常量表达式];
类型说明符是任一种基本数据类型。
数组名是用户定义的数组标志符。
方括号中的常量表达式表示数据元素的个数,也称为数组的长度。
数组定义之后,还应该给数组的各个元素赋值。给数组赋值的方法除了用赋值语句对数组元素逐个赋值外,还可采用初始化赋值。初始化赋值的一般形式为:
类型说明符 数组名[常量表达式]={值,值……值};
其中在{}中的各数据值即为各元素的初值,各值之间用逗号间隔。
例如,下面的语句定义了一个字符型数组,该数组有10个元素,对这10个元素进行了初始化。
char Navigation[10]={'F','L','F','F','R','B','L','B','B','Q'};
如何才能把放入数组中的元素引用出来呢?
一维数组的引用
数组元素是组成数组的基本单元。数组元素也是一种变量,其标识方法为数组名后跟一个下标,下标表示了元素在数组中的顺序号(从0开始计数)。数组元素的一般形式为:
数组名[下标]
其中下标只能为整型常量或整型表达式。若为小数时,系统将自动取整。
例如:
Navigation[0](第一个字符:'F') Navigation[5](第六个字符:'B')
字符串和字符串结束标志
字符串常量是指用一对双引号括起来的一串字符。如“Chian”、“A”、“333212-6589”等。双引号只起定界作用,双引号括起的字符串中不能是双引号('')和反斜杠(\),它们特有的表示法在转义字符中介绍。
在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。字符串常量在存储时,系统自动在字符串的末尾加一个“串结束标志”,即ASCII码值为0的字符NULL,常用“\0”表示。因此在程序中,长度为n字符的字符串常量在内存中占有n+1个字节的存储空间。
C语言允许用字符串的方式对数组作初始化赋值,如Navigation[10]的初始化赋值可写为:
char Navigation[10]={"FLFFRBLBBQ"};
或者去掉“{}”,写为:
char Navigation[10]="FLFFRBLBBQ";
要特别注意字符与字符串的区别,除了表示形式不同外,其存储性质也不相同,字符'A'只占1个字节,而字符串"A"占2个字节。
下面的例程采用字符数组定义一系列复杂的运动。
例程:NavigationWithSwitch.c
输入、保存、编译、下载并运行程序NavigationWithSwitch.c;
#include<BoeBot.h> #include<uart.h> void Forward(void) { int i; for(i=1;i<=65;i++) { P1_1=1; delay_nus(1700); P1_1=0; P1_0=1; delay_nus(1300); P1_0=0; delay_nms(20); } } void Left_Turn(void) { int i; for(i=1;i<=26;i++) { P1_1=1; delay_nus(1300); P1_1=0; P1_0=1; delay_nus(1300); P1_0=0; delay_nms(20);} } } void Right_Turn(void) { int i; for(i=1;i<=26;i++) { P1_1=1; delay_nus(1700); P1_1=0; P1_0=1; delay_nus(1700); P1_0=0; delay_nms(20); } } void Backward(void) { int i; for(i=1;i<=65;i++) { P1_1=1; delay_nus(1300); P1_1=0; P1_0=1; delay_nus(1700); P1_0=0; delay_nms(20); } } int main(void) { char Navigation[10]={'F','L','F','F','R','B','L','B','B','Q'}; int address=0; uart_Init(); printf("Program Running!\n"); while(Navigation[address]!='Q') { switch(Navigation[address]) { case'F':Forward();break; case'L':Left_Turn();break; case'R':Right_Turn();break; case'B':Backward();break; } address++; } while(1); }
你的机器人是否走了一个矩形?如果它走得更像一个梯形,可能需要调节转动程序中for循环的循环次数,使其旋转精确的90°。
NavigationWithSwitch.c是如何工作的
在程序主函数中定义了一个字符数组如下所示:
char Navigation[10]={'F','L','F','F','R','B','L','B','B','Q'};
这个数组中存储的是一些命令:F表示向前运动,L表示向左转,R表示向右转,B表示向后退,Q表示程序结束。之后,定义了一个int型变量address,用来作为访问数组的索引。
接着是一个while循环,这个循环的条件表达式与前面的不同:只有当前访问的数组值不为Q时,才执行循环体内的语句。在循环体内,每次执行switch语句后,都要更新address,以使下次循环时执行新的运动。
switch语句
switch语句是一种多分支选择语句,其一般形式如下:
switch(表达式){ case常量表达式1: 语句1;break; case常量表达式2: 语句2;break; … case常量表达式n: 语句n;break; default: 语句n+1;break; }
其语义是,计算表达式的值,逐个与其后的常量表达式值相比较,当表达式的值与某个常量表达式的值相等时,即执行其后的语句。如表达式的值与所有case后的常量表达式均不相同时,则执行default后的语句。
在本例程中,当Navigation[address]为'F'时,执行向前运动的函数Forward();当Navigation[address]为'L'时,执行向左转的函数Left_Turn();当Navigation[address]为'R'时,执行向右转的函数Right_Turn();当Navigation[address]为'B'时,执行向后运动的函数Backward()。
● 你可以更改现有的数组和增加数组的长度来获取新的运动路线;
● 试着更改、增加或删除数组中的字符,重新运行程序,记住,数组中的最后字符应该是“Q”;
● 更改数组使机器人进行熟悉的向前、左、右和后一系列的运动。
例程:NavigationWithValues.c
在本例程中,将不使用子函数,而是使用三个整型数组来存储控制机器人运动的三个变量,即循环的次数和控制左右电机运动的两个参数,具体定义如下:
int Pulses_Count[5]={65,26,26,65,0}; int Pulses_Left[4]={1700,1300,1700,1300}; int Pulses_Right[4]={1300,1300,1700,1700};
int型变量address作为访问数组的索引值,每次用address提取一组数据:Pulses_Count[address],Pulses_Left[address],Pulses_Right[address],这些变量值被放在下面的代码块中,作为机器人运动一次的参数。
for(int counter=1;counter<=Pulses_Count[address];counter++) { P1_1=1; delay_nus(Pulses_Left[address]); P1_1=0; P1_0=1; delay_nus(Pulses_Right[address]); P1_0=0; delay_nms(20); }
address加1,再提取一组数据,作为机器人下次运动的参数。依次继续直至Pulses_Count[address]=0时,机器人停止运动。具体程序如下:
#include<BoeBot.h> #include<uart.h> int main(void) { int Pulses_Count[5]={65,26,26,65,0}; int Pulses_Left[4]={1700,1300,1700,1300}; int Pulses_Right[4]={1300,1300,1700,1700}; int address=0; int counter; uart_Init(); printf("Program Running!\n"); while(Pulses_Count[address]!=0) { for(counter=1;counter<=Pulses_Count[address];counter++) { P1_1=1; delay_nus(Pulses_Left[address]); P1_1=0; P1_0=1; delay_nus(Pulses_Right[address]); P1_0=0; delay_nms(20); } address++; } while(1); }
● 输入、保存并运行程序NavigationWithValues.c;
● 你的机器人是否已经做了我们所熟悉的向前、向左、向右、向后的运动呢?现在是不是有点厌烦了呢?你还想让机器人做其他的动作或者创建你自己的程序吗?
该你了——设计你自己的程序
● 以一个新的文件名保存程序NavigationWithValues.c;
● 用下面的代码代替三个数组;
int Pulses_Count[10]={60,80,100,110,110,110,100,80,60,0}; int Pulses_Left[10]={1700,1600,1570,1520,1500,1480,1430,1400,1300,1500}; int Pulses_Right[10]={1300,1400,1430,1480,1500,1520,1570,1600,1700,1500};
● 运行更改后的程序,观察机器人会做些什么;
● 输入、保存并运行程序,你的机器人是不是按你的想法运动呢?