【蓝桥杯嵌入式】【复盘】第15届国赛真题
1. 前言
最近在准备16届的蓝桥杯嵌入式赛道的国赛,打算出一个系列的博客,记录STM32G431RBT6这块比赛用板上所有模块可能涉及到的所有考点,如果有错误或者遗漏欢迎各位大佬斧正。
本系列博客会分为以下两大类:
1.1. 单独模块的讲解
在这部分,我会分享自己总结的各个模块的相关配置、代码书写模板,涉及到的大致框架如下:
这个框架后续可能会不断更新,欢迎各位给出建议。
这一大类相关的文章链接如下(持续补充中):
【蓝桥杯嵌入式】【模块】一、系统初始化-CSDN博客
【蓝桥杯嵌入式】【模块】二、LED相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】三、LCD相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】四、按键相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】五、ADC相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】六、PWM相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】七、IIC相关配置及代码模板-CSDN博客
【蓝桥杯嵌入式】【模块】八、UART相关配置及代码模板-CSDN博客
1.2. 蓝桥杯各届的真题、模拟题复盘及个人答案
在这一部分,我会分享个人练过的所有题的复盘思路及代码,每篇文章结构如下:
这一大类相关的文章链接如下(持续补充中):
【蓝桥杯嵌入式】【复盘】第13届国赛真题_蓝桥杯嵌入式13届国赛题-CSDN博客
【蓝桥杯嵌入式】【复盘】第14届国赛真题-CSDN博客
【蓝桥杯嵌入式】【复盘】第15届省赛真题-CSDN博客
以下是本篇博客正文内容:
2. 4t评测结果
4t平台网址:学单片机,上4T - 4T评测网
错了一个评测点,源于对赛题要求的理解方式不确定,这里我先保持自己的题目理解方法。
3. 个人解答代码
仓库地址:lanqiao/15_true at main · Dukiyaaa/lanqiao
如果不嫌麻烦的话,可以点个star~
4. 重点和易错点
这一套题,根据我个人的做题经历,我认为应该关注的有以下几点:
1. 按键双长按。
2. 路径行进相关的逻辑
4.1 按键双长按
我的核心代码如下:
void key_task(void)
{for(uint8_t i = 0;i < 4;i++){if(key_buf[i].key_is_down == 1){switch(i){case 0:key_buf[i].key_is_down = 0;if(ST == 0){// 收到了目的地坐标,切换为运行,否则不变if((FP[0] != -1) && (FP[1] != -1)){ST = 1;}else{ST = 0;}}else if(ST == 1){ST = 2;}else if(ST == 2){ST = 1;}break;case 1:key_buf[i].key_is_down = 0;lcd_num++;if(lcd_num > 2){lcd_num = 0;}if(lcd_num == 1){is_select_R = 1;}break;case 2:key3_step = 1;break;case 3:key4_step = 1;break;}}if(key_buf[2].key_is_down == 0){if(key3_step == 1){key3_step = 0;// 按下了if(key_buf[2].key_is_long == 0){// 短按// 参数界面起作用if(lcd_num == 1){is_select_R ^= 1;}}else if(key_buf[2].key_is_long == 1){// 长按if(lcd_num == 0){if(ST == 0){if(key_buf[3].key_is_long == 1){// 清0:TS和TTTS = 0;TT = 0;}}}}}key_buf[2].key_time = 0;key_buf[2].key_is_long = 0;}if(key_buf[3].key_is_down == 0){if(key4_step == 1){key4_step = 0;// 按下了if(key_buf[3].key_is_long == 0){// 短按// 参数界面起作用if(lcd_num == 1){if(is_select_R == 1){R += 0.1f;if(R > 2.09f){R = 1.0f;}}else if(is_select_R == 0){B += 10;if(B > 100){B = 10;}}}}else if(key_buf[3].key_is_long == 1){// 长按if(lcd_num == 0){if(ST == 0){if(key_buf[2].key_is_long == 1){// 清0:TS和TTTS = 0;TT = 0;}}}}}key_buf[3].key_time = 0;key_buf[3].key_is_long = 0;}}
}
基于状态机思想,在按键3/4按下后进入下一个状态,下一个状态的逻辑在按键松开后触发,松开后判断是长按还是短按,如果是长按,则需要同步判断另外一个按键是否也是长按。
4.2 路径行进相关的逻辑
这部分是这套题的重点和难点,感觉已经无关乎于单片机平台了,核心的考察在于代码逻辑。
我的核心代码如下:
void work_task(void)
{ // 更新TP,如果当前的TP被删除了则需要更新if((pos[TP_ptr] == -1) && (pos[TP_ptr + 1] == -1)){if(pos_ptr == 0){}else{TP_ptr += 2;if(TP_ptr >= 399){TP_ptr = 0;}}}else{TP[0] = pos[TP_ptr];TP[1] = pos[TP_ptr + 1];}if(ST == 1){// 更新TP后,需要计算当前路径的方向参数if(check_distance_flag == 0){check_distance_flag = 1;distance = sqrt((TP[0] - CP[0]) * (TP[0] - CP[0]) + (TP[1] - CP[1]) * (TP[1] - CP[1]));cos_theta = (TP[0] - CP[0]) / distance;sin_theta = (TP[1] - CP[1]) / distance;left_distance = distance;second_flag = 0;}// 每秒执行的行进逻辑if(second_flag == 1){second_flag = 0;left_distance = left_distance - SE;TT++;TS += SE;if(left_distance < 0){// 已抵达TP,TP需要更新为下一个路径点CP[0] = TP[0];CP[1] = TP[1];pos[TP_ptr] = -1;pos[TP_ptr + 1] = -1;TP_ptr += 2;if(TP_ptr >= 399){TP_ptr = 0;}TP[0] = pos[TP_ptr];TP[1] = pos[TP_ptr + 1];RN--;if(RN == 0){CP[0] = FP[0];CP[1] = FP[1];TS += left_distance;}check_distance_flag = 0;// 到达目的地相关的逻辑if((CP[0] == FP[0]) && (CP[1] == FP[1])){ST = 0;FP[0] = -1;FP[1] = -1;TP_ptr = 0;pos_ptr = 0;RN = 0;}}else{CP[0] += (SE * cos_theta);CP[1] += (SE * sin_theta);}}}else{second_flag = 0;}
}
我认为重点如下:
1. TP的更新
TP代表下一个目路径点,我采用了一个数组来存储其x,y坐标值,同时用一个TP_ptr指针来进行维护,TP的更新主要发生在以下三种情况:
a.当前行进到了TP,则TP需要更新到下一个路径点。
b.当前的TP被删除了,则TP需要更新到下一个路径点。
c.走到最终的目标点了,TP变成NF。
具体的实现见代码即可。
2. CP的更新
CP的更新是我的失分点,我的理解如下:
a.当走出一步之后,没有到达TP,则CP正常在x和y方向上加上这一步走了的距离。
b.走出一步之后达到了TP,则CP更新为TP。
这里我的理解应该是不够正确的,因为照这样写出来的代码评分会有一个错误,我猜测正确的逻辑可能如下:
b.走出一步之后,如果超过了TP,则需要调整方向,往更新后的TP方向再迈一步。
但碍于时间问题,这个方法我并没有去细究,我大致的思路是超过TP之后,重新计算一边方向,把超出来的那部分加到CP上,但要这样实现的话,我当前代码的方向更新逻辑就得大改,所以我先保留这个想法。
3. 路径点的存储方式
我使用了一个400大小的固定一维数组来存储,每相邻的两位表示一组x,y坐标,元素类型为int16_t,初始化为全-1,表示没有路径点。使用一个pos_ptr来维护,元素新增时,使用pos_ptr;元素需要删除时,则遍历之后将其值改为-1。具体代码如下:
void uart_task(void)
{if(uart_struct.uart_end_flag == 1){// 处理逻辑if((uart_struct.uart_rcv_buf[0] == '(' )&& (uart_struct.uart_ptr > 0) && (uart_struct.uart_rcv_buf[uart_struct.uart_ptr - 1] == ')')){uint32_t count = 0;for(uint32_t i = 0;i < uart_struct.uart_ptr;i++){if(uart_struct.uart_rcv_buf[i] == ','){count++;}}if(count % 2 == 1){if(ST == 0){// 设置途径,目标点, 尚未检查中间数据格式不符合的情况for(uint32_t i = 1;i < uart_struct.uart_ptr;i++){if((uart_struct.uart_rcv_buf[i] != ',') && ((uart_struct.uart_rcv_buf[i] != ')'))){tmp = tmp * 10 + uart_struct.uart_rcv_buf[i] - '0';}else if(uart_struct.uart_rcv_buf[i] == ','){pos[pos_ptr] = tmp;tmp = 0;pos_ptr++;}else if(uart_struct.uart_rcv_buf[i] == ')'){pos[pos_ptr] = tmp;tmp = 0;pos_ptr++;}}if((pos_ptr % 2) == 0){RN = pos_ptr / 2;FP[0] = pos[pos_ptr - 2];FP[1] = pos[pos_ptr - 1];printf("Got it");}else{pos[pos_ptr] = -1;pos_ptr--;printf("Error");}}else{printf("Busy");}}else{printf("Error");}}else if((uart_struct.uart_rcv_buf[0] == '{' )&& (uart_struct.uart_ptr > 0) && (uart_struct.uart_rcv_buf[uart_struct.uart_ptr - 1] == '}')){uint32_t tmp[2] = {0}, tmp_ptr = 0, tmpX, tmpY;for(uint32_t i = 1;i < uart_struct.uart_ptr;i++){if((uart_struct.uart_rcv_buf[i] != ',') && ((uart_struct.uart_rcv_buf[i] != '}'))){tmp[tmp_ptr] = tmp[tmp_ptr] * 10 + uart_struct.uart_rcv_buf[i] - '0';}else if(uart_struct.uart_rcv_buf[i] == ','){tmp_ptr++;}else if(uart_struct.uart_rcv_buf[i] == ')'){tmp_ptr++;}}tmpX = tmp[0];tmpY = tmp[1];uint32_t i = 0;for(;i < pos_ptr - 1;i += 2){if((pos[i] == tmpX) && (pos[i + 1] == tmpY)){pos[i] = -1;pos[i + 1] = -1;RN--;break;}}if(i < pos_ptr - 1){printf("Got it");}else{printf("Nonexistent");}}else if((uart_struct.uart_rcv_buf[0] == '[' )&& (uart_struct.uart_ptr > 0) && (uart_struct.uart_rcv_buf[uart_struct.uart_ptr - 1] == ']')) {if(ST == 1){uint8_t tmp;tmp = uart_struct.uart_rcv_buf[1] - '0';if((tmp >= 1) && (tmp <= 4)){scene = tmp - 1;printf("Got it");}else{printf("Device offline");}}else{printf("Device offline");}}else if(uart_struct.uart_rcv_buf[0] == '?' ){switch(ST){case 0:printf("Idle");break;case 1:printf("Busy");break;case 2:printf("Wait");break;}}else if(uart_struct.uart_rcv_buf[0] == '#' ){printf("(%d,%d)", CP[0], CP[1]);}else{printf("Error");}uart_struct.uart_ptr = 0;uart_struct.uart_end_flag = 0;uart_struct.uart_start_flag = 0;uart_struct.uart_time_cnt = 0;memset(uart_struct.uart_rcv_buf, 0 ,sizeof(uart_struct.uart_rcv_buf));}
}
但实际上,我认为这个代码是还有需要优化的地方的:
1. 数组满了之后,需要做一个重置的操作;但是由于测评点没有灌太多的目标点进去,所以暂时没出问题。
2. 查找元素的查找方法可以优化,比如用二分查找。
3. 存储路径点的数据结构可以改成链表,甚至我认为这道题本身就是想考察链表,因为相关的操作用链表实现非常的简单可观,虽然用数组同样也能实现。
5. 总结
本文总结了个人在练习15届国赛过程中的复盘及易错点、重难点分析,主要内容在按键双长按、路径行进相关的逻辑。