分享21年电赛F题

您所在的位置:网站首页 智能药物载体类别及原理 分享21年电赛F题

分享21年电赛F题

2024-07-13 12:10| 来源: 网络整理| 查看: 265

这里写目录标题 前言一、赛题分析1、车型选择2、巡线1、OpenMv循迹2、灰度循迹 3、装载药品4、识别数字5、LED指示6、双车通信7、转向方案1、开环转向2、位置环速度环闭环串级转向3、MPU6050转向 二、调试经验分享1、循迹2、识别数字3、转向4、双车通信5、逻辑处理6、心态问题 总结开源链接

前言

自己是今年准备电赛的同学一名,电赛结束了,想把自己之前刷过的题目,通过这篇文章,来分享一波做这道题的思路和调试方法

自己在做之前的电赛题目时,也苦苦没有思路,不知道该怎么去下手,面对题目的要求和限制,应该如何去分析和实现

由于我们主要是准备小车相关的,大部分时间都用来刷电赛期间出现的小车题目了

其中包括21年F题——智能送药小车、22年C题——小车跟随系统、22年B题——自动泊车系统等

可是今年电赛题目并没有小车,小车和无人机一起

可以看出电赛题目更贴近于综合,对个人能力的要求更高了

好了,废话不多说,我会在这段时间,将学习到的知识和做电赛题目的代码一步一步写出文章分享和教学,希望以后的同学可以参考学习,而不是盲目无从下手 在这里插入图片描述

一、赛题分析

赛题地图如下 在这里插入图片描述

1、车型选择

在做这道题目的时候,采用过好几种车型,并且采用过不同的方案,其中一种是

四轮小车,前轮为舵机转向,后两轮为驱动轮三轮小车,前轮为万向轮或牛眼轮,后两轮为驱动轮

在实践的过程中,因为题目要求是转向90°,这种情况下三轮小车前轮为从动轮的更占优势,转向更为方便,而舵机前轮后轮驱动的车型转向就无法像三轮一般丝滑,略显笨重,但是也可以实现

2、巡线

巡线有两种方案

OpenMv巡线灰度循迹

这两种循迹方案都是差不多的,都是检测当前小车行进的位置的偏差的误差值来改变小车的行进方向

就是接收误差,将误差带入PID进行计算,得出巡线补偿,使小车始终保持在期望中心

1、OpenMv循迹

这部分我就不多写了,OpenMv有很多种方法,来巡线,这部分是队友负责,我大概也知道一些,比如二值化、色块追踪、模拟灰度传感器等

32这边始终接收到的数据就是一个偏差Err,根据这个Err来计算PID,调节PID参数,到达期巡线望值

最后这个计算出来的值一般也是串起来的,循线环输出带入速度环的输入,两轮不同的方向,一边+,一边-,调节PID参数即可丝滑循迹

float location_pid_realize(_pid *pid, float actual_val) { /* 计算偏差 这里的偏差是指 巡线偏差 设定的巡线期望值 和 MV传回的巡线实际值 得偏差 */ pid->err = pid->target_val - actual_val; pid->integral += pid->err; // 误差累积 // if (pid->err >= 1000) // 积分限幅 // { // pid->err = 0; // } // if (huidu.output >= 550) // huidu.output = 550; // if (huidu.output actual_val = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->err_last); /*误差传递*/ pid->err_last = pid->err; /*返回当前实际值*/ return pid->actual_val; } /** * @brief 巡线pid输出函数 后轮差速 * @param actual_val:实际值 * @note 无 * @retval 通过PID计算后的输出 */ float OpenMV_location_pid_control(void) { float Expect_Pwm = 0.0; // 当前控制值 Pid_location = OpenMv_data1 * 10; // 获取巡线模块当前误差 后轮差速 // Pid_location = 0; Expect_Pwm = location_pid_realize(&OpenMv_pid_track, Pid_location); // 进行 PID 计算 // #if defined(PID_ASSISTANT_EN) // set_computer_value(SEND_FACT_CMD, CURVES_CH1, &actual_speed, 1); // 给通道 1 发送实际值 // #endif return Expect_Pwm; } 2、灰度循迹

这部分我使用的是五路灰度循迹,也是根据灰度管检测当前时刻的巡线变化来得出巡线偏差,带入PID进行计算,这和OpenMv巡线类似,我给出如何获得误差,大家就可以把误差带入PID进行计算了

最后这个计算出来的值一般也是串起来的,循线环输出带入速度环的输入,两轮不同的方向,一边+,一边-,调节PID参数即可丝滑循迹

/* * @Author: _oufen * @Date: 2023-04-15 09:06:56 * @LastEditTime: 2023-07-27 19:50:46 * @Description: 五路灰度传感器 */ /* Includes -------------------------------------------------------------------------------------------------------------*/ #include "tracking.h" /* Define -----------------------------------------------------------------------------------------------------------------*/ PID_Track pid_track; /* Functions -----------------------------------------------------------------------------------------------------------------*/ /** * @brief 五路灰度GPIO初始化 * @param None * @retval None * 分别使用到了 PB0/PB1/PB8/PB9/PA4 */ void Tracking_Init(void) { // GPIO初始化 上拉输入 MY_GPIO_Init(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9, GPIO_Mode_IPD); MY_GPIO_Init(GPIOA, GPIO_Pin_4, GPIO_Mode_IPD); } /** * @brief 五路灰度GPIO初始化 * @param None * @retval None * 分别使用到了 PB0/PB1/PB8/PB9/PA4 * 计划使用中间三路进行巡线 最左端和最右端进行识别十字路口和丁字路口 * 这里行驶的距离和巡线要有巡线补偿 * 低电平有效输出 感应黑色输出高电平 */ void Tracking_Control(void) { } void PID_xun_init(void) // 五路灰度循迹pid初始化 { pid_track.Kp = 35.0; pid_track.Ki = 0.0; pid_track.Kd = 0.0; pid_track.err = 0.0; pid_track.err_last = 0.0; pid_track.integral = 0.0; pid_track.output = 0.0; } /** * @brief 循迹模块对应所有初始化 * @param None * @retval None */ void Tracking_All_Init(void) { Tracking_Init(); // 循迹GPIO初始化 PID_xun_init(); // 循迹PID参数初始化 } float PID_output(void) { if ((L2 == 1) && (L1 == 0) && (M == 0) && (R1 == 0) && (R2 == 0)) // 10000 { pid_track.err = -3; } else if ((L2 == 1) && (L1 == 1) && (M == 0) && (R1 == 0) && (R2 == 0)) // 11000 { pid_track.err = -2; } else if ((L2 == 0) && (L1 == 1) && (M == 0) && (R1 == 0) && (R2 == 0)) // 01000 { pid_track.err = -1.5; } else if ((L2 == 0) && (L1 == 1) && (M == 1) && (R1 == 0) && (R2 == 0)) // 01100 { pid_track.err = -1; } else if ((L2 == 1) && (L1 == 1) && (M == 1) && (R1 == 0) && (R2 == 0)) // 11100 { pid_track.err = -4; } else if ((L2 == 0) && (L1 == 0) && (M == 1) && (R1 == 0) && (R2 == 0)) // 00100 { pid_track.err = 0; } else if ((L2 == 0) && (L1 == 0) && (M == 1) && (R1 == 1) && (R2 == 0)) // 00110 { pid_track.err = 1; } else if ((L2 == 0) && (L1 == 0) && (M == 0) && (R1 == 1) && (R2 == 0)) // 00010 { pid_track.err = 1.5; } else if ((L2 == 0) && (L1 == 0) && (M == 0) && (R1 == 1) && (R2 == 1)) // 00011 { pid_track.err = 2; } else if ((L2 == 0) && (L1 == 0) && (M == 0) && (R1 == 0) && (R2 == 1)) // 00001 { pid_track.err = 3; } else if ((L2 == 0) && (L1 == 0) && (M == 1) && (R1 == 1) && (R2 == 1)) // 00111 { pid_track.err = 4; } // 十字路口 else if ((L2 == 0) && (L1 == 0) && (M == 1) && (R1 == 0) && (R2 == 1)) // 00101 { pid_track.err = 4; } else pid_track.err = 0; pid_track.integral += pid_track.err; pid_track.output = pid_track.Kp * pid_track.err + pid_track.Ki * pid_track.integral + pid_track.Kd * (pid_track.err - pid_track.err_last); pid_track.err_last = pid_track.err; return pid_track.output; } /*****************************************************END OF FILE*********************************************************/ 3、装载药品

是否装载药品采用红外对管来实现

这部分我也不多说了,就是检测红外的高低电平,分别对应药品的状态

4、识别数字

识别数字,我们采用的是OpenMv4和OpenMv4Plus,病房前识别数字采用模板匹配,小车行进时采用神经网络匹配数字,效果很好

但是由于我不是负责这方面的,所以,只知道一个大概思路

5、LED指示

这个也没什么好说的,到达相应的状态时,点亮和熄灭LED就行了

6、双车通信

这里我们采用的方案有两种

蓝牙通信模块,HC-05Zigbee通信模块

其实,配置好了之后,这两种使用都是一样的,不同的方面就是Zigbee相较于蓝牙配置好配置一些,更方便一些

就一般的通信来说,蓝牙足够了

关于这方面的通信模块,一般肯定不会只发一个数据,这个时候就需要定义数据包协议,以保证发方和收方接收的数据的准确性

我自己定义的数据包协议和OpenMv协议相同

数据包为 b3 b3 data1 data2 data3 5b

其中b3 b3为帧头,5b为帧尾

下方是相关代码

/* * @Author: _oufen * @Date: 2023-07-06 15:05:38 * @LastEditTime: 2023-07-07 16:55:46 * @Description: */ #include "usart3.h" int Bluetooth_Receive_Buff[6]; // 蓝牙接收数据 int16_t data1; // 三个有效数据位 第一个为 模式选择 int16_t data2; // 第二个为 停车标志 int16_t data3; // 第三个为 主车走的圈数 /** * @brief 串口3初始化 * @param 无 * @retval 无 */ void uart3_init(u32 bound) { // GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能USART3,GPIOB时钟 // USART3_TX GPIOB2 RX PA2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA2 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB2 // USART3_RX GPIOB3 TX PA3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // PB11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB3 // USART3 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能 NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化VIC寄存器 // USART 初始化设置 USART_InitStructure.USART_BaudRate = bound; // 串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式 USART_Init(USART3, &USART_InitStructure); // 初始化串口2 USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 开启串口接受中断 USART_Cmd(USART3, ENABLE); // 使能串口2 } /** * @brief 串口3发送数据包 * @param 无 * @retval 无 */ void u3_sendData(u8 *str) { u8 i = 0; for (i = 0; i err > 180) // 防止小车转到180度时一直旋转的问题 pid->err = pid->err - 360; if (pid->err err = pid->err + 360; pid->integral += pid->err; // 误差累积 // if (pid->err >= 1000) // 积分限幅 // { // pid->err = 0; // } // if (huidu.output >= 550) // huidu.output = 550; // if (huidu.output actual_val = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->err_last); /*误差传递*/ pid->err_last = pid->err; /*返回当前实际值*/ return pid->actual_val; } /** * @brief 角度环pid输出函数 * @param actual_val:无 * @note 无 * @retval 通过PID计算后的输出 */ float Angle_pid_control(void) { float Expect_Pwm = 0.0; // 当前控制值 // pid_speed1.actual_val = ((float)Param.UnitTime_Motor1Pluse * 1000.0 * 60.0) / (RESOULTION_TOTAL_RATIO * REDUCTION_RATIO * PID_PERIOD); // Pid_speed2 = ((float)Param.UnitTime_Motor2Pluse * 60000.0) / 17680; Pid_Actual_angle = KLM(yaw); // 实际值 为yaw 滤波后的数据 Expect_Pwm = Angle_pid_realize(&pid_angle, Pid_Actual_angle); // 进行 PID 计算 // #if defined(PID_ASSISTANT_EN) // set_computer_value(SEND_FACT_CMD, CURVES_CH1, &actual_speed, 1); // 给通道 1 发送实际值 // #endif return Expect_Pwm; } 二、调试经验分享

大家在实际做题的时候肯定会出现非常多的问题,不要慌,慢慢来,分析造成这种问题的原因是什么,从现象出发,再回到代码

等调出想要的代码的实际效果时,那一刻就会感觉之前的努力都值了

关于这道题,主要调试的有以下几部分

1、循迹

这个循迹,只要偏差值没错,补偿值两轮,一正一负,后面就是调节PID的问题了

2、识别数字

这个也是关键,如何准确识别数字,然后小车根据MV发过来的数据进行运动 识别数字的准确率也要有相应的保证,小车一切行进的基础都是在准确识别到数字的基础上的

3、转向

在调试的时候,多次发现小车不能完全转向90度,造成小车循迹出线,压黑线等 这个时候,要检查一下小车的硬件结构是否合理,程序是不是哪个地方写错了等

4、双车通信

通信部分,我建议的是,首先在上位机上模拟OpenMv或者蓝牙主机,发送定义的数据包协议,将接收到的数据在OLED上进行显示,如果接收到正确的数据,即可证明通信无问题,再去排除其他问题

5、逻辑处理

其实,在掌握了以上知识点后,我觉得逻辑还是很重要的,由自己亲身经历过,就是你知道如何实现单个功能,但是融合起来后就完全不行了,在比赛过程中是很浪费时间的,会造成时间的大量浪费,再加上身体的高强度运作和心态,就悲剧了

千万要在平时加强对程序逻辑的判断和处理,作者本人亲身经历,不要学我 比如这道题目,我首先是在OpenMv识别数字后,放上药品才可以小车行进去指定的病房送药,一二号病房没有太大难度,写死即可,后面的病房要根据OpenMv二次判断,来进行更正进病房,并要求返回药房。

这个逻辑就很重要,识别到Mv就返回一个标志位,左边为0x3A,右边即为0x3B等。发挥部分,从车等待主车卸载药品后取药,这个时候是主车卸载完药品后才给从车发行进标志,从车才可以动,防止撞车等

6、心态问题

不要被一种方案限制住了,比赛吗,有无限可能的,发挥不同的思路和方法,只要能实现就是好方法

我已经给大家以身试法过了,这次参加的电赛,由于没有车,只能做E题,赛前玩了玩二维云台追踪小球,大部分心思还是压在了车上,而这道题目比赛完后,再次复盘回想起这道题,是自己的思维局限把自己限定死了,导致越调越不行,前两天,想着闭环跑,可惜没有实现,后面自己稀里糊涂的实现了闭环,但是速度不可控,还是会超出黑线,误差很大,导致每分,自己也很急,就这样,第一次电赛也是最后一次就结束了

总归来说还是心态和实力,想的太多,每次力争最优解

电赛还是很考验人的,虽说可能结果不会怎么样,但是我觉得在准备电赛得过程中还是很棒得,一起每天早起得队友,大家互相配合,完成题目得喜悦,一起解决问题得思考,还是很有意义的。

总结

希望大家吸取教训,大家有什么不懂得问题也可以在评论区留言,看到了得话就肯定会恢复给大家解答,希望自己得绵薄之力可以帮助大家

大家一定要不断的加强训练,对学习的东西进行一个总结和汇总,加强控制题目的训练,而不是仅仅做小车,小车是一个载体,其内部原理也是控制的精细程度

开源链接

我会在这两天将自己这个开源链接的代码进行一个讲解

我采用了不同的主控,不同的巡线方案、大家可以自行访问,进行下载

下方为gitee开源链接,请大家点一个star,谢谢 oufen / 2021年电赛F题-智能送药小车

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3