一、 综述: a) 项目名称:小车自动走迷宫; b) 小组成员: i. 周杰:负责总体规划,及ARM编程; ii. 宋大成:负责车轮驱动; iii. 陈潇:负责红外驱动; c) 小车图片: MicroMouse615: MicroMouse120: 二、 项目介绍: 电脑鼠走迷宫竞赛的目的是制作一个微型机器人,它能在最短的时间内穿越迷宫到达终点。参赛的机机器人称为“电脑鼠”,将电脑鼠放入迷宫并启动操作的人称为“操作员”。 电脑鼠的基本功能是从起点开始走到终点,这个过程称为一次“运行”,所花费的时间称为“运行时间”。从终点回到起点所花费的时间不计算在运行时间内。从电脑鼠的第一次激活到每次运行开始,这段期间所花费的时间称为“迷宫时间”。如果电脑鼠在比赛时需要手动辅助,这个动作称为“碰触”。竞赛使用这三个参数,从速度﹑求解迷宫的效率和电脑鼠的可靠性三个方面来进行评分。器人称为“电脑鼠”,将电脑鼠放入迷宫并启动操作的人称为“操作员”。 在G02小组的工作中,基本完成了电脑鼠的驱动部分和算法部分:电脑鼠可以进行直线行走、90度左转、90度右转和180度后转;能准确监测左前右三个方向的挡板,能准确判断左右间距是否恰当;能进行调距运动,以保持与左右挡板的距离;能简单判断通路,并计算记忆合适通路;并按照通路完成迷宫行走。 但是,由于某些原因,小车的没有取得完美的效果:由于电机转速控制不当,小车直线行走效果不佳(参看第条);左转右转不够准确,只能依靠调整函数补偿;算法不够优秀,正在探究更好的算法。这些问题都在努力克服中,我们不会因为检查完毕就放弃小车的调试。 三、 项目整体结构: MicroMouse102 电脑老鼠,采用美国LuminaryMicro 公司生产的32 位ARM CortexM3处理器LM3S102,控制和检测红外传感器;主CPU 根据检测到的传感信号,控制电机驱动电路调整行走路径,直到到达终点。 四、 硬件部分介绍: LED 电路 电脑鼠有5 个独立的LED,通过LM3S 系统单片机的GPIO 口直接控制,如图 1.6 所示。电路采用了I/O 口灌电流的驱动方式来驱动LED,LM3S 系统单片机的灌电流为2~8mA(可配置),所以不需要驱动就可以点亮LED。GPIO 引脚输出高电平时LED 熄灭,低电平时LED 点亮。 电机驱动电路 电机采用直流减速电机,最高输出转速为800 转/分钟,工作电压为DC3V。电机驱动 电路采用专用的单相直流电动机桥式驱动芯片。 车速检测电路 车速检测用于检测并记录车体运行的路径,通过车速检测记录车体做迷宫的坐标,同 时也起到控制车速和保持左右双轮的速度一致。 检测原理:在左轮和右轮的内则都贴有的光电码盘,码盘由两种颜色组成白色和黑色。 红外发射管安装在车轮光电检测码盘的检测区域,当红外发射与接收管正对着黑色边时, 红外线没有被反射,接收管的电阻很大;当红外发射与接收管正对着白色边时,红外线被 反射,接收管的电阻很小。 红外检测电路 红外检测电路是用于迷宫挡板的检测,分为左侧、右侧、前方三个方向,三个方向的 检测原理相同,某一个方向的检测电路。 CPU 及晶振电路 电脑鼠的单片机、晶体振荡器和LDO输出原理如图所示。该单片机选用LM3S102 微处理器。 五、 软件部分介绍: 一体化红外接收头工作原理 一体式红外线接收传感器IRM8601S,它内部集成自动增益控制电路、带通滤波电路、 解码电路及输出驱动电路。当连续收到38KHz 的红外线信号时,将产生脉宽10ms 左右的 低电平。如果没有收到信号,便立即输出高电平。Send 为发射控制端,高 电平时发射38KHz 的红外信号。Out 为接收输出端,低电平表示收到信号。 检测障碍物的软件设计 根据接收头是否检测到经过反射的红外线信号,就可以判断是否存在障碍物。由于接 收头检测到信号时只产生一个负脉冲,所以只需要在检测时使能红外线发射,一次检测结 束后使能无效,程序设计参考流程图如图2.5 所示。 接收头有一定的 响应时间 开始发送38KHz 的红外线 迷宫挡板检测 调制信号产生 本设计中采用定时器1 产生38KHz 的调制信号,由PB5 输出,该端口连接到图2.3 中 的Pulse 端口。在中断中翻转PB5 输出信号,所以要产生频率为f 的脉冲,定时器的频率 要为2f。在本设计中要产生38KHz 的频率,定时器中断频率为76KHz。 程序清单 3.1 为定时器1 的初始化函数,程序清单 3.2 为中断服务函数,在这里翻转 PB5 口输出状态。 程序清单 3.1 定时器1 初始化 void PULSEIni(void) { GPIODirModeSet(GPIO_PORTB_BASE, SEND | PULSE, GPIO_DIR_MODE_OUT); // 设置为输 出 GPIOPinWrite( GPIO_PORTB_BASE,SEND | PULSE,0); // 红外线初始时停止发射 SysCtlPeripheralEnable( SYSCTL_PERIPH_TIMER1 ); // 使能定时器1 外设 TimerConfigure(TIMER1_BASE, TIMER_CFG_32_BIT_PER); // 设置定时器1 为周期触发 TimerLoadSet(TIMER1_BASE, TIMER_A, SysCtlClockGet()/76000); // 设置定时器装载值 TimerIntEnable(TIMER1_BASE, TIMER_TIMA_TIMEOUT); TimerEnable(TIMER1_BASE, TIMER_A); IntEnable(INT_TIMER1A); } 程序清单 3.2 定时器1 服务函数 void Timer1A_ISR(void) { TimerIntClear(TIMER1_BASE, TIMER_TIMA_TIMEOUT); // 清除定时器1 中断 GPIOPinWrite(GPIO_PORTB_BASE, PULSE,GPIOPinRead(GPIO_PORTB_BASE, PULSE) ^ PULSE); // 翻转GPIO B5 端口 } 抗干扰处理 红外线在空气中传播和反射受外界的干扰,如果测量距离刚好处在能够检测到信号的 临界状态,保持距离不变,传感器输出信号也可能不确定。这样就需要在软件中进行抗干 扰处理。参考程序如程序清单3.3 所示。 程序清单3.3 抗干扰处理程序 GPIOPinWrite( GPIO_PORTB_BASE,SEND , SEND); // 发送脉冲 Delay(150); // 延时 for(i=0,j=0;i<10;i++) // 检测接收信号 { if(GPIOPinRead(GPIO_PORTA_BASE, OUT_L)==0) j++; } GPIOPinWrite( GPIO_PORTB_BASE,SEND , ~SEND); // 停止发送 if(j>5) // 左边存在挡板 { } else // 左边存在支路 { } 图3.1 为抗干扰程序在Micromouse 中运行后用逻辑分析仪抓到的波形图,Pulse 为 38KHz 的输出信号,Send 高电平有效,有效时发送红外线脉冲,OUT 为一体化接收头输 …… …… 出端,该图所示为接收头探测到障碍物,软件在Send 信号无效(下降沿)前完成检测OUT 输出信号,从图中可以看出,此时正处于OUT 有效信号的中间,所以软件里延时参数能 保证正确检测到信号。 图3.1 传感器检测波形图 软件设计参考 为用一组红外实现两组参数(是否存在挡板和是否太接近挡板)的检测流程图。在Micromouse 中,用到了三组(左、前、右)反射式红外检测传感器,左边和右边的传感器各自都需要检测两组参数,而前方的传感器只需要探测有无挡板,存在挡板就必须根据策略转换行进方向,若不存在就可以继续前进。如图3.3 所示为Micromouse 红外检测的程序设计流程图。红外检测参考程序见程序清单 3.4 所示,该程序中使用了五个LED 用来指示传感器检测的状态,由于这几个LED 硬件上连接到JTAG,关于如何切换GPIO和JTAG功能参见6 使用JTAG 引脚作GPIO。 此频率仅作为参考,要根据实际检测距离来确定。结合可调电阻R1 变可以实现挡板和防碰撞的检测。 程序清单 3.4 Micromouse 红外检测函数(见附录) 电机的调速 电机的调速 直流电机的转速控制在本设计中通过PWM来控制,LM3S102 单片机则刚有两路PWM 输出,非常适合用于控制两个电机的转速。 两路PWM 是LM3S102 通用定时器0(Timer0)的三种工作模式之一,16 位PWM 模 式。该模式是将一个32 位的定时器,折分成两个16 位的定时器TimerA 和TimerB。这些 定时器为计数寄存器(GPTMTnR)递减计数,递减到0 时自动加载预装载值(GPTMTnILR)。 当然预装载值也是由用户设定,该直也就决定了定时周期,也即PWM 的输出周期。 当计数器的值与预装载值相等时,输出PWM 信号有效,当计数器的值与匹配寄存器 (GPTMnMATCHR)的值相等时,输出PWM 信号失效。通过软件可以设定PWM 输的信 号有效和信号无效的电平状态。当GPTMCTL 寄存器的TnPWML 位值为0 时,信号有效 为高电平,信号无效为低电平;TnPWML 位值为1 时,则反之。如图 4.1 所示。 输出信号 计数 0x411A 0xC350 TnPWML=0 TnPWML=1 TnEN置位 GPTMTnR=GPTMnMR GPTMTnR=GPTMnMR 时间 图 4.1 16 位PWM 模式输出 占空比的约定:占空比为在一个周期内,输出有信号有效电平占整个周期时间的比率。 在这里为以统一软件控制的约定,用户API 函数输入的占空比值越大,电机转速越快,正 向运行和反向运行都一样。 为了简化占空比输出的计算,将计数寄存器与匹配寄存器值相等时,输出的电平信号 为驱动电机的有效信号。例如将PWM 周期时间设定为60000 个时钟节拍,需要输出驱动 电机的占空比为75%,则设置匹配寄存器值为75*6000。 由于电机的转向不一样,所以电机驱动的有效电平也需要调整,通过控制TnPWML 实现。 2 程序设计 Timer0 的两路16 定时器TimerA 和TimerB 的PWM 输出引脚分别为PB0 和PB6,PB0 和PB6 分别控制左轮和右轮驱动器TA7291S 的IN1 引脚,而它们的IN2 引脚分别由GPIO 输出的PA4 和PA5 控制。 左轮的控制函数如程序清单 4.1 所示。 该函数的第1 个参数sel 为选择轮子的控制方式:0 为停止,1 为轮子向前,2 为轮子 向后;percen 参数为占空比,其最大值为99,最小值为1,对于轮子的停止控制该参数无 效。 程序清单 4.1 左轮控制函数 void LeftWheelRun(int sel,unsigned char percen) { switch(sel) { /*轮子停止转动*/ case 0: TimerDisable(TIMER0_BASE,TIMER_A); // 禁止定时器 GPIOPinWrite(GPIO_PORTA_BASE,LWC2,0xff); // 控制引脚输出高电平 GPIODirModeSet(GPIO_PORTB_BASE, LWC1, GPIO_DIR_MODE_OUT); // GPIO 输出 GPIOPinWrite(GPIO_PORTB_BASE,LWC1, 0xff); // GPIO 输出高电平 break; /*左轮向前*/ case 1: GPIOPinWrite(GPIO_PORTA_BASE,LWC2, 0xff); // PA4 输出高电平 TimerControlLevel(TIMER0_BASE,TIMER_A,true); // PWM 有效电平方向 GPIODirModeSet(GPIO_PORTB_BASE, LWC1, GPIO_DIR_MODE_HW); // PWM 输出 TimerMatchSet(TIMER0_BASE,TIMER_A,percen*600); // 设置占空比 TimerEnable(TIMER0_BASE,TIMER_A); // 使能定时器 break; /*左轮向后*/ case 2: GPIOPinWrite(GPIO_PORTA_BASE,LWC2, 0); // PA4 输出低电平 TimerControlLevel(TIMER0_BASE,TIMER_A,false); // PWM 有效电平方向 GPIODirModeSet(GPIO_PORTB_BASE, LWC1, GPIO_DIR_MODE_HW); // PWM 输出 TimerMatchSet(TIMER0_BASE,TIMER_A,percen*600); // 设置占空比 TimerEnable(TIMER0_BASE,TIMER_A); // 使能定时器 break; } } 右轮的控制函数如程序清单 4.2 所示。 该函数的第1 个参数sel 为选择轮子的控制方式:0 为停止,1 为轮子向前,2 为轮子 向后;percen 参数为占空比,其最大值为99,最小值为1,对于轮子的停止控制该参数无 效。 程序清单 4.2 右轮控制函数 void RightWheelRun(int sel,unsigned char percen) { switch(sel) { /*轮子停止转动*/ case 0: TimerDisable(TIMER0_BASE,TIMER_B); // 禁止定时器 GPIOPinWrite(GPIO_PORTA_BASE,RWC2,0xff); // 控制引脚输出高电平 GPIODirModeSet(GPIO_PORTB_BASE, RWC1, GPIO_DIR_MODE_OUT); // GPIO 输出 GPIOPinWrite(GPIO_PORTB_BASE,RWC1,0xff); // GPIO 输出高电平 break; /*右轮向后*/ case 2: GPIOPinWrite(GPIO_PORTA_BASE,RWC2, 0xff); // PA4 输出高电平 TimerControlLevel(TIMER0_BASE,TIMER_B,true); // PWM 有效电平方向 GPIODirModeSet(GPIO_PORTB_BASE, RWC1, GPIO_DIR_MODE_HW); // PWM 输出 TimerMatchSet(TIMER0_BASE,TIMER_B,percen*600); // 设置占空比 TimerEnable(TIMER0_BASE,TIMER_B); // 使能定时器 break; /*右轮向前*/ case 1: GPIOPinWrite(GPIO_PORTA_BASE,RWC2, 0); // PA4 输出低电平 TimerControlLevel(TIMER0_BASE,TIMER_B,false); // PWM 有效电平方向 GPIODirModeSet(GPIO_PORTB_BASE, RWC1, GPIO_DIR_MODE_HW); // PWM 输出 TimerMatchSet(TIMER0_BASE,TIMER_B,percen*600); // 设置占空比 TimerEnable(TIMER0_BASE,TIMER_B); // 使能定时器 break; } } 需要注意的是,当PWM 信号禁止后,其输出引脚的电平状态是保持静止时的状态(可 能为低电平也可能为高电平),导致电机可能不能停止,所以在制停电机时,需要将PWM 引脚改为GPIO 输出,并且出高电平,使电机刹车停止。 定时器PWM 初始化函数如程序清单 4.3 所示。 程序清单 4.3 定时器PWM 初始化 void PWMTimer0AIni(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // 使能定时器0 模 块 GPIODirModeSet(GPIO_PORTB_BASE, LWC1|RWC1 , GPIO_DIR_MODE_OUT); /* 控制引脚输出*/ GPIOPinWrite(GPIO_PORTB_BASE, LWC1|RWC1 , 0xff); // GPIO 输出高电 平 GPIODirModeSet(GPIO_PORTA_BASE, LWC2|RWC2 , GPIO_DIR_MODE_OUT); // 控制引脚输出 GPIOPinWrite(GPIO_PORTA_BASE, LWC2|RWC2 , 0xff); // GPIO 输出高电 平 /* 定时器配置*/ TimerConfigure(TIMER0_BASE,TIMER_CFG_16_BIT_PAIR |TIMER_CFG_A_PWM|TIMER_CFG_B_PWM); // 16 位PWM 输出 TimerControlLevel(TIMER0_BASE,TIMER_A,false); //有效信号为低电 平 TimerControlLevel(TIMER0_BASE,TIMER_B,false); TimerLoadSet(TIMER0_BASE,TIMER_A,60000); // 设定PWM 频率 TimerLoadSet(TIMER0_BASE,TIMER_B,60000); } Micromouse 车速检测 车速检测程序设计 本设计中选用了LM3S102 的PA0 和PB1 分别检测左轮和右轮的下降沿的脉冲个数, 为了快速向应检测信号,使用了下降沿触发中断。LM3S102 单片机的特点,任何一个GPIO 引脚都可以配置为中断输入,并且可以作任意设定为高电平触发、低电平触发、下降沿触 发、上升沿触发和上升或下降沿触发5 种模式。本应用中使用下降沿触发,其初始化如程 序清单 5.1 所示。 程序清单 5.1 轮子脉冲检测初始化 void WheelPulseIni(void) { // 配置引脚为输入 GPIODirModeSet(GPIO_PORTA_BASE, PULSE_R, GPIO_DIR_MODE_IN); GPIODirModeSet(GPIO_PORTB_BASE, PULSE_L, GPIO_DIR_MODE_IN); // 配置引脚下降沿触发中断 GPIOIntTypeSet(GPIO_PORTA_BASE,PULSE_R,GPIO_FALLING_EDGE); GPIOIntTypeSet(GPIO_PORTB_BASE,PULSE_L,GPIO_FALLING_EDGE); // 使能引脚输入中断 GPIOPinIntEnable(GPIO_PORTA_BASE,PULSE_R); GPIOPinIntEnable(GPIO_PORTB_BASE,PULSE_L); // 使能GPIO PA 口和GPIO PB 口中断 IntEnable(INT_GPIOA); IntEnable(INT_GPIOB); } 左右轮检测脉冲中断处函数如程序清单 5.2 所示。 程序清单 5.2 左右轮检测脉冲中断处函数 //------------------------------------------------------------------------------------ // 函数名称: GPIO_Port_A_ISR // 函数功能: 右轮检测脉冲中断处函数 //------------------------------------------------------------------------------------ void GPIO_Port_A_ISR (void) { unsigned char IntStatus; IntStatus = GPIOPinIntStatus(GPIO_PORTA_BASE,true); // 读PA 口中断状态 if(IntStatus&PULSE_R) // 是否为左轮脉冲中断 { PulCount_R++; if(PulCount_R >= RightPulse) { RightWheelRun(0, 1); WheelStop_R= 1; } GPIOPinIntClear(GPIO_PORTA_BASE,PULSE_R); // 清中断 } } //------------------------------------------------------------------------------------ // 函数名称: GPIO_Port_B_ISR // 函数功能: 左轮检测脉冲中断处函数 //------------------------------------------------------------------------------------ void GPIO_Port_B_ISR (void) { unsigned char IntStatus; IntStatus = GPIOPinIntStatus(GPIO_PORTB_BASE,true); // 读PA 口中断状态 if(IntStatus&PULSE_L) // 是否为右轮脉冲中断 { PulCount_L++; if(PulCount_L>= LeftPulse) { WheelStop_L= 1; LeftWheelRun(0, 1); } GPIOPinIntClear(GPIO_PORTB_BASE,PULSE_L); // 清中断 } } 六、 系统DV:参看附件: a) 直线行走3格+右转: 该运动中,小车首先直线行走3格(50CM),然后右转,并在碰到障碍物后右转,直线行走一格,再右转一次; b) 围绕小桌运动: 该运动中,小车围绕方形小桌绕圈,判断安全距离、检测是否存在通路,并自动修正方向。 七、 测试情况: 经检测,小车能较好的完成给定的运行任务,较为准确地直走、左右转,及180度转。小车完全可以完成探究迷宫、自动寻路等任务,并以较短的时间完成迷宫行走。 但是,小车依旧存在些问题:直走中有摆动现象,左右转也做不到90度(大概85度到95度之间),180度更不好;算法不够优秀,时间依旧较长。虽然这些问题更为琐碎更为难处理,但我们有信心,暑假间会完全克服。 八、 曾遇到的问题: 1.车轮行进速度不一: 同一函数中,小车的左右转速相差比较大,我们尝试采取了以下几种方式解决 1)。调占空比:无效,原因不明。在与别的小组交流之后,我们尝试改写了原先的驱动函数,使速度函数与正反转函数区分开:
(原函数 & 改进后的函数 请见附录)
还不太明白是为什么,把两个函数分开写了之后,发现有一定效果。
2)。脉冲补偿: 这也是演示程序的方法,在执行完一次运行任务之后,把相差的脉冲补偿到下一次任务的设定中:
PWMTimer0AIni(); // PWM初始化 PULSEIni(); // 调制信号初始化 WheelPulseIni(); // 测速初始化 while(1) { LeftPulse = 10; // 设定电机运行任务 RightPulse = 10; WheelStop_L = 0; // 清零电机停止标志位 WheelStop_R = 0; LeftWheelRun(1, 99); // 启动左右电机 RightWheelRun(1, 99); while(!(WheelStop_L && WheelStop_R))// 等待运行结束,状态在中断中改变 Check_Infrared(0); // 等待过程中进行红外检测 PulCount_L -= LeftPulse; // 误差补偿到下一次运动中 PulCount_R -= RightPulse; }
想法很好,实际行不通。 以最低误差来算,假设左轮10个脉冲,右轮9个,小车明显走出一条弧线;而当下次补偿到下次之中时,小车还是先走同样的路程,然后左轮停转,右轮转2个脉冲(上次误差+ 这次误差),表现出了明显的“一瘸一拐”的情况;而且,在我们的调试过程中,出现了不稳定的左右摆动,这是由于“半个脉冲”的问题,小车不能有效识别比较小的距离差,在最好的状态下,小车也存在计数的问题,比如,左轮刚转就开始向下的触发,右轮快转一圈才有第一次的向下触发。而且,这种误差是随机的,与小车车轮的起始位置有关,不好避免。
3).从硬件下手: 在左右电机同加固定电压时,清楚地看到转速不一样,因此,最好的办法还是从硬件上下手,通过串联电阻来解决问题。这是我们的一个想法,还没开始实施,准备在暑假时再想想软件解决办法,不行就改电路。(电机内阻,运行时电流)
2。检测信号的波形: 受灯光的影响,可以看到,红外接受得到的波形不时很好。检测初速的还好,因为幅度比较大,所以没有误判的问题;检测挡板的波形振动比较大。按照电路图,尝试着变换的电阻,取得不错的效果,以下函数足够完成判断:
( 源程序详见附录)
3。编译器的问题: 大量铁的事实证明,crosswork不好用,推荐使用Keil for ARM。前期使用的是crosswork,很多不明就里的问题,换到Keil for ARM就解决了,说明crosswork对小车的支持不够好。很多函数,在crosswork上,完全起不到效果,跑出来都知道是怎么回事。我们不是在责怪crosswork不好,但至少说明,不易于上手。 总结了一些发现的问题: 1)。不能出现汉字:还好,无非就是存盘载入时麻烦点。 2)。只能装C盘:刚开始装在D盘,整天蓝屏。 3)。头文件载入:很多头文件需要手动一一载入,还是查百度知道的,说明文件上没有。 4)。编译显示错误,不显示为什么错:开始时,发现错误就在Keil上找错。。。。。。 5)。脱机运行问题:想脱机运行?需要改很多。。。。。。 因此,不推荐用crosswork。
八、 心得体会: a) 辛苦:在刚着手时,一点头绪没有。于是从单片机、电路图、红外与电机一点点看,为了一个小车重新学了很多东西;后来接到小车,开始忙着调试写程序,面临着很多的问题。由于没有足够的资料,三人齐心协力一点点地摸索着走。经历过一连几天夜夜2、3点,也有过一整天三份外卖一起叫的时间。由于我们对自己的要求特别高,所以在每一个小的细节上都力求完美,花费了很多时间精力。 b) 兴趣:与别的课程不一样的地方,电脑鼠非常的好玩,有点像小时候的四驱车,但多了的是科技含量。看着小车在手中逐渐的成长,探路行进等功能一一实现,心中的好奇与兴奋是很难说的清的。因为兴趣,所以很多的辛苦都可以忽略不计,很多的焦头烂额也都能谈笑而过。 c) 荣耀:最喜欢做的一件事,是把小车打开,让他在寝室里自己逛着玩。没养过小孩,但相信看着自己的孩子一点点学会走路的感觉,和我们看小车的感觉一样。也许正是因为我们比别的组走过更艰难的路,所以在取得小小成绩后比别人更为的骄傲。现在的小车已经能完成基本任务了,我们期待着他能早点的大学毕业~~~~~~~~~
最后,我们在此要感谢每一位队友的努力与付出,感谢兄弟间的团结与友爱;我们也要感谢其余小组给与的帮助,彼此的交流帮助我们更好的进步;感谢助教的指导,在ARM的编程中给我们指出了最重要的方向;我们当然更要感谢张老师的教导,感谢张老师对我们错误的包容指教,和提供的广阔的学习空间。在暑假的日子里,我们会再接再厉,一定会在竞赛中取得成绩!
G02小组全体成员 2008/7/7 |
|