第十五届蓝桥杯单片机国赛
由于第十五届国赛题目难度较大,书写代码量较多,本篇文章只介绍处理运动规划的一种方法。
串口解析部分已经在另一篇文章中介绍了:传送门
本题的难点主要在这两部分,其余较简单不做过多介绍。
附件:第十五届蓝桥杯单片机国赛程序设计题目
一、模板搭建
#include <STC15F2K60S2.H>
#include "mydefine.h"
#include "string.h"
#include "stdio.h"
#include "math.h"typedef unsigned char u8;
typedef unsigned int u16;
typedef signed char s8;//延迟变量
idata u8 key_slow, seg_slow, wave_slow, iic_slow, move_slow;
//key
idata u8 key_val, key_down, key_up, key_old;
//seg
idata u8 seg_pos;
idata u8 seg_dis_mode; //0-坐标页面 1-速度页面 2-参数页面
pdata u8 seg_buf[8] = {10,10,10,10,10,10,10,10};
pdata u8 seg_point[8] = {0,0,0,0,0,0,0,0};
//led
pdata u8 led[8] = {0,0,0,0,0,0,0,0};
idata bit led_flash; //L1闪烁标志位
//超声波
idata u8 wave;
//AD
idata u16 rb2_100x; //光敏电阻放大100倍
idata bit moon_flag; //夜间场景标志位 1
//运动状态
idata u8 move_state; //0-空闲 1-运行 2-等待
idata u8 r_10x = 10, r_10x_set = 10;
idata s8 b, b_set;
idata u16 freq;
idata u16 speed_10x; //运动速度放大10倍
pdata u16 point[6] = {0,0,0,0,0,0}; //坐标数组 0~1设备初始坐标 2~3当前坐标 4~5目的地坐标
idata bit set_mode; //选择参数 0-R 1-B
//串口
idata u8 uart_tick;
pdata u8 uart_buf[10] = {0,0,0,0,0,0,0,0,0,0};
idata u8 uart_buf_index;
idata bit uart_flag;
idata bit uart_rec_flag; //串口接收到有效的坐标数据标志位
//运动解析
idata float dx_dis, dy_dis;
idata float distance; //两点间距离
idata float accross_distance; //已经走过的距离
idata bit arrive_flag; //到达终点标志位
idata bit negative_x_flag, negative_y_flag; //负数标志位
//计时变量
idata u8 time_100ms; //L1闪烁时间
idata u16 time_1s;
idata u16 time_3s; //到达终点亮3svoid move_proc() //运行状态100ms进入一次运动解析函数
{if(move_slow) return;move_slow = 1;
}void key_proc()
{if(key_slow) return;key_slow = 1;key_val = key_disp();key_down = key_val & ~key_old;key_up = ~key_val & key_old;key_old = key_val;
}void seg_proc()
{if(seg_slow) return;seg_slow = 1;
}void led_proc()
{}void wave_proc()
{if(wave_slow) return;wave_slow = 1;}void iic_proc()
{if(iic_slow) return;iic_slow = 1;
}void uart_proc()
{if(!uart_buf_index) return;if(uart_tick > 10){uart_flag = uart_tick = 0;//串口解析 memset(uart_buf, 0, uart_buf_index);uart_buf_index = 0;}
}void Timer0_Init(void) //1毫秒@12.000MHz
{TMOD &= 0xF0; //设置定时器模式TMOD |= 0x05;TL0 = 0; //设置定时初始值TH0 = 0; //设置定时初始值TF0 = 0; //清除TF0标志TR0 = 1; //定时器0开始计时
}void Timer1_Init(void) //1毫秒@12.000MHz
{AUXR &= 0xBF; //定时器时钟12T模式TMOD &= 0x0F; //设置定时器模式TL1 = 0x18; //设置定时初始值TH1 = 0xFC; //设置定时初始值TF1 = 0; //清除TF1标志TR1 = 1; //定时器1开始计时ET1 = 1; //使能定时器1中断EA = 1;
}void Timer1_Isr(void) interrupt 3
{if(++key_slow == 10) key_slow = 0;if(++seg_slow == 100) seg_slow = 0;if(++wave_slow == 160) wave_slow = 0;if(++iic_slow == 160) iic_slow = 0;if(++move_slow == 100) move_slow = 0;if(++seg_pos == 8) seg_pos = 0;seg_disp(seg_pos, seg_buf[seg_pos], seg_point[seg_pos]);led_disp(led);relay(move_state == 1);if(uart_flag) uart_tick++;if(++time_1s == 1000){time_1s = 0;freq = (TH0 << 8) | TL0;TH0 = TL0 = 0x00;speed_10x = ((3.14*r_10x*freq/1000.f)+b) * 10;}
}void Uart1_Isr(void) interrupt 4
{if(RI){uart_flag = 1;uart_tick = 0;uart_buf[uart_buf_index++] = SBUF;RI = 0;}if(uart_buf_index > 10) {uart_flag = uart_tick = 0;memset(uart_buf, 0, uart_buf_index);uart_buf_index = 0;}
}void main()
{system_init();Uart1_Init();Timer0_Init();Timer1_Init();while(1){led_proc();uart_proc();key_proc();seg_proc();move_proc();iic_proc();wave_proc();}
}
二、路径规划准备
1.串口解析坐标
uart_proc
void uart_proc()
{u8 ch, i;u16 count_x = 0, count_y = 0;bit count_flag = 0, date_useful = 1; //逗号标志位,本次接收数据有效标志位if(!uart_buf_index) return;if(uart_tick > 10){uart_flag = uart_tick = 0;//串口解析 if(strcmp(uart_buf, "?") == 0){if(!move_state)printf("Idle");else if(move_state == 1)printf("Busy");else printf("Wait");}else if(strcmp(uart_buf, "#") == 0)printf("(%u,%u)", point[2],point[3]);else if(uart_buf[0] == '(' && uart_buf[uart_buf_index-1] == ')'){for(i = 1; i < uart_buf_index - 1; i++){ch = uart_buf[i];if(ch >= '0' && ch <= '9'){!count_flag ? (count_x = count_x * 10 + ch - '0') : (count_y = count_y * 10 + ch - '0');}else if(ch == ',')count_flag = 1;else{date_useful = 0;break;}}if(date_useful && count_flag){if(!move_state){printf("Got it");point[4] = count_x;point[5] = count_y;uart_rec_flag = 1; //接收有效数据标志位}else printf("Busy");}elseprintf("Error");}elseprintf("Error");memset(uart_buf, 0, uart_buf_index);uart_buf_index = 0;}
}
2.按键S4切换设备运行状态
- 空闲状态切换运行状态:
串口接收到正确的坐标后按下S4- 运行状态切换等待状态:
1.有障碍物(超声波测距<30)直接切换等待状态 2.无障碍物手动按下S4切换等待状态- 等待状态切换运行状态:
无障碍物(超声波测距>=30)按下按键S4切换等待状态- 运行状态切换空闲状态:
设备运动到目的地自动切换
三、路径规划
1.路径规划准备
要求出设备在运行状态中的实时坐标有很多种方法,大差不差的都是求设备实时运动的距离(图中对应AC)与AB直线距离的比值与其他两条边的比例计算实时坐标C(point[2],point[3]): A C A B \frac{AC}{AB} ABAC = p o i n t [ 2 ] d x \frac{point[2]}{dx} dxpoint[2] = p o i n t [ 3 ] d y \frac{point[3]}{dy} dypoint[3]
(dx = point[4] - point[0],dy = point[5] - point[1])
point[2] = d x ∗ A C A B \frac{dx*AC}{AB} ABdx∗AC
point[3] = d y ∗ A C A B \frac{dy*AC}{AB} ABdy∗AC
有了上面推出来的公式,就要在接收到正确的坐标数据,按下按键S4后进行预处理了,也就是求出上面公式需要的dx
,dy
,distance
。
由于坐标数据的范围是0~999cm,为了防止unsigned int
型数据的平方导致数据溢出,这边的dx
,dy
,distance
均定义为float
型。
key_proc
idata float distance; //两点间距离
idata bit negative_x_flag, negative_y_flag; //负数标志位
void key_proc()
{float dx = 0, dy = 0;if(key_slow) return;key_slow = 1;key_val = key_disp();key_down = key_val & ~key_old;key_up = ~key_val & key_old;key_old = key_val;switch(key_down){case 4:if(!move_state && uart_rec_flag) //空闲状态下接收到有效坐标{uart_rec_flag = 0; //重置标志位dx = point[4] - point[0];dy = point[5] - point[1];distance = sqrt(dx * dx + dy * dy);move_state = 1;//切换运行状态}break;}
}
point[2] = d x ∗ A C A B \frac{dx*AC}{AB} ABdx∗AC
point[3] = d y ∗ A C A B \frac{dy*AC}{AB} ABdy∗AC
为了方便后续计算,可以直接定义变量dx_dis = d x A B \frac{dx}{AB} ABdx, dy_dis = d y A B \frac{dy}{AB} ABdy;
point[2] = dx_dis*AC
point[3] = dy_dis*AC
key_proc
void key_proc()
{float dx = 0, dy = 0;if(key_slow) return;key_slow = 1;key_val = key_disp();key_down = key_val & ~key_old;key_up = ~key_val & key_old;key_old = key_val;switch(key_down){case 4:if(!move_state && uart_rec_flag) //空闲状态下接收到有效坐标{uart_rec_flag = 0; //重置标志位dx = point[4] - point[0];dy = point[5] - point[1];distance = sqrt(dx * dx + dy * dy);dx_dis = dx / distance;dy_dis = dy / distance;move_state = 1;//切换运行状态}}
}
由于接收到的终点坐标(point[4],point[5])并不一定大于起点坐标(point[0],point[1]),所以在计算的时候还需要考虑绝对值。
key_proc
idata float dx_dis, dy_dis;
idata float distance;//两点间距离
idata bit negative_x_flag, negative_y_flag; //负数标志位
void key_proc()
{float dx = 0, dy = 0;if(key_slow) return;key_slow = 1;key_val = key_disp();key_down = key_val & ~key_old;key_up = ~key_val & key_old;key_old = key_val;switch(key_down){case 4:switch(move_state){case 0:if(uart_rec_flag) //空闲状态下接收到有效坐标{uart_rec_flag = 0; //重置标志位//判断绝对值negative_x_flag = (point[4] < point[0]);dx = (!negative_x_flag) ? (point[4] - point[0]) : (point[0] - point[4]);negative_y_flag = (point[5] < point[1]);dy = (!negative_y_flag) ? (point[5] - point[1]) : (point[1] - point[5]);//求两点距离已经比例值x / a 和 y / b(怕unsigned int型数据溢出)distance = sqrt(dx * dx + dy * dy);dx_dis = dx / distance;dy_dis = dy / distance;move_state = 1;//切换运行状态}break;case 1: //无障碍物时运行切换等待move_state = 2; break;case 2: //无障碍物时等待切换运行if(wave > 30)move_state = 1;break;}break;case 5://重置if(!move_state){point[0] = point[1] = point[2] = point[3] = 0;}break;}
}
2.路径规划
point[2] = dx_dis*AC
point[3] = dy_dis*AC
接下来要求的就是设备实时运动距离AC,求直线距离AC有很多种方法,这边介绍一种简单的方法:由于已知设备运行的速度V,可以限定特定时间段(比如:100ms),设备在100ms内运动的距离也就是V * 100ms
,设备累计运动距离也就是T * V * 100ms
(T是设备运动了多少个100ms)
void move_proc() //运行状态100ms进入一次运动解析函数
{if(move_slow) return;move_slow = 1;if(move_state == 1){/** 速度定义时放大了10倍 使用时先/10.f变成正常值* 由于速度的单位是cm/s 1s=1000ms 运动解析函数的调度是100ms调度1次* 所以每次进入运动解析函数时所经过的直线距离=速度/10.f=speed_10x/100.f* 再根据三角形三条边在经过相同的时间所走的比例相同可以求出当前位置*/accross_distance += (speed_10x / 100.f); //printf("%.1f\n",accross_distance); //dbug/** 如果当前走过的距离<两点间的距离 计算x,y走过的比例值* 原理:假设三角形三条边分别为a,b,c(斜边)* 某时间走过的直线距离为s,那么当前位置(x,y)满足以下公式* s / c = x / a = y / b* 下面的公式中之所以增加了标志位negative_x_flag和negative_y_flag* 是因为设备接收的目的地距离不一定大于初始距离,要考虑绝对值* ps:本次测评好像没有测目的地距离小于初始距离的情况,可以偷懒*/if(accross_distance < distance){point[2] = (!negative_x_flag) ? (point[0] + accross_distance * dx_dis): (point[0] - accross_distance * dx_dis);point[3] = (!negative_y_flag) ? (point[1] + accross_distance * dy_dis): (point[1] - accross_distance * dy_dis);//printf("%u,%u\n",point[2],point[3]); //dbug}else //到达目的地时更新初始坐标以及重置已经走过的距离{arrive_flag = 1; //到达标志位(LED亮使用)move_state = 0; //切换为空闲状态accross_distance = 0; //重置已经走过的距离point[0] = point[2] = point[4];point[1] = point[3] = point[5];}}
}