暑假STM32小车项目总结

用了两个星期的时间终于把小车做好了,因为组里另一个项目的原因,我只是在该项目的开头与中期偏后的阶段有深度参与。涉及超声波驱动程序、小车避障算法的完善,和蓝牙控制的整套代码。

先简单叙述一下小车所实现的功能:1.超声波避障;2.蓝牙控制。

其中,蓝牙控制功能要实现的包括1.驱动小车向前后左右运动;2.驱动小车在地面走出矩形的图案。

1.在调试小车时遇到的问题和解决办法。

调试小车不像调试C语言的代码可以随时看到每个变量的值,而且这次我们用的是STM32C8T6超迷你板。这板子很邪门,在mini板上可以正常运行的程序明明引脚图和端口复用一模一样但在小板上就是不能运行。

(1)超声波返回的很多不准确数据导致小车运动异常

超声波测距元件所测出来的数据不是精准数据,而且受到实际环境的影响很明显,所以在大量返回数据中有很多异常数据,这些数据会导致小车的运动异常。而我们的解决思路也很明确——过滤掉一些明显异常的数据。超声波模块(HC-SR04)测距范围为0~400cm,我们将模块返回的数据中大于500cm的数据过滤掉,并通过多次接收数据取平均值的方式提高测距的精确度,通过这种方法小车的运动更加稳定。

(2)对一些极端的情况缺少考虑

在设计小车的运动算法时,我们没有考虑到当小车斜对墙面运动时因为超声波模块自身原因导致数据异常的情况。因为这一次我们的设计方案为将三个超声波模块分别放于小车前方检测小车前方、左侧、右侧是否有障碍物。小车对于这一问题我们的解决思路为:当小车判断自己应该直行时,在执行的语句下同时判断左、右两侧超声波模块返回的数据,如果一侧距离小于5cm则小车向另一侧转向。

(3)对超声波避障和蓝牙控制之间的关系没有充分认识

在设计程序时我们把蓝牙控制和超声波避障视为两个互相独立的程序,设计思路为蓝牙控制与超声波避障不能同时执行。但事实是蓝牙控制由串口中断启动,即在超声波避障运行的同时蓝牙控制的功能不受影响。但蓝牙控制不能长时间地改变并保持小车的运动状态,因为串口中断服务函数执行完之后程序依然会继续在超声波避障中不断循环小车运动状态会不断被改变。为了在蓝牙控制时能够跳出避障功能我们在蓝牙控制的循环中加入了一句while循环,其跳出条件为接收数据寄存器中的值与57的差为零。所以当不满足跳出循环条件时,程序会一直卡在该while循环中,不会进入到避障的程序。

(4)之后就是临时加的一个需求——让小车自己跑出一个矩形图案,矩形的长和宽在每次都可以通过蓝牙串口自定义

小车的运动会受到很多外部环境的影响,比如地面的光滑程度、电池的剩余电量。对于这一功能算法的设计思路,我们先是测出小车的行驶速度,然后在算出小车的转向角速度。根据角速度算出小车转向90度所用的时间。之后不微断调小车的转向时间,以使小车转出90度的角度。

2.程序源码

(1)超声波避障初始化

#ifndef __HCSR_H
#define __HCSR_H
#include "sys.h"

void TIM2_Cap_Init(u16 arr,u16 psc);

#endif


-----------------------------------------------------------------------
#include "sys.h"

void TIM2_Cap_Init(u16 arr,u16 psc)
{		 
	RCC->APB1ENR|=1<<0;   	//TIM2 时钟使能 
	RCC->APB2ENR|=1<<2;    	//使能PORTA时钟  
	RCC->APB2ENR|=1<<3;    	//使能PORTB时钟  
	
	
	GPIOA->CRL&=0XFFFF000F;	//PA0,PA1清除之前设置  
	GPIOA->CRL|=0X00008880;	//PA0,PA1输入  
	GPIOA->ODR|=0<<1;		//PA0,PA1下拉
	GPIOA->ODR|=0<<2;		//PA0,PA1下拉
	GPIOA->ODR|=0<<3;		//PA0,PA1下拉
	
	
  GPIOB->CRL&=0X000FFFFF;//PB7清除之前设置
  GPIOB->CRL|=0X33300000;//PB7推挽输出
  GPIOB->ODR|=1<<7;      //PB7 输出高
	GPIOB->ODR|=1<<6;
	GPIOB->ODR|=1<<5;
	
	
 	TIM2->ARR=arr;  		//设定计数器自动重装值   
	TIM2->PSC=psc;  		//预分频器 
	//CH2
	TIM2->CCMR1|=1<<8;		//CC2S=01 	选择输入端 IC1映射到TI1上
 	TIM2->CCMR1|=1<<12; 	//IC2F=0001 配置输入滤波器 以Fck_int采样,2个事件后有效
 	TIM2->CCMR1|=0<<10; 	//IC2PS=00 	配置输入分频,不分频 
	TIM2->CCER|=0<<5; 		//CC2P=0	上升沿捕获
	TIM2->CCER|=1<<4; 		//CC2E=1 	允许捕获计数器的值到捕获寄存器中
	//CH3
	TIM2->CCMR2|=1<<0;		//CC3S=01 	选择输入端 IC1映射到TI1上
 	TIM2->CCMR2|=1<<4; 		//IC3F=0001 配置输入滤波器 以Fck_int采样,2个事件后有效
 	TIM2->CCMR2|=0<<2; 		//IC3PS=00 	配置输入分频,不分频 
	TIM2->CCER|=0<<9; 		//CC3P=0	上升沿捕获
	TIM2->CCER|=1<<8; 		//CC3E=1 	允许捕获计数器的值到捕获寄存器中
	//CH4
	TIM2->CCMR2|=1<<8;		//CC4S=01 	选择输入端 IC1映射到TI1上
 	TIM2->CCMR2|=1<<12; 	//IC4F=0001 配置输入滤波器 以Fck_int采样,2个事件后有效
 	TIM2->CCMR2|=0<<10; 	//IC4PS=00 	配置输入分频,不分频
	TIM2->CCER|=0<<13; 		//CC4P=0	上升沿捕获
	TIM2->CCER|=1<<12; 		//CC4E=1 	允许捕获计数器的值到捕获寄存器中
	
 
	//中断使能
	TIM2->DIER|=1<<2;   	//允许捕获2中断	
	TIM2->DIER|=1<<3;   	//允许捕获3中断	
	TIM2->DIER|=1<<4;   	//允许捕获4中断	
	
	TIM2->DIER|=1<<0;   	//允许更新中断	
	TIM2->CR1|=0x01;    	//使能定时器2
	MY_NVIC_Init(2,0,TIM2_IRQn,2);//抢占2,子优先级0,组2	   
}

(2)蓝牙控制函数(串口中断服务函数)

#ifndef __SMG_H
#define __SMG_H 
#include "sys.h"
#include "delay.h"


#define USART_REC_LEN  			200  //定义最大接收字符数
#define EN_USART3_RX    			1		//使能(1)/禁止(0)串口接收
extern u8  USART3_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节,末字节为换行符
extern u16 USART3_RX_STA;         		//接收状态标记



#endif


------------------------------------------------------------------------
#include "sys.h"
#include "lanya.h"
#include "delay.h"
#include "usart.h"
#include "dianji.h"

u8 BTFlag;
#if EN_USART1_RX 
 
void Juxing()//小车的矩形运动函数
{
	u8 chang,kuan,x,y;//chang、kuan分别是小车要走矩形的长和宽的值
	delay_ms(200);
	printf("Chang:\r\n");
	while(1)
	{
		printf("Input Chang:\r\n");
		delay_ms(200);
		if(USART1->SR&(1<<5))//当串口接收到消息后跳出循环
		{
			chang=USART1->DR-'0';//将字符型的数据转换为整型数据
			break;
		} 
	}
	printf("长:%d\r\n",chang);//打印串口接收到的数据
	USART1->SR=0;//串口的接收标志位清零,为下一次接收宽度数据做准备
	while(1)
	{
		printf("Input Kuan:\r\n");
		delay_ms(200);
		if(USART1->SR&(1<<5))
		{
			kuan=USART1->DR-'0';//当串口接收到数据后跳出循环
			break;
		}
	}
	printf("宽:%d\r\n",kuan);//打印宽度数据
	//当前小车速度为0.25米每秒则小车每走1cm要用40ms所以以1cm为单位每走1cm耗时40ms用for函数驱动小车运动
	for(x=0;x<chang*10;x++)//直走长
	{
		GO(300,300);
		delay_ms(40);
	}
	RIGHT(300,300);//右拐
	delay_ms(785);
	for(y=0;y<kuan*10;y++)//直走宽
	{
		GO(300,300);
		delay_ms(40);
	}
	RIGHT(300,300);//右拐
	delay_ms(785);
	for(x=0;x<chang*10;x++)//直走长
	{
		GO(300,300);
		delay_ms(40);
	}
	RIGHT(300,300);//右拐
	delay_ms(785);
	for(y=0;y<kuan*10;y++)//直走宽
	{
		GO(300,300);
		delay_ms(40);
	}
	RIGHT(300,300);//右拐
	delay_ms(785);
	STOP();
}
//通过蓝牙串口发送字符消息通过判断字符消息控制小车运动
u8 USART1_RX_BUF[USART_REC_LEN];    
u16 USART1_RX_STA=0;      	  
void USART1_IRQHandler(void)
{
	char res;	//定义字符型变量‘res’
	if(USART1->SR&(1<<5))
	{		
		res=USART1->DR;// 令res等于串口接收到的数据
		printf("\r\n%d",res);		
		//通过判断res的值改变小车运动状态并通过串口将运动状态发送给手机
		if(res==50)//
		{	
			GO(300,300);//小车前进
			printf("\r\nGo Stright");
		}
		else if(res==56)	
		{
			BACK(300,300);//小车后退
			printf("\r\nGo Back");
		}
		else if(res== 52)
		{
			LEFT(300,300);//小车左转
			printf("\r\nTurn Left");			
		}
		else if(res==54)
		{
			RIGHT(300,300);//小车右转
			printf("\r\nTurn Right");
		}
		else if(res==53)
		{
			STOP();//小车停止
			printf("\r\nStop");
		}
		else if(res=='7')//小车走矩形
		{
			delay_ms(200);
			Juxing();//小车的矩形运动函数
		}
	}
} 
#endif		
 
 

(3)主函数(含超声波避障判断、执行函数)

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "hcsr.h"
#include "lanya.h"
#include "dianji.h"
//定时器2通道1-4输入捕获配置
//arr:自动重装值
//psc:时钟预分频数

//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到高电平;1,已经捕获到高电平了.
//[5:0]:捕获高电平后溢出的次数
u8  TIM2CH1_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM2CH1_CAPTURE_Date2;	//数据2
u16 TIM2CH1_CAPTURE_Date1;	//数据1
//CH2
u8  TIM2CH2_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM2CH2_CAPTURE_Date2;	//数据2
u16 TIM2CH2_CAPTURE_Date1;	//数据1
//CH3
u8  TIM2CH3_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM2CH3_CAPTURE_Date2;	//数据2
u16 TIM2CH3_CAPTURE_Date1;	//数据1
//CH4
u8  TIM2CH4_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM2CH4_CAPTURE_Date2;	//数据2
u16 TIM2CH4_CAPTURE_Date1;	//数据1
 
//定时器2中断服务程序	 
void TIM2_IRQHandler(void)
{ 		    
	u16 tsr;
	tsr=TIM2->SR;
	//CH2中断处理
	if((TIM2CH2_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{
		if(tsr&0X01)//溢出
		{	    
			if(TIM2CH2_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM2CH2_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM2CH2_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM2CH2_CAPTURE_Date2=0XFFFF;
				}else TIM2CH2_CAPTURE_STA++;
			}	 
		}
		if(tsr&0x04)//捕获2发生捕获事件
		{	
			if(TIM2CH2_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{	  			
				TIM2CH2_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
			    TIM2CH2_CAPTURE_Date2=TIM2->CCR2;	//获取当前的捕获值.
	 			TIM2->CCER&=~(1<<5);			//CC2P=0 设置为上升沿捕获
			}else  								//还未开始,第一次捕获上升沿
			{ 
				TIM2CH2_CAPTURE_Date2=0;
				TIM2CH2_CAPTURE_STA=0X40;		//标记捕获到了上升沿
				TIM2CH2_CAPTURE_Date1=TIM2->CCR2;
				TIM2->CCER|=1<<5; 				//CC2P=1 设置为下降沿捕获 
			}		    
		}			     	    					   
 	}
	//CH3中断处理
	if((TIM2CH3_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{
		if(tsr&0X01)//溢出
		{	    
			if(TIM2CH3_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM2CH3_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM2CH3_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM2CH3_CAPTURE_Date2=0XFFFF;
				}else TIM2CH3_CAPTURE_STA++;
			}	 
		}
		if(tsr&0x08)//捕获3发生捕获事件
		{	
			if(TIM2CH3_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{	  			
				TIM2CH3_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
			    TIM2CH3_CAPTURE_Date2=TIM2->CCR3;	//获取当前的捕获值.
	 			TIM2->CCER&=~(1<<9);			//CC3P=0 设置为上升沿捕获
			}else  								//还未开始,第一次捕获上升沿
			{ 
				TIM2CH3_CAPTURE_Date2=0;
				TIM2CH3_CAPTURE_STA=0X40;		//标记捕获到了上升沿
				TIM2CH3_CAPTURE_Date1=TIM2->CCR3;
				TIM2->CCER|=1<<9; 				//CC3P=1 设置为下降沿捕获 
			}		    
		}			     	    					   
 	}
	//CH4中断处理
	if((TIM2CH4_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{
		if(tsr&0X01)//溢出
		{	 
			if(TIM2CH4_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM2CH4_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM2CH4_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM2CH4_CAPTURE_Date2=0XFFFF;
				}else TIM2CH4_CAPTURE_STA++;
			}	 
		}
		if(tsr&0x10)//捕获4发生捕获事件
		{	
			if(TIM2CH4_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{	  			
				TIM2CH4_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
			    TIM2CH4_CAPTURE_Date2=TIM2->CCR4;	//获取当前的捕获值.
	 			TIM2->CCER&=~(1<<13);			//CC4P=0 设置为上升沿捕获
			}else  								//还未开始,第一次捕获上升沿
			{ 
				TIM2CH4_CAPTURE_Date2=0;
				TIM2CH4_CAPTURE_STA=0X40;		//标记捕获到了上升沿
				TIM2CH4_CAPTURE_Date1=TIM2->CCR4;
				TIM2->CCER|=1<<13; 				//CC4P=1 设置为下降沿捕获 
			}		    
		}			     	    					   
 	}
	
	
	TIM2->SR=0;//清除中断标志位 	    
}

u32 CEJU2(void)
{
	while(1)
	{
		u32 temp2=0;
		PBout(7)=1;                        //打开TRIG
		delay_us(20);
		PBout(7)=0;												 //关闭TRIG
		if(TIM2CH2_CAPTURE_STA&0X80)			//成功捕获到了一次高电平
		{
			temp2=TIM2CH2_CAPTURE_STA&0X3F;
			temp2*=65536;						//溢出时间总和
			temp2+=TIM2CH2_CAPTURE_Date2;		
			temp2-=TIM2CH2_CAPTURE_Date1;		//得到总的高电平时间
			temp2*=0.017;										//得到距离
			printf("TIM2_CH2_LENGTH:%d cm\r\n",temp2);		//打印距离
			TIM2CH2_CAPTURE_Date1=0;
 			TIM2CH2_CAPTURE_STA=0;				//开启下一次捕获
 		}
		if(temp2<500) return temp2;   //返回距离值
		else return 0;
	}
}

u32 CEJU3(void)
{
	while(1)
	{
		u32 temp2=0;
		PBout(6)=1;
		delay_us(20);
		PBout(6)=0;
		if(TIM2CH3_CAPTURE_STA&0X80)			//成功捕获到了一次高电平
		{
			temp2=TIM2CH3_CAPTURE_STA&0X3F;
			temp2*=65536;						//溢出时间总和
			temp2+=TIM2CH3_CAPTURE_Date2;		
			temp2-=TIM2CH3_CAPTURE_Date1;		//得到总的高电平时间
			temp2*=0.017;									
			printf("TIM2_CH3_LENGTH:%d cm\r\n",temp2);	
			TIM2CH3_CAPTURE_Date1=0;
 			TIM2CH3_CAPTURE_STA=0;				//开启下一次捕获
 		}
		if(temp2<500) return temp2;
		else return 0;
	}
}


u32 CEJU4(void)
{
	while(1)
	{
		u32 temp2=0;
		
			PBout(5)=1;
			delay_us(20);
			PBout(5)=0;
			if(TIM2CH4_CAPTURE_STA&0X80)			//成功捕获到了一次高电平
			{
				temp2=TIM2CH4_CAPTURE_STA&0X3F;
				temp2*=65536;						//溢出时间总和
				temp2+=TIM2CH4_CAPTURE_Date2;		
				temp2-=TIM2CH4_CAPTURE_Date1;		//得到总的高电平时间
				temp2*=0.017;
				printf("TIM2_CH4_LENGTH:%d cm\r\n",temp2);		
				TIM2CH4_CAPTURE_Date1=0;
				TIM2CH4_CAPTURE_STA=0;				//开启下一次捕获
			}
		if(temp2<500) return temp2;        
			else return 0;
	}
}

int main(void)
{	
	u32 i=3,j;
	u32 temp2=0,temp3=0,temp4=0;
	extern u8 opq;
	Stm32_Clock_Init(9);					  	//初始化系统时钟		
	delay_init(72);								//初始化延时	
	uart_init(72,9600);						//串口初始化
	
	TIM2_Cap_Init(0XFFFF,72-1);					//超声波初始化,以2Mhz的频率计数 
	TIM_PWM1_Init(1000-1,36-1); 	//电机初始化,不分频。PWM频率=72000/(899+1)=80Khz
		printf("Choose moudle:");
	
			CEJU2();           //超声波预初始化
			CEJU3();
			CEJU4();
			delay_ms(1000);
	
			while(1)  
			{		
				while(USART1->DR-57)     //当接收的数据为字符型9时,跳出循环
				{
					;
				}
				temp2=0;        //每一次新循环,将temp2,temp3,temp4清零
				temp3=0;
				temp4=0;
				while(i)       //检测三次正超声波发回的值
				{
					j=CEJU4();
					temp4+=j;
					if(j!=0) i--; 
				}
				temp4=temp4/3;   //取三次超声波发回的值的平均数,以减小误差
				i=3;
				j=0;
				if(temp4<40)     //如果前方距离小于40cm,则进行判断
				{
					while(i)				//检测三次左超声波发回的值
					{
						j=CEJU2();
						temp2+=j;
						if(j!=0) i--; 
					}
					temp2=temp2/3;
					i=3;
					j=0;
					if(temp2<40)    //如果左侧距离小于40cm,则进行判断
					{
						while(i)     //检测三次右超声波发回的值
						{
							j=CEJU3();
							temp3+=j;
							if(j!=0) i--; 
						}
						temp3=temp3/3;
						i=3;
						j=0;
						if(temp3<40)     //如果右侧距离小于40cm,则进行后退,左转
						{
							BACK(300,300);
							printf("back\r\n");
							delay_ms(500);
							LEFT(300,300);
							delay_ms(500);
						}
						else             //如果右侧距离大于40cm,则右转
						{
							RIGHT(300,300);
							printf("right\r\n");
							delay_ms(500);
						}
					}
					else								//如果右侧距离大于40cm,则左转
					{
						LEFT(300,300);
						printf("left\r\n");
						delay_ms(500);
					}
				}
				else                  //如果前侧距离大于40cm,则判断
				{
					while(i)						//检测三次左超声波发回的值
					{
						j=CEJU2();
						temp2+=j;
						if(j!=0) i--; 
					}
					temp2=temp2/3;
					i=3;
					j=0;
					if(temp2<=5)				//如果左侧距离小于5cm,则右转
					{
						RIGHT(200,200);
						delay_ms(500);
					}	
					while(i)							//检测三次右超声波发回的值
					{
						j=CEJU3();
						temp3+=j;
						if(j!=0) i--; 
					}
					temp3=temp3/3;
					i=3;
					j=0;
					if(temp3<=5)				//如果右侧距离小于5cm,则左转
					{
						LEFT(200,200);
						delay_ms(500);
					}
					GO(300,300);				//直行
					delay_ms(500);
				printf("go\r\n");
				}
				TIM2->CNT=0;          //每一次循环将CNT值清零
		}
}




 

  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值