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

1.摘 要

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

实现效果

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

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

若是你很懒,不想自己一步一步自己做的话,可以直接去下载论文和源代码。
代码:【代码】基于STM32及OpenMV的云台追踪装置_毕业设计
论文:【论文】基于STM32及OpenMV的云台追踪装置_毕业设计
全家桶:【毕设全家桶】基于Stm32及OpenMV的云台追踪装置_毕业设计

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

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控制舵机旋转,可以更加准确跟快速的对目标进行追踪。

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

版权声明:本文为CSDN博主「番杰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JIE15164031299/article/details/119617832

1.摘 要

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

实现效果

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

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

若是你很懒,不想自己一步一步自己做的话,可以直接去下载论文和源代码。
代码:【代码】基于STM32及OpenMV的云台追踪装置_毕业设计
论文:【论文】基于STM32及OpenMV的云台追踪装置_毕业设计
全家桶:【毕设全家桶】基于Stm32及OpenMV的云台追踪装置_毕业设计

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

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控制舵机旋转,可以更加准确跟快速的对目标进行追踪。

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

版权声明:本文为CSDN博主「番杰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JIE15164031299/article/details/119617832

生成海报
点赞 0

番杰

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

STM32F4最小系统硬件设计

对于硬件工程师来讲,想要入门STM32相关的开发,我想除了深入阅读一下STM32的数据手册外,最实用且有效的方法就是自己实际做一个STM32的最小系统板了。本文将以一个小的STM32F427VG的电路最