增加第二次补充内容:硬件部分改为PCB,主控芯片替换为STM32F103C8T6,详细请见“7. 第二次补充(2022.10.22)”

1.摘 要

本文通过OpenMV作为是识别模块去识别被测物体(以红色小球为例),当其识别到红色小球后,判断小球中心点所在的区域信息,并将其区域标志位通过串口发送给STM32,当STM32接收到位置信息后对x轴、y轴的两个舵机参数进行操作,最后通过定时器输出合适的PWM波,控制舵机旋转相应的角度,使OpenMV摄像头对准被测物体,以实现物体追踪功能。

实现效果

【毕业设计】基于Stm32及OpenMV的云台追踪装置

我会把我做该毕业设计的整体思路以及部分的主要代码在文章中详细介绍,很简单的,在追踪部分没有用到PID控制哦!大家可以放心食用(手动滑稽)!很容易复刻的!

若是你很懒,不想自己一步一步自己做的话,可以直接去下载论文和源代码。

代码:【代码】基于STM32及OpenMV的云台追踪装置_毕业设计

论文:【论文】基于STM32及OpenMV的云台追踪装置_毕业设计

全家桶:【毕设全家桶】基于Stm32及OpenMV的云台追踪装置_毕业设计

(温馨提示:是要花钱的哦,我觉得你看完这篇文章完全可以自己做出来的,没必要花钱的,所以请一定要仔细看下去哦!)

2022.3.12 第一次补充:

新添加了PID控制追踪的代码:

【代码(PID控制)】基于Stm32及OpenMV的云台追踪装置_毕业设计

注:在2022.3.12前,购买过我的云台追踪的代码或是全家桶的小伙伴,可以带着购买记录私信我,可以免费获得这个PID控制的代码。

2.整体功能分析

完成一个好的设计是要在实现了它的功能的基础上,所以在进行设计之前需要确定一下本设计要实现的功能,然后再进行相应的设计。在本设计中使用OpenMV作为视觉模块,STM32作为主控制MCU去控制舵机旋转,以实现追踪功能。整体功能流程图如图2.1所示。

OpenMV:作为识别模块,主要实现物体识别的功能,并将识别到的物体的位置信息通过串口传递给STM32。

STM32的串口部分:主要功能是与OpenMV进行数据交互,接收OpenMV发来的位置信息。

STM32的定时器部分:主要功能是通过库函数输出PWM波,从而控制X、Y轴的两个舵机旋转。以达到追踪效果。

整体功能流程图

整体功能流程图

3.硬件选型

本小结主要对整个毕业设计过程所需要的硬件设备:DS3120舵机、STM32F103ZET6、LED补光板、3S锂电池、OpenMV4 Cam H7稳压板进行介绍,然后进行整个硬件的连接。

3.1 OpenMV4 Cam H7

本设计中OpenMV选用星瞳科技代理的OpenMV4 Cam H7,如图3.1所示。选择那一个型号的OpenMV都可以,主要影响的只是图像的清晰度(图像大小),性能越好的OpenMV可以在更高的清晰度下运行,而差的可能会在该清晰度下报错。

OpenMV

本设计中OpenMV部分主要实现三个功能:完成被测物体的识别(以红色小球为例)、寻找最大色块区域、通过串口发送被测物体的位置信息。在这儿强调一下,其引脚3.3v-5v耐压,过高的电压会烧掉其中的芯片。

3.2 STM32F103ZET6

本次设计中使用STM32F103ZET6最小系统板作为核心MCU,实物如图3.3所示。使用其他的芯片已ok的,只需要确定该芯片可以输出两路PWM波、一个串口就行。

STM32F103ZET6最小系统板

本设计中STM32部分主要使用串口和定时器来实现:通过串口接收OpenMV发来的数据、通过定时器输出PWM波,以实现控制舵机旋转追踪的目的。

只强调一下,其引脚3.3v-5v耐压,过高的电压会烧掉其中的芯片。

3.3 DS3120舵机

舵机选用达盛舵机科技有限公司生产的DS3120型号的舵机,实物图及尺寸如图3.5所示。该型号舵机运行温度在-15℃~70℃,工作电压范围为4.8v-6.8v,驱动方式为PWM波,脉冲范围为500~2500μsec,控制角度为:180° 。这里我虽然使用了180°舵机,但是在追踪过程中,我通过在软件中限制了舵机的最大旋转角度,避免追踪过程中舵机旋转角度过大对舵机后面的接线造成影响。

舵机

工作原理:通过给舵机的信号线(橙黄色)输入周期为20ms的PWM波,通高电平的时间为:0.5 ~ 2.5ms,可以使舵机旋转0~180°。如图3.6所示。

舵机旋转原理图

3.4 LED补光板

因为OpenMV进行颜色识别时,对环境的光照有一定的要求,光照强度的变换会直接对识别造成影响,所以为避免环境光线变化的影响,在本次设计中加入LED补光板,补光板由6个贴片LED及电阻组成,贴片LED选用CF12V3T3R00,电阻选用100欧姆,供电则直接接3S锂电池,11.1v供电。

补光板

3.5 供电及稳压

供电使用3s锂电池作为整体装置的供电,3S锂电池可以提供11.1V的电压,3S锂电池实物图如图3.9所示。

锂电池

因为OpenMV、舵机、STM32都不能直接11.1V电压,所以需要对电压进行稳压到5v,才能给它们供电,使用稳压板进行稳压,稳压板选用基于LM2596芯片的DC-DC稳压模块,稳压板的输入端连接3S锂电池,用电压表测量输出端电压,用螺丝刀旋转稳压板上的可调节电位器,直到输出端输出电压为5v即可。

稳压板

3.6 硬件连接

硬件连接部分使用杜邦线连接,连接如下:3S锂电池接稳压板输入端以及直接给LED补光板供电,稳压板输出端接OpenMV的VIN和GND引脚、STM32的5V和GND引脚以及两个舵机的正(红色)负(棕色)极。OpenMV的P4引脚(串口3的TX)接STM32的PA10引脚(串口1的RX),OpenMV的P5引脚(串口3的RX)接STM32的PA9引脚(串口1的TX),STM32的PC7引脚(定时器3通道2)接x轴的舵机的信号线(橙黄色),STM32的PC7引脚(定时器3通道1)接y轴的舵机的信号线(橙黄色)。

硬件接线图

实物三视图

4.软件功能实现

软件部分的功能主要分为两部分,一个是OpenMV部分,另一是STM32部分,OpenMV主要实现功能:完成被测物体的识别、寻找最大色块区域、判断被测物体所在区域、通过串口发送被测物体的位置信息。STM32部分主要实现功能:使用串口接收OpenMV发来的数据、通过定时器输出PWM波、以及实现控制舵机旋转追踪的目的。

4.1 OpenMV部分的功能实现 4.1.1 整体逻辑分析

首先对OpenMV部分实现功能:首先进行摄像头的初始化,确保其可以正常的使用。其次设置图像格式,选用RGB模式,使其图像为彩色模式,然后设置图像大小,设置为QVGA格式,分辨率为320*240 dpi。而后设置颜色阈值,确保OpenMV可以识别到此颜色。然后设置白平衡,关闭自动白平衡,避免对识别的影响。接着对串口初始化,确保串口可以正常的发送数据。到此准备工作完成,进入识别部分,首先截取图像并返回,然后判断图像内是否识别到红色区域:否则循环等待,是则判断区域是否为最大区域,否则返回等待,是则用矩形框标出中心位置,判断中心位置所在的区域,并将位置信息通过串口发出。以上部分除了判断所在区域外的所有代码都可以在星瞳科技的官网上找到,并且官方还有配套的详细讲解。

戳这里直接进入:星瞳科技官网

整体流程图

4.1.2 对被测物体的识别

本设计中被测物体为一个红色小球,因此对于物体的识别主要为颜色识别,在编程中首先需要对OpenMV的红色的阈值进行调整,打开阈值编辑器,对LAB的阈值进行调整,使二进制图像中只有红色区域的映像。

调整阈值

注:在不同的光照环境下会对颜色识别造成很大的影,所以请在稳定的光照环境下调整阈值以及识别。

调整好红色阈值后,赋值LAB阈值的参数,并赋值给red_threshold,调用MicroPython函数库中的image.find_blobs()函数,对该色域进行识别。该功能部分程序如下:

import sensor, image, time, pyb
from pyb import UART
red_threshold   = (14, 68, 11, 70, 9, 56) #红色阈值设定
sensor.reset() # 初始化摄像头传感器.
sensor.set_pixformat(sensor.RGB565) # 使用RGB565.
sensor.set_framesize(sensor.QVGA) # 使用QVGA.
sensor.skip_frames(10) # 让新设置生效.
sensor.set_auto_whitebal(False) # 关闭自动白平衡.
clock = time.clock() # Tracks FPS.
while(True):
    img = sensor.snapshot() # 拍照并返回图像.
    blobs = img.find_blobs([red_threshold])
    if blobs:
        img.draw_rectangle(blobs.rect())
        img.draw_cross(blobs.cx(), blobs.cy())

其中:

while循环之前为OpenMV初始化部分:

RGB565为彩色模式(颜色识别嘛,肯定得用彩色模式);

QVGA为图像大小(320dpi * 160dpi,像素点),这个可以根据选的OpenMV的性能来选择;

关闭白平衡:是指关闭系统的自动白平衡,自动白平衡会对颜色识别造成影响。

while循环内:

image.find_blobs()函数:参数主要为6个:LAB的最大最小值(可以通过之前的阈值编辑器中得到)。

img.draw_rectangle()函数:则是用矩形框框出识函数参数的区域。

img.draw_cross()函数:在参数位置上绘制出十字架。其中函数参数blobs.cx(), blobs.cy():分别为blobs区域的中心x、y轴坐标。

4.1.2 寻找最大色块区域

在OpenMV追踪识别的过程中,可能出现背景或是其他区域出现小面积的红色区域,如图所示。这会对识别造成影响,所以需要用程序过滤掉那些小的红色区域。

色块

通过对识别到的红色区域进行比较,找出所有红色区域中最大的区域,即可避免背景中的小面积红色区域对识别的影响,该部分程序如下:

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
if blobs:
    max_blob=find_max(blobs) 

该程序中,定义一个名为find_max()的子函数,该函数主要实现的功能就是寻找最大的红色区域,在主函数中调用该函数以实现该功能,避免背景中的小面积红色区域对识别的影响。结果如图所示。

max

4.1.3 判断被测物体所在区域

本段程序是判断被测物体所在区域,将OpenMV拍摄到的画面分为五个区域,分别为中心区域、左上区域、右上区域、左下区域、右下区域,分布结构如图所示。

区域分布

区域位置X轴范围Y轴范围

中心区域

135-175 dpi

110-130 dpi

左上区域

0-160 dpi

120-240 dpi

右上区域

160-320 dpi

120-240 dpi

左下区域

0-160 dpi

0-120 dpi

右下区域

160-320 dpi

0-120 dpi

区域分布主要是按照像素分布,我使用到QVGA格式(320*240),使用其他大小的格式,可以按照大小自行进行分配。

本段程序主要通过识别到物体的中心点所在像素位置判断其所在区域所完成,部分程序如下:

    x_max = 320
    x_min = 0
    x_1 = 135 #中心区域左边界
    x_2 = 175 #中心区域右边界
    
    y_max = 240
    y_min = 0
    y_1 = 110 #中心区域上边界
    y_2 = 130 #中心区域下边界
    flag = 0#位置信息标志
    if max_blob.cx()>= x_min  and max_blob.cx() <= 160 and\
       max_blob.cy() >= 120 and max_blob.cy() <= y_max :
            flag = 1
    if max_blob.cx()>=160 and max_blob.cx() <= x_max and\
       max_blob.cy() >=120 and max_blob.cy() <= y_max :
            flag = 2
   if max_blob.cx()>= x_min and max_blob.cx() <= 160 and \
      max_blob.cy() >= y_min and max_blob.cy() <= 120 :
            flag = 3
  if max_blob.cx()>= 160 and max_blob.cx() <= x_max and \
     max_blob.cy() >= y_min and max_blob.cy() <= 120 :
            flag = 4
  if max_blob.cx()>= x_1 and max_blob.cx() <= x_2 and \
     max_blob.cy() >= y_1 and max_blob.cy() < =y_2 :
            flag = 5

本部分程序逻辑为先判断被测物体的中心位置是否在左上区域,如果在则标志位flag被赋值为1,否则为不变(初始值为0),判断被测物体的中心位置是否在右上区域,如果在则标志位flag被赋值为2,否则为不变,判断被测物体的中心位置是否在左下区域,如果在则标志位flag被赋值为3,否则为不变,判断被测物体的中心位置是否在右下区域,如果在则标志位flag被赋值为4,否则为不变,判断被测物体的中心位置是否在中心区域,如果在则标志位flag被赋值为5,否则为不变。流程图如图所示。

流程图

4.1.5 串口发送数据

本部分主要介绍串口发送数据给STM32的程序,发送的数据则是上一部分判断物体中心位置所在区域的标志位,主要使用uart.write()函数来实现本功能。该函数为封装好的库函数,直接调用即可,部分程序如下:

import pyb
from pyb import UART
uart = UART(3, 115200)  #串口3初始化,波特率115200
while(True):
output_str="%d" %flag #方式1
    print('you send:',output_str) 
    uart.write(output_str+'\r\n')

串口

4.2 STM32部分软件功能的实现 4.2.1 整体逻辑分析

首先对STM32部分实现功能的整体逻辑进行分析,首先进行系统时钟的初始化,确保STM32的时钟周期正常运行。其次中断优先级的初始化,本程序中主要用到了串口1的接收中断,所以只需要配置串口1的中断优先级。然后进行串口1的初始化,确保串口1可以正常的接收数据。而后进行定时器3通道1以及通道2的初始化,确保可以使其输出PWM波控制舵机旋转。舵机回归初始位置,能够使装置上电后,舵机回到初始位置。最后判断串口是否接收到数据,如果串口接收到数据,则进入串口中断,执行相应的程序。反之,则进行等待。整体流程图如图所示。其次在使用串口接收OpenMV发来的数据、通过定时器输出PWM波的功能进行独立的分析。

流程图

4.2.2 串口接收数据

本部分程序主要为串口初始化,以及串口中断函数的编写,主要实现STM32通过串口1 接收OpenMV发送来的数据。当接收到数据时,STM32的串口1接收完成位USART_IT_RXNE会被置1。进入串口1中断,运行串口中断内的程序。使用库函数编程,部分程序如下。

//串口1时钟使能:
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//串口1引脚配置:
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX串口输出PA9
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;	 //复用推挽输出
	GPIO_Init(GPIOA,&GPIO_InitStructure);  
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX	串口输入PA10
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模拟输入
	GPIO_Init(GPIOA,&GPIO_InitStructure); /*初始化GPIO*/
//串口1初始化设置:
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b; 
	USART_InitStructure.USART_StopBits = USART_StopBits_1; 
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	
	USART_Init(USART1,&USART_InitStructure); //初始化串口1
	USART_Cmd(USART1, ENABLE);  //使能串口1 
//中断优先级配置:
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

4.2.3 定时器输出PWM波

本部分程序主要为定时器3通道1和通道2的初始化,并使用STM32的固件库中函数TIM_SetCompare1(TIM_TypeDef* TIMx,uint16_t Compare1)输出PWM波,使舵机旋转。该函数有两个参数,第一个参数为定时器号,第二个参数为占空比的参数。部分程序如下。

//使能定时器3的时钟:
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//配置定时器3的引脚:
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);
//初始化定时器3的基本配置:
	TIM_TimeBaseInitStructure.TIM_Period=per;   //自动装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; 
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);	
//初始化输出比较通道2:
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC2Init(TIM3,&TIM_OCInitStructure); //输出比较通道2初始化
//使能定时器3:
	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); 
	TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器
	TIM_Cmd(TIM3,ENABLE); //使能定时器

4.2.4 控制舵机旋转

舵机旋转角度主要是由占空比决定,所以通过传递给函数TIM_SetCompareX()的参数即可控制舵机旋转的角度,编程思路在STM32接收到OpenMV发送的数据后,进入串口中断,在串口中断函数内编写控制舵机旋转的程序,接收到的数据分为5种情况,即接收到被测物体所在的区域信息:1、2、3、4、5分别对应左上、右上、左下、右下、中心五个区域。接收到不同的标志位信息进行不同的动作。流程如图所示。

路程图

同时防止舵机追踪时出现旋转角度过大,可通过软件进行限制,部分程序如下:

		Data=USART_ReceiveData(USART1); //读取接收到的数据	
		switch (Data)
		{
			case '1':Variable_X( 1 ); Control_X (pwm_x);
                     Variable_Y( 0 ); Control_Y (pwm_y);break;
			case '2': Variable_X( 0 ); Control_X (pwm_x);
                      Variable_Y( 0 ); Control_Y (pwm_y);break;
			case '3': Variable_X( 1 ); Control_X (pwm_x);
                      Variable_Y( 1 ); Control_Y (pwm_y);break;
			case '4': Variable_X( 0 ); Control_X (pwm_x);
                      Variable_Y( 1 ); Control_Y (pwm_y);break;
			case '5':break;	
       }

以上程序中,定义了Variable_X( )、Variable_Y( )两个子函数,子函数中有两个参数:0和1 ,主要实现对x、y轴舵机参数的减小和增加。同时在两个子函数中加入if语句,当舵机旋转参数增加到一定时,保持固定不变。同理减小到一定值时,也固定保持不变。以Variable_X( )子函数为例,部分程序如下:

unsigned int Variable_X(unsigned char flag)
{
	if (flag == 1)
	{
		pwm_x += para; 
		if (pwm_x >18600)     //x轴的左边界
			pwm_x = 18600 ;
	}
	else 
	{
		pwm_x -= para;
		if (pwm_x <17600)   //x轴的右边界
			pwm_x = 17600;
	}
	return pwm_x;
}

其中,para代表着x、y轴两个舵机每次旋转的角度,通过宏定义,在调试过程中修改宏定义即可。

5. 总结

到这里呢,就算是把我做毕设时的思路写了下来,也是我写毕业论文的思路,总体来说,我在实现追踪的功能上取巧了,只是能实现简单的、“粗糙的” 追踪目标,而没有使用PID控制,也欢迎大家使用PID控制去实现追踪的效果,大概的思路差不多:在OpenMv的部分,在识别到目标后,返回物体中心点的横纵坐标的值,通过串口发给Stm32,32在收到数据后与图像的中心点坐标进行比较(我用的是320*240,那么中心点坐标就是160,120),将二者的差值作为参数,通过PID控制舵机旋转,可以更加准确跟快速的对目标进行追踪。

另:欢迎大家给出宝贵的意见!

6. 第一次补充(2022.3.12) 6.1 PID控制追踪

本次补充的内容为:添加了使用PID控制进行动态追踪,相比于之前的只是用来比例控制(P)更加的稳定和丝滑,这里我就不扩展介绍PID了,感兴趣的或是不太了解的小伙伴可以自己去B站搜索一下,那里大佬们讲的通俗易懂更好理解一些。

6.2 PID控制代码部分

依旧分为两部分:OpenMV的代码和STM32的代码

6.2.1 OpenMV代码

相比于之前的OpenMV的代码,改动的部分仅仅为将串口传递的“区域信息”改为“被测物体中心点的x、y轴坐标”。

   ...
   x = max_blob.cx()
   y = max_blob.cy()
   ...
   output_str="%d,%d,%dE" %(x,y,flag) #方式1
   uart.write(output_str)

其中:

x: 表示为被测物体的中心点x轴坐标

y: 表示为被测物体的中心点y轴坐标

, : 为英文下的逗号,作为数据间的分隔符

flag:为标志位,取值为0,1,2;

flag取值说明

表示并没有识别到物体

识别到物体,但物体没有在中心区域

识别到物体,且物体在中心区域

E: 表示数据帧的结束,可以以任何字符作为结束,本代码中以’E’为例

6.2.2 STM32代码

主要改动为:对串口接收到的数据帧处理、PID控制部分

(1)数据帧处理

if(str[i-1] == 'E')
{
	for(i = 0;str[i] != 'E';i++ )
	{	
		if(str[i]  ==  ',' )
		{	
			if(number_flag ==  0)
			{
				x_long  = j; // x的位数
			}
			else if (number_flag ==  1)
			{
				y_long = j; // y的位数
			}
			number_flag ++;
			j = 0;
		}
		else
		{
			if(number_flag == 0)
			{
				x_array[j] = str[i] -'0';
				j++;
			}
			else if(number_flag == 1)
			{
				y_array[j] = str[i] -'0';
				j++;
			}
			else if(number_flag == 2)
			{
				RED_flag = str[i] - '0';
			}
		}
	}
	if(RED_flag == 1)
	{
		switch(x_long)
		{
			case 1 : x_location = x_array[0]; break ;
			case 2 : x_location = x_array[0]*10 + x_array[1]; break ;
			case 3 : x_location = x_array[0]*100 + x_array[1]*10 + x_array[2]; break ;
			default: break ;
		}
		switch(y_long)
		{
			case 1 : y_location = y_array[0]; break ;
			case 2 : y_location = y_array[0]*10 + y_array[1]; break ;
			case 3 : y_location = y_array[0]*100 + y_array[1]*10 + y_array[2]; break ;
			default: break ;	
		}
	PID_realize(x_location,y_location); // pid控制
	Control_X(pwm_x); // 输出x轴PWM
	Control_Y(pwm_y); // 输出y轴PWM
	}
	else
	{
		PID_init(); 
	}	
	i = 0;	

(2)PID参数初始化部分

	pid_x.Set=160.0; // 图像x轴的中心位置  本代码对应OpenMV使用QVGA格式(320*240)所以为160,视情况更改
	pid_x.Actual=0.0;
	pid_x.err=0.0;
	pid_x.err_last=0.0;
	pid_x.err_next=0.0;
	pid_y.Set=120.0;// 图像y轴的中心位置 
	pid_y.Actual=0.0;
	pid_y.err=0.0;
	pid_y.err_last=0.0;
	pid_y.err_next=0.0;
	Kp=KP; // 使用宏定义,参数整定直接对pid.h中的宏定义进行修改即可
	Ki=KI; //同上
	Kd=KD; //同上

(3)PID控制部分

	pid_x.Actual = x;
	pid_x.err = pid_x.Set - pid_x.Actual;
	float increment_x = Kp*(pid_x.err-pid_x.err_next)+Ki*pid_x.err+Kd*(pid_x.err-2*pid_x.err_next+pid_x.err_last);
	pwm_x -=(int)increment_x;
	if(pwm_x >= 18500)  // 限制x轴舵机的临界值
		pwm_x = 18500;
	else if(pwm_x <=17700)
		pwm_x = 17700;
	pid_x.err_last = pid_x.err_next;
	pid_x.err_next = pid_x.err;
	pid_y. Actual = y;
	pid_y.err = pid_y.Set - pid_y.Actual;
	float increment_y = Kp*(pid_y.err-pid_y.err_next)+Ki*pid_y.err+Kd*(pid_y.err-2*pid_y.err_next+pid_y.err_last);
	pwm_y += (int)increment_y;
	
	if(pwm_y >= 19100) // 限制y轴舵机的临界值
		pwm_y = 19100;
	else if(pwm_y <=18300)
		pwm_y = 18300;
		
	pid_y.err_last=pid_y.err_next;
	pid_y.err_next=pid_y.err;

6.3 第一次补充总结

本来在最初准备做这个毕设的时候,就是准备使用PID进行追踪的,后来因为时间的原因,取巧只使用比例控制,这次用PID实现了,也算是完成了当时的一个小小的心愿吧。

最后希望希望诸君共勉吧,哦对了,在写PID代码的时候,我突然想到可以在openmv部分加上人脸识别,然后实现追踪人脸的效果,哈哈哈哈这个我就不一定什么时候做了,算是留一个小小的坑吧,希望以后的我会给这个坑填上。

7. 第二次补充(2022.10.22)

本次补充内容为硬件部分更改为PCB,代码部分优化了OpenMV部分代码,MCU从STM32F103ZET6替换为STM32F103C8T6,程序部分进行修改和优化。加之3D打印的支架,整体较之前更加小巧美观。

12

7.1 硬件部分 7.1.1主控PCB

之前的洞洞板更改为PCB,整体部分看起来更加简洁,更加小巧,而且将主控从STM32F103ZET6系统板替换为STM32F103C8T6最小系统板,大幅降低成本,除了openmv之外,剩下的器件所有加起来用不到100块,openmv4 H7官方价格429元,闲鱼250-300之间,若是只用openmv做毕设后续不准备再用了,推荐在闲鱼上(或是其他二手途径),若是过后还准备继续深入的了解,我建议在官网上买,若是预算比较充足可以购买H7 plus。主控PCB尺寸:4.8cm*7.48。

PCB示意图:

PCB

实物图片:

PCB实物

焊接后:

焊接

7.1.2 补光板PCB

为了更准确的识别和追踪,在不稳定的光源下进行补光。

PCB示意图:

pcb

实物图:

焊接

7.1.3OPENMV串口扩展板

主要是连接openmv的串口RX、TX引脚和5v和GND引脚,方便走线。

PCB示意图:

pcb

7.2 代码部分

此次改动追踪部分使用PID控制,不再使用最开始的分区域的开环控制。

7.2.1 openmv部分

Openmv代码进行优化,去除中间区域,取消flag = 2 的状态。

   ...
   x = max_blob.cx()
   y = max_blob.cy()
   ...
   output_str="%d,%d,%dE" %(x,y,flag) #方式1
   uart.write(output_str)

其中:

x: 表示为被测物体的中心点x轴坐标

y: 表示为被测物体的中心点y轴坐标

, : 为英文下的逗号,作为数据间的分隔符

flag:为标志位,取值为0,1;

flag取值说明

没有识别到物体

识别到物体

E: 表示数据帧的结束,可以以任何字符作为结束,本代码中以’E’为例。

7.2.2 stm32部分

由于主控从STM32F103ZET6系统板替换为STM32F103C8T6最小系统板,所以串口和的控制舵机输出pwm的定时器引脚映射更改:PC6\PC7 ->PA6\PA7,pid部分代码进行部分优化,pid参数重新整定。新版pid代码与旧版的pid代码(zet6)改动并不算大,整体控制感兴趣的小伙伴可以自行进行修改。

c8t6

7.2.3 3D建模

通过3d打印openmv支架和舵机支架。

openmv支架:

openmv支架

舵机支架:

舵机支架

7.4 演示与购买 7.4.1 演示

【毕业设计】基于STM32及OpenMV的云台追踪装置(升级版)

7.4.2 购买

若是小伙伴对最新版的追踪装置,可以通过本人的微信(Jie20211028)购买代码和PCB,麻烦请在好友申请时添加说明。另外,若是有小伙伴想购买之前的代码、论文、全家桶,也可以通过本人微信购买,有七折优惠哦!

(1)新版PCB+3D建模:188元

注:只是出售工程文件,并非实物,购买后可自行打印,pcb和3d在华秋和立创可以免费打印,具体可见:

PCB:如何通过嘉立创免费打印立创EDA设计的PCB 、如何通过华秋DFM免费打印四层PCB

3D打印:如何通过嘉立创免费3D打印

(2)新版pid代码:138元

新版PID代码的主控为STM32F103C8T6,适用于新版的PCB,不适用旧版的洞洞板设计,新版pid代码与旧版的pid代码(zet6)改动并不算大,感兴趣的小伙伴可以自行修改。

(3)新版全家桶:288元

全家桶包含:PCB、3D建模、新版PID代码(STM32F103C8T6)、加上赠送的一些资料:参考论文(论文为旧版设计的论文)、外文文献、外文翻译等等。

在这里插入图片描述

7.5 第二次补充总结

本以为第二次补充会是对人脸进行追踪,没想到是更新了硬件,不过用洞洞板确实是比较麻烦,走线看起来还特别的乱,pcb看起来就好太多太多了;主控的话,从zet6替换为c8t6,成本大幅降低,尺寸也大幅降低,当然哈,性能丝毫不受影响;本来我是准备直接使用c8t6芯片,不使用最小系统板的,但是考虑到可能有的小伙伴没办法完好无损的焊上LQFP48封装,所以最后选择了最小系统板;整体而言,新版设计较旧版的更加的容易复刻,不能说是有手就行,只要是多少用点心,想把毕设做出来,就肯定能复刻出来。

下一次补充我还是打算做人脸的追踪,有可能会单独出一篇博文简绍。


本文由转载于互联网,如有侵权请联系删除!