STM32循迹小车/Android蓝牙控制小车(一)

STM32循迹小车

第一天:硬件平台介绍
硬件平台:

控制模块:正点原子STM32精英开发板
驱动模块:L298N两路电机驱动模块,两路pwm控制,正反转控制
转向控制:S3010舵机,由20ms脉宽pwm信号控制转向
循迹模块:5路光电对管循迹模块RS016
蓝牙模块:HC-05双工蓝牙模块
小车平台:10年前飞思卡尔比赛车模

软件设计平台:

STM32程序开发:MDK5 Keil uVision5
Android APP开发:AndroidStudio2.2.2

预计实现功能:

1、遥控模式,通过红外遥控控制前进后退停止转向,连续调速
2、自动模式,通过循迹模块自动检测黑线并跟踪行驶
3、手机控制,通过Android定制APP控制小车前进后退停止转向,连续调速

已经实现功能:遥控模式,下附主要源代码:

#define MIN_zou 1000 //向左
#define Z_qian 1200  //向前
#define MAX_you 1400 //向右
/*
	循迹车引脚接线:
	PG13/PG14引脚控制车前进(1,0)/倒退(0,1),接L298控制引脚IN4/IN3
	定时器3通道2对应引脚PB5控制舵机转向,由20ms脉宽920-2120高电平控制。黑线(接地),红线(电源线),白色(控制线)
	定时器3通道4对应引脚PB1控制车驱动电机转速
*/

//pwm波输出 开发板验证
void TIM3_PWM_Init(u16 arr,u16 psc)
	{
		GPIO_InitTypeDef GPIO_InitStrcut;
		TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrcut;
		TIM_OCInitTypeDef TIM_OCInitStrcut;	
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//复用端口需要使能AFIO时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);	//使能TIM3时钟	
		GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);	//端口部分重映射	
		//设置该引脚为复用输出功能,输出 TIM3 CH2 的 PWM 脉冲波形 GPIOB.5
		GPIO_InitStrcut.GPIO_Mode = GPIO_Mode_AF_PP;				//复用推挽输出							
		GPIO_InitStrcut.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_1;						//
		GPIO_InitStrcut.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOB,&GPIO_InitStrcut);
		//定时器初始化
		TIM_TimeBaseInitStrcut.TIM_CounterMode=TIM_CounterMode_Up;//计数模式
		TIM_TimeBaseInitStrcut.TIM_Period=arr;						//重装值
		TIM_TimeBaseInitStrcut.TIM_Prescaler=psc;					//分频值
		TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrcut);	
		//PWM相关寄存器初始化
		TIM_OCInitStrcut.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2
		TIM_OCInitStrcut.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
		TIM_OCInitStrcut.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性高
		TIM_OC2Init(TIM3, &TIM_OCInitStrcut); //初始化外设 TIM3 OC2通道2
		TIM_OC4Init(TIM3, &TIM_OCInitStrcut); //初始化外设 TIM3 OC2通道4
		TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);	//装载预装值
		TIM_Cmd(TIM3,ENABLE);		//使能定时器3
	}

void Init_GPIO1(void)
{	
	 GPIO_InitTypeDef  GPIO_InitStructure;
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);	 //使能PB,PE端口时钟	
	 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;				 //LED0-->PB.5 端口配置
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	 GPIO_Init(GPIOG, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5	
}

 int main(void)
 {	 
	u8 key;				//键值
	u16 led0pwmval=Z_qian,pwm_sudu=0;
	u8 *str=0;	 
	delay_init();	    	 //延时函数初始化	  
	Init_GPIO1();
	LCD_Init();			   		//初始化LCD   
	W25QXX_Init();				//初始化W25Q128
	Remote_Init();
	TIM3_PWM_Init(14399,99);
	TIM_SetCompare2(TIM3,led0pwmval);
	TIM_SetCompare4(TIM3,pwm_sudu);	
	POINT_COLOR=RED;
	while(font_init()) 		//检查字库
	{	    
		LCD_ShowString(30,50,16,"Font Error!",0);
		delay_ms(200);				  
		LCD_Fill(30,50,240,66,WHITE);//清除显示	     
		delay_ms(200);				  
	} 
	Show_Str(30,30,200,16,"循迹小车调试界面",16,0);
	Show_Str(30,70,200,16,"PWM捕获寄存器值:",16,0);	
	Show_Str(30,100,200,16,"遥控接收值:",16,0);	
	Show_Str(30,130,200,16,"按键连按次数:",16,0);
	Show_Str(30,160,200,16,"按键键值:",16,0);
	Show_Str(30,190,200,16,"速度PWM值:",16,0);	
	POINT_COLOR=BLUE;
	Show_Str(30,210,200,64,"|<< :向左  2 :前进",16,0);	
	Show_Str(30,230,200,64,">>| :向右  8 :倒退",16,0);	
	Show_Str(30,250,200,64,">|| :向前  5 :停止",16,0);	
	Show_Str(30,270,200,64,"VOL+/- :快/慢",16,0);
	Show_Str(30,300,200,64,"上/下  :左/右",16,0);	
//	Show_Str(30,350,200,64,"2 :前进",16,0);	
//	Show_Str(30,370,200,64,"8 :倒退",16,0);	
//	Show_Str(30,390,200,64,"5 :停止",16,0);	
	
	while(1)
	{	
		key=Remote_Scan();	
		if(key)
		{		
			switch(key)
			{
				case 98:
				{
					str="UP";
					led0pwmval += 20;
					if(led0pwmval >= MAX_you)led0pwmval=MAX_you;
					TIM_SetCompare2(TIM3,led0pwmval);
				}break;	    
				case 194:
				{
					str="RIGHT";
					led0pwmval = MIN_zou;
					TIM_SetCompare2(TIM3,led0pwmval);
				}break;	   
				case 34:
				{
					str="LEFT";
					led0pwmval = MAX_you;
					TIM_SetCompare2(TIM3,led0pwmval);
				}break;		  
				case 168:
				{
					str="DOWN";
					if(led0pwmval >= (MIN_zou+20))led0pwmval -= 20;
					else led0pwmval = MIN_zou;
					TIM_SetCompare2(TIM3,led0pwmval);
				}break;			
				case 2:
				{
					str="PLAY";
					led0pwmval=Z_qian;
					TIM_SetCompare2(TIM3,led0pwmval);
				}break;	
				case 224:
				{
					str="VOL-";
					if(pwm_sudu >=100)pwm_sudu-=100;
					else pwm_sudu=0;
					TIM_SetCompare4(TIM3,pwm_sudu);
				}break;		  		   
				case 144:
				{
					str="VOL+";
					if(pwm_sudu <= 8000)pwm_sudu+=100;
					else pwm_sudu=8000;	
					TIM_SetCompare4(TIM3,pwm_sudu);					
				}break;	
				case 56:
				{
					str="8";
					TIM_SetCompare4(TIM3,pwm_sudu);
					delay_ms(100);
					PGout(13) = 0;
					PGout(14) = 1;
				}break;	
				case 152:
				{
					str="2";
					TIM_SetCompare4(TIM3,pwm_sudu);
					delay_ms(100);
					PGout(13) = 1;
					PGout(14) = 0;
				}break;	
				case 24:
				{
					str="5";
					if(pwm_sudu>=3000){
						TIM_SetCompare4(TIM3,pwm_sudu/2);
						delay_ms(200);
					}	
					PGout(13) = 0;
					PGout(14) = 0;
					delay_ms(20);
				}break;					
			}
			LCD_ShowNum(30+8*16,70,led0pwmval,5,16);	
			LCD_ShowNum(30+8*10,100,key,4,16);		
			LCD_ShowNum(30+8*14,130,RmtCnt,3,16);	
			LCD_ShowString(30+8*10,160,16,"      ",0);	
			LCD_ShowString(30+8*10,160,16,str,0);		
			LCD_ShowNum(30+8*14,190,pwm_sudu,5,16);			
		}
		delay_ms(150);
	}
}

红外接收解码:

void Remote_Init(void){
	
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_9);//设置上拉
	
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//多少次有效数据计数一次
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseInitStruct.TIM_Period=10000;//计数器自动重装载值
	TIM_TimeBaseInitStruct.TIM_Prescaler=72-1;//预分频值,1Hz频率计数,每个时钟周期1us
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct); //定时器初始化
	
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_4;//选择红外接收端口对应的通道
	TIM_ICInitStruct.TIM_ICFilter=0x03;//滤波器参数选择
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;//先设置上升沿捕获
	TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;//每次上升沿都捕获一次
	TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;//匹配对应通道
	TIM_ICInit(TIM4,&TIM_ICInitStruct);
	
	TIM_Cmd(TIM4,ENABLE);//使能定时器4
	
	NVIC_InitStruct.NVIC_IRQChannel = TIM4_IRQn;  //TIM4中断
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级0级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStruct);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器	

	TIM_ITConfig( TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE);//允许更新中断 ,允许CC4IE捕获中断		

}

//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留	
//[4]:标记上升沿是否已经被捕获								   
//[3:0]:溢出计时器
u8  RmtSta=0;	  	  
u16 Dval;		//下降沿时计数器的值
u32 RmtRec=0;	//红外接收到的数据	   		    
u8  RmtCnt=0;	//按键按下的次数	  
//定时器4中断服务程序	 
void TIM4_IRQHandler(void){	
	if(TIM_GetITStatus(TIM4,TIM_IT_Update) != RESET){
		if(RmtSta&0x80){ //收到了引导码
			RmtSta &= ~(1<<4);
			if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;	//第一次溢出中断,标记已经完成一次按键的键值信息采集
			if((RmtSta&0X0F)<14)RmtSta++;
			else
			{
				RmtSta&=~(1<<7);					//清空引导标识
				RmtSta&=0XF0;						//清空计数器	
			}
		}
	}
	if(TIM_GetITStatus(TIM4,TIM_IT_CC4) != RESET){
		if(RDATA){	//捕获中断产生的时候红外输入引脚为高电平,表示捕获到上升沿
			TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);						//CC4P=1	设置为下降沿捕获
			TIM_SetCounter(TIM4,0);							//清空定时器值
			RmtSta|=0X10;							//标记上升沿已经被捕获
		}else{
			Dval = TIM_GetCapture4(TIM4);//保存捕获值
			TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);//切换到上升沿捕获
			if(RmtSta & 0x10){  //上一次已经捕获到上升沿,说明此次捕获下降沿有效
				if(RmtSta&0X80)//接收到了引导码
				{
					if(Dval>300&&Dval<800)					//560为标准值,560us
					{
						RmtRec<<=1;					//左移一位.
						RmtRec|=0;					//接收到0	   
					}else if(Dval>1400&&Dval<1800)	//1680为标准值,1680us
					{
						RmtRec<<=1;					//左移一位.
						RmtRec|=1;					//接收到1
					}else if(Dval>2200&&Dval<2600)	//得到按键键值增加的信息 2500为标准值2.5ms
					{
						RmtCnt++; 					//按键次数增加1次
						RmtSta&=0XF0;				//清空计时器		
					}
				}else if(Dval>4200&&Dval<4700)		//4500为标准值4.5ms
				{
					RmtSta|=1<<7;					//标记成功接收到了引导码
					RmtCnt=0;							//清除按键次数计数器
				}
			}
			RmtSta &= ~(1<<4);
		}
	}
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC4);	 	    
}
//处理红外键盘
//返回值:
//0,没有任何按键按下
//其他,按下的按键键值.
u8 Remote_Scan(void)
{        
	u8 sta=0;       
   u8 t1,t2;  
	if(RmtSta&(1<<6))//得到一个按键的所有信息了
	{ 
	   t1=RmtRec>>24;			//得到地址码
	   t2=(RmtRec>>16)&0xff;	//得到地址反码 
 	   if((t1==(u8)~t2)&&t1==REMOTE_ID)//检验遥控识别码(ID)及地址 
	   { 
	      t1=RmtRec>>8;
	      t2=RmtRec; 	
	      if(t1==(u8)~t2)sta=t1;//键值正确	 
		}   
		if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了
		{
		 	RmtSta&=~(1<<6);//清除接收到有效按键标识
			RmtCnt=0;		//清除按键次数计数器
		}
	}  
   return sta;
}

后续功能开发待续,不定时更新,业余作品,预计一个月左右更新完成。
源码开发过程中随时会修正更改,暂时不提供完整代码。

下面附完整源码

STM32循迹小车/Android蓝牙控制小车(一)
STM32循迹小车/Android蓝牙控制小车(二)
STM32循迹小车/Android蓝牙控制小车(三)

Android端蓝牙控制APP源码以及apk安装包下载
小车端STM32控制源码下载

猜你喜欢

转载自blog.csdn.net/q296264785/article/details/105521743