前言
自己是今年準備電賽的同學一名,電賽結(jié)束了,想把自己之前刷過的題目,通過這篇文章,來分享一波做這道題的思路和調(diào)試方法
自己在做之前的電賽題目時,也苦苦沒有思路,不知道該怎么去下手,面對題目的要求和限制,應該如何去分析和實現(xiàn)
由于我們主要是準備小車相關(guān)的,大部分時間都用來刷電賽期間出現(xiàn)的小車題目了
其中包括21年F題——智能送藥小車、22年C題——小車跟隨系統(tǒng)、22年B題——自動泊車系統(tǒng)等
可是今年電賽題目并沒有小車,小車和無人機一起
可以看出電賽題目更貼近于綜合,對個人能力的要求更高了
好了,廢話不多說,我會在這段時間,將學習到的知識和做電賽題目的代碼一步一步寫出文章分享和教學,希望以后的同學可以參考學習,而不是盲目無從下手
一、賽題分析
賽題地圖如下
1、車型選擇
在做這道題目的時候,采用過好幾種車型,并且采用過不同的方案,其中一種是
- 四輪小車,前輪為舵機轉(zhuǎn)向,后兩輪為驅(qū)動輪
- 三輪小車,前輪為萬向輪或牛眼輪,后兩輪為驅(qū)動輪
在實踐的過程中,因為題目要求是轉(zhuǎn)向90°,這種情況下三輪小車前輪為從動輪的更占優(yōu)勢,轉(zhuǎn)向更為方便,而舵機前輪后輪驅(qū)動的車型轉(zhuǎn)向就無法像三輪一般絲滑,略顯笨重,但是也可以實現(xiàn)
2、巡線
巡線有兩種方案
- OpenMv巡線
- 灰度循跡
這兩種循跡方案都是差不多的,都是檢測當前小車行進的位置的偏差的誤差值來改變小車的行進方向
就是接收誤差,將誤差帶入PID進行計算,得出巡線補償,使小車始終保持在期望中心
1、OpenMv循跡
這部分我就不多寫了,OpenMv有很多種方法,來巡線,這部分是隊友負責,我大概也知道一些,比如二值化、色塊追蹤、模擬灰度傳感器等
32這邊始終接收到的數(shù)據(jù)就是一個偏差Err,根據(jù)這個Err來計算PID,調(diào)節(jié)PID參數(shù),到達期巡線望值
最后這個計算出來的值一般也是串起來的,循線環(huán)輸出帶入速度環(huán)的輸入,兩輪不同的方向,一邊+,一邊-,調(diào)節(jié)PID參數(shù)即可絲滑循跡
float location_pid_realize(_pid *pid, float actual_val)
{
/* 計算偏差 這里的偏差是指 巡線偏差 設(shè)定的巡線期望值 和 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 <= -600)
// huidu.output = -600;
/*PID算法實現(xiàn)*/
pid->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輸出函數(shù) 后輪差速
* @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 發(fā)送實際值
// #endif
return Expect_Pwm;
}
2、灰度循跡
這部分我使用的是五路灰度循跡,也是根據(jù)灰度管檢測當前時刻的巡線變化來得出巡線偏差,帶入PID進行計算,這和OpenMv巡線類似,我給出如何獲得誤差,大家就可以把誤差帶入PID進行計算了
最后這個計算出來的值一般也是串起來的,循線環(huán)輸出帶入速度環(huán)的輸入,兩輪不同的方向,一邊+,一邊-,調(diào)節(jié)PID參數(shù)即可絲滑循跡
/*
* @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參數(shù)初始化
}
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、裝載藥品
是否裝載藥品采用紅外對管來實現(xiàn)
這部分我也不多說了,就是檢測紅外的高低電平,分別對應藥品的狀態(tài)
4、識別數(shù)字
識別數(shù)字,我們采用的是OpenMv4和OpenMv4Plus,病房前識別數(shù)字采用模板匹配,小車行進時采用神經(jīng)網(wǎng)絡(luò)匹配數(shù)字,效果很好
但是由于我不是負責這方面的,所以,只知道一個大概思路
5、LED指示
這個也沒什么好說的,到達相應的狀態(tài)時,點亮和熄滅LED就行了
6、雙車通信
這里我們采用的方案有兩種
- 藍牙通信模塊,HC-05
- Zigbee通信模塊
其實,配置好了之后,這兩種使用都是一樣的,不同的方面就是Zigbee相較于藍牙配置好配置一些,更方便一些
就一般的通信來說,藍牙足夠了
關(guān)于這方面的通信模塊,一般肯定不會只發(fā)一個數(shù)據(jù),這個時候就需要定義數(shù)據(jù)包協(xié)議,以保證發(fā)方和收方接收的數(shù)據(jù)的準確性
我自己定義的數(shù)據(jù)包協(xié)議和OpenMv協(xié)議相同
數(shù)據(jù)包為 b3 b3 data1 data2 data3 5b
其中b3 b3為幀頭,5b為幀尾
下方是相關(guān)代碼
/*
* @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]; // 藍牙接收數(shù)據(jù)
int16_t data1; // 三個有效數(shù)據(jù)位 第一個為 模式選擇
int16_t data2; // 第二個為 停車標志
int16_t data3; // 第三個為 主車走的圈數(shù)
/**
* @brief 串口3初始化
* @param 無
* @retval 無
*/
void uart3_init(u32 bound)
{
// GPIO端口設(shè)置
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; // 搶占優(yōu)先級3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子優(yōu)先級3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根據(jù)指定的參數(shù)初始化VIC寄存器
// USART 初始化設(shè)置
USART_InitStructure.USART_BaudRate = bound; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字長為8位數(shù)據(jù)格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一個停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 無奇偶校驗位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 無硬件數(shù)據(jù)流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收發(fā)模式
USART_Init(USART3, &USART_InitStructure); // 初始化串口2
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 開啟串口接受中斷
USART_Cmd(USART3, ENABLE); // 使能串口2
}
/**
* @brief 串口3發(fā)送數(shù)據(jù)包
* @param 無
* @retval 無
*/
void u3_sendData(u8 *str)
{
u8 i = 0;
for (i = 0; i < 2; i++) // b3 b3
{
USART_SendData(USART3, 0xb3);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) != SET)
;
}
for (i = 0; i < 3; i++) // 發(fā)送三位有效位
{
USART_SendData(USART3, str[i]);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) != SET)
;
}
USART_SendData(USART3, 0x5b); // 0x5b
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) != SET)
;
}
void Bluetooth_Data(void) // 處理藍牙接收的數(shù)據(jù)
{
data1 = Bluetooth_Receive_Buff[2];
data2 = Bluetooth_Receive_Buff[3];
data3 = Bluetooth_Receive_Buff[4];
}
/**
* @brief 串口3接收數(shù)據(jù)包 解析接收數(shù)據(jù)包 解析數(shù)據(jù)包 b3 b3 data1 data2 data3 5b
* @param 無
* @retval 無
*/
void Bluetooth_Receive_Data(int16_t data)
{
int i = 0;
static u8 state = 0; // 初始化狀態(tài)值
if (state == 0 && data == 0xb3) // 第一個幀頭 0xb3
{
state = 1;
Bluetooth_Receive_Buff[0] = data;
}
else if (state == 1 && data == 0xb3) // 第二個幀頭 0xb3
{
state = 2;
Bluetooth_Receive_Buff[1] = data;
}
else if (state == 2) // 第一個有效數(shù)據(jù)
{
state = 3;
Bluetooth_Receive_Buff[2] = data;
}
else if (state == 3) // 第二個有效數(shù)據(jù)
{
state = 4;
Bluetooth_Receive_Buff[3] = data;
}
else if (state == 4) // 第三個有效數(shù)據(jù)
{
state = 5;
Bluetooth_Receive_Buff[4] = data;
}
else if (state == 5) // 接收幀尾
{
if (data == 0x5b) // 是幀尾 直接回到初始狀態(tài)
{
state = 0;
Bluetooth_Receive_Buff[5] = data;
Bluetooth_Data(); // 處理數(shù)據(jù)
}
else if (data != 0x5b) // 不是幀尾 所有數(shù)據(jù)置0
{
state = 0;
for (i = 0; i < 6; i++)
{
Bluetooth_Receive_Buff[i] = 0;
}
}
}
else
{
state = 0;
for (i = 0; i < 6; i++)
{
Bluetooth_Receive_Buff[i] = 0;
}
}
}
/**
* @brief 串口3接收中斷
* @param 無
* @retval 無
*/
void USART3_IRQHandler(void) // 串口2中斷服務(wù)程序
{
uint8_t ReceiveData;
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) // 接收中斷(接收到的數(shù)據(jù)必須是0x0d 0x0a結(jié)尾)
{
USART_ClearITPendingBit(USART3, USART_IT_RXNE); // 清除中斷標志
ReceiveData = USART_ReceiveData(USART3); // 接收的數(shù)據(jù)
Bluetooth_Receive_Data(ReceiveData);
Bluetooth_Data();
// USART_SendData(USART3, ReceiveData);
// 測試收發(fā)數(shù)據(jù)
// if (ReceiveData == 58) // 當接收到0x3A后 接收數(shù)據(jù)燈亮 這個是基于最小系統(tǒng)板的測試程序
// {
// MY_GPIO_Init(GPIOC, GPIO_Pin_13, GPIO_Mode_Out_PP);
// }
}
}
/********************************* 測試藍牙接收數(shù)據(jù) ****************************************************/
// 在主函數(shù)中顯示 但是要先引入usart3頭文件 注意要在主函數(shù)中初始化 連線連接無錯誤
// 在上位機中以定義協(xié)議格式發(fā)送 b3 b3 01 02 03 5b
// 接收數(shù)據(jù)在OLED上顯示 如發(fā)送數(shù)據(jù)和接收數(shù)據(jù)相同 則表示接收協(xié)議無錯誤
// 下一步就是接收藍牙主機的數(shù)據(jù)
// // 顯示串口3接收數(shù)據(jù) 這是藍牙從機 接收數(shù)據(jù)
// OLED_ShowNum(3, 8, data1, 2); // 有效數(shù)據(jù)1
// OLED_ShowNum(3, 11, data2, 2); // 有效數(shù)據(jù)2
// OLED_ShowNum(3, 14, data3, 2); // 有效數(shù)據(jù)3
/*********************************** 結(jié)束 *************************************************************/
7、轉(zhuǎn)向方案
轉(zhuǎn)向方案可有很多種了嘞
在經(jīng)過實際測試后,我總結(jié)出來了主要的三種轉(zhuǎn)向方案
- 開環(huán)轉(zhuǎn)向
- 位置環(huán)速度環(huán)串級轉(zhuǎn)向
- MPU6050轉(zhuǎn)向
1、開環(huán)轉(zhuǎn)向
就是電機IO,分別給0、1,然后給一個PWM,記錄此時兩輪的脈沖數(shù)相加的絕對值,當超過設(shè)定的脈沖時就停止
這時設(shè)定的脈沖,就可以是90°的脈沖和180度的脈沖,這需要自己一步一步的測試
我經(jīng)過測試發(fā)現(xiàn)可行,并且還挺穩(wěn)定的
下面給出相關(guān)代碼
/**
* @brief 轉(zhuǎn)向 開環(huán)轉(zhuǎn)向
* @param 脈沖值;方向(左1 右2) pwm
* @retval 無
*/
void Car_turn(int pluse, int mode, int pwm)
{
while (1)
{
if (mode == 1) // 向左
{
Load_Motor_PWM(-pwm, pwm);
}
else if (mode == 2) // 向右
{
Load_Motor_PWM(pwm, -pwm);
}
if (pathLenth > pluse) // 達到預設(shè)角度
{
break;
}
}
Load_Motor_PWM(0, 0); // 立馬定住
Motor_Left_DIR(STOP);
Motor_Right_DIR(STOP);
}
void TIM6_IRQHandler(void) // 10ms
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
LED_RED = !LED_RED; // 測試程序是否卡死
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
a = -Read_Encoder(2);
b = Read_Encoder(3);
pathLenth += MyABS(a) + MyABS(b); // 累積脈沖數(shù)
// rpm 330脈沖
// speed1 = ((float)a * 60000.0) / 13200;
// speed2 = ((float)b * 60000.0) / 13200;
}
}
可以看出當小車當前脈沖到達自己設(shè)置的脈沖時,小車就停止,這個時候小車差速,即可到達期望角度
// Car_turn(2000, 1, 1000); // 開環(huán)轉(zhuǎn)彎 左轉(zhuǎn)90
// Car_turn(2000, 2, 1000); // 開環(huán)轉(zhuǎn)彎 右轉(zhuǎn)90
// Car_turn(4000, 1, 1000); // 掉頭180
2、位置環(huán)速度環(huán)閉環(huán)串級轉(zhuǎn)向
位置環(huán)為外環(huán),速度環(huán)為內(nèi)環(huán),位置環(huán)的輸出即是速度環(huán)的輸入
這個時候小車旋轉(zhuǎn)的角度就是以小車兩輪為直徑的圓的四分之一的距離,這個距離如何計算呢
小車轉(zhuǎn)一圈的脈沖我們知道,假定1040個脈沖
那么當前行進的脈沖數(shù)/小車轉(zhuǎn)一圈的脈沖數(shù),我們就是不是就知道了小車轉(zhuǎn)了幾圈,然后把這個×小車轉(zhuǎn)一圈的周長,就知道了小車行進的距離
把小車行進距離轉(zhuǎn)換為脈沖帶入位置環(huán),得到輸出后賦值給速度環(huán)即可,使小車轉(zhuǎn)90°的距離恒定,掉頭的距離也恒定,轉(zhuǎn)向就完成了
這部分代碼有些多,我在文末會貼出代碼,大家可以自行查看
3、MPU6050轉(zhuǎn)向
通過yaw角的反饋,來改變小車的旋轉(zhuǎn)方向,yaw角為90°時即轉(zhuǎn)向90°,yaw角為180°時即掉頭
這個也是串起來的PID,角度環(huán)的輸出是速度環(huán)的輸入
調(diào)節(jié)好PID參數(shù)后即可絲滑轉(zhuǎn)向
/**
* @brief 角度環(huán)pid輸出函數(shù)
* @param actual_val:實際值 6050yaw
* @note 無
* @retval 通過PID計算后的輸出
*/
float Angle_pid_realize(_pid *pid, float actual_val)
{
/* 計算偏差 這里的偏差是指 巡線偏差 設(shè)定的巡線期望值 和 MV傳回的巡線實際值 得偏差 */
pid->err = pid->target_val - actual_val;
if (pid->err > 180) // 防止小車轉(zhuǎn)到180度時一直旋轉(zhuǎn)的問題
pid->err = pid->err - 360;
if (pid->err < -180)
pid->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 <= -600)
// huidu.output = -600;
/*PID算法實現(xiàn)*/
pid->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 角度環(huán)pid輸出函數(shù)
* @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 濾波后的數(shù)據(jù)
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 發(fā)送實際值
// #endif
return Expect_Pwm;
}
二、調(diào)試經(jīng)驗分享
大家在實際做題的時候肯定會出現(xiàn)非常多的問題,不要慌,慢慢來,分析造成這種問題的原因是什么,從現(xiàn)象出發(fā),再回到代碼
等調(diào)出想要的代碼的實際效果時,那一刻就會感覺之前的努力都值了
關(guān)于這道題,主要調(diào)試的有以下幾部分
1、循跡
這個循跡,只要偏差值沒錯,補償值兩輪,一正一負,后面就是調(diào)節(jié)PID的問題了
2、識別數(shù)字
這個也是關(guān)鍵,如何準確識別數(shù)字,然后小車根據(jù)MV發(fā)過來的數(shù)據(jù)進行運動
識別數(shù)字的準確率也要有相應的保證,小車一切行進的基礎(chǔ)都是在準確識別到數(shù)字的基礎(chǔ)上的
3、轉(zhuǎn)向
在調(diào)試的時候,多次發(fā)現(xiàn)小車不能完全轉(zhuǎn)向90度,造成小車循跡出線,壓黑線等
這個時候,要檢查一下小車的硬件結(jié)構(gòu)是否合理,程序是不是哪個地方寫錯了等
4、雙車通信
通信部分,我建議的是,首先在上位機上模擬OpenMv或者藍牙主機,發(fā)送定義的數(shù)據(jù)包協(xié)議,將接收到的數(shù)據(jù)在OLED上進行顯示,如果接收到正確的數(shù)據(jù),即可證明通信無問題,再去排除其他問題
5、邏輯處理
其實,在掌握了以上知識點后,我覺得邏輯還是很重要的,由自己親身經(jīng)歷過,就是你知道如何實現(xiàn)單個功能,但是融合起來后就完全不行了,在比賽過程中是很浪費時間的,會造成時間的大量浪費,再加上身體的高強度運作和心態(tài),就悲劇了
千萬要在平時加強對程序邏輯的判斷和處理,作者本人親身經(jīng)歷,不要學我
比如這道題目,我首先是在OpenMv識別數(shù)字后,放上藥品才可以小車行進去指定的病房送藥,一二號病房沒有太大難度,寫死即可,后面的病房要根據(jù)OpenMv二次判斷,來進行更正進病房,并要求返回藥房。
這個邏輯就很重要,識別到Mv就返回一個標志位,左邊為0x3A,右邊即為0x3B等。發(fā)揮部分,從車等待主車卸載藥品后取藥,這個時候是主車卸載完藥品后才給從車發(fā)行進標志,從車才可以動,防止撞車等
6、心態(tài)問題
不要被一種方案限制住了,比賽嗎,有無限可能的,發(fā)揮不同的思路和方法,只要能實現(xiàn)就是好方法
我已經(jīng)給大家以身試法過了,這次參加的電賽,由于沒有車,只能做E題,賽前玩了玩二維云臺追蹤小球,大部分心思還是壓在了車上,而這道題目比賽完后,再次復盤回想起這道題,是自己的思維局限把自己限定死了,導致越調(diào)越不行,前兩天,想著閉環(huán)跑,可惜沒有實現(xiàn),后面自己稀里糊涂的實現(xiàn)了閉環(huán),但是速度不可控,還是會超出黑線,誤差很大,導致每分,自己也很急,就這樣,第一次電賽也是最后一次就結(jié)束了
總歸來說還是心態(tài)和實力,想的太多,每次力爭最優(yōu)解
電賽還是很考驗人的,雖說可能結(jié)果不會怎么樣,但是我覺得在準備電賽得過程中還是很棒得,一起每天早起得隊友,大家互相配合,完成題目得喜悅,一起解決問題得思考,還是很有意義的。
總結(jié)
希望大家吸取教訓,大家有什么不懂得問題也可以在評論區(qū)留言,看到了得話就肯定會恢復給大家解答,希望自己得綿薄之力可以幫助大家
大家一定要不斷的加強訓練,對學習的東西進行一個總結(jié)和匯總,加強控制題目的訓練,而不是僅僅做小車,小車是一個載體,其內(nèi)部原理也是控制的精細程度
開源鏈接
我會在這兩天將自己這個開源鏈接的代碼進行一個講解
我采用了不同的主控,不同的巡線方案、大家可以自行訪問,進行下載
下方為gitee開源鏈接,請大家點一個star,謝謝
oufen / 2021年電賽F題-智能送藥小車文章來源:http://www.zghlxwxcb.cn/news/detail-641154.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-641154.html
到了這里,關(guān)于分享21年電賽F題-智能送藥小車-做題記錄以及經(jīng)驗分享的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!