2023年电赛E题完整设计暨电赛全记录

2023-11-15

目录

一.2023年E题完整设计

<1>选择方案

任务一:实现按键按下复位(基础部分)

任务二:实现激光点绕边框一周(基础部分)

任务三:实现激光点绕A4纸边缘一周(基础部分)

任务四:实现绿色激光追踪红色激光(发挥部分)

<2>任务分配

<3>代码分析

1.stm32上关键源码分析

I.基本部分

II.模块代码

(1)Timer——定时器延时函数模块

(2)servo_motor——云台舵机控制模块

①控制舵机的旋转

②控制激光点到达某一像素点

③与上位机jetson nano通讯接收点坐标

④得到一点坐标对应的舵机pwm波OC值

⑤控制激光点沿四边形巡线

III.主函数与中断函数部分

(1)红色激光云台

(2)绿色激光云台

2.jetson nano上关键源码分析

I.相机参数的调整

II.关键识别算法

(1)铅笔线识别及顶点的计算

(2)A4纸顶点识别及巡线顶点的计算

(3)区分红绿激光

二.学习资料分享

<1>学习笔记

<2>其他资料

三.备赛阶段记录

四.电赛总结及经验教训

<1>本次比赛作品的不足、改进之处

<2>本次比赛的经验教训


“愿大家都少走弯路,在迷茫时看到希望!”

一.2023年E题完整设计

<1>选择方案

任务一:实现按键按下复位(基础部分)

方法①:识别四顶点位置->连接对角线得到中心点->PID调节使激光点与中心点重合

方法②:识别四顶点位置->对角顶点坐标求平均值得中心点位置->PID调节使重合

方法③:固定所有器件位置,保证各点PWM值不变,得到中心点PWM固定值,开环设定

任务二:实现激光点绕边框一周(基础部分)

步骤I:激光点由中心点到达边线左上角

步骤II:顺时针绕一圈

        方法①:两点定线,先确定两点坐标,连线确定等分点,使用PID算法在等分点间移动

        方法②:不使用PID,利用与目标点坐标差计算移动方向,每次移动距离为舵机最小精度值

        方法③:求PWM和坐标(x,y)的函数关系(近似线性),直接设定PWM值到达指定点

任务三:实现激光点绕A4纸边缘一周(基础部分)

(与任务二区别:矩形放置角度可以倾斜;要区分两矩形宽度以识别A4纸)

任务四:实现绿色激光追踪红色激光(发挥部分)

方法①:区分红绿色激光并得到坐标->PID直接跟踪

<2>任务分配

将上述任务分解成多个要完成的技术,以便分工:

1.硬件平台搭建

2.stm32控制算法:

①PID控制激光点移动到目标点算法(核心)

②舵机以最小分度值移动算法(细微调节)

③给定两点以及等分数计算所有等分点算法(线上移动减少偏差)

④在PID寻点时获取基本点(矩形顶点及中心)PWM值算法

⑤stm32和jetson nano的通信规则设计与数据互传

3.OpenCV识别算法

①识别铅笔线边框:灰度图转换->阈值分割成二值图->霍夫直线变换得到直线上两点(非端点)->从得到的多条直线中筛选去重->编写“已知两直线上两点求直线交点”算法->求得四端点

②识别A4纸边框:阈值分割后利用Harris角点检测出A4框的8个顶点->编写“从8个顶点中识别两两相邻顶点”算法->求得框中心线4顶点

③区分红绿激光点算法:转换到Hsv色彩空间->分别设置阈值,在Hsv空间中二值化图像提取红绿色区域以得到激光点坐标        

4.主函数(程序流程)设计

5.电赛报告书写

<3>代码分析

1.stm32上关键源码分析

I.基本部分

(1)引脚使用说明

//*************************引脚使用说明*************************
/*
oled.h				GPIOA PIN0/1
bluetooth.h			GPIOA PIN2/3
joystick.h			GPIOA PIN4/5 ADC1_CH4/5 GPIOB PIN11/12/13 EXTI12/13
Pwm.h				GPIOA PIN8/11 TIM1_CH1/4 50hz
usart.h				GPIOA PIN9/10 TX/RX Black/White
beep.h				GPIOB PIN14
led.h				GPIOB PIN15
Timer.h				TIM2/3
*/

(2)头文件声明

//************************头文件声明************************
#include "public.h"				//公用引用函数封装
//#include "bluetooth.h"		//蓝牙模块
#include "oled.h"				//OLED显示屏模块
#include "Pwm.h"				//PWM波生成模块
#include "servo_motor.h"		//云台控制函数模块
#include "joystick.h"			//摇杆控制模块
#include "string.h"				
#include "Delay.h"				
#include "Timer.h"				//定时器模块
#include "usart.h"				//uart通信模块
#include "beep.h"				//蜂鸣器模块
#include "led.h"				//led灯模块
#include "dma.h"				//dma数据转存模块

(3)全局变量和宏定义声明

//************************全局变量和宏定义声明************************
//#define OpenLoop_OL		//开环实现功能执行
#define CloseLoop_CL		//闭环实现功能执行

extern float Voltage[2];	//ad测量电压值[0.3.3]			//ad.c
extern char  USART_RX_INFO[USART_REC_LEN];	//uart接收数据	//usart.c
extern int x,y;				//激光当前坐标					//servo_motor.c
extern int Vertex[4][2];	//四顶点位置						//servo_motor.c
extern int Vertex_Peak_Pos[4][2];
extern int Vertex_A4[4][2];
extern Pwm Center_Pwm;
extern Pwm Peak_Pwm[4];
extern Pwm A4_Pwm[4];

int Programme_Progress=0;					//比赛程序进度
int order=0;								//蓝牙接收到的命令
int Main_Wait_Stop_Sign =1;					//主程序等待标志位
extern int JoyStick_Control_Stop_Sign;		//摇杆控制程序结束标志位
int Get_Depend_Point_Pos_Stop_Sign=1;
int Get_A4_Point_Pos_Stop_Sign=1;
extern int Follow_Track_Stop_Sign;			//矩形寻迹结束标志位
extern int Follow_Point_Stop_Sign;			//绿激光跟随红激光结束标志位

II.模块代码
(1)Timer——定时器延时函数模块
#include "Timer.h"

//TIM2/3

void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	TIM_InternalClockConfig(TIM3);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 60000 - 1;	//分辨率1us,最大60ms
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
}

void Timer_delay_us(int xus)
{
	TIM_Cmd(TIM2,ENABLE); //启动定时器
	while(TIM2->CNT < xus);
	TIM2->CNT = 0;
	TIM_Cmd(TIM2,DISABLE); //关闭定时器
}

void Timer_delay_ms(int xms)
{
	int i=0;
	for(i=0;i<xms;i++)Timer_delay_us(1000);
}

//外部中断专用延时函数
void EXIT_LINE_Timer_delay_us(int xus)
{
	TIM_Cmd(TIM3,ENABLE); //启动定时器
	while(TIM3->CNT < xus);
	TIM3->CNT = 0;
	TIM_Cmd(TIM3,DISABLE); //关闭定时器
}

void EXIT_LINE_Timer_delay_ms(int xms)
{
	int i=0;
	for(i=0;i<xms;i++)EXIT_LINE_Timer_delay_us(1000);
}

说明:

在Timer_Init()中开启了两个定时器TIM1/2,由Timer_delay_us()和EXIT_LINE_Timer_delay_us()分别使用,分别在中断函数内外使用,避免重复调用冲突

(2)servo_motor——云台舵机控制模块
①控制舵机的旋转
int Oc_Lp[4]={750,750,750,750};
int Oc_Vp[4]={763,763,763,763};
/*********************************************************
函数功能:云台水平方向旋转
*********************************************************/
void Spinnig_Level(int diff)
{
	if(diff<0)
	{
		Oc_Lp[0]=Oc_L=(Oc_L+diff)<660?660:(Oc_L+diff);
	}
	else if(diff>0)
	{
		Oc_Lp[0]=Oc_L=(Oc_L+diff)>840?840:(Oc_L+diff);	
	}
	TIM_SetCompare1(TIM1,Oc_L);	
	int i;
	for(i=3;i>0;i--)Oc_Lp[i]=Oc_Lp[i-1];
}

说明:

这里的Oc_Lp存储的是控制舵机的pwm波参数中的OC寄存器中的值,作为舵机运动最基本的函数,舵机的控制通过改变pwm波参数中的OC寄存器中的值实现。这里定义数组实现记忆功能,可存储前三次的OC值。并通过三元运算符设定上下限,将最终的OC值通过TIM_SetCompare1()设定。

②控制激光点到达某一像素点
/*********************************************************
函数功能:云台控制激光点到达某一点
函数参数:目标点的坐标
*********************************************************/
int x=360,y=360;		//跟随点当前坐标
int Reach_Pos_CL_Stop_Sign=1;
//云台水平方向旋转PID值
float Level_Kp=0.06;
float Level_Ki=0.02;
float Level_Kd=0.01;
//云台竖直方向旋转PID值
float Vert_Kp=0.06;
float Vert_Ki=0.02;
float Vert_Kd=0.01;
void Reach_Pos_CL(int Target_X,int Target_Y,int Reach_Pos_CL_MODE)
{
	int Sign(int num);
	void Get_Point_Pos(void);
	int near(int Target_X,int Target_Y);
	
	int diff_x,diff_y;
	while(Reach_Pos_CL_Stop_Sign)
	{
		Timer_delay_ms(30);
		Get_Point_Pos();
		if(near(Target_X,Target_Y)<=6)
		{
			Beep_Times(10,1,NORMAL_MODE);
			break;
		}
		if(Reach_Pos_CL_MODE==PID_MODE && near(Target_X,Target_Y)>60)					//用pid计算舵机单位数
		{
			diff_x=Pid_Control(Level_Kp,Level_Ki,Level_Kd,Target_X,x,PID_REALIZE);
			diff_y=Pid_Control(Vert_Kp,Vert_Ki,Vert_Kd,Target_Y,y,PID_REALIZE);
		}
		else if(Reach_Pos_CL_MODE==MINMIZE_MODE)		//以舵机最小分辨率为单位
		{
			diff_x=-Sign(x-Target_X);
			diff_y=-Sign(y-Target_Y);
		}
		else if(Reach_Pos_CL_MODE==PID_MODE && near(Target_X,Target_Y)<=60)					//用pid计算舵机单位数
		{
			diff_x=-Sign(x-Target_X);
			diff_y=-Sign(y-Target_Y);
			Timer_delay_ms(30);
		}
		Spinnig_Level(X_DIR*diff_x);
		Spinnig_Vert(Y_DIR*diff_y);
		Timer_delay_ms(20);
	}
}

int Sign(int num)
{
	if(num>5)return 1;
	else if(num<-5)return -1;
	else return 0;
}

int my_abs(int a,int b)
{
	return a-b>0?a-b:b-a;
}

int near(int Target_X,int Target_Y)
{
	return my_abs(Target_X,x)+my_abs(Target_Y,y);
}

说明:

输入参数:目标点像素坐标;追踪模式(PID【PID与最小精度混合】模式和最小精度值模式)

追踪过程:

——得到当前激光点坐标:Get_Point_Pos()

——如果接近目标点则蜂鸣器鸣叫并退出【near(Target_X,Target_Y)<=6,说明当前坐标与目标横纵坐标差之和{“距离”}小于6个像素】

——如果使用PID模式:

        ——“距离”大于60时采用PID算法快速靠近,计算出OC变化值diff_x、diff_y

        ——“距离”小于60时使用最小精度模式缓慢靠近,利用“符号函数sign()”计算diff

——调用Spinnig_Level()、Spinnig_Level()进行水平和垂直舵机的旋转

③与上位机jetson nano通讯接收点坐标

a.激光点坐标的实时接收

/*********************************************************
函数功能:stm32获取当前激光坐标
*********************************************************/
void Get_Point_Pos(void)
{
	if(USART_RX_INFO[0]=='x')				//检查数据定位是否正确(上位机发送信息为:x123y456)
	{
		x=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
	}
	if(USART_RX_INFO[4]=='y')				//检查数据定位是否正确(上位机发送信息为:x123y456)
	{
		y=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
	}
}

说明:

规定上位机每次发送数据格式为:以#开头,以$结尾;stm32usart模块对接收数据进行解析

上位机坐标数据格式为:x123y456;123、456代表三位坐标值,字符'x'、'y'起定位作用

stm32对接收到的字符坐标进行解析如上

b.特殊坐标接收

//********************************************************高级控制函数(CloseLoop--CL)********************************************************
int Vertex_Peak_Pos[4][2];
int Center_Pos[2];
Pwm Center_Pwm;
Pwm Peak_Pwm[4];
Pwm A4_Pwm[4]; 
//获取重要点坐标
void Get_Point_5(void)
{
	int i,j;
	while(1)
	{
		for(i=0;i<8;i++)
		{
			if(USART_RX_INFO[4*i]=='a'+i)continue;
			else break;
		}
		if(i==8)
		{
			for(i=0;i<4;i++)
			{
				for(j=0;j<2;j++)Vertex_Peak_Pos[i][j]=(USART_RX_INFO[4*(2*i+j)+1]-'0')*100+(USART_RX_INFO[4*(2*i+j)+2]-'0')*10+(USART_RX_INFO[4*(2*i+j)+3]-'0');
			}
			break;
		}
	}
	
	while(!(USART_RX_INFO[0]=='i'&&USART_RX_INFO[4]=='j'));
	
	Center_Pos[0]=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
	Center_Pos[1]=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
	Beep_Times(50,5,NORMAL_MODE);
	
}

说明:

这里接收的是铅笔线框四个顶点的坐标和中心点坐标,但是一次发送的数据长度不能太长,这里拆分成两部分接收(数据格式为:axxxbxxxcxxx...hxxx共8组值四个坐标),关键在于两部分的衔接

while(!(USART_RX_INFO[0]=='i'&&USART_RX_INFO[4]=='j'));确保收到四个顶点坐标后持续等待中心点坐标的发送

④得到一点坐标对应的舵机pwm波OC值
int sum_num(int *num,int n)
{
	int i,sum;
	for(i=sum=0;i<n;i++)sum+=num[i];
	return sum;
}	
//获取目标点pwm值
void Get_Pwm(int px,int py,Pwm *target_pwm,int n)
{
	Reach_Pos_CL(px,py,PID_MODE);
	target_pwm->level=sum_num(Oc_Lp,n)/n;
	target_pwm->vert=sum_num(Oc_Vp,n)/n;
}

说明:

通过②控制函数控制激光点到达指定点后记录目标点pwm值并返回;Pwm结构体定义如下

typedef struct Pwm{
	int level;
	int vert;
}Pwm;

可以通过改变参数n的值选择是否滤波,4>n>1时进行滤波,取前几次OC值的平均值,不建议滤波

⑤控制激光点沿四边形巡线
//巡线
void Follow_Track(int Vertex[4][2],int divide_num)
{
	int i,j;
	float sub_l,sub_v;
	Pwm Vertex_Pwm[4];
	for(i=0;i<4;i++)Get_Pwm(Vertex[i][0],Vertex[i][1],&Vertex_Pwm[i],1);
	
	for(i=0;i<4;i++)
	{
		sub_l=(Vertex_Pwm[(i+1)%4].level-Vertex_Pwm[i].level);	//下一个顶点与当前顶点pwm之差
		sub_v=(Vertex_Pwm[(i+1)%4].vert-Vertex_Pwm[i].vert);	//下一个顶点与当前顶点纵坐标之差
		for(j=0;j<divide_num;j++)
		{
			Reach_Pos_OL(Vertex_Pwm[i].level+j*sub_l/divide_num,Vertex_Pwm[i].vert+j*sub_v/divide_num);
			Timer_delay_ms(200);
		}
		Reach_Pos_OL(Vertex_Pwm[(i+1)%4].level,Vertex_Pwm[(i+1)%4].vert);
		Timer_delay_ms(300);
	}
	
	Beep_Times(50,5,NORMAL_MODE);
}

说明:

输入参数:四边形顺时针顺序顶点坐标、每段等分数divide_num

巡线过程:

——得到四个顶点坐标对应的水平、数值舵机OC值

——在for循环内依次经过四个顶点,视作四个大任务

        ——内部使用for循环分解小任务,根据等分段数divide_num计算等分点横纵pwm值并移动至

——任务结束鸣叫示意

III.主函数与中断函数部分
(1)红色激光云台
//*************************主函数部分*************************
//重新重启初值还原设置
void Programme_Reset(void)
{
	Beep_Times(1000,1,NORMAL_MODE);
	Led_Times(1000,1,NORMAL_MODE);
	Programme_Progress=0;
	
	Main_Wait_Stop_Sign=1;
	JoyStick_Control_Stop_Sign=1;
	Follow_Track_Stop_Sign=1;
	
	Get_A4_Point_Pos_Stop_Sign=1;
	Get_Depend_Point_Pos_Stop_Sign=1;
}

int main(void)
{	
	//********************初始化程序********************
	Timer_Init();				//定时器初始化
//	BlueToothInit(9600,USART_Parity_No,USART_StopBits_1,USART_WordLength_8b);	//蓝牙初始化
	OLED_Init();				//oled初始化
	Beep_Init();				//蜂鸣器初始化
	Led_Init();					//led灯初始化
	TIM1_PWM_Init(9999,143);	//一周期20ms,分辨率20ms/10000)
	TIM_SetCompare1(TIM1,750);	//对齐角度为90度(1.5ms)
	TIM_SetCompare4(TIM1,763);	//对齐角度为90度(1.5ms)
	uart_init(115200);			//uart1初始化
	JoyStick_Init();			//JoyStick摇杆初始化
			
	//*************************比赛程序部分*************************
	while(1)
	{
		int i;
		//重新重启初值还原设置
		Programme_Reset();
//		Reach_Pos_CL(50,50,PID_MODE);
		
		Axes_Init();
		
//		Follow_Track(Vertex_Peak_Pos,1);
		
		while(Main_Wait_Stop_Sign);
		//摇杆控制
		JoyStick_Control();
	
		
//#ifdef OpenLoop_OL
//			Follow_Track_OL();
//#endif			
//#ifdef CloseLoop_CL
//		//等待上位机发送初始坐标
//		Get_Depend_Point_Pos();
//		//环绕正方形顺时针旋转一周
//		while(Get_Depend_Point_Pos_Stop_Sign);

		//Follow_Track_CL(Vertex_Peak_Pos,2,PID_MODE);
		
//#endif

		Pwm_Track(Peak_Pwm,1);
		while(Follow_Track_Stop_Sign);
				
		Get_A4_Point_Pos();
		Timer_delay_ms(2000);
//		Follow_Track_CL(Vertex_A4,4,MINMIZE_MODE);
//		Follow_Track(Vertex_A4,4);
		for(i=0;i<4;i++)Get_Pwm(Vertex_A4[i][0],Vertex_A4[i][1],&A4_Pwm[i],1);
		Pwm_Track(A4_Pwm,6);
		while(Get_A4_Point_Pos_Stop_Sign);
		
	}
}
//*********************************************中断函数部分*********************************************
//按键中断函数
void EXTI15_10_IRQHandler()
{
	if (EXTI_GetITStatus(EXTI_Line11) == SET)
	{
		EXIT_LINE_Timer_delay_ms(10);										
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)		//软件防抖
		{
			Beep_Times(50,2,EXIT_LINE_MODE);
			Reach_Pos_OL(Oc_L,Oc_V);						//保持激光当前指向位置
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0); 	//等待按键松开

			//再次按下才退出
			EXIT_LINE_Timer_delay_ms(10);
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==1);		
			EXIT_LINE_Timer_delay_ms(10);										
			if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);		//软件防抖
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11==1)); 	//等待按键松开
			Beep_Times(50,2,EXIT_LINE_MODE);
			
			EXTI_ClearITPendingBit(EXTI_Line11);
		}
	}
	
	else if (EXTI_GetITStatus(EXTI_Line12) == SET)
	{
		EXIT_LINE_Timer_delay_ms(10);										
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0)		//软件防抖
		{
			Programme_Progress++;
			Beep_Times(500,1,EXIT_LINE_MODE);
			if(Programme_Progress==1)
			{
				Main_Wait_Stop_Sign=0;
			}
			else if(Programme_Progress==2)
			{
				JoyStick_Control_Stop_Sign=0;
			}
			else if(Programme_Progress==3)
			{
//				Get_Depend_Point_Pos_Stop_Sign=0;
				Follow_Track_Stop_Sign=0;
			}
			else if(Programme_Progress==4)
			{
				Get_A4_Point_Pos_Stop_Sign=0;
//				Follow_Track_Stop_Sign=0;
			}
			else if(Programme_Progress==5)
			{
//				Get_A4_Point_Pos_Stop_Sign=0;
			}
			else if(Programme_Progress==6)
			{
				;
			}
			else if(Programme_Progress==7)
			{
				;
			}
			else
			{
				Programme_Reset();
			}
			
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0); 	//等待按键松开
			EXTI_ClearITPendingBit(EXTI_Line12);
		}
		
	}
	
	else if (EXTI_GetITStatus(EXTI_Line13) == SET)
	{
		EXIT_LINE_Timer_delay_ms(10);										
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0)		//软件防抖
		{
			Beep_Times(50,3,EXIT_LINE_MODE);
			
			Reach_Pos_OL(Center_Pwm.level,Center_Pwm.vert);
			
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0); 	//等待按键松开

			//再次按下才退出
			EXIT_LINE_Timer_delay_ms(10);
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==1);		
			EXIT_LINE_Timer_delay_ms(10);										
			if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0);		//软件防抖
			while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13==1)); 	//等待按键松开
			Beep_Times(50,3,EXIT_LINE_MODE);
			
			EXTI_ClearITPendingBit(EXTI_Line13);
		}
	}	
}

说明:

主函数与中断函数相辅相成,程序整体使用外部中断推进以及实现一些特殊功能(立即复位);

由于主函数内小功能函数都借助while()循环实现,设置循环标志位Stop_Sign和程序阶段标志位Programme_Progress来推进主函数;

按下GPIOB,GPIO_Pin_12的按键触发中断,Programme_Progress++以及相应的Stop_Sign=0,以控制目前运行小功能停止并进入下一阶段;

在程序开始和结束处执行Programme_Reset()函数,重置各标志位实现程序重新运行;

注意:

在中断函数内部涉及到的延时函数统统使用EXIT_LINE_Timer_delay_ms()函数,区别外部使用的Timer_delay_ms()函数,防止调用冲突程序卡死

(2)绿色激光云台
//绿车
int r_x=0,r_y=0;
void Get_RaG_Point_Pos(void)
{
	if(USART_RX_INFO[0]=='g'&& USART_RX_INFO[8]=='r'&& USART_RX_INFO[4]=='y'&& USART_RX_INFO[12]=='y')				//检查数据定位是否正确(上位机发送信息为:x123y456)
	{
		x=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
		y=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
		r_x=(USART_RX_INFO[9]-'0')*100+(USART_RX_INFO[10]-'0')*10+USART_RX_INFO[11]-'0';
		r_y=(USART_RX_INFO[13]-'0')*100+(USART_RX_INFO[14]-'0')*10+USART_RX_INFO[15]-'0';
	}
}

void G_Follow_R(int Reach_Pos_CL_MODE)
{
	int Sign(int num);
	void Get_RaG_Point_Pos(void);
	int near(int r_x,int Target_Y);
	
	int diff_x,diff_y,dis;
	while(Reach_Pos_CL_Stop_Sign)
	{
		if(x==0&&y==0)Reach_Pos_OL(750,750);
		Get_RaG_Point_Pos();
		dis=near(r_x,r_y);
		if(dis<=20)
		{
			Beep_Times(300,1,NORMAL_MODE);
			Led_Times(300,1,NORMAL_MODE);
			continue;
		}
		if(Reach_Pos_CL_MODE==PID_MODE && dis>60)					//用pid计算舵机单位数
		{
			Get_RaG_Point_Pos();
			diff_x=Pid_Control(Level_Kp,Level_Ki,Level_Kd,r_x,x,PID_REALIZE);
			diff_y=Pid_Control(Vert_Kp,Vert_Ki,Vert_Kd,r_y,y,PID_REALIZE);
		}
		else if(Reach_Pos_CL_MODE==MINMIZE_MODE)		//以舵机最小分辨率为单位
		{
			Get_RaG_Point_Pos();
			diff_x=-0.5*Sign(x-r_x);
			diff_y=-0.5*Sign(y-r_y);
		}
		else if(Reach_Pos_CL_MODE==PID_MODE && dis<=60)					//用pid计算舵机单位数
		{
			Get_RaG_Point_Pos();
			diff_x=-0.4*Sign(x-r_x);
			diff_y=-0.4*Sign(y-r_y);

		}
		Spinnig_Level(X_DIR*diff_x);
		Spinnig_Vert(Y_DIR*diff_y);
		Timer_delay_ms(20);
	}
}

说明:

上位机数据格式为:g123y123r123y123,实时传输红绿激光点两个坐标;

执行点到点的跟踪即可,在主函数中不断重复即可,即while(1)G_Follow_R(PID_MODE);

2.jetson nano上关键源码分析

文件说明:

mian_10、main_11、mian_12是测试函数,分别测试铅笔线识别效果A4纸识别效果红绿激光分别识别效果。设置了滑动条供调参使用,确定好参数

q_1、q_2、q_3即为三个问题对应的程序,分别实现发送铅笔线顶点和中心坐标后实时传输红色激光点坐标发送A4纸顶点坐标后实时传输红色激光点坐标实时传输红色和绿色激光点坐标

I.相机参数的调整
string gstreamer_pipeline(int capture_width, int capture_height, int display_width, int display_height, int framerate, int flip_method)
{
   return "nvarguscamerasrc exposurecompensation=1 ! video/x-raw(memory:NVMM), width=(int)" + to_string(capture_width) + ", height=(int)" +
       to_string(capture_height) + ", format=(string)NV12, framerate=(fraction)" + to_string(framerate) +
       "/1 ! nvvidconv flip-method=" + to_string(flip_method) + " ! video/x-raw, width=(int)" + to_string(display_width) + ", height=(int)" +
       to_string(display_height) + ", format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink";
}

这里设置好管道参数,主要调整曝光和饱和度,方便之后线条的检测以及红绿激光的区分

可以参考:NVIDIA Jetson Nano 2GB 系列文章(9):调节 CSI 图像质量

II.关键识别算法
(1)铅笔线识别及顶点的计算

变量解析:

int Find = 0, l_x = 0, l_y = 0, r_x = 0, r_y = 0;
int l[2][2],r[2][2],u[2][2],d[2][2];
int ul[2],ur[2],dl[2],dr[2],ce[2];

Find有效个数标志位,表示找到了几组有效的边上两点;

l_x、l_y、r_x、r_y寻找标志位,为1则分别表示上下左右边未找到有效值的两点值

l[2][2]、r[2][2]、u[2][2]、d[2][2]分别存储上下左右边上两点坐标

ul[2]、ur[2]、dl[2]、dr[2]、ce[2]分别存储最终的顶点和中心点坐标

过程:

——转换成灰度图->阈值划分成二制图->霍夫直线检测得到直线并输出直线上两点坐标

        ——设计算法过滤筛选重复直线并存储两点坐标

for (size_t i = 0; i < linesPPHT.size(); i++) {
                x1 = linesPPHT[i][0], y1 = linesPPHT[i][1], x2 = linesPPHT[i][2], y2 = linesPPHT[i][3];
                line(image, Point(x1, y1), Point(x2, y2), Scalar(0), 1, 8);

                if (x1 < 150 && x2 < 150 && myabs(x2 - x1) < 3 && !l_x){Find++;l_x = (x2 + x1) / 2;l[0][0]=x1;l[0][1]=y1;l[1][0]=x2;l[1][1]=y2;}
                else if (y1 < 150 && y2 < 150 && myabs(y1 - y2) < 3 && !l_y){Find++;l_y = (y1 + y2) / 2;u[0][0]=x1;u[0][1]=y1;u[1][0]=x2;u[1][1]=y2;}
                else if (x1 > 570 && x2 > 570 && myabs(x2 - x1) < 3 && !r_x){Find++;r_x = (x2 + x1) / 2;r[0][0]=x1;r[0][1]=y1;r[1][0]=x2;r[1][1]=y2;}
                else if (y1 > 570 && y2 > 570 && myabs(y1 - y2) < 3 && !r_y){Find++;r_y = (y1 + y2) / 2;d[0][0]=x1;d[0][1]=y1;d[1][0]=x2;d[1][1]=y2;}
            }

linesPPHT是霍夫直线检测函数的输出,linesPPHT.size()表示检测到直线的条数;这里根据直线上两点坐标值大小判断属于四条边的那一条;属于其中一条且之前未存储(标志位为1)(见if语句中的判断)则存储并将找点标志位Find+1;Find==4时即寻找结束

——由于霍夫直线检测算法得到的并非顶点而是直线上两点,设计求两直线交点函数

void crossline(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,int cross[2])
{
    cross[0]=(y3*x4*x2-y4*x3*x2-y3*x4*x1+y4*x3*x1-y1*x2*x4+y2*x1*x4+y1*x2*x3-y2*x1*x3)/(x4*y2-x4*y1-x3*y2+x3*y1-x2*y4+x2*y3+x1*y4-x1*y3);
    cross[1]=(-y3*x4*y2+y4*x3*y2+y3*x4*y1-y4*x3*y1+y1*x2*y4-y1*x2*y3-y2*x1*y4+y2*x1*y3)/(y4*x2-y4*x1-y3*x2+x1*y3-y2*x4+y2*x3+y1*x4-y1*x3);
}

输入的(x1,y1)~(x4,y4)是两条直线上四点坐标,输出交点坐标并赋值给cross;

crossline(l[0][0],l[0][1],l[1][0],l[1][1],u[0][0],u[0][1],u[1][0],u[1][1],ul);
crossline(r[0][0],r[0][1],r[1][0],r[1][1],u[0][0],u[0][1],u[1][0],u[1][1],ur);
crossline(l[0][0],l[0][1],l[1][0],l[1][1],d[0][0],d[0][1],d[1][0],d[1][1],dl);
crossline(r[0][0],r[0][1],r[1][0],r[1][1],d[0][0],d[0][1],d[1][0],d[1][1],dr);

crossline(ul[0],ul[1],dr[0],dr[1],ur[0],ur[1],dl[0],dl[1],ce);

输入之前得到的坐标计算四个顶点值和中心坐标

——向下位机stm32输出坐标

sprintf(m,"#a%03db%03dc%03dd%03de%03df%03dg%03dh%03d$\n",ul[0],ul[1],ur[0],ur[1],dr[0],dr[1],dl[0],dl[1]);
uart.sendUart(m);
usleep(50000);
sprintf(m,"#i%03dj%03d$\n",ce[0],ce[1]);
uart.sendUart(m);

(2)A4纸顶点识别及巡线顶点的计算

过程:

——灰度图->二值化->角点检测得到角点坐标CornerImg

        ——设计算法过滤筛选得到八个顶点P[8][2](绝缘胶布内外边形成两个矩形)

#define MAX_DIS 20
int Is_Exit(int i, int j)
{
    int k = 0;
    for (k = 0; k < Find; k++) 
    {
        if (myabs(P[k][0]-i)+ myabs(P[k][1]-j)<MAX_DIS)return 1;
    }
    return 0;
}
int P[8][2] = { 0 };
int Find = 0;

for (int j = 0; j < CornerImg.rows; j++) {
    for (int i = 0; i < CornerImg.cols; i++) {
        if (CornerImg.at<float>(j, i) > 150.0f) {
            if (!Is_Exit(i, j))
            {                            
                P[Find][0] = i;
                P[Find][1] = j;
                Find++;
            }
        }
    }
}

Is_Exit()函数遍历已经视作有效的点,如果与当前坐标(i,j)接近则不存储;找到八个有效点退出

——设计根据八个顶点P[8][2]求得巡线四边形的顶点Vertex[4][2](同一个角的内外顶点的中点)

int Vertex[4][2] = { 0 };
int sign[8] = { 0 };
int i,j,k,dis,min = 1000;
int temp1, temp2;
for (k=0,i = 0; i < 8; i++)
{
    if (sign[i])continue;
    min = 2000;
    for (j = 0; j < 8; j++)
    {
        if (i == j||sign[j])continue;
        dis = myabs(P[i][0] - P[j][0]) + myabs(P[i][1] - P[j][1]);
        if (dis< min)
        {
            min = dis;
            temp1 = i;
            temp2 = j;
        }
    }
    sign[temp1] = 1;
    sign[temp2] = 1;
    Vertex[k][0] = (P[temp1][0] + P[temp2][0])/2;
    Vertex[k][1] = (P[temp1][1] + P[temp2][1])/2;
    k++;
}

这里使用for循环遍历P[8][2]中顶点,将距离最近的两点视为A4纸一个角内外两边的两个顶点,求其中点存储在Vertex[4][2]中

——设计算法使巡线的四个端点按照顺时针传输给下位机,否则巡线顺序错误

int temp;
//先整体按y值大小排序
for(i=0;i<4;i++)
{
    for(min=Vertex[i][1],j=k=i;j<4;j++)
    {
        if(Vertex[j][1]<=min)k=j;
    }
    temp=Vertex[k][0];
    Vertex[k][0]=Vertex[i][0];
    Vertex[i][0]=temp;
    temp=Vertex[k][1];
    Vertex[k][1]=Vertex[i][1];
    Vertex[i][1]=temp;
}
//y值中等的两点按x值排序
if(Vertex[1][0]<Vertex[2][0])
{
    temp=Vertex[1][0];
    Vertex[1][0]=Vertex[2][0];
    Vertex[2][0]=temp;
    temp=Vertex[2][1];
    Vertex[2][1]=Vertex[1][1];
    Vertex[1][1]=temp;
}
if(Vertex[0][0]&& Vertex[0][1]&&Vertex[1][0]&&Vertex[1][1]&&Vertex[3][0]&& Vertex[3][1]&&Vertex[2][0]&& Vertex[2][1])
{
    sprintf(m,"#k%03dl%03dm%03dn%03do%03dp%03dq%03dr%03d$\n", Vertex[0][0], Vertex[0][1],Vertex[1][0], Vertex[1][1],Vertex[3][0], Vertex[3][1],Vertex[2][0], Vertex[2][1]);
    u.sendUart(m);
}

观察任意矩形顶点坐标规律,要顺时针发送,可将y值最小的作为第一个发送,y值最大的第三个发送,介于中间的两点按x值大小判断,x小的最后发送,大的第二个发送

即先整体按y值大小排序,y值中等的两点按x值排序->排序后按0~1~3~2的顺序发送坐标

(3)区分红绿激光

过程如下:

Point color_recognite(Mat image, Scalar Low, Scalar High)
{

    vector<vector<Point>> g_vContours;
    vector<Vec4i> g_vHierarchy;
    vector<Mat> hsvSplit;
    double maxarea = 0;
    int maxAreaIdx = 0;
    Mat g_grayImage, hsv, g_cannyMat_output;

    cvtColor(image, hsv, COLOR_BGR2HSV);
    split(hsv, hsvSplit);
    equalizeHist(hsvSplit[2], hsvSplit[2]);
    merge(hsvSplit, hsv);
    inRange(hsv, Low, High, g_grayImage);//二值化识别颜色

    //开操作 (去除一些噪点)
    Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));
    morphologyEx(g_grayImage, g_grayImage, MORPH_OPEN, element);

    //闭操作 (连接一些连通域)
    morphologyEx(g_grayImage, g_grayImage, MORPH_CLOSE, element);
    //  Canny(g_grayImage, g_cannyMat_output, 80, 80 * 2, 3);

    // 寻找轮廓
    findContours(g_grayImage, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

    //假设contours是用findContours函数所得出的边缘点集
    RotatedRect box;
    Point centre;

    if (g_vContours.size() != 0)
    {
        for (int index = 0; index < g_vContours.size(); index++)
        {
            double tmparea = fabs(contourArea(g_vContours[index]));
            if (tmparea > maxarea)
            {
                maxarea = tmparea;
                maxAreaIdx = index;//记录最大轮廓的索引号
            }
        }

        box = minAreaRect(g_vContours[maxAreaIdx]);
        rectangle(image, box.boundingRect(), Scalar(0, 0, 255), 2);
        centre = box.center;
    }
    return centre;
}

关键是调用inRange()函数HSV色彩空间二值化的阈值上下限设置

可以参考:OpenCV学习笔记-inRange()阈值操作函数怎么用_cv.inrange函数

并设置滑动条调整参数获得经验值

最终评判标准:

激光在绝缘胶布上是能否识别(黑色胶布吸光;通过提高曝光,调参,增大激光功率等可以解决)

红绿激光靠近时能否区分(红绿在HSC空间互斥,更亮的会掩盖另一个;调inRange()参数解决)

二.学习资料分享

<1>学习笔记

OpenCV学习笔记——《基于OpenCV的数字图像处理》_switch_swq的博客-CSDN博客

图像识别小车(电源部分)——电赛学习笔记(1)_switch_swq的博客-CSDN博客

图像识别小车(电机部分)——电赛学习笔记(2)_switch_swq的博客-CSDN博客

图像识别小车(jetson nano部分)——电赛学习笔记(3)_switch_swq的博客-CSDN博客

图像识别小车(PCB设计)——电赛学习笔记(4)_switch_swq的博客-CSDN博客

PID控制算法理解_switch_swq的博客-CSDN博客

<2>其他资料

1.唐老师讲电赛的个人空间-唐老师讲电赛个人主页-哔哩哔哩视频

2.电赛资料:电赛资料_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:1234

3.我的“电赛”、“VS Studio”、“cmake”、“opencv”、“makefile”、“linux、操作系统”、“stm32”收藏夹

3.以及上面笔记中所包含信息

三.备赛阶段记录

7.2.2023

  • 问题
    1. Nano板子供电5v4A,需求电流较大
    2. 实验室现有开关电源模块似乎调不了5v,需自己设计电源模块
    3. 作为底层主要负责,如何为项目打下坚实基础
    4. 硬件使用有明确目标,如何学习
  • 解决
    1. 先不考虑电源模块,用适配器及直流电源供电
    2. 先把电机控制写好,提供友善接口
    3. 先用简单硬件过度,后期转高级的。如电机先用直流后用伺服。先拼起来,再细化雕琢
  • 收获
    1. 用vscode远程开发jetson,下载remote-ssh插件,ssh jetson@IP地址;快捷键ctrl+o调出要打开页面

7.3.2023

  • 问题
    1. Stm32很多知识忘记,如定时器和A/D、D/A;是否需要复习,因复习耗时且不一定需要stm32
    2. 目前目标尚不明确
  • 解决
    1. 先将stm32相关知识看完,stm32作为保底
    2. 先搭一个蓝牙遥控小车!
  • 收获
    1. 修好了学长的小车,看到了PID实现双轮平衡小车的现象
    2. 学会了MG995型号舵机控制(控制脉冲占空比实现角度控制)
    3. 搭建了测试平台(OLED屏、蓝牙、i2c通讯)
    4. 搭建了简单的两轮遥控小车,采用直流电机控制,未加入PWM波调速,实现简单的前进、倒退、转弯。

7.4.2023

  • 问题
    1. 昨天做的小车电源直接冒烟,因为电源采用两节3.7V锂电池供电,buck升压到12V以匹配LM298N,电流过大
  • 解决
    1. 暂时给l298n提供5V电压,驱动能力下降,但系统可以运行。以后电源模块之后重新设计或使用小功率电机
  • 收获
    1. 主要将昨天搭好的小车完善,并加入了测压模块(利用STM32的ADC外设)
    2. 复习了stm32相关知识(外部中断,定制器TIM设置,定时器比较OC产生PWM波)
    3. 打开了jetson nano的摄像头,它睁开了眼

7.5.2023

  • 问题
    1. 编码器旋转无响应,电机也不动了
    2. 烧了一个stm32板子,当时接的自制稳压模块,之前都是好的,不知道什么原因
  • 解决
    1. 重新测试电机是否能正常工作
    2. 在小车到达前有时间学习MPU6050,相关姿态轨迹传输算法
  • 收获
    1. 在jetson nano上跑了例程及自己上传的几张图片
    2. 学会了linux的vim的使用
    3. 复习了stm32相关知识(定时器IC输入捕获模式)
    4. 学会了超声波测距CS100A模块和红外传感模块以及电机编码器部分

7.6.2023

  • 问题
    1. 如何解决电机编码器输出波形峰值小,stm32无法接收
    2. Pwm波和电源需供电,不然波形失真
  • 解决
    1. 昨天电机不转是因为接线不紧,编码器不行是因为输出电压太小,只有0.5v左右
    2. 考虑IO口输出模式,不行加电压比较器,ref=0.33V
    3. 要重新系统性设计电源了,所有信号共地!
  • 收获
    1. 拼好了大车,发现了诸多问题,舵机控制程序完成
    2. 浅浅学了PCB绘制流程

7.7.2023

  • 问题
    1. 控制函数太过简陋,后续仍需不断升级
    2. 图像识别进度为零
  • 解决
    1. 《基于opencv的数字图像处理技术》
  • 收获
    1. 用洞洞板搭建好了电源系统(12V-5V-3.7V),系统完全移植到大车
    2. 新车编码器输出足够大,无需放大器,编码器计数正常

7.8.2023

  • 收获
    1. 学会在Windows配置OpenCV环境,掌握OpenCV图像视频基本操作以及一些基础知识
    2. PCB绘制进展

7.9.2023

  • 收获
    1. 学会在linux中运行调用OpenCV的c++文件(cmake的使用)
    2. 学习OpenCV基本数据结构和类的使用
    3. 进一步了解VS studio上编译选项配置以及debug和release的区别

7.10.2023

  • 收获
    1. 学会OpenCV灰度变换、直方图、边缘检测、霍夫检测直线和圆

7.11.2023

  • 问题
    1. 源码在linux上无法运行(OpenCV调用摄像头出问题,采用CMake方法编译)
  • 解决
    1. 今天下午加晚上未解决
  • 收获
    1. 学会阈值分割(图像二值化方法)

7.12.2023

  • 问题
    1. C++无法编译成功,Mat类未定义引用(QT上编译)
  • 解决
    1. 使用python编写运行成功
  • 收获
    1. 看完特征提取和目标检测(HOG特征+SVM基本流程;LBP特征+级联分类器)

7.13.2023

  • 问题
    1. 依旧无法运行以C++运行OpenCV代码
    2. 可以运行的OpenCV代码不能直接以videocapture capture(0)的方法获取视频流
  • 解决
    1. 重新系统性安装OpenCV库并重走CMake流程
    2. 将视频流通过管道gstreamer传输
  • 收获
    1. 学会CMake以及基本编译链接流程
    2. 重新安装配置OpenCV4.8.0,成功在jetson nano上运行OpenCV代码

7.14.2023

  • 问题
    1. 但运行自己编写的直线检测程序过于卡顿,一秒一帧
    2. 蓝牙模块无法正常工作
  • 解决
    1. 霍夫直线检测运算量大,不使用该算法
    2. 调整视频大小及帧率
    3. 经检测应是蓝牙模块问题,重新购买
  • 收获
    1. 使用画好的pcb搭建小车,将全部器件搭载在小车上

7.15.2023

  • 问题
    1. 电机控制出错,一边电机不受控制
    2. 目前控制算法学的太少,但图像识别进展不够
  • 解决
    1. GPIO口选到了下载口JTDI/O,换GPIO口控制
    2. 先用超声波模块、MPU6050、红外传感等模块写避障、路径记录、寻迹等功能
  • 收获
    1. 学会jetson nano上的GPIO使用(基本和树莓派一样)
    2. 解决了电机的基本控制问题并将电机的四控制线改成了两根
    3. jetson使用电池供电(器件全供地);实现stm32与jetson nano的usart通信(照搬蓝牙)

7.16.2023

  • 问题
    1. 超声波测距模块中断代码写的不好,拔下模块进入while循环等待,系统卡住
    2. 拉肚子
  • 解决
    1. 使用static变量,进入中断模式改为EXTI_Trigger_Rising_Falling...
    2. 休息一天(今日中午至明天中午)

7.17.2023

  • 问题
    1. 欲添加mpu6050模块,但其与oled、蓝牙、超声波模块冲突(非引脚分配问题)
  • 解决
    1. 更改方案,debug试试。仍不行

7.18.2023

  • 问题
    1. 昨天问题仍然存在
    2. 多个中断之间不协调,影响超声波测距精度。以及测角度过于耗时
  • 解决
    1. 使用江科大自动化的例程代码,简洁明了,解决冲突
    2. 更改各个中断优先级,控制mpu6050的使用
  • 收获

7.19.2023

  • 收获
    1. 学会PID算法
    2. 重新绘制PCB,解决若干问题

7.20.2023

  • 问题
    1. SysTick定时器冲突问题(外部和中断同时调用delay_us函数会卡死)
  • 解决
    1. 避免了0.1s定时器中断(数据刷新)的SysTick定时函数
  • 收获
    1. 使用编码器利用PID编写行驶给定长度函数及测速

7.21.2023

  • 问题
    1. 后退时编码器反向计数,上限不明确,速度测算出现问题
  • 解决
    1. 通过TIM_EncoderInterfaceConfig设置编码器反转依旧向上计数
  • 收获
    1. 编写小车倒车定长距离

7.22.2023

  • 收获
    1. 编写小车以恒定速度行驶和拐弯90度算法

7.23.2023

  • 收获
    1. 焊好新到的板子

7.24.2023

  • 问题
    1. Jetson配置难,yolo难跑通
    2. 要求设计完整程序,在jetson开机时自动执行
  • 解决
    1. 学习OpenCV备用
    2. 学习python或c++可执行文件Linux开机自动执行方法

7.25.2023

  • 问题
    1. 采用硬盘直接克隆方式克隆SD卡依旧无法启动jetson nano系统
  • 解决
    1. 烧录官方镜像文件,成功还原系统。并发现python和C++环境已经配好,之前不会用。解决yolov5摄像头实时检测问题,方案参考亚博论坛。C++也是,g++编译时加上一个参数就行

7.26.2023

  • 收获
    1. 看今年电赛器件清单,简单编写完云台代码,购买K210等器材

7.27.2023

  • 收获
    1. 简单编写完红外寻迹功能
    2. 解决nano开机启动python文件

7.28.2023

  • 问题
    1. 分析电赛清单,云台摄像头加激光笔应该涉及到动态物体追踪
  • 解决
    1. 学习视频目标跟踪

7.29.2023

  • 问题
    1. Stm32定时器资源有限无法满足云台的加入
  • 解决
    1. 使用pca9685驱动
  • 收获
    1. 采用stm32管脚重定义解决pca9685驱动的使用问题

7.30.2023

  • 问题
    1. Jetson nano的C++库不包含串口uart相关内容
  • 解决
    1. 使用其设备/dev/tthTSH1,研究网上代码
  • 收获
    1. 编写以及pca9685控制云台函数

7.31.2023

  • 问题
    1. 霍夫圆检测一定也不稳定
  • 解决
    1. 调整参数或使用深度学习识别物体的方法
  • 收获
    1. 终于解决串口通信问题,实现C++语言的nano和电脑以及stm32通信
    2. 完善霍夫圆检测代码,加入uart传输圆心坐标
    3. 学会nano开机自启动程序方法
    4. 编写stm32的PID点跟踪函数,实现点跟踪

8.1.2023

  • 问题
    1. 走定长不精确,大约是设定5cm行驶6cm这个比例
  • 收获
    1. 编写摇杆控制云台程序
    2. 发现之前使用的pid算法全犯了低级错误,本应用float定义PID值结果用了int,修改后大范围应用,各个控制加入PID平稳精确了很多

四.电赛总结及经验教训

<1>本次比赛作品的不足、改进之处

1.stm32和jetson nano通讯不稳定(可能原因:杜邦线传输能力差、波特率可能设置高了【但低了影响系统处理速度】)(实际原因:while写成了if,导致时机很难对上,通信规则设计失误!

2.stm32主函数设计不行,没花时间改进,想要重复运行某个程序只能重启,人机交互也不友好

3.比赛报告没有在头脑风暴之后就开始写,导致后期书写太急,不够规范

4.linux操作不熟,开机自启动程序出现问题,且jetson nano上的程序设计缺乏系统性结构性(每问都写了一个程序,而不是整合成一个大的测试程序)

5.所有任务完成太晚,没有留下时间仔细调试调参找问题。而且全流程过一遍后立马就要封箱了,急急忙忙乱改代码导致出现了意想不到的错误!再给一天就刚好了啊!!!

<2>本次比赛的经验教训

1.器件准备很重要:比赛发布器件清单后要备齐,最好每个器件都多买几个。以满足比赛器件需求并防止比赛时器件损坏!(本次比赛oled屏、舵机都反复坏过)

2.器件精度很重要:比赛前统计自己所有器件清单,并实测是否可以使用?精度如何?硬件精度不足会直接导致结果无法满足!(本次比赛刚开始使用的舵机为20kg大扭矩低精度,调了一晚PID参数舵机仍然运动不准,最后才发现是精度问题)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2023年电赛E题完整设计暨电赛全记录 的相关文章

随机推荐

  • Python 常用写法

    时间 1 now import datetime now datetime now nowStr now strftime now Y m d H M S list 1 分割最后一个 arr str rsplit 1 2 数组 加索引 en
  • 学习C++项目——select模型,poll模型和epoll模型

    学习计算机网络编程 一 思路和学习方法 本文学习于 C语言技术网 www freecplus net 在 b 站学习于 C 语言技术网 并加以自己的一些理解和复现 如有侵权会删除 接下来应该是网络编程部分最难也是最常用的部分 同时在这一章我
  • 系统分析与设计——UML图总结

    一 前言 期末考试之前 我复习系统分析与设计的时候对UML图做了一些知识汇总 现在放到博客上 如果有不对或不恰当的地方 欢迎各位指正 本文仅仅起记录作用 可随意转载 荣幸之至 截图来自网络或是老师的PPT 二 概述 系统模型的三个主要部分
  • flutter 键盘挡住输入框问题

    bool isShowKeyboard false double keyboardSize 260 软键盘高度 类添加with WidgetsBindingObserver 生命周期监听器 class SendRedEveDialogSta
  • 浏览器上实现CNN可视化——清楚看到每一层卷积后的图

    目录 本文作用 CNN神经网络可视化工具1 解释器学习笔记 CNN神经网络可视化工具2 本文作用 学习卷积神经网络时 我们只知道输入一张图片后 通过一顿操作 便可以提取图片中的特征 我们对于其内部的操作 只有理论了解 并没有做到眼见为实 这
  • 使用cloudflare tunnel免费内网穿透,实现网站的外网访问和远程桌面

    前言 Cloudflare Tunnel是Cloudflare Zero Trust中的一个产品 它能够帮助用户将位于内网中的服务暴露到公网上 从而使得外部用户可以通过互联网访问这些服务 相比较于frp ngrok等内网穿透工具 使用Clo
  • 人工智能数学基础--概率与统计9:概率运算、加法公理、事件的独立性、概率乘法定理、条件概率、全概率公式以及贝叶斯公式

    一 概述 这大半年都很忙 学习时间太少 导致概率论的学习停滞不前 期间AI大佬herosunly推荐了陈希孺老先生的概率论教材 与最开始学习的美版M R 斯皮格尔等著作的 概率与统计 表示差异比较大 具体请见 人工智能数学基础 概率与统计7
  • ESP32-IDF环境搭建以及使用

    1默认已经安装了esp32 idf和vscode配置 离线版的esp32idf安装 windows eap32安装这里参考博客ESP32c3开发环境搭建 IDF V4 4离线版安装使用 esp idf v4 4 2 可能会遇到的问题 问题篇
  • 修改elementUI样式未生效问题(挂载到了body标签上)

    修改挂载到body标签上elementUI样式问题 目录 修改挂载到body标签上elementUI样式问题 前言 一 适用范围 二 示例 1 目标 2 实现思路 修改自带样式方法 最后看效果 总结 前言 在使用element ui库的时候
  • Aspose.Slides for Java Crack

    Aspose Slides for Java Crack Added support for changing the color of leader lines in pie charts Added new AfterAnimation
  • 2012年终总结 - I T征途

    2012年终总结 I T征途 在2012年年初的时候 自己曾写了一个规划 2012 这一年我该做些啥 里面简单的介绍了一下2012年 我应该做的事儿 如今到了为2012结账的时候 我想借助那篇文档来总结这一年我的所作所为 2012年 我该给
  • 用Sipp 对Asterisk 进行性能测试的工作笔记-1

    公司需要 对Asterisk 进行一定的性能测试 测试目标 1 IVR 支持多少路2 一对一通话 支持多少路3 不同编解码的性能影响 4 通话中 录音 支持多少路 测试工具 sipp http sipp sourceforge net 辅助
  • createrepo:创建本地源

    4月20日 createrepo 创建本地源 repodata作为软件的仓库 其目录下有四个必要文件 filelists xml gz other xml gz primary xml gz 和repomd xml md 意思是 metad
  • IDEA 中 .properties文件的中文显示乱码问题的解决办法

    今天使用IDEA 搭建Spring Boot 项目 配置application properties 配置文件 录入中文 在右下角出现如下截图提示语 重新打开application properties 文件出现汉字乱码 依据提示信息修改源
  • “你爱我,我爱你,蜜雪冰城甜蜜蜜“秋天的第一杯奶茶!Python安排!!

    立秋了 大家秋天的第一杯奶茶都安排上了么 前一段时间我相信很多人都被 你爱我 我爱你 蜜雪冰城甜蜜蜜 这首歌洗脑了 所以今天就爬取了某度地图上蜜雪冰城门店分布 看看全国有多少家蜜雪冰城 能不能满足大家的需求啦 哈哈哈 数据采集 首先 我们打
  • Linux部署宝塔

    1 linux服务器安装宝塔 宝塔地址 https www bt cn new download html 点击上方地址 进入下方页面 点击安装版本 复制第一个命令 得确认你服务器是centos 远程连接服务器 复制此命令运行 运行成功后
  • [CISCN2019 华东南赛区]Web11 SSTI

    这道SSTI 差点给我渗透的感觉了 全是API 我还想去访问API看看 发现这里读取了我们的ip 我们抓包看看是如何做到的 没有东西 我们看看还有什么提示 欸 那我们可不可以直接修改参数呢 我们传递看看 发现成功了 是受控的 这里我就开始没
  • mysql某批量更新导致死锁

    查询当前数据库全部线程 show full processlist 查询当前运行的全部事务 select from information schema innodb trx 查询锁情况 select from information sc
  • 碰撞改变材质颜色_bp

    感谢来自程序员的暴击 学习资料来于 https www bilibili com video BV125411h7c4 p 22 最大的收获是 材质编辑器上 1维向量到4维向量的生成 会者不难 难者不会 方法很简单 鼠标左键 数字1就会生成
  • 2023年电赛E题完整设计暨电赛全记录

    目录 一 2023年E题完整设计 lt 1 gt 选择方案 任务一 实现按键按下复位 基础部分 任务二 实现激光点绕边框一周 基础部分 任务三 实现激光点绕A4纸边缘一周 基础部分 任务四 实现绿色激光追踪红色激光 发挥部分 lt 2 gt