项目要求: (1)根据题目要求 在30s内将小车倒入库2,并能够自动出库,整个过程中小车不压边界线。(20分)
(2)30s内将小车侧方进入库5中并出库,整个过程小车不压边界线。(20分)
单项倒车入库/出库②:移除图 2 中库 1、库 3 内停放的车辆,逐条按照第(1)项要求,电动车完成“邻库无车”时的倒车入库/出库。(20 分)
(4)单项侧方入库/出库②:移除图 3 中库 4、库 6 内停放的车辆,逐条按照第(2)项要求,电动车完成“邻库无车”时的侧方入库/出库。(20 分)
(5)连续倒车与侧方入库/出库①:如果参赛者确认其作品能连续完成第(1)、(2)项功能要求,可直接测试本项,成功完成的将在第(1)、(2)项得分基础上加 5 分。此刻泊车场地(图 1)中的库 1、3、4、6 内均居中停有车辆。电动车自图 1 中“发车区 1”内一键启动泊车,按第(1)、2)项要求,连续完成自动倒车及侧方入库/出库。(5 分)
(6)连续倒车与侧方入库/出库②:如果参赛者确认能够连续完成第(3)、
(4)项功能要求,则直接测试第(6)项,成功完成的可在第(3)、(4)项得分基础上加 5 分。此刻同时移除泊车场地(图 1)中的库 1、3、4、6 内所有停放车辆,电动车自图 1 中“发车区 1”内一键启动泊车,按照第(3)、(4)项要求,连续完成自动倒车与侧方入库/出库。(5 分)
(7)其他 (10 分)
观看了整个项目要求之后第一反应就排除了红外的方案,因为红外的精度达不到题目的要求。立马就决定用OPPENMV进行图像处理,在前进的过程中通过find_blobs函数得到所画框的左顶点y轴的值,通过串口给到stm32f407通过pid计算给出相应差值达到巡线的目的。(这里没有在openmv中计算是因为我所用的这款openmv的版本有点低,容易卡死。)走直线的项目要求达到了,下一个就是在需要停车进行泊车的地方进行泊车,这个我是通过识别跳变的方式来进行计数的,在相应需要停下的位置停车。最后就是泊车的整个过程了,这个方法有很多种,具体如何去做得看各自的喜好以及对模块掌握的要求,我泊车是开环写的,比较简单。
下面是openmv的具体实现方法
import sensor, image, time,math,pyb
from pyb import UART,LED
import json
import ustruct
import pyb
#THRESHOLD = (91, 3) #二分化系数
ROI=(15,45,65,9)
BINARY_VISIBLE = True
sensor.reset()
sensor.set_vflip(True) #垂直翻转
sensor.set_hmirror(True) #水平翻转
sensor.set_pixformat(sensor.RGB565) #彩色
#sensor.set_pixformat(sensor.GRAYSCALE) #黑白
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
#red_threshold_01=((7, 67, -39, 66, -23, 84))
black_threshold=(1, 12, -66, 48, -12, 48)
clock = time.clock()
ROIQIANM=(4,172,61,25)
ROIZHONGJIAN=(139,104,34,49)
ROIXUNXIAN=(3,165,75,52)
ROIJISHU=(187,180,95,24)
ROIXUN=(190,218,40,23)
ROICHEKU=(300,140,18,44)
uart = UART(3,115200)
uart.init(115200,bits=8,parity=None,stop=1)
b=0
c=0
d=0
daoche=0
H=0
I=0
J=0
while(True):
clock.tick()
img = sensor.snapshot()
#寻找色块
img.draw_rectangle(ROIZHONGJIAN)
img.draw_rectangle(ROIQIANM)
#img.draw_rectangle(ROIJISHU)
blobs0 = img.find_blobs([black_threshold],roi=ROIQIANM)
if blobs0:
largest_blob0 = max(blobs0,key=lambda c:c.pixels())
img.draw_rectangle(largest_blob0.rect())
blobs1 = img.find_blobs([black_threshold],roi=ROIQIANM)
if blobs1:
largest_blob1 = max(blobs1,key=lambda c:c.pixels())
img.draw_rectangle(largest_blob1.rect())
if c*b==1 and blobs0 and blobs1:
d=d+1
c=0
b=0
blobs2 = img.find_blobs([black_threshold],roi=ROIZHONGJIAN,pixels_threshold = 10)
if blobs2:
b=1
largest_blob2 = max(blobs2,key=lambda c:c.pixels())
img.draw_rectangle(largest_blob2.rect())
else:
c=1
blobs3 = img.find_blobs([black_threshold],roi=ROIXUN)
if blobs3:
largest_blob3 = max(blobs3,key=lambda c:c.pixels())
img.draw_rectangle(largest_blob3.rect())
daoche=largest_blob3.y()
blobs4 = img.find_blobs([black_threshold],roi=ROICHEKU)
if blobs4:
largest_blob4 = max(blobs4,key=lambda c:c.pixels())
img.draw_rectangle(largest_blob4.rect())
I=1
else:
J=1
if J*I==1:
H=H+1
I=0
J=0
if blobs0:
output_str=bytearray([0x2c,0x12,largest_blob0.cy(),d,daoche,H,0x5B])
uart.write(output_str)
print(largest_blob0.cy(),d,daoche,H)
这里串口的收发数据直接进行封包,给两个包头,一个包尾一共8位数据。STM32F407方面就直接解包,将数据进行读取、使用。
下面是32的串口对数据的收,解包录用:
#include "sys.h"
#include "usart.h"
#include "led.h"
#include "pwm.h"
static u8 Cx=0,Cy=0,Cw=0,Ch=0;
extern int dier;
extern int dier1;
extern int zuihou;
int xunbudao=0;
//
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
int Vertical_Kp1=50;
int Vertical_Kd1=10;
int y,x,w,h;
int back1;
int biaozhi=1;
extern uint8_t Fore,Left,Right;
extern int mubiaoshudu;
extern int ruku;
extern int buxuanxian;
int left,right,on,down;
int leftpwm=0,rightpwm=0;
int Bluetooth_data;
void USART1_IRQHandler(void) //串口1中断服务程序
{
int lastcx;
u8 com_data;
u8 i;
static u8 RxCounter1=0;
static u16 RxBuffer1[10]={0};
static u8 RxState = 0;
static u8 RxFlag1 = 0;
if( USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) //接收中断
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志
com_data = USART_ReceiveData(USART1);
if(RxState==0&&com_data==0x2C) //0x2c帧头
{
RxState=1;
RxBuffer1[RxCounter1++]=com_data;
}
else if(RxState==1&&com_data==0x12) //0x12帧头
{
RxState=2;
RxBuffer1[RxCounter1++]=com_data;
}
else if(RxState==2)
{
RxBuffer1[RxCounter1++]=com_data;
if(RxCounter1>=10||com_data == 0x5B) //RxBuffer1接受满了,接收数据结束
{
RxState=3;
RxFlag1=1;
Cx=RxBuffer1[RxCounter1-5];
Cy=RxBuffer1[RxCounter1-4];
Cw=RxBuffer1[RxCounter1-3];
Ch=RxBuffer1[RxCounter1-2];
}
}
else if(RxState==3) //检测是否接受到结束标志
{
if(RxBuffer1[RxCounter1-1] == 0x5B)
{
USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断
if(RxFlag1)
{
//LED1=!LED1;
leftpwm=150;
rightpwm=150;
y=Cy;
x=Cx;
w=Cw;
h=Ch;
if(back1!=1||dier==0&&dier1==1||zuihou==0)
{
TIM_SetCompare1(TIM2,1550+(48*(Cx-185)));//175
}
if(ruku==1&&buxuanxian==0)
{
//TIM_SetCompare1(TIM2,1550+(60*(223-Cw)));//78偏大
}
//停车标志
if(Cy==7&&biaozhi==1)
{
//mubiaoshudu=0;
Set_Pwm(0,0);
back1=1;
}
}
RxFlag1 = 0;
RxCounter1 = 0;
RxState = 0;
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
else //接收错误
{
RxState = 0;
RxCounter1=0;
for(i=0;i<10;i++)
{
RxBuffer1[i]=0x00; //将存放数据数组清零
}
}
}
else //接收异常
{
RxState = 0;
RxCounter1=0;
for(i=0;i<10;i++)
{
RxBuffer1[i]=0x00; //将存放数据数组清零
}
}
}
else{xunbudao=1;}
}
#endif
void usart6_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOG,GPIO_PinSource9,GPIO_AF_USART6); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOG,GPIO_PinSource14,GPIO_AF_USART6); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_14; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART6, &USART_InitStructure); //初始化串口1
USART_Cmd(USART6, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX
USART_ITConfig(USART6, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART6_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
void USART6_IRQHandler(void) //串口1中断服务程序
{
if(USART_GetITStatus(USART6,USART_IT_RXNE)!=0) // 接收中断标志位拉高
{
// Bluetooth_data=USART_ReceiveData(USART6);
// LED1=!LED1 ; // 保存接收到的指令
// if(Bluetooth_data==0x00)Fore=0,Back=0,Left=0,Right=0; // 刹车
//
// else if(Bluetooth_data==0x01)Fore=1,Back=0,Left=0,Right=0;
// // 前进
// else if(Bluetooth_data==0x05)Fore=0,Back=1,Left=0,Right=0; // 后退
// else if(Bluetooth_data==0x03)Fore=0,Back=0,Left=1,Right=0; // 左转
// else if(Bluetooth_data==0x07)Fore=0,Back=0,Left=0,Right=1; // 右转
// else Fore=0,Back=0,Left=0,Right=0;
//
}
}
int Vertical1(int a,int b)
{
int PWM_out;
PWM_out = Vertical_Kp1*(b-a);
if(PWM_out<10&&PWM_out>-10)
{PWM_out=0;}
if(PWM_out>50&&PWM_out<-50)
{PWM_out=0;}
return PWM_out*50+1500;
}
然后主函数对串口发过来的数据直接处理利用,就可以达到后续的一系列的项目要求了,纵观整个项目,难以程度比较简单,对于没有学过openmv 的同学而言就有一定的难度了,需要学习然后收发数据,以及利用什么函数用来巡线。
在这个项目的期间也遇到不少的麻烦,回想起来,在第一个倒车入库后出库,开始第二侧方停车的时候应该如何计数,当时确实难道我了,但是通过一次次尝试,最终利用一共比较不稳妥的方式给解决了,出库的时候将舵机的角度给小一点,让小车转一个大湾,当小车再次扫到边界线的时候就可以开始进行侧方的计数了(我倒车入库的标志位所画的框和侧方停车的框是一个框,我这里是将这个数值付给一个变量,让这个标志位继续计数,达到7的时候停车开始侧方)
将这几个问题解决了之后,后续就剩下了简单的 pid的调整,泊车舵机的角度的调整,侧方的角度的调整。将这几个简单的数值调整好了之后整个项目就完成了