openMV追小球的云台——openMV与stem32串口通讯

前言

  我也是首先跟着官方的教学视频走,一步一步来可云台始终无法追踪目标物体,并且舵机抖动的厉害,如果你也和我遇到了同样的问题,不妨试试我的解决方案。

studio_video_1717661936413

功能实现思路

  openMV进行图像识别,寻找目标物体并将中心坐标传给单片机,单片机接受数据后通过pid算法来控舵机运行的角度。

代码部分

  既然需要openMV与单片机串口通讯,那么除了usart.c、sys.c和delay.c这些基本文件还需要对openMV端和stm32端进行配置。

openMV配置

import sensor,image,time#引入感光元件的模块
from pyb import Servo
from pyb import UART
import json


# 设置摄像头
sensor.reset()#初始化感光元件
sensor.set_pixformat(sensor.RGB565)#设置为彩色
sensor.set_framesize(sensor.QQVGA)#设置图像的大小160*120
#sensor.set_windowing((80,80))#窗口
sensor.skip_frames(10)#跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。
sensor.set_auto_whitebal(False)
sensor.set_auto_gain(False)
sensor.set_hmirror(True)
sensor.set_vflip(True)

uart = UART(3, 115200)
ROI=(80,30,15,15)
red=(70, 52, 77, 56, 23, 62)
k=1490#距离=k/直径的像素
k2=0.08#实际的大小=k*直径的像素

def find_max(blobs):         #最大面积
    max_size=0
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob

# 一直拍照
while(True):
    img = sensor.snapshot()#拍摄一张照片,img为一个image对象
    blobs=img.find_blobs([red],invert=False,area_threshold=1000,pixels_threshold=30)#寻找色块
    if blobs:
        max_blob = find_max(blobs)
        img.draw_rectangle(max_blob[0:4])
        img.draw_cross(max_blob[5],max_blob[6])#寻找色块
        output_str='[%d,%d]'%(max_blob[5],max_blob[6])
        img_data=bytearray([0x2C,7,4,max_blob[5],max_blob[6],1,0x5B])
        uart.write(img_data)
        print('Red:',output_str)
        

stm32配置

openmv.c

#include "openmv.h"
#include "usart.h"
int openmv[7];//stm32接收数据数组
int16_t data1;
int16_t data2;
int16_t data3;
int16_t data4;
 
 
int i=0;
 
void Openmv_Receive_Data(int16_t data)//接收Openmv传过来的数据
{
	static u8 state = 0;
	if(state==0&&data==0x2C)
	{
		state=1;
		openmv[0]=data;
	}
	else if(state==1&&data==7)
	{
		state=2;
		openmv[1]=data;
	}
	else if(state==2)
	{
		state=3;
		openmv[2]=data;
	}
	else if(state==3)
	{
		state = 4;
		openmv[3]=data;
	}
	else if(state==4)
	{
        state = 5;
        openmv[4]=data;
	}
	else if(state==5)
	{
        state = 6;
        openmv[5]=data;
	}
	else if(state==6)		//检测是否接受到结束标志
	{
        if(data == 0x5B)
        {
            state = 0;
            openmv[6]=data;
            Openmv_Data();
        }
        else if(data != 0x5B)
        {
            state = 0;
            for(i=0;i<7;i++)
            {
                openmv[i]=0x00;
            }           
        }
	}    
	else
		{
			state = 0;
            for(i=0;i<7;i++)
            {
                openmv[i]=0x00;
            }
		}
}
 
void Openmv_Data(void)
{
    data1=openmv[0];
    data2=openmv[3];
    data3=openmv[4];
    data4=openmv[5];
 
}

openmv.h

#ifndef  __OPENMV_H
#define  __OPENMV_H
#include "sys.h"
extern int openmv[7];//stm32接收数据数组
extern int16_t data1;
extern int16_t data2;
extern int16_t data3;
extern int16_t data4;
 
void Openmv_Receive_Data(int16_t data);
void Openmv_Data(void);
#endif

pwm.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void xPWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
}
void yPWM_SetCompare1(uint16_t Compare1)
{
	TIM_SetCompare1(TIM2, Compare1);		//设置CCR1的值
}

pwm.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void xPWM_SetCompare2(uint16_t Compare);
void yPWM_SetCompare1(uint16_t Compare1);

#endif

pid.c

#include "pid.h"

float Kp_x=1.75,     //2
	    Ki_x=0.15,  //0.15
      Kd_x=0.03;    //2
float www,zzz;
int pwm_xpid(int xerror)
{
	int pid_ActualPwm;
  static float pid_Integral,pid_Voltage,error_Last;
	pid_Integral+=xerror;
	www=pid_Integral;
	if (pid_Integral<-6000) pid_Integral=-6000;
	if (pid_Integral>6000) pid_Integral=6000;
	pid_Voltage=Kp_x*xerror+Ki_x*pid_Integral+Kd_x*(xerror-error_Last);	
	error_Last=xerror;
	pid_ActualPwm=pid_Voltage*1;
	if (pid_ActualPwm<-1000) pid_ActualPwm=-1000;
	if (pid_ActualPwm>1000) pid_ActualPwm=1000;
	return pid_ActualPwm;
}


float Kp_y=1.55,     //1
	    Ki_y=0.15,  //0.15
      Kd_y=0.02;    //2

int pwm_ypid(int yerror)
{
	int pid_ActualPwm;
  static float pid_Integral,pid_Voltage,error_Last;
	pid_Integral+=yerror;
  zzz=pid_Integral;
	if (pid_Integral<-6000) pid_Integral=-6000;
	if (pid_Integral>6000) pid_Integral=6000;
	pid_Voltage=Kp_y*yerror+Ki_y*pid_Integral+Kd_y*(yerror-error_Last);	
	error_Last=yerror;
	pid_ActualPwm=pid_Voltage*1;
	if (pid_ActualPwm<-1000) pid_ActualPwm=-1000;
	if (pid_ActualPwm>1000) pid_ActualPwm=1000;
	return pid_ActualPwm;
}

pid.h

#ifndef _PID_H
#define _PID_H
#include "sys.h"
int pwm_xpid(int xerror);
int pwm_ypid(int yerror);
#endif

main.c

// STM32 PA9--P5 OPENMV
// STM32 PA10-P4 OPENMV 
// STM32 GND-GND OPENMV
// OLED SCL-PB8
// OLED SDA-PB9

#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "oled.h"
#include "openmv.h"
#include "timer.h" 
#include "pwm.h"
#include "pid.h"
 
extern int openmv[7];//stm32接收数据数组
extern int16_t data1;
extern int16_t data2;
extern int16_t data3;
extern int16_t data4;
int16_t data;
int pid_xerror,pid_yerror,xpwm,ypwm;

int main(void)
{ 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init();      //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	PWM_Init();
	LED_Init();					  //初始化LED
 	OLED_Init();           //初始化LCD FSMC接口	
	OLED_ShowString(1,1,"CX:");	
	OLED_ShowString(2,1,"CY:");
	OLED_ShowString(3,1,"XPWM:");
	OLED_ShowString(4,1,"YPWM:");
  while(1) 
	{	
		//Openmv_Receive_Data(data);
		//Openmv_Data();
	    if (data2 >0&&data3 >0)
		{	pid_xerror=data2-80;
			pid_yerror=60-data3;
			xpwm=pwm_xpid(pid_xerror);
			ypwm=pwm_ypid(pid_yerror);
			xPWM_SetCompare2(1500-xpwm);
			yPWM_SetCompare1(1500-ypwm);
			OLED_ShowSignedNum(3, 7, xpwm , 4);
			OLED_ShowSignedNum(4, 7, ypwm , 4);
			
		}
		else 
        {
			xPWM_SetCompare2(1500-xpwm);
			yPWM_SetCompare1(1500-ypwm);
			xpwm=0;
			ypwm=0;
	
        }	
     	OLED_ShowNum(1, 5, data2, 3);
	 	OLED_ShowNum(2, 5, data3, 3);
		delay_ms(150);
	} 
}

结语

!注: 由于舵机型号、电压等问题,光复制粘贴我的代码大概率不能实现功能,诸多参数需要调节。

程序源码:【闲鱼】https://m.tb.cn/h.gVhHrMz?tk=OAL4WxJkDTx HU0854 「我在闲鱼发布了【#电赛项目——追小球的云台!!!】」
点击链接直接打开,需要可以看一下,制作不易感谢支持!!!

参考文章:stm32与openmv进行串口通讯_openmv与stm32串口-CSDN博客

               

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值