先上视频!

1.首先我们来看看题目

 

         题目还是比较简单明白的,基础题只需要用到一个云台去做这个题目,发挥题需要用到两个云台去解决,首先前几题除了写死没有更好的办法了,我们直接来说一下发挥题怎么做吧,怎么去解决这个问题吧。

        做发挥题的时候我是遇到了很多问题的,开始我用的方法是区域法,就是让我识别到的绿点在我的像素中心,刚好我的红点也在我的像素中心。于是我用了这样的方法:将openmv分成5个区域,这样的的话,识别到的点在不同区域就可对应的处理了,将我们寻找到的发送给单片机去处理,不同区域不同的方法解决问题。

IDE(openMV摄像头代码):基于python

# Single Color RGB565 Blob Tracking Example
#
# This example shows off single color RGB565 tracking using the OpenMV Cam.
from pyb import UART#开启串口
import sensor, image, time, math

threshold_index = 0 # 0 for red, 1 for green, 2 for blue

# Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max)
# The below thresholds track in general red/green/blue things. You may wish to tune them...
thresholds = [(63, 100, 6, 69, -41, 42)] # generic_blue_thresholds


uart = UART(3, 9600)
x_max = 320
x_min = 0
x_1 = 155 #中心区域左边界
x_2 = 163 #中心区域右边界

y_max = 240
y_min = 0
y_1 = 115 #中心区域上边界
y_2 = 123 #中心区域下边界
flag = 0#位置信息标志


sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()

def find_max(blobs):    #定义寻找色块面积最+大的函数
    max_size=0
    for blob in blobs:
        if blob.pixels() > max_size:
            max_blob = blob
            max_size = blob.pixels()
    return max_blob
# Only blobs that with more pixels than "pixel_threshold" and more area than "area_threshold" are
# returned by "find_blobs" below. Change "pixels_threshold" and "area_threshold" if you change the
# camera resolution. "merge=True" merges all overlapping blobs in the image.

while(True):
    clock.tick()
    img = sensor.snapshot()
    for blob in img.find_blobs([thresholds[threshold_index]], area_threshold=50, pixels_threshold=300, area_threshold=200, merge=True):
        # These values depend on the blob not being circular - otherwise they will be shaky.
        if blob.elongation() > 0.5:
            img.draw_edges(blob.min_corners(), color=(255,0,0))
            img.draw_line(blob.major_axis_line(), color=(0,255,0))
            img.draw_line(blob.minor_axis_line(), color=(0,0,255))
        # These values are stable all the time.
        img.draw_rectangle(blob.rect())
        img.draw_cross(blob.cx(), blob.cy())#坐标数据
        # Note - the blob rotation is unique to 0-180 only.

        img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20)


        if blob.cx()>= x_min  and blob.cx() <= 160 and\
            blob.cy() >= 120 and blob.cy() <= y_max :
                flag = 1
        if blob.cx()>=160 and blob.cx() <= x_max and\
            blob.cy() >=120 and blob.cy() <= y_max :
                flag = 2
        if blob.cx()>= x_min and blob.cx() <= 160 and \
            blob.cy() >= y_min and blob.cy() <= 120 :
                flag = 3
        if blob.cx()>= 160 and blob.cx() <= x_max and \
            blob.cy() >= y_min and blob.cy() <= 120 :
                flag = 4
        if blob.cx()>= x_1 and blob.cx() <= x_2 and\
            blob.cy() >= y_1 and blob.cy() <=y_2 :
                flag = 5
        output_str="%d" %flag #方式1
        print('you send:',output_str)
        #time.sleep(0.02)
        uart.write('@'+output_str+'\r\n')

这个方法的32端代码,我用的是C8T6的最小系统去作为我们的主控制的,代码如下:

串口端接受数据包:(状态机)

//文本数据包处理格式
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);

		if (RxState == 0)
		{
			if (RxData == '@' && Serial_RxFlag == 0)
			{
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if (RxState == 1)
		{
			if (RxData == '\r')
			{
				RxState = 2;
			}
			else
			{
//				strncpy(&Serial_RxPacket[pRxPacket],RxData,1);
				Serial_RxPacket = RxData;
//				pRxPacket ++;
			}
		}
		else if (RxState == 2)
		{
			if (RxData == '\n')
			{
				RxState = 0;
//				Serial_RxPacket[pRxPacket] = '\0';
				Serial_RxFlag = 1;
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

对串口数据进行处理,根据识别到的点不同位置,让舵机去运动:

Where_Addr=Serial_RxPacket-48;
			OLED_ShowNum(4,10,Where_Addr,1);
			switch(Where_Addr)
			{
				case  1:
					{
						AngleY--;
						AngleX++;
					}
					break;
				case  2:
					{
						AngleY--;
						AngleX--;
					}
					break;
				case  3:
					{
						AngleY++;
						AngleX++;
					}
					break;
				case  4:
					{
						AngleY++;
						AngleX--;
					}
					break;
			    case  5:
					{
						been_on();
						LED1_Turn();
						Delay_ms(50);
					}break;

			}

   方法修改:               

        但是这样做有很多弊端,题目要求我们在距离60cm,但是60cm,虽然在了像素中间,但是是斜的,误差很大,无法达到题目要求,这时候我们只能用PID算法来做这个题目了,让他们两个点在像素上无限接近,最后达到题目要求:但是去和用pid算法呢???

        这里我们求出红点和绿色点的坐标位置,然后最差值通过pid算法的公式带进去,分别进行pid进行调参,最后到达稳定状态;

        在摄像头方面的编程,我们用python进行将两个点的坐标求出来,然后通过串口将数据发送到32单片机进行处理:

import sensor, image, time,lcd,machine
from machine import UART
from fpioa_manager import fm

red_threshold   = (60, 98, 5, 127, 113, -128)
blue_threshold   = (55, 99, -18, -1, 4, 11)#(83, 58, -95, -1, 92, -51)#(65, 100, -113, 102, 3, 71)#(100, 60, -25, 106, 2, 3)

lcd.init()
sensor.reset() # Initialize the camera sensor.
sensor.set_pixformat(sensor.RGB565) # use RGB565.
sensor.set_framesize(sensor.QVGA) # use QQVGA for speed.
sensor.skip_frames(10) # Let new settings take affect.
sensor.set_auto_whitebal(False) # turn this off.
clock = time.clock() # Tracks FPS.
sensor.set_auto_gain(False)
lcd.rotation(2)

#映射UART2的两个引脚

fm.register(0, fm.fpioa.UART1_RX, force=True)  #GPIO0
fm.register(1, fm.fpioa.UART1_TX, force=True)  #GPIO1

#初始化串口,返回调用句柄
uart_A = UART(UART.UART1, 115200, 8, 0, 1)

def find_max(blobs):   #找到最大色块
    max_size = 0
    for blob in blobs:
        if blob.pixels() > max_size:
            max_blob=blob
            max_size = blob.pixels()
    return max_blob

while(True):
    img = sensor.snapshot() # 截取并返回一张图片

    blobs = img.find_blobs([red_threshold])
    blobs2 = img.find_blobs([blue_threshold])
    if blobs:
        if blobs2:
            max_blob=find_max(blobs)#红色色块
            max_blob2=find_max(blobs2)#绿色色块
            img.draw_rectangle(max_blob.cx()-10,max_blob.cy()-10,20,20)  #传入识别到的最大图块的x,y,w,h
            img.draw_rectangle(max_blob2.cx()-10,max_blob2.cy()-10,20,20)  #传入识别到的最大图块的x,y,w,h
            img.draw_cross(max_blob.cx(), max_blob.cy()) #在中心点画十字
            img.draw_cross(max_blob2.cx(), max_blob2.cy()) #在中心点画十字

            output_XY="@%d,%d$%d&%d#" % (max_blob.cx(),max_blob.cy(),max_blob2.cx(),max_blob2.cy())   #以@为开头,逗号分割,#号结尾,发送字符串,由单片机处理得到目标位置中心坐标

            uart_A.write(output_XY)   #传出数据
            print(output_XY)

    lcd.display(img)

32端的代码:

        串口中断对数据包处理:


//获取数据包
void USART1_IRQHandler(void)    //发送可变包长字符
{
	static uint8_t RxState = 0;   //定义静态变量表示状态位,根据不同状态位进行不同操作
	static uint8_t pRxPacket = 0;  //表示发送的数据到第几个
	Rx_Config=0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)
		{
			if (RxData == '@')  //判断是否为起始位@同时判断
			{
				RxState = 1;   // 起始位判断成功后将给RxState置1
				pRxPacket = 0;
			}
		}
		else if (RxState == 1)   
		{
			if (RxData == '#')  //优先判断是否为第一位结束位
			{
				
				Serial_RxPacket[pRxPacket] = '\0';
				RxState = 0;
				pRxPacket = 0;
				//设计结束标志位
				Rx_Config=1;
				
			}
			else
			{
				Serial_RxPacket[pRxPacket] = RxData;  //将接收到的字符存到数组
				pRxPacket ++;
			}
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}


        首先我们设计了定时器中断,没隔一段时间发生中断去处理pid进行舵机的控制:

#include "stm32f10x.h"                  // Device header

//内部时钟

void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);   //开启APB1上的tim3时钟控制
	
	//TIM_InternalClockConfig(TIM3);     //设置内部时钟TIM3
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    //定义开启TIM时钟接口体,并配置参数
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);   //给未定义的结构体初始值
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //选择时钟分频方式(滤波器)
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;   //计数方式:向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 2000 - 1;    //ARR自动重装器值设置
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;   //PSC预分频值设置
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //高级定时器中的
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);   //初始化定时器
	
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);   //清除TIM2复位后生成更新事件以
											//重新加载预分频器
											//和
											//重复计数器立即值
	
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);   //给TIM3中断控制使能
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //选择优先级分组方式
	
	NVIC_InitTypeDef NVIC_InitStructure;    //定义NVIC结构体并赋值
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;    //选择TIM2通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    //给所选的通道使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;    //赋值抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;    //赋值响应优先级
	NVIC_Init(&NVIC_InitStructure);    //定义NVIC
	
//	TIM_Cmd(TIM3, ENABLE);   //使能TIM3外设
}

        在pid方面我们独一数据包将进行处理,处理数据包,获取每个点的坐标,然后求出差值,用pid算法进行调参数,控制pwm占空比 

float PID_KP_x =0.45;  //0.6
float PID_KI_x =0.065;	//0.07
float PID_KD_x =0.0;


float PID_KP_y =0.45;  //0.45
float PID_KI_y =0.034; //0.05
float PID_KD_y =0.0;

extern char Serial_RxPacket[40];

char Data_x[10];
char Data_y[10];

char DataBlue_x[10];
char DataBlue_y[10];

int16_t Data_xx;
int16_t Data_yy;

int16_t DataBlue_xx;
int16_t DataBlue_yy;

extern int Trace_Flag;//追踪标志位
/******************位置式****************/
/*******************速度环控制 - 位置式****************/
int16_t Servo_PID_x(int16_t Error)  //传入误差
{
	static int16_t Error_last, Error_difference , Error_All;   
	//静态变量储存,上次误差,上次误差与本次误差差值,误差累积
	
	int16_t Differential;      //返回的最终速度
	
	Error_difference = Error - Error_last;        //本次误差 - 上次误差
	
	Error_All += Error;     //误差累计
	
	if(Error_All >= 6000)        //积分限幅
	{
		Error_All =  6000;
	}
	if(Error_All <= -6000)
	{
		Error_All = -6000;
	}
	//位置式PID,速度闭环
	Differential = PID_KP_x*Error + PID_KI_x * Error_All + PID_KD_x * Error_difference;    
	Error_last = Error;     //储存上次误差
	
	return Differential;
}

int16_t Servo_PID_y(int16_t Error)  //传入误差
{
	static int16_t Error_last, Error_difference , Error_All;   
	//静态变量储存,上次误差,上次误差与本次误差差值,误差累积
	
	int16_t Differential;      //返回的最终速度
	
	Error_difference = Error - Error_last;        //本次误差 - 上次误差
	
	Error_All += Error;     //误差累计
	
	if(Error_All >= 6000)        //积分限幅
	{
		Error_All =  6000;
	}
	if(Error_All <= -6000)
	{
		Error_All = -6000;
	}
	//位置式PID,速度闭环
	Differential = PID_KP_y*Error + PID_KI_y * Error_All + PID_KD_y * Error_difference;    
	Error_last = Error;     //储存上次误差
	
	return Differential;
}

/****************** 数据包解析函数 *******************/
//数据包的处理
//截取数据
void analysis(void)
{
	uint8_t n = 0;
	uint8_t s = 0;
	
	uint8_t n2 = 0;
	uint8_t s2 = 0;
	 uint8_t i=0;
	
	while(Serial_RxPacket[i] != ',')
	{
		++i;
		
	}//得到数据长度
	
	for(n=0;n<i;n++)//存入数据
	{
		Data_x[n] = Serial_RxPacket[n];
	}
	Data_x[i] = '\0';//数据结束符号
	Data_xx = atoi(Data_x);
	

	
	n = i;
	do
	{
		++i;
	}
	while(Serial_RxPacket[i] != '$');
	
	for(n+=1;n<i;n++)
	{
		Data_y[s] = Serial_RxPacket[n];
		s+=1;
	}
	Data_y[s] = '\0';
	Data_yy = atoi(Data_y); //将接收到的字符数字转换成10进制数
	
	n = i;
	do
	{
		++i;
	}
	while(Serial_RxPacket[i] != '&');//
	for(n+=1;n<i;n++)
	{
		DataBlue_x[n2] = Serial_RxPacket[n];
		n2+=1;
	}
	DataBlue_x[n2] = '\0';
	DataBlue_xx = atoi(DataBlue_x); //将接收到的字符数字转换成10进制数


	n = i;
	do
	{
		++i;
	}
	while(Serial_RxPacket[i] != '\0');///
	
	for(n+=1;n<i;n++)
	{
		DataBlue_y[s2] = Serial_RxPacket[n];
		s2+=1;
	}
	DataBlue_y[s2] = '\0';
	DataBlue_yy = atoi(DataBlue_y); //将接收到的字符数字转换成10进制数
	

	i = 0;
}

void TIM3_IRQHandler(void)
{

	int16_t Error_x;  //定义误差
	int16_t Error_y;
	int16_t PWM_x;   //定义经过PID后的PWM
	int16_t PWM_y;
		
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{

		analysis();
		
		
		Error_x =DataBlue_xx - Data_xx;  //计算误差
		Error_y =DataBlue_yy - Data_yy;

		if(Error_x>0&&Error_x<8)
		{
			Error_x = 0;
		}
		else if(Error_x<0 && Error_x>-8)
		{
			Error_x = 0;
		}
		else if(Error_y>0&&Error_y<8)
		{
			Error_y = 0;
		}
		else if(Error_y<0 && Error_y>-8)
		{
			Error_y = 0;
		}
		if(Error_y ==0&&Error_x == 0)//表示到得了识别点,距离达到要求了
		{

			been_on();//蜂鸣器响

			LED2_ON();//led亮
			//关闭定时器
			//设计结束标志位
			Trace_Flag=0;

		}
		
		PWM_x = Servo_PID_x(Error_x);  //计算PID控制后的PWM
		PWM_y = Servo_PID_y(Error_y);
		
		PWM_SetCompare1(1500 + PWM_x); //舵机角度控制  下面的舵机   x轴
		PWM_SetCompare2(1500 + PWM_y); //舵机角度控制  上面的舵机   y轴
				
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);	
	}
}

        最终我们达到了题目的要求,四天三夜电子设计大赛真的学到了很多!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐