OPENMV和STM32的识别追踪小车(详细版)之STM32端

2 篇文章 5 订阅
2 篇文章 13 订阅

OPENMV和STM32的识别追踪小车(详细版)之STM32端

二:STM32的介绍以及程序

2.1(STM32F407介绍)

STM32F407提供了工作频率为168 MHz的Cortex™-M4内核(具有浮点单元)的性能。我的32是正点原子的最小系统板,它大部分的引脚和探索者是一样的。

2.2(引脚分配)

串口1:PA9/10复用为串口1用于和OPENMV通信。
PWM:PA6/7复用为定时器3用于PWM输出。
IN1–4: PF2/3/6/7作为电机输出。

2.3(程序讲解)

主要程序就是串口接收处理程序,也就是和OPENMV的通信问题。下面开始讲解一下代码。

(1)首先是接收初始化和接收中断函数
#include "sys.h"
#include "usart.h"	
EN_USART1_RX   //如果使能了接收
 	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.

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_50MHz;	//速度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_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断

	//Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、


	
}


void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}
			else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}   		 
  }
}	

这里的0X0A和0X0D就是我OPENMV端发送的\r\n,分别是回车换行,对应的ASCII是0X0D和0X0A。所以这两个数为我们的接收完成的一个检测标志。

(2)接下来是串口接收处理的函数
#include "dispose.h" 
#include "sys.h" 
#include "usart.h"
#include "led.h"


double q,x,m,nb,mb,z;
int j,i,c1,w1,v1;
 u8 v=0;	


void dispose_init(void)    //数据接收处理函数
	
{
	  m=0.0;
	   v=0;
		 j=0;
		 q=0.0;
	 if(USART_RX_STA&0x8000)   
     {
       x=0.0;	//接收新的数X置0		 
		  if(USART_RX_BUF[0]=='x')  //如果接收到的是X
		  { v++;
		 	  while(USART_RX_BUF[v]!='z')
		   { 						
			   if(USART_RX_BUF[1]!='-')       //是正数的情况
			  {
				 c1=0;
          if(USART_RX_BUF[3]=='.')    //如果是十位数
				 {					
			     if(USART_RX_BUF[v]=='.')
					 v++;
				   if(USART_RX_BUF[v]!='.')
				   m=pow(10.0,-q+1);
				   nb=USART_RX_BUF[v]-48;    //ASCii码减去48是对应的数字
				   x+=nb*m;
           q++;
				   v++;
			   }
				
				  if(USART_RX_BUF[2]=='.')    //如果是个位数和小数
				 {		  						
				   if(USART_RX_BUF[v]=='.')
					 v++;
				   if(USART_RX_BUF[v]!='.')
				   m=pow(10.0,-q);
				   nb=USART_RX_BUF[v]-48;
				   x+=nb*m;
           q++;
				   v++;
			    }
			   } 	
			
			   else if(USART_RX_BUF[1]=='-')    //如果是负数
			  {
					c1=1;
					if(USART_RX_BUF[4]=='.')    //如果是负十位数
					{					
						if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
							 v++;
						if(USART_RX_BUF[v]!='.')
							 m=pow(10.0,-q+1);
						nb=USART_RX_BUF[v]-48;
						x+=nb*m;
						q++;
						v++;
					}
						
							if(USART_RX_BUF[3]=='.')    //如果是负个位数和小数
						{	  						
							if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
								 v++;
							if(USART_RX_BUF[v]!='.')
								 m=pow(10.0,-q);
							nb=USART_RX_BUF[v]-48;
							x+=nb*m;
							q++;
							v++;
						}				
					 }
				  }  
				 }
		
		 
		 
			 if(USART_RX_BUF[v]=='z')     //当接收到Z的时候
		 {   
			 q=0.0;
			 z=0.0;
			 v++;			 
			 v1=v;
			 while(USART_RX_BUF[v]!='e')    //我设置的openmv中发送的格式是以e结尾的
					{
							if(USART_RX_BUF[v1]!='-')        //是正数的情况
			         {
								 w1=0;
								 if(USART_RX_BUF[v1+2]=='.')    //如果是十位数
				        {		
									if(USART_RX_BUF[v]=='.')
										 v++;
									if(USART_RX_BUF[v]!='.')
								  m=pow(10.0,-q+1);
									mb=USART_RX_BUF[v]-48;
									z+=mb*m;
								  v++;
									q++;
								}
								
								if(USART_RX_BUF[v1+1]=='.')    //如果是个位数和小数
				        {		
									if(USART_RX_BUF[v]=='.')
										 v++;
									if(USART_RX_BUF[v]!='.')
								  m=pow(10.0,-q);
									mb=USART_RX_BUF[v]-48;
									z+=mb*m;
								  v++;
									q++;
								}
								
							 }
							 
							 
			else if(USART_RX_BUF[v1]=='-')    //如果是负数
			{
				w1=1;
				if(USART_RX_BUF[v1+3]=='.')    //如果是负十位数
				{					
				if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
					 v++;
				if(USART_RX_BUF[v]!='.')
				   m=pow(10.0,-q+1);
				mb=USART_RX_BUF[v]-48;
				z+=mb*m;
        q++;
				v++;
			  }
				
				  if(USART_RX_BUF[v1+2]=='.')    //如果是负个位数和小数
				{		  						
				   if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
					 v++;
				   if(USART_RX_BUF[v]!='.')
				   m=pow(10.0,-q);
				   mb=USART_RX_BUF[v]-48;
				   z+=mb*m;
           q++;
				   v++;
			  }				
			}							 
		}
	}
}	
	USART_RX_STA=0;    //清空接收的数组
		
}


这里的串口处理函数我写的比较长,重复的地方比较多,需要改进。其实可以简单一些的,我这里写的是可以接收到两位小数的数据,一种更简单的方法的话就是在OPENMV端把小数扩大为整数并传输,这样传输的话就简单很多。
我的格式是(“x%.2fz%.2fe”% print_args+’\r\n’),打个比方就是x-1.22z-6.78e+回车换行。我的思路是通过把字母后的数字加起来存放在mb,nb中,字母和回车换行就被剔除了。

(3)这是PWM的函数
#include "pwm.h"
#include "sys.h"

void pwm_Init(u32 arr,u32 psc)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;                   //GPIO结构体初始化
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;     //定时器结构体初始化
	TIM_OCInitTypeDef TIM_OCInitStructure;                 //
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);   //使能定时器14的时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);   //使能GPIOC时钟
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3);  //GPIOF6复用为定时器
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM3);  //GPIOF7复用为定时器
	
	
	
	GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF;            //IO口复用模式
	GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;					 //推挽输出     				
	GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;				//设置为管脚6,7
	GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;         	 //上拉
	GPIO_InitStructure.GPIO_Speed =GPIO_High_Speed;				 //100Ms
	GPIO_Init(GPIOA,&GPIO_InitStructure);									 //GPIO初始化
	
	
	TIM_TimeBaseInitStructure.TIM_Prescaler =psc;                   //预分频系数
	TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;  //向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period =arr;  					          //自动装载值
	TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;      //
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);						  //初始化定时器3
	
	//初始化TIM8 Channe11 PWM模式
	TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_PWM1;              //选择定时器为PWM1,(TIM脉冲宽度调制模式2)
	TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;  //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_Low;       //输出极性:TIM输出比较极性低
//  TIM_OCInitStructure.TIM_Pulse = 0; 
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);       					      //根据T指定的参数初始化外设TIM3OC1
	TIM_OC2Init(TIM3,&TIM_OCInitStructure);       					      //根据T指定的参数初始化外设TIM3OC2
	      					                                            
	
	TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);             //使能TIM3在CCR1上的预装载寄存器
	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);             //使能TIM3在CCR2上的预装载寄存器
	          
	
	TIM_ARRPreloadConfig(TIM3, ENABLE);                          //使能APRE(arr)
	TIM_Cmd( TIM3,ENABLE); 
	 TIM_CtrlPWMOutputs(TIM3, ENABLE);
  
}

PWM呢就是用定时器3来实现,这里应该没什么问题。PWM输出要注意占空比和PWM频率。这里简单介绍一下PWM。首先,确定TIMx的时钟,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,这时的TIMx时钟为72MHz,用这个TIMx时钟72MHz除以(PSC+1),再把这个值乘以(ARR+1)得出PWM频率。如果想调整PWM占空比精度,则只需降低PSC寄存器的值即可。还有一点要注意的是PWM频率太慢,车轮会有卡卡停停的现象。 频率太高,小车则动不了。

(4)电机驱动模块管脚初始化
#include "en.h"

void En_Init(void)
{    	 
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟

  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6|GPIO_Pin_3|GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化
	
	GPIO_SetBits(GPIOF,GPIO_Pin_7|GPIO_Pin_6|GPIO_Pin_3|GPIO_Pin_2);//GPIOF9,F10设置高

}

也就是IN1–4的初始化

(5)小车驱动代码
void car_mode(void)
{
	
	if(x>1.1&&c1==0&&z<14)//左转c1标志正数
	{ 
		IN1=0;
		IN2=1;
		IN3=0;
		IN4=1;
		TIM_SetCompare1(TIM3,3600);
	    TIM_SetCompare2(TIM3,3600);   //修改比较值(ccr),改变占空比	 		                       		 
	    delay_ms(200);
		LED0=!LED0;	
		x=0.0;
	    LED0=0;
	}
	
	
	else if(x>1.1&&c1==1&&z<14) //右转
	{	
		IN1=1;
		IN2=0;
		IN3=1;
		IN4=0;
		TIM_SetCompare1(TIM3,3600); 
	    TIM_SetCompare2(TIM3,3600);  //修改比较值(ccr),改变占空比
        delay_ms(200);	  
		LED0=!LED0;
		
		x=0.0;
		c1=0;   
		LED0=0;
	}
	
	
		else if(z>14.5&&w1==1&&x<2.3) //直走
	{	
		IN1=0;
		IN2=1;
		IN3=1;
		IN4=0;
	    TIM_SetCompare1(TIM3,3600);   //修改比较值(ccr),改变占空比
		TIM_SetCompare2(TIM3,3600);   //修改比较值(ccr),改变占空比	
		delay_ms(250);
		w1=0;                        //w1置0
		LED0=!LED0;	                        
		z=0.0;
		LED0=0;
	
	}
	
	 else if(z<5&&w1==1&&x<1.2) //后退
	{
		IN1=1;
		IN2=0;
		IN3=0;
		IN4=1;
	    TIM_SetCompare1(TIM3,3600);   //修改比较值(ccr),改变占空比
		TIM_SetCompare2(TIM3,3600);   //修改比较值(ccr),改变占空比
	    LED0=!LED0;
		delay_ms(200);
		z=0.0;
		w1=0;  		//w1置0
		LED0=0;
	}
	
	 else if(z==0&&x==0)  //停
	{  
		z=0.0;
		x=0.0;
		IN1=0;
		IN2=0;
		IN3=0;
		IN4=0;		
		LED1=!LED1;
		delay_ms(500);
		
	}

}


/*

void car_pid()
{
	last_x_error=x_error;
	x_error=x-1;
	ut=x_P*x_error-x_D*(last_x_error-x_error);
	
 	L_Value=ut*2000;
	R_Value=ut*2000;
	
  L_Value=L_Value<1000?L_Value:1200;
  R_Value=R_Value<1000?R_Value:1200;	
	

 
}
*/

这是简单的驱动,没有加入PID,不过也还算比较精确,慢慢调一下PWM的占空比和频率就可以了。PID算法我打算回学校之后拿个有编码器的电机还有舵机再做出来,这样可以实现比较精确的控制了。

小车任务基本完成了,希望可以为你提供一些帮助~ 感谢~

说一下下一个项目的想法。通过树莓派运行ROS实现雷达导航以及制图,同时还有语音识别功能,跟随功能,算是这量小车的升级版吧!加油!预计3个月完成,需要学习树莓派,LINUX,Py。等做好后,继续写博客 。

具体源码地址 点这里.

  • 61
    点赞
  • 526
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
OpenMVSTM32都是常用于嵌入式系统开发的工具,其中OpenMV是一款基于Python编程语言的开源嵌入式视觉开发平台,而STM32是一种基于ARM Cortex-M处理器内核的单片机OpenMVSTM32可以结合使用,实现各种应用,例如追踪小车控制。 具体实现方法如下: 1.在OpenMV上进行图像处理,通过OpenMV的摄像头获取图像数据,使用Python代码进行图像处理,识别小车的位置和方向等信息。 2.将处理后的数据通过串口传输到STM32上,STM32根据接收到的数据控制小车运动。 3.STM32根据接收到的数据调整小车的运动方向和速度,使其朝向目标位置运动。 以下是参考代码: OpenMV部分: ```python import sensor, image, time, pyb, ustruct from pyb import UART #初始化串口 uart = UART(3, 115200) #初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time = 2000) sensor.set_auto_whitebal(False) def find_blobs(): #寻找小车颜色区域 for i in range(10): img = sensor.snapshot() blobs = img.find_blobs([(0, 100, -128, 127, -128, 127)], pixels_threshold=100, area_threshold=100) if blobs: #找到颜色区域后计算中心点坐标和面积 max_blob = max(blobs, key=lambda b: b.pixels()) x = max_blob.cx() y = max_blob.cy() w = max_blob.w() h = max_blob.h() return (x,y,w,h) while(True): blobs_data = find_blobs() if blobs_data: #将数据打包发送到串口 uart.write(ustruct.pack("<hhhh", blobs_data, blobs_data, blobs_data, blobs_data[3])) time.sleep(10) ``` STM32部分: ```c #include "stm32f4xx.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #include "misc.h" #define BUF_SIZE 8 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; uint8_t buf[BUF_SIZE]; void USART3_IRQHandler(void) { if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { static uint8_t i = 0; buf[i++] = USART_ReceiveData(USART3); if (i >= BUF_SIZE) { i = 0; int16_t x,y,w,h; memcpy(&x, buf, 2); memcpy(&y, buf+2, 2); memcpy(&w, buf+4, 2); memcpy(&h, buf+6, 2); //根据接收到的数据调整小车运动方向和速度 } } } int main(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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(USART3, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); USART_Cmd(USART3, ENABLE); while(1) { } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值