一、简介
课设题目,这里简单总结一下思路。
二、功能分析
- 任意穿行 (寻找终点)
实现对步进电机的控制,包括正转反转及调速;对电机动作组进行封装,实现各种标准动作(前进一格,左转,右转,掉头,修正)。
- 记录信息 (遍历迷宫)
通过建立8*8迷宫数组,在低四位以绝对方向记录墙的信息,高四位记录来的方向。以便后期分析和回溯。
- 最短路径 (BFS算法)
利用广度优先算法计算等高表,从终点反向查找最短路径。
三、硬件驱动程序
1、数码管驱动
//数码管驱动程序======================================***it tube1 = P4^3;***it tube2 = P4^2;uchar table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x88,0x83,0xc6,0xa1,0x86,0x8e,0x00};void view1(uchar num){ tube1=1;tube2=0;P0=table[num];}void view2(uchar num){ tube1=0;tube2=1;P0=table[num];}1234567891011 2、红外传感器组驱动
//红外传感器组驱动程序=================================***it A0 = P4^0;***it A1 = P2^0;***it A2 = P2^7;***it irR1 = P2^1;***it irR2 = P2^2;***it irR3 = P2^3;***it irR4 = P2^4;***it irR5 = P2^5;bit irC=0, irL=0, irR=0, irLU=0, irRU=0;void init_tim2(uint us){ // 初始化TIM2 EA = 1; ET2 = 1; TH2 = RCAP2H = (65536-us)/256; TL2 = RCAP2L = (65536-us)%256; TR2 = 1;}void ir_on(uchar num){ // 开启第num-1号红外发射级 A0 = (num)&0x01; A1 = (num)&0x02; A2 = (num)&0x04;}void ir_test(){ // 测试某个方向的红外 while(1){ if(irL||irR||irC){ beep = 0; }else{ beep = 1; } }}void tim2() interrupt 5{ // TIM2中断服务函数 static bit ir = 0; static unsigned char n=1; TF2 = 0; if(!ir) { ir_on(n-1); }else{ if(n==1){ if(irR1) irC=0; else irC=1; }else if(n==2){ if(irR2) irLU=0; else irLU=1; }else if(n==3){ if(irR3) irL=0; else irL=1; }else if(n==4){ if(irR4) irR=0; else irR=1; }else if(n==5){ if(irR5) irRU=0; else irRU=1; } } if(ir) n++; if(n>5) n=1; ir=~ir;}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 3、步进电机驱动
//步进电机驱动程序====================================uchar forward[]={0x11,0x33,0x22,0x66,0x44,0xcc,0x88,0x99};uchar reverse[]={0x11,0x99,0x88,0xcc,0x44,0x66,0x22,0x33};uchar for_rev[]={0x11,0x93,0x82,0xc6,0x44,0x6c,0x28,0x39};static uint step = 0;void delay(uint z){ // 延时函数 uchar i, j; while(--z){ _nop_(); i=2; j=199; do{ while(--j); }while(--i); }}uchar fix_path(uchar i){ // 路线修正 step = 0; while((irRU||irLU) && !irC){ if(irLU) P1 = forward[i++] | 0xf0; else if(irRU) P1 = reverse[i++] | 0x0f; if(i==8) i=0; step++; delay(2); } return i;}void go_rel(uchar relD){ // 向某个相对方向前进一格 uchar num; uchar i, j; if(relD == 0) num = 104; if(relD == 1||relD == 3) num = 48; if(relD == 2) num = 100; for(j=0;j ; if(relD == 3) P1 = reverse; if(relD == 0){ P1 = for_rev; i = fix_path(i); } if(relD==0) num -= step/16; delay(2); } }}
四、算法分析
1、遍历迷宫
遍历迷宫(回溯法)的一般流程如下:
1.1、定义绝对方向和相对方向
//算法部分=====================================================/** 绝对方向(以迷宫左上为参照): 相对方向: * 0 0:直行 * 3 1 1:右转 * 2 2:掉头 * 3:左转**//** 工具函数集(0):一些必要的初始化及通用函数 * 初始化数组 * 高四位记录来的方 * 低四位依据绝对方向记录记录四周信息(默认来的方向无墙)**/void init_map(uchar map[SIZE][SIZE]){ // 初始化数组 uchar i, j; for(i=0;i[j] = 0xff; } }}uchar abs_to_rel(uchar absD, uchar absD_t){ // 绝对方向->相对方向 //absD:当前绝对方向 //absD_t:期望转换到哪个绝对方向 uchar relD = 0; relD = (absD_t - absD) % 4; if(relD > 127) relD += 4; //返回相对方向 return relD;}12345678910111213141516171819202122232425262728291.2、记录当前坐标的迷宫信息
当前坐标为初始值时(说明没走过)记录信息,高四位记录来的方向,低四位依据绝对方向记录四周信息。由于在整个行进过程中(遍历阶段)迷宫数组的值只会被写入一次,这保证了在高四位中始终记录的是最开始时小车进入的方向。依此特点,小车可以在回溯或冲刺时有效安全地使用这些信息。
/** 关键函数集(1):记录信息 * 当前坐标为初始值时,记录信息: * 高四位记录来的方向 * 低四位依据绝对方向记录记录四周信息(默认来的方向无墙)**/uchar read_ir(uchar relD){ // 读取相对方向的红外值 if(relD == 0) return irC; // 前红外 if(relD == 1) return irR; // 右红外 if(relD == 3) return irL; // 左红外 return 0;}void collect_info(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 记录信息 if(maze[now.y][now.x] == 0xff){ // 如果当前坐标未被写入,则写入(整个过程中低四位只写入一次) uchar val_wall = 0xf0; uchar k = 0; uchar i; for(i=0; i<4; i++){ // 循环判断4个绝对方向 k = read_ir(abs_to_rel(absD, i)); val_wall |= (k<1.3、选择一个方向
更具记录的迷宫数组信息,选择一个合适的方向。扫描四轴,如果存在没走过的格子,直接返回这个方向。如果四周都走过,那么读取本坐标的高四位来的方向,选择回溯方向。
/** 关键函数集(2):选择方向 * 根据记录的迷宫数组信息,选择合适的方向 * 扫描四周,如果存在没走过的格子,直接走之 * 如果四周都走过,那么读取高四位来的方向,进行回溯**/uchar is_path(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 判断此方向是否连通 return !((maze[now.y][now.x]>>absD)&0x01);}uchar is_new(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 判断此方向是否为新格子 if(absD==0) return (maze[now.y-1][now.x]>>4)==0x0f; if(absD==1) return (maze[now.y][now.x+1]>>4)==0x0f; if(absD==2) return (maze[now.y+1][now.x]>>4)==0x0f; if(absD==3) return (maze[now.y][now.x-1]>>4)==0x0f; return 0;}uchar search_dir(uchar maze[SIZE][SIZE], xyTypeDef now, uchar flag){ // 选择方向 uchar i; uchar pre = maze[now.y][now.x] >> 4; uchar back; if(!flag){ // 冲刺标记位,如果不冲刺,则需要扫描四个方向是否可走 for(i=0; i<4; i++){ if(is_path(maze, now, i) && is_new(maze, now, i)){ // 判断该方向是否有墙和是否走过 return i; } } } // 如果冲刺或者四个方向不可走,直接读取高四位进行冲刺引导或回溯 if(pre<=1) back = pre+2; if(pre>=2) back = pre-2; return back;}123456789101112131415161718192021222324252627282930311.4、向这个方向前进
这方面没啥说的,动起来就对了。
/** 关键函数集(3):执行 * 根据上一步得到的信息,执行**/void go_to_next(xyTypeDef *now, uchar *absD, uchar absD_t){ uchar relD = abs_to_rel(*absD, absD_t); // 刷新当前坐标和当前绝对方向 if(absD_t == 0) (now->y)--; if(absD_t == 1) (now->x)++; if(absD_t == 2) (now->y)++; if(absD_t == 3) (now->x)--; *absD = absD_t; // 执行 go_rel(relD); if(relD != 0) go_rel(0);}
2、最短路径
2.1、建立等高表
等高表原理类似于等高线,在选定起点后,与其相连的点可以设置为该点高度值+1,利用广度优先算法遍历迷宫格,本迷宫的等高表数值为:
将其可视化后,它的形状类似一座山。
2.2、反向查找
既然等高表概念理解了,那么寻找最短路径就是从山顶扔下一颗球,由重力选择的路径即为最短路径。(在机器学习领域会有一个局部最优概念,这里不阐述)
/** 关键函数集(4):寻找最优路径 * 回溯到起点后,开始根据广度优先算法寻找最优路径 (1)首先根据之前记录的迷宫数组建立等高表 (2)根据等高表反向记录最优路径 (3)将路径记录到迷宫数组的高四位,这样可以复用之前的结构,将小车引导至终点**/bit bfs(uchar maze[SIZE][SIZE],xyTypeDef beg, xyTypeDef end){ // 广度优先算法 //参数初始化 uchar height[SIZE][SIZE] = {0xff}; // 初始化等高表 xyTypeDef queue_xy[15]; // 初始化队列(经过测试,长度管够) uchar queue_len = 1; // 初始化队列长度标记量 xyTypeDef queue_head; // 初始化队头 uchar j, i; // 初始化迭代下标 xyTypeDef temp; // 初始化一个临时变量 xyTypeDef now; // 初始化当前位置 init_map(height); height[beg.y][beg.x] = 0; queue_xy[0].x = beg.x; queue_xy[0].y = beg.y; while(queue_len>0){ // 当队列不为空 queue_head = queue_xy[0]; // 队首元素出队 queue_len--; for(j=0; j127||temp.y>127) continue; if(is_path(maze, queue_head, i) && height[temp.y][temp.x]==255){ // 如果该坐标连通且第一次访问 height[temp.y][temp.x] = height[queue_head.y][queue_head.x] + 1; // 该坐标等高表+1 queue_xy[queue_len++] = temp; // 该坐标入队 } } } // 等高表建立完毕,开始反向查找最优路径 now.x = end.x; now.y = end.y; while(!(now.x==0&& now.y==0)){ // 如果还没到起点 for(i=0; i<4; i++){ // 扫描四个方向 temp = now; if(i == 0) temp.y = now.y-1; if(i == 1) temp.x = now.x+1; if(i == 2) temp.y = now.y+1; if(i == 3) temp.x = now.x-1; if(temp.x>127||temp.y>127) continue; if(is_path(maze, now, i) && (height[temp.y][temp.x]==height[now.y][now.x]-1)){ // 如果该坐标连通且高度递减 maze[temp.y][temp.x] |= 0xf0; // 初始化迷宫数组该坐标的高四位 maze[temp.y][temp.x] &= ((i<<4)|0x0f); // 将这个方向写入高四位 now = temp; // 切换焦点 break; } } } return 1;}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960613、整体架构
int main() { uchar maze[SIZE][SIZE] = {0xff}; // 定义迷宫数组,整个迷宫的数据均存储在这里 uchar absD = 1; // 定义开始时的绝对方向 uchar absD_t; // 定义目标方向(暂无) bit flag = 0; // 定义冲刺标记量 xyTypeDef beg; // 定义起点坐标 xyTypeDef now; // 定义当前坐标 xyTypeDef end; // 定义终点坐标 beg.x = 0; beg.y = 0; now = beg; end.x = 7; end.y = 7; init_map(maze); init_tim2(5000); //ir_test(); while(1){ if(now.x==end.x&&now.y==end.y&&flag){ // 如果冲刺完毕,退出程序 beep = 0; delay(100); beep = 1; break; } if(now.x==beg.x&&now.y==beg.y&&absD==3){ // 如果回溯到起点,开始冲刺 beep = 0; delay(100); beep = 1; flag = bfs(maze, beg, end); } collect_info(maze, now, absD); // (1)记录迷宫信息 absD_t = search_dir(maze, now, flag); // (2)确定下一个方向 go_to_next(&now, &absD, absD_t); // (3)执行该方向 delay(1000); // 延时,便于手扶 view1(now.x); view2(now.y); // 刷新数码管显示当前坐标 } while(1);}1234567891011121314151617181920212223242526272829303132333435363738五、完整代码
#include #include //相关变量初始化===============================#define uchar unsigned char#define uint unsigned int#define SIZE 8sfr P4 = 0xe8;***it beep = P3^7;typedef struct { uchar x; uchar y;}xyTypeDef;//数码管驱动程序======================================***it tube1 = P4^3;***it tube2 = P4^2;uchar table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x88,0x83,0xc6,0xa1,0x86,0x8e,0x00};void view1(uchar num){ tube1=1;tube2=0;P0=table[num];}void view2(uchar num){ tube1=0;tube2=1;P0=table[num];}//红外传感器组驱动程序=================================***it A0 = P4^0;***it A1 = P2^0;***it A2 = P2^7;***it irR1 = P2^1;***it irR2 = P2^2;***it irR3 = P2^3;***it irR4 = P2^4;***it irR5 = P2^5;bit irC=0, irL=0, irR=0, irLU=0, irRU=0;void init_tim2(uint us){ // 初始化TIM2 EA = 1; ET2 = 1; TH2 = RCAP2H = (65536-us)/256; TL2 = RCAP2L = (65536-us)%256; TR2 = 1;}void ir_on(uchar num){ // 开启第num-1号红外发射级 A0 = (num)&0x01; A1 = (num)&0x02; A2 = (num)&0x04;}void ir_test(){ // 测试某个方向的红外 while(1){ if(irL||irR||irC){ beep = 0; }else{ beep = 1; } }}//步进电机驱动程序====================================uchar forward[]={0x11,0x33,0x22,0x66,0x44,0xcc,0x88,0x99};uchar reverse[]={0x11,0x99,0x88,0xcc,0x44,0x66,0x22,0x33};uchar for_rev[]={0x11,0x93,0x82,0xc6,0x44,0x6c,0x28,0x39};static uint step = 0;void delay(uint z){ // 延时函数 uchar i, j; while(--z){ _nop_(); i=2; j=199; do{ while(--j); }while(--i); }}uchar fix_path(uchar i){ // 路线修正 step = 0; while((irRU||irLU) && !irC){ if(irLU) P1 = forward[i++] | 0xf0; else if(irRU) P1 = reverse[i++] | 0x0f; if(i==8) i=0; step++; delay(2); } return i;}void go_rel(uchar relD){ // 向某个相对方向前进一格 uchar num; uchar i, j; if(relD == 0) num = 104; if(relD == 1||relD == 3) num = 48; if(relD == 2) num = 100; for(j=0;j<num;j++){ for(i=0;i<8;i++){ if(relD == 1||relD == 2) P1 = forward[i]; if(relD == 3) P1 = reverse[i]; if(relD == 0){ P1 = for_rev[i]; i = fix_path(i); } if(relD==0) num -= step/16; delay(2); } }}//算法部分=====================================================/** 绝对方向(以迷宫左上为参照): 相对方向: * 0 0:直行 * 3 1 1:右转 * 2 2:掉头 * 3:左转**//** 工具函数集(0):一些必要的初始化及通用函数 * 初始化数组 * 高四位记录来的方 * 低四位依据绝对方向记录记录四周信息(默认来的方向无墙)**/void init_map(uchar map[SIZE][SIZE]){ // 初始化数组 uchar i, j; for(i=0;i<SIZE;i++){ for(j=0;j<SIZE;j++){ map[i][j] = 0xff; } }}uchar abs_to_rel(uchar absD, uchar absD_t){ // 绝对方向->相对方向 //absD:当前绝对方向 //absD_t:期望转换到哪个绝对方向 uchar relD = 0; relD = (absD_t - absD) % 4; if(relD > 127) relD += 4; //返回相对方向 return relD;}/** 关键函数集(1):记录信息 * 当前坐标为初始值时,记录信息: * 高四位记录来的方向 * 低四位依据绝对方向记录记录四周信息(默认来的方向无墙)**/uchar read_ir(uchar relD){ // 读取相对方向的红外值 if(relD == 0) return irC; // 前红外 if(relD == 1) return irR; // 右红外 if(relD == 3) return irL; // 左红外 return 0;}void collect_info(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 记录信息 if(maze[now.y][now.x] == 0xff){ // 如果当前坐标未被写入,则写入(整个过程中低四位只写入一次) uchar val_wall = 0xf0; uchar k = 0; uchar i; for(i=0; i<4; i++){ // 循环判断4个绝对方向 k = read_ir(abs_to_rel(absD, i)); val_wall |= (k<<i); } maze[now.y][now.x] &= val_wall; // 将墙的信息写入低四位 maze[now.y][now.x] &= ((absD<<4)|0x0f); // 将来的方向写入高四位 }}/** 关键函数集(2):选择方向 * 根据记录的迷宫数组信息,选择合适的方向 * 扫描四周,如果存在没走过的格子,直接走之 * 如果四周都走过,那么读取高四位来的方向,进行回溯**/uchar is_path(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 判断此方向是否连通 return !((maze[now.y][now.x]>>absD)&0x01);}uchar is_new(uchar maze[SIZE][SIZE], xyTypeDef now, uchar absD){ // 判断此方向是否为新格子 if(absD==0) return (maze[now.y-1][now.x]>>4)==0x0f; if(absD==1) return (maze[now.y][now.x+1]>>4)==0x0f; if(absD==2) return (maze[now.y+1][now.x]>>4)==0x0f; if(absD==3) return (maze[now.y][now.x-1]>>4)==0x0f; return 0;}uchar search_dir(uchar maze[SIZE][SIZE], xyTypeDef now, uchar flag){ // 选择方向 uchar i; uchar pre = maze[now.y][now.x] >> 4; uchar back; if(!flag){ // 冲刺标记位,如果不冲刺,则需要扫描四个方向是否可走 for(i=0; i<4; i++){ if(is_path(maze, now, i) && is_new(maze, now, i)){ // 判断该方向是否有墙和是否走过 return i; } } } // 如果冲刺或者四个方向不可走,直接读取高四位进行冲刺引导或回溯 if(pre<=1) back = pre+2; if(pre>=2) back = pre-2; return back;}/** 关键函数集(3):执行 * 根据上一步得到的信息,执行**/void go_to_next(xyTypeDef *now, uchar *absD, uchar absD_t){ uchar relD = abs_to_rel(*absD, absD_t); // 刷新当前坐标和当前绝对方向 if(absD_t == 0) (now->y)--; if(absD_t == 1) (now->x)++; if(absD_t == 2) (now->y)++; if(absD_t == 3) (now->x)--; *absD = absD_t; // 执行 go_rel(relD); if(relD != 0) go_rel(0);}/** 关键函数集(4):寻找最优路径 * 回溯到起点后,开始根据广度优先算法寻找最优路径 (1)首先根据之前记录的迷宫数组建立等高表 (2)根据等高表反向记录最优路径 (3)将路径记录到迷宫数组的高四位,这样可以复用之前的结构,将小车引导至终点**/bit bfs(uchar maze[SIZE][SIZE],xyTypeDef beg, xyTypeDef end){ // 广度优先算法 //参数初始化 uchar height[SIZE][SIZE] = {0xff}; // 初始化等高表 xyTypeDef queue_xy[15]; // 初始化队列(经过测试,长度管够) uchar queue_len = 1; // 初始化队列长度标记量 xyTypeDef queue_head; // 初始化队头 uchar j, i; // 初始化迭代下标 xyTypeDef temp; // 初始化一个临时变量 xyTypeDef now; // 初始化当前位置 init_map(height); height[beg.y][beg.x] = 0; queue_xy[0].x = beg.x; queue_xy[0].y = beg.y; while(queue_len>0){ // 当队列不为空 queue_head = queue_xy[0]; // 队首元素出队 queue_len--; for(j=0; j<queue_len; j++){ // (由于用数组模拟队列,所以需要将所有元素手动前移) queue_xy[j] = queue_xy[j+1]; } for(i=0; i<4; i++){ // 判断四个方向 temp = queue_head; if(i == 0) temp.y = queue_head.y-1; if(i == 1) temp.x = queue_head.x+1; if(i == 2) temp.y = queue_head.y+1; if(i == 3) temp.x = queue_head.x-1; if(temp.x>127||temp.y>127) continue; if(is_path(maze, queue_head, i) && height[temp.y][temp.x]==255){ // 如果该坐标连通且第一次访问 height[temp.y][temp.x] = height[queue_head.y][queue_head.x] + 1; // 该坐标等高表+1 queue_xy[queue_len++] = temp; // 该坐标入队 } } } // 等高表建立完毕,开始反向查找最优路径 now.x = end.x; now.y = end.y; while(!(now.x==0&& now.y==0)){ // 如果还没到起点 for(i=0; i<4; i++){ // 扫描四个方向 temp = now; if(i == 0) temp.y = now.y-1; if(i == 1) temp.x = now.x+1; if(i == 2) temp.y = now.y+1; if(i == 3) temp.x = now.x-1; if(temp.x>127||temp.y>127) continue; if(is_path(maze, now, i) && (height[temp.y][temp.x]==height[now.y][now.x]-1)){ // 如果该坐标连通且高度递减 maze[temp.y][temp.x] |= 0xf0; // 初始化迷宫数组该坐标的高四位 maze[temp.y][temp.x] &= ((i<<4)|0x0f); // 将这个方向写入高四位 now = temp; // 切换焦点 break; } } } return 1;}int main() { uchar maze[SIZE][SIZE] = {0xff}; // 定义迷宫数组,整个迷宫的数据均存储在这里 uchar absD = 1; // 定义开始时的绝对方向 uchar absD_t; // 定义目标方向(暂无) bit flag = 0; // 定义冲刺标记量 xyTypeDef beg; // 定义起点坐标 xyTypeDef now; // 定义当前坐标 xyTypeDef end; // 定义终点坐标 beg.x = 0; beg.y = 0; now = beg; end.x = 7; end.y = 7; init_map(maze); init_tim2(5000); //ir_test(); while(1){ if(now.x==end.x&&now.y==end.y&&flag){ // 如果冲刺完毕,退出程序 beep = 0; delay(100); beep = 1; break; } if(now.x==beg.x&&now.y==beg.y&&absD==3){ // 如果回溯到起点,开始冲刺 beep = 0; delay(100); beep = 1; flag = bfs(maze, beg, end); } collect_info(maze, now, absD); // (1)记录迷宫信息 absD_t = search_dir(maze, now, flag); // (2)确定下一个方向 go_to_next(&now, &absD, absD_t); // (3)执行该方向 delay(1000); // 延时,便于手扶 view1(now.x); view2(now.y); // 刷新数码管显示当前坐标 } while(1);}void tim2() interrupt 5{ // TIM2中断服务函数 static bit ir = 0; static unsigned char n=1; TF2 = 0; if(!ir) { ir_on(n-1); }else{ if(n==1){ if(irR1) irC=0; else irC=1; }else if(n==2){ if(irR2) irLU=0; else irLU=1; }else if(n==3){ if(irR3) irL=0; else irL=1; }else if(n==4){ if(irR4) irR=0; else irR=1; }else if(n==5){ if(irR5) irRU=0; else irRU=1; } } if(ir) n++; if(n>5) n=1; ir=~ir;}
|
|
2021-10-21 16:31:59
评论
举报
|
|
|