2小时学会编程.docx
《2小时学会编程.docx》由会员分享,可在线阅读,更多相关《2小时学会编程.docx(33页珍藏版)》请在冰豆网上搜索。
2小时学会编程
5.3两小时学会编程
编程就是把你对一件事的想法和思路用机器语言表达出来。
比如你正坐在沙发上,想要倒一杯水喝。
首先你要站起来,然后拿起杯子,再倒水。
编程也是一样的,你要完成一项任务,就要把中间的每一个步骤都分解开来,然后一一解决。
举个例子说吧,让机器人走一个正方形。
正方形是由四条长度相等的直线组成的,所以机器人所走的路径就是:
直走,长度为a;转九十度;直走a;转九十度;直走a;转九十度;直走a,转九十度。
这就是程序。
当然这样写机器并不能理解,怎样让机器读懂你的思路呢。
下面具体介绍一些编程的方法。
5.3.1编程的方法
程序有一定的结构。
常见的有顺序结构、选择结构和循环结构。
顺序结构就是先做第一件事,再做第二件事,再做第三件事……拿刚才的例子来说,先走直线,然后转九十度,再走直线……这就是顺序结构。
一开始喝水的例子:
站起,拿杯子,倒水,也是一个顺序结构。
选择结构就是有一个判断条件,当条件成立的时候做时间1,不成立的时候做事件2。
就象过马路一样,如果是红灯就要停下,如果不是红灯就可以走。
停和走就是两个事件,红灯是否亮就是判断条件。
循环结构分为当循环和直到循环两种。
当循环就是当条件p成立时,反复执行A操作,否则跳出循环。
直循环就是先执行A操作,再判断p是否成立,直到p成立时跳出循环。
现在我们就把刚才让机器人走正方形的例子写出来。
让大家有一个比较直观的了解。
其中如果有不理解的函数没有关系,下一节中会作出详细的介绍。
这里只要求大家知道程序编写的过程。
voidmain()/*建立一个主函数*/
{
drive(100,0);/*直走*/
sleep(1.0);/*走1秒,随意控制一定长度*/
drive(50,50);/*左转*/
sleep(0.6);/*转0.6秒,大致九十度*/
drive(100,0);/*直走*/
sleep(1.0);/*走1秒,随意控制一定长度*/
drive(50,50);/*左转*/
sleep(0.6);/*转0.6秒,大致九十度*/
drive(100,0);/*直走*/
sleep(1.0);/*走1秒,随意控制一定长度*/
drive(50,50);/*左转*/
sleep(0.6);/*转0.6秒,大致九十度*/
drive(100,0);/*直走*/
sleep(1.0);/*走1秒,随意控制一定长度*/
drive(50,50);/*左转*/
sleep(0.6);/*转0.6秒,大致九十度*/
stop();/*停止*/
}
这就是一个简单的程序,用的是顺序结构,是不是很容易理解呢?
也许大家会觉得这样做太复杂了,有没有简单一点的方法呢?
当然是有的。
下面这个程序也能让机器人走出直线,看上去就简单多了。
voidmain()/*定义一个主函数*/
{
inti;/*定义一个中间变量*/
for(i=0;i<4;i++)/*只要i小于4就一直循环,即循环执行4次*/
{
drive(100,0);/*直走*/
sleep(1.0);/*走1秒,随意控制一定长度*/
drive(50,50);/*左转*/
sleep(0.6);/*转0.6秒,大致九十度*/
}
stop();/*停止*/
}
这个程序用的是什么结构大家看出来了吗?
对,当循环结构。
所以对于这种每一个步骤所做的工作都一样,又要连续做几次的事件,我们就可以考虑用循环结构来编写程序。
再来介绍一个用选择结构编写的程序。
在控制能力风暴的程序编写中,大部分都是运用这种结构的。
voidmain()
{
inti;
i=ir_detect();/*ir_detect用于检测前方是否有障碍物,有为1*/
if(i=0)
{
drive(100,0);/*前方没有障碍物直走*/
}
else
{
drive(50,50);/*否则左转*/
}
}
是不是和红绿灯的原理很接近呢?
这个程序中还运用了传感器数据的接收,传感器是用来感测周围环境的工具,就象我们的眼睛、鼻子和耳朵一样,它们可是机器人身上必不可少的组成部分,在机器人项目里大家就会体会到传感器的用途了。
上面介绍的这些程序看上去还是很容易理解的吧!
可是这些毕竟只是简单的程序,只要稍微动动脑子就能把事情的各个步骤都分清楚了,如果遇到比较复杂的问题,光是拍拍脑袋可是想不清楚的,这就需要大家把手和脑一起用起来了。
我们要学习画图了。
接下来我们就来认识一下这个对我们编程有很大作用的工具:
结构流程图。
流程图表达的信息就是整个时间发展过程。
在流程图里,我们统一用菱形表示条件,用长方形表示事情的一个个过程、阶段,用箭头表示流程的方向。
光是说比较不容易理解,下面我们就来看一个流程图。
这就是流程图。
先检测光线强弱,接下来判断条件是否成立亮度低于临界值”就是条件,当条件满足时,就右转,不满足就左转;然后显示亮度值。
无论一件事多复杂都可以用流程图来描述,然后只要根据流程图中的步骤用C语言写下来就可以了。
复杂的程序可能包含多个任务,而且每一个任务都比较复杂,如果把那么多任务都在一起,看上去可能会显得很乱。
所以我们再学习一个新的概念:
主程序和子程序。
子程序表达的是每一个分步骤所需完成的任务。
主程序里如果做到这个动作,只要输入子程序的名字就可以了。
下面我们还是来看一下喝水的例子。
为了方便理解我把程序名都用中文代替,程序也只是为了表达子程序是如何调用的,并不是规范的写法。
主函数()(main)
{
站起来;
拿起杯子;
倒水;
}
站起来()
{
站起来的过程;
}
拿杯子()
{
拿杯子的过程;
}
倒水()
{
倒水的过程;
}
明白了吗?
这就是子程序的调用。
以后我们还会再介绍一种子程序的调用方法:
进程的调用。
它们之间是有区别的,以后我们在学习。
介绍了那么多方法,大家一定已经跃跃欲试了吧,现在还不行,我们还缺了一个重要的环节,那就是库函数。
也就是刚才出现在程序中的drive(),sleep(),stop()等函数,只有在知道了它们的使用方法以后,你编写出来的程序才能用来操作机器人。
5.3.2常用库函数
电机控制
1、stop()
关闭两个电机。
2、motor(intm,intp)
以功率级别p启动电机m。
功率级别从(-100到+100)。
m=1表示左马达,m=2表示右马达。
例如:
motor(1,50);
让右马达以功率100启动。
2、drive(inta,intb)
同时设定两个电机的速度,a平移速度,b旋转速度。
也可以这样来理解,a-b为左电机转速,a+b为右电机转速。
例如:
drive(100,0);
这时左右电机转速都为100,机器人走直线。
下面我们来看看以上两个函数的运用。
例1、
voidmain()
{
drive(60,30);
sleep(5.0);
stop();
}
大家来考虑一下,drive(60,30),两个电机是怎样运作的?
当运行了5秒钟以后,执行stop()函数,机器人会怎么样呢?
为什么sleep()函数中一定要输入5.0而不是5呢?
例2、这个程序是让机器人走一个圆,大家看一下这个程序和上面的程序有什么区别
voidmain()
{
motor(0,30);
motor(1,90);
sleep(5.0);
stop();
}
有什么发现吗?
对了,这两个程序实现的效果是一样的。
现在请大家再考虑一个问题,drive()函数和motor()函数有什么区别呢?
4、dcmotor3(intflag)flag=1启动电机。
flag=0关闭电机。
Dcmotor3这个电机是用来控制电扇的。
所以flag=1电扇旋转,flag=0电扇停止。
运行一下以下这个函数,大家就能掌握这个函数了。
voidmain()
{
dcmotor3
(1);
sleep(5.0);
dcmotor3(0);
}
风扇转动5秒后自动停止。
传感器输入
1、photo_right()右光敏电阻
photo_left()左光敏电阻
photo_right()和photo_right()的返回值是数字量。
范围从0到255,数值越大说明探测到的光线越暗。
最亮的时候输出值为5。
看看室内和室外的光线有什么区别。
voidmain()
{
intleft;
intright;
while
(1)
{
left=photo_left();
right=photo_right();
printf("left=%d",left);
printf("right=%d",right);
printf(“\n”);
sleep(0.5);
}
}
这里的while
(1),是让传感器不断地检测信号。
pintf(“\n”)是为了刷新屏幕,因为能力风暴的屏幕只有两行,\n是回车的意思,这样下一次显示就仍旧是从第一行输出了。
Sleep(0.5)是让数据在屏幕上停留0.5秒后自动刷新。
屏幕上显示的值表示了房间内的明暗程度,数值会不断变化,表示每个地方的亮度是不同的。
2、analog
(2)麦克风
他的变化范围是0—255。
一般最静时输出值为127,声音有变化时,数值也会有变化。
输入下面这个函数,然后对着麦克风说话,看看屏幕上的值会有什么变化。
voidmain()
{
inti;
while
(1)
{
i=analog
(2);
printf("sound=%d",i);
printf("\n");
sleep(0.5);
}
}
屏幕上显示的值可能会变化不大,这是由于硬件的约束,只可以粗略地表示出声音的轻响程度。
3、ir_detect()红外线传感器,利用红外线反射的时间差来判断障碍物的方位。
返回值,0b00:
没有障碍,0b01:
右边有障碍,0b10:
左边有障碍,0b11:
前方有障碍。
这些数字都是二进制的,在程序编写过程中,可以用上面给出的数字,也可以把它们转化成十进制。
下面的例子就是用十进制编写的。
注意:
这是没有改装时的情况,如果对能力风暴的红外传感器进行改装的话,就不能用上面的方法进行判断了。
试试用手分别遮住左边和右边的红外发射器,看看结果。
记住,一定要用手掌,用手指是没有效果的。
voidmain()
{
inti;
while
(1)
{
ir=ir_detect();/*接受信号*/
if(ir=1)
{
printf("\nright”);
sleep(0.5);
}
if(ir=2)
{
printf("\nleft");
sleep(0.5);
}
}
}
屏幕上会不断显示left和right,表示左边或右边有障碍物。
4、bumper()碰撞传感器检测。
它是由四位二进制数表示的,下面的数字可以告诉你那里受到了碰撞。
左前右前左后右后
0010000110000100
由此可推出
前(左前+右前)后(左后+右后)左(左前+左后)右(右前+右后)
0011110010100101
上面的数字是二进制数,书写时记得要在数字前加0b,表示二进制。
我们来写一个程序,看看碰撞环受到碰撞时,bumper()函数是怎么工作的。
voidmain()
{intbmpr;
printf("\n");
while
(1)
{bmpr=bumper();
printf("BumperTest");
if(bmpr==0)printf("none\n");/*bumper=0,说明没有碰撞*/
else
{if(bmpr==0b1000)printf("\nBackLeft");
else
{if(bmpr==0b0100)printf("\nBackRight");
else
{if(bmpr==0b0010)printf("\nFrontLeft");
else
{if(bmpr==0b0001)printf("\nFrontRight");
}
}
}
}
sleep(0.5);
}
}
输入这个程序后,当你碰机器人碰撞环的时候,屏幕上就会显示是那里受到了碰撞。
大家还可以自己加入对:
前、后、左、右的判断。
声音
1、beep()产生一段0.3秒500赫兹的音频信号。
发声结束后返回。
输入程序听听看。
voidmain()
{
beep();
}
程序运行时你会听到3秒钟的蜂鸣声。
2、tone(floatfrequency,floatlength)产生一个秒长为length,音调为frequency赫兹的音频信号。
3、set_beeper_pitch(floatfrequency)设置音频频率为frequencyHz。
4、beeper_on()打开喇叭
beeper_off()关闭喇叭。
以上三个函数可以通过下面这个函数来帮助了解。
voidmain()
{
tone(500.0,2.0);
set_beeper_pitch(1000.0);
beeper_on();
sleep(5.0);
beeper_off();
}
你会先听到两秒钟较低的声音,再听到五秒钟较高的声音。
进程调度
1、start_process(function-call(...),[TICKS],[STACK-SIZE])
创建新进程。
star_process取得一个进程标识,创建一个进程。
两个可选参数:
进程的时间片数和栈大小。
返回一个整数,新进程的进程ID。
2、kill_process(intpid)撤消进程。
进程是用来控制函数的执行的,如果大家不理解,没有关系,在下面介绍多任务的那一小节中会有介绍。
浮点运算
sin(floatangle)返回角的正弦。
角以弧度为单位。
cos(floatangle)返回角的余弦。
角以弧度为单位。
tan(floatangle)返回角的正切。
角以弧度为单位。
atan(floatangle)返回角的余切。
角以弧度为单位。
sqrt(floatnum)返回num的均方根。
log10(floatnum)返回num的以10为底的对数。
log(floatnum)返回num的自然对数。
exp10(floatnum)返回10的num次方。
exp(floatnum)返回e的num次方。
a^(float)返回a的b次方。
这些函数是帮助大家进行数学运算的,使用起来非常方便,就象按计算器一样。
我们来看看下面这个函数的运行结果。
不妨和计算器算出的结果进行一下比较。
voidmain()
{
sin(0.5);
tan(0.5);
sqrt(9.0);
3.0^(2.0);
}
以上就是常见的库函数,熟悉了他们以后,就可以让机器人作简单的动作了。
试试让机器人跳个舞吧。
如果不知道怎么办,可以下载一下交互式C语言自带的dance.c,看看机器人是怎么运动的,考虑一下。
5.3.3多任务
交互式C语言支持多任务。
什么是多任务呢?
我们不妨想像一下,支持多任务的语言,就像一个有交通警的十字路口。
十字路口有东南西北四个方向,交通警让东边先过一辆,再让南边走一辆,然后西边走一辆,在北边走一辆。
然后重复。
看上去就像四条路上的车辆在一起运动一样。
不采用进程调用的程序,就好象我们在第一节中介绍的子程序调用,它是怎么工作的呢?
子程序的调用,就像一个没有警察官的十字路口,各路车辆自由行驶,最终大家都是可以通过路口的,但是这个过程显得比较混乱,没有秩序。
在程序的执行中,有可能是做完A再做B,也有可能是做一半A就开始做B,没有规律。
对于同步性要求不是很高的程序可以使用子程序调用的方式,但对于同步性要求很高的程序就一定要使用进程。
比如:
机器人寻找火源,他一边要防止自己撞到墙上,一边要搜索那里有火,两个过程必须同时进行,这时就要用到进程的调用了。
对于程序来说呢,看上去就像是好几个程序同时执行。
其实在电脑中的执行是有先后顺序的,先做A的一部分,再做B的一部分,然后是C的一部分……只是时间间隔很短,看不出来而已。
在上一章中我们曾经看到过一个库函数,start_process()和kill_process(),这两个函数就是帮助实现多任务的。
start_process()用于启动一个程序,它的格式是这样的:
start_process(function-call(...),[TICKS],[STACK-SIZE])
TICKS和STACK-SIZE是两个参数,分别表示:
进程的时间片数和栈大小。
现阶段硬要去理解他的意义不大。
一般我们在使用这个函数时只要知道在start_process()的括号里放入你需要执行的子程序。
如果有多个子程序需要同时进行,只要连续写几个start_process(…)就可以了。
另一个函数kill_process(),用于取消一个进程,也就是让一个程序停止执行。
它的格式是这样的:
kill_process(intpid)
这个函数的使用要和start_process()结合起来。
我们来看一个例子:
voidmain()
{
intpid;
pid=start_process(check_sensor());
sleep(1.0);-
kill_process(pid);
}
这个例子中check_sensor(),这个程序执行了1秒后终止。
这就是kill_process(),的用法。
为了让大家更深地对多任务进行了解,我们在机器人项目一节中会举一个台球碰撞的例子
到这里,大家就已经学会了多个任务的的执行。
程序的编写过程基本就是这些内容了,接下来就要让机器人来执行你编写的程序了。
在把程序下载到机器人上去之前,还有一个必不可少的环节,那就是程序的调试。
5.3.4程序的调试方法
所谓程序的调试就是对程序的查错和排错。
程序编写过程中的错误有很多种,有语法错误,逻辑错误等,要想成功调试,首先要知道常见的错误有哪些,如何解决。
下面列举几种常见的错误,供初学者参考。
1、忘记定义变量。
例如:
voidmain()
{
while
(1)
{
ir=ir_dectect();
printf("\nir=%d",ir);
sleep(0.5);
}
}
C语言要求对每一个变量先定义后使用,如果没有定义变量,程序下载时会出现这样的话“未说明的变量ir”。
应在函数体的开头加“intir”。
2、输入输出的数据类型与所用格式说明符不一致。
例如:
voidmain()
{
drive(100,0);
sleep
(1);
}
下载这个命令时会出现这样的话“参数不兼容(要求是,给的是)”。
杂这个程序中sleep()函数的参数要求是实型,而1是整型,要把1改成1.0。
3、括号不配对
例如:
voidmain()
{
intir;
while
(1)
{ir=ir_detect();
printf("\nir=%d",ir);
sleep(0.5);
}
这个程序编译时会出现这样一句话,“语法错误在符号""附近。
出现这句话的原因是:
有两个左括号却只有一个右括号。
应该改成
voidmain()
{
intir;
while
(1)
{ir=ir_detect();
printf("\nir=%d",ir);
sleep(0.5);}
}
4、在应该加括号的地方忘记了加括号。
例如:
for(i=0;i<4;i++)
drive(100,0);
sleep(1.0);
这里在for后面本来应该有括号,保证程序的循环,现在没有括号,程序只执行一次就结束了。
应改为
for(i=0;i<4;i++)
{
drive(100,0);
sleep(1.0);
}
5、在不该加分号的地方加了分号,该加的地方缺省了。
例如:
if(i=1);
if是条件语句,不需要加分号。
drive(100,0)
drive()是命令函数,后面要加分号。
以上这些是初学者最容易犯的错误,注意了这些错误以后,我们就可以来说说调试了。
调试程序一般应经过以下几个步骤:
1、自己检查。
在写好一个程序以后,不要匆匆忙忙上机,而应对纸面上的程序进行人工检查。
这一步是十分重要的,它能发现大家由于疏忽而造成的多数错误。
为了更有效地进行人工检查,所编的程序应尽量做到以下几点:
(1)尽量多加注释,帮助理解每段程序的作用。
注释就是前面大家经常见到的/**/这种形式。
两个星号中间的语句就是注释语句,在C语言中被注释掉的语句是不参加编译的。
有时也把需要修改的程序用星号注释掉这样如果修改后仍不正确,还有原稿可以参考。
(2)尽量多用子程序,主程序要尽量简短。
这样既易于阅读也便于调试。
2、在人工检查无误后,就可以上机调试了。
在编译时出现的语法错误,可以根据提示的信息具体找出程序中错误的位置和类型。
有时提示错误的行并不一定就是出错误的行,错误可能在上下一行中出现。
3、在改正语法错误后就可以执行程序了,有时候明明没有错误了,执行出来的结果还是不是自己要求的那样,这是怎么回事呢?
出现这种情况很可能是因为在程序的计算过程中有错误。
比如:
本来应该是a+b,结果你输入时成了a-b。
在语法上确实没有错误,但结果可就差得很远了。
4、最后一种错误就是逻辑错误,那可是你一开始就犯的错误了。
出现这种问题时,你就需要拿出原来的流程图,看一下思路是不是正确,判断条件是不是找错了,与和
或的关系是不是错了……。
如果觉得整段程序看起来太复杂,不妨在每段子程序里多加几个“printf”,分段看看程序执行中哪一步出了问题。
总之,程序调试是一项细致深入的工作,需要下功夫、动脑子、善于多积累经验。
而且需要极好的耐心,千万不能遇到问题就失去信心,慢慢来,你一定能够做好的。
5.3.4机器人项目
讲了那么多编程的有关知识,现在我们开始自己动动手吧。
做一个有趣的试验,帮助大家巩固一下前面学到的知识。
让你的机器人可以跟着你走,如果撞上了你,它会停一停;如果看不见你了,它也会停下来。
先来想一下这个程序分成几个部分,应该用什么结构和方法来编写?
我们来分析一下。
机器人的行走路线是这样的。
撞到障碍物,看不到东西:
停;左边有人:
往左走;右边有人:
往右走;前方有人:
往前走。
下面我们来看一下程序。
voidmain()
{
i