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