C51单片机应用与C语言程序设计
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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

● 运行更改后的程序,观察机器人会做些什么;

● 输入、保存并运行程序,你的机器人是不是按你的想法运动呢?