这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » xxJian智能车DIY进程贴

共16条 2/2 1 2 跳转至
菜鸟
2015-06-03 13:15:14     打赏
11楼


[开发进程]实验七、超声波实验



第一部分,电机不转观察超声波测距的结果

实验代码使用Mini Balance V2.5 顶配版源码。

实验原理:Windows上面,只要调用printf()函数就可以往命令上中显示数字符号,聪明的ARM开发人员,将自己编了printf(),使用方法跟Windows上面的一样,只不过所须显示的数据是通过串口输出。

Usart1.c中,可以看到,工程设计者自定义了fputc()这函数,可以估计printf()内真正实现输出char的也是通过调用fputc

/************************************************************/

/*串口printf必备程序*/

/************************************************************/

int fputc(int ch, FILE *f)

{

    USART1->DR=(u8)ch;

    while((USART1->SR&0X40)==0);

    return ch;

} 



要使用printf()这个自定义的函数,都必须将Use MicroLIB选上。过程:

右键工程->Options for target->TargetTarget页面内的Code Generation内选中Use MicroLIB

通过改变USARTx,就可以将prtinf()输出的串口改变。例如在这例程中可以将USART1改为USART3,即可以实现蓝牙调试。

实验步骤:

Mini Balance V2.5 顶配版源码的基础上,要修改的地方有以下几个:

修改1main.c18行,改为:u8 Flag_Bizhang=1;

修改1原因:工程设计者设计成使得用户改变Flag_Bizhang这个参数,来决定小车是否在代码运行中执行超声波测距的代码。另外值得注意的是,//=====如果Flag_Bizhang1,则把TIM2初始化为超声波接口 然后不采集左路编码器,通过右路编码器和陀螺仪间接算出左路车轮速度 //=====如果Flag_Bizhang0,则把TIM2初始化为编码器接口

以上绿色的地方就是工程设计者的注释。十分赞!

修改2MiniBalance.c262printf("&d \r\n",Distance);

修改2 原因:原本这行代码被注释掉了,我取消注释以后,是这个结果(真是超出预料):



仔细看后,才发现应该改为:printf("%d \r\n",Distance);

修改后才会出现正确的结果:



OLED中第一行就是距离(JULI  xxxx)

第二部分,蔽障自平衡小车

待续……

超声波模块是网上买的5块钱不够的,可能质量不过关,平衡开始以后,偶尔就会出现往后走的情况。估计是数据测量错误了。。。

暂时就这样,这段时间改的代码都是小东西,模块还没深入学习,是这样的了。。。


菜鸟
2015-06-15 14:52:27     打赏
12楼

实验八、通过编码器采样数据得到前进距离试验

实验背景:

一直打算做个小车的行驶记录仪。现在先做好如何将小车集成的编码器的采样结果转换为小车的位移。

个人理解,前段时间已经做了实验三、四,分别通过有线还有无线的方式将小车的采样数据传输到电脑上位机软件进行分析,得到小车的编码器采样结果。可以将其结果理解成电机的转动速度。

 

我们都知道,速度对时间的积分就是位移。

黄色是小车编码器的输出结果。对应着速度,假如在STM32 5MS中断函数中,将编码器的结果乘以0.005,就得到一个与小车的位移成比例关系的一个参数。


 

也就是编码器的输出1,也并不代表小车移动速度为1mm/s,也就是说这个结果还需要转换。

 

 

实验需要的硬件

实验过程中,使用到了:


上图包括两个UartUSB模块(其实只需要一个即可,一个用于蓝牙主模块接电脑USB,另外一个用于给STM32 下载程序),BC-04蓝牙主从一体模块,以及EEPW平衡小车

实验过程:

注意本试验中用distance表示小车移动的距离。

实验过程1、对左边的编码器的采样结果对时间进行积分

void TIM1_UP_TIM16_IRQHandler(void) 中断子程序中,加入以下代码:

if(Encoder_Left>0)

                     distance = distance + (float)Encoder_Left*0.005;    

我加上这个if判断的原意是为了只得到小车的前进距离,而不看小车的后退距离。让小车保持平衡,就会得到下图中的数据。小车的平衡过程中,会前进4cm,后退4cm,总共占了8cm的距离。但是看下图,紫色的阶梯波形,每一个台阶的幅值都达不到80。所以需要乘以一个放大系数。



下面的过程的目的,是得到这个放大系数。

实验过程2、对distance进行校正,得到正确的距离

本次校正,打算使用小车的蓝牙将数据传送到电脑中,具体过程请看实验四。

但是小车的蓝牙是从模块,只能选择连接手机或者是电脑。我先用手机操作小车。



让小车移动一个距离。这个距离我用地面上的瓷砖可以数得到。这种瓷砖是60mm*60mm的。我让小车移动了6个瓷砖的距离。

让后手机断开和小车的蓝牙连接,让小车保持在原地。将BC-04主模块通电。它会自动和小车上的SPP-CA连接。并同时小车会不断将数据发送到电脑中。



通过分析数据,得到这时候左编码器的结果的积分是-1653。而实际距离是3600。所以要将积分结果乘以3600/1653。这个放大系数,就是所要得到的校正系数。

具体校正代码:

在代码中,我试过了不可以直接用distance = distance*2.176542;

这样来校正。而是另外弄一个中间变量。

void oled_show(void)子函数中

注释掉MPU6050的滤波器的显示代码,而用以下代码替代。

int disSHow;
disSHow = distance*2.176542;
if(disSHow<0)
			OLED_ShowNumber(45,0, (-disSHow),5,12);
		else
			OLED_ShowNumber(45,0, disSHow,5,12);

 

就可以实现小车移动距离的实时显示啦!

记得如果需要oled的实时显示需要将

u8 Way_Angle=1;改为2或者3。笔者是用2的。

 

结论

本实验在Mini Balance V2.5 标准版源码的基础上,进一步分析编码器的数据。统一了蓝牙无线调试、电机编码器采样结果计算、OLED显示这几个小实验,通过积分、校正,得到了小车的真正移动距离。



菜鸟
2015-07-04 13:32:36     打赏
13楼

[开发进程]实验九、移动中避障自平衡小车实验

小车标配版,另外加上超声波测距模块,就可以完成本实验。


超声波模块可以直接接到板子上,而不用拆卸压克力板。如果要安装NRF24L01,就需要拆……

笔者使用了源代码:Mini Balance V2.5 顶配版源码(标准版基础上增加摄像头底层 超声波 NRF24L01

顶配版源代码的使用:

并使用超声波蔽障需要修改一下蔽障使能的标志位:

Main.c的第18行:

u8 Flag_Bizhang=0;                          //避障标志位0:不避障  1:避障   接入超声波模块 把Flag_Bizhang改成1就可以进入避障模式了


需要改变成:

u8 Flag_Bizhang=1;                          //避障标志位0:不避障  1:避障   接入超声波模块 把Flag_Bizhang改成1就可以进入避障模式了


另外值得注意的是:

Main.c9

u8 Way_Angle=1;




改为



u8 Way_Angle=2;


那么在小车运行时候才会更新oled显示哦。因为默认Way_Angle=1,在自平衡启动时候是不更新的oled的。以下是证据:




顶配版程序的实验现象:

将小车运行自平衡,发现车子在手机遥控前进的时候,往障碍物走,发现到了障碍物前,车子也没有理性的往后退,直到车子撞到障碍并翻倒在地上。

原因分析:

估计是手机蓝牙的控制优先权比蔽障功能的优先权高,所以造成就算通过超声波模块测量得到前面很近的地方有障碍物,车子也没后退。

int velocity(int encoder_left,int encoder_right)函数中,可以看到控制小车前后是根据全局变量Flag_QianFlag_Hou的。而这两个标志参数,两个函数的影响,第一个void USART3_IRQHandler(void)是在uart3.c内第66行,这个函数内的相关代码代码一

		if(uart_receive==0x00)	Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0;//////////////刹车
		if(uart_receive==0x01)	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;//////////////前
		if(uart_receive==0x05)	Flag_Qian=0,Flag_Hou=1,Flag_Left=0,Flag_Right=0;//////////////后
		else if(uart_receive==0x02||uart_receive==0x03||uart_receive==0x04)	
													Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;
		else if(uart_receive==0x06||uart_receive==0x07||uart_receive==0x08)	
													Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;






第二个函数是void TIM1_UP_TIM16_IRQHandler(void),在MiniBalance.c内第27行。

相关代码代码二



		  if(Flag_Bizhang==1)
			{	
		  if(Distance<100)Flag_Hou=1; else Flag_Hou=0; //===简单避障:遇到障碍物就退 } 




这样做的缺点是,假如手机遥控小车前进,那么会使Flag_Qian=1Flag_Hou=0。所以此时蔽障功能并没有用!

解决方案:

代码一执行完成后也需要执行代码二。原本当手机遥控小车前进,就不管有没有障碍物存在也要前进。现在将代码二复制到代码一的后面,就可以了。




上图中红色矩形内的代码就是加进去才能在遥控前进时候也能正常蔽障的代码。里面的u32 ObstacleDistance=180;我在main.c中定义了全局变量。并在main.h中声明。

extern u32 ObstacleDistance;

这个参数我取了180,可是实际中,由于小车前进的太快了。刹车距离根本不只200mm。实际中还需要增加这个值的大小。



---------------------------------------------------分割线-------------------------------------------------------------------------------------

方案二、


在实验过程中,发现上面的方案不行。因为这样在实际过程中就不能遥控小车后退了。。。

十分蛋疼

于是我再加入两个全局变量,用于协调void USART3_IRQHandler(void)和void TIM1_UP_TIM16_IRQHandler(void)  对前进后退往左往右Flag_Qian、Flag_Hou、Flag_Left、Flag_Right的操作。

u32 Distance,Distance_prev=0;            //Distance_prev的作用是记录前一个Distance的值、用于滤波
u32 ObstacleDistance=240;

u8 Flag_Obstacle=0;
u8 Backward=0;

并在 头文件中加入声明:.

extern u32 Distance,Distance_prev;
extern u32 ObstacleDistance;

extern u8 Flag_Obstacle;
extern u8 Backward;

ObstacleDistance=240是自己定义的,如果障碍物在小车前距离在该参数内,避障功能有效,注意单位是mm。

Backward这个参数,Usart3本身直接控制Flag_Hou,现在改为直接控制Backward,间接控制Flag_Hou。

将usart3.c中void USART3_IRQHandler(void)相关代码只有单纯的前进后退和原地左右旋转的,改为更加灵活的可以转弯的。介绍参数:

Flag_Bizhang是避障使能Flag。ObstacleDistance是笔者定义的,避障功能在该距离内有效。Flag_Obstacle=1的话,说明避障功能有效,暂时忽略蓝牙遥控的控制。backward是蓝牙控制小车往后走。因为避障也控制往后,所以不可以直接用Flag_Hou这个参数。


		if(Flag_Obstacle == 0)
		{
			Flag_Qian=0,Backward=0,Flag_Left=0,Flag_Right=0;			
			if(uart_receive==0x00)	Flag_Qian=0,Backward=0,Flag_Left=0,Flag_Right=0;//////////////刹车
			if(uart_receive==0x01)	Flag_Qian=1,Backward=0,Flag_Left=0,Flag_Right=0;//////////////前
			if(uart_receive==0x02)	Flag_Qian=1,Backward=0,Flag_Left=0,Flag_Right=1;//////////////前进右
			if(uart_receive==0x03)	Flag_Qian=0,Backward=0,Flag_Left=0,Flag_Right=1;//////////////原地右
			if(uart_receive==0x04)	Flag_Qian=0,Backward=1,Flag_Left=0,Flag_Right=1;//////////////后退右
			if(uart_receive==0x05)	Flag_Qian=0,Backward=1,Flag_Left=0,Flag_Right=0;//////////////后
			if(uart_receive==0x06)	Flag_Qian=0,Backward=1,Flag_Left=1,Flag_Right=0;//////////////后退左
			if(uart_receive==0x07)	Flag_Qian=0,Backward=0,Flag_Left=1,Flag_Right=0;//////////////原地左
			if(uart_receive==0x08)	Flag_Qian=1,Backward=0,Flag_Left=1,Flag_Right=0;//////////////前进左
		}
		else
		{
			Flag_Qian=0,Backward=1,Flag_Left=0,Flag_Right=0;
		}

void TIM1_UP_TIM16_IRQHandler(void)  内的代码也需要修改。下面这段代码的几个参数介绍:

Flag_Bizhang是避障使能Flag。ObstacleDistance是笔者定义的,避障功能在该距离内有效。Flag_Obstacle=1的话,说明避障功能有效,暂时忽略蓝牙遥控的控制。backward是蓝牙控制小车往后走。因为避障也控制往后,所以不可以直接用Flag_Hou这个参数。

			if(Flag_Bizhang==1)
			{	
				if(Distance>ObstacleDistance)
					Flag_Obstacle = 0;
				else
					Flag_Obstacle = 1;
				
				if(Flag_Obstacle == 1)
					Flag_Hou = 1;
				else
					if(Backward==1)
						Flag_Hou = 1;
					else
						Flag_Hou = 0;
			}



然后就是在读取超声波距离的函数中,假如了一阶低通滤波器,作用就是距离的结果对距离突然发生变化变得不那么敏感。也有过滤掉测量误差的作用。


void Read_Distane(void)
{   
	 PAout(2)=1;
	 delay_us(15);  
	 PAout(2)=0;	
			if(TIM2CH4_CAPTURE_STA&0X80)//成功捕获到了一次高电平
		{
			Distance=TIM2CH4_CAPTURE_STA&0X3F;
			Distance*=65536;					//溢出时间总和
			Distance+=TIM2CH4_CAPTURE_VAL;		//得到总的高电平时间
			Distance=Distance*170/1000;
			//printf("&d \r\n",Distance);
			TIM2CH4_CAPTURE_STA=0;			//开启下一次捕获
			
			Distance = 0.6*Distance + 0.4*Distance_prev;
			
		}				
}
 

此外,由于插入了超声波模块,小车如果转弯太快也会摔倒,因此转弯速率Turn_Amplitude我改为了600:

int Turn_Amplitude=600;

Movement前进后退速率我也相应的改为600了,别太快。。

if(1==Flag_Qian)    Movement=-600;                 //===如果前进标志位置1 位移为负
else if(1==Flag_Hou)      Movement=600;          //===如果后退标志位置1 位移为正
else  Movement=0;



视频中中万用板上做的是我的蓝牙控制器。不过请忽略板子上面的DIP引脚的IC。。。

板子的硬件上存在I2C  eeprom、还有MPU6050、rgb灯、MCU板子、摇杆、主从一体蓝牙模块。但是暂时有作用的只是将摇杆的结果划分为0x00、0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08这几个结果发送到蓝牙。




下面放出视频



视频地址:http://player.youku.com/player.php/sid/XMTI3NzAzNzA2OA==/v.swf






结论

本楼的实验在Mini Balance V2.5 顶配版源码(标准版基础上增加摄像头底层 超声波 NRF24L01)(简称顶配版V2.5)的基础上修改,原先的工程程序下载到小车内,小车只有在自平衡的时候才会考虑超声波测量的距离,而在蓝牙遥控小车的过程中,会完全忽略车前是否有障碍物、即完全失去了避障的效果。


本楼的代码解释了假如对顶配版V2.5做一些修改,就可以实现小车在收到手机遥控信息的时候,也可以实现小车的前进避障。实验结果放出了视频,显示有较好的结果。


菜鸟
2015-08-20 16:44:42     打赏
14楼

 [开发进程]实验十、另类走8

背景:

  上文通过对左右编码器采样出来的数据进行积分(distance = distance + (float)Encoder_Left*0.005;)得到了小车前进的距离。

其中distance是自己定义的小车前进距离。

那么,同样的道理,可以通过对左右编码器之差进行积分turning = turning + (float)(Encoder_Left-Encoder_Right)*0.005;,得到小车转弯的大概的角度。

  笔者试验室的地板是方块的,60cm*60cm,那么为什么不可以走方形的八字呢。这种八字就是数码管的8

 

原理:

本试验的原理很简单,要使车子走数码管8字,只需要使小车按照这个路线图走就可以了。


只是调试过程比较痛苦……



实验代码

这个实验基于<Mini Balance V2.5 标准版源码卡尔曼版本> 

修改了两个源文件。分别是show.c和MiniBalance.c

以下是这两个修改后的文件的代码:


//show.c
#include "show.h"
#include "MiniBalance.h"
/**************************************************************************
作者:平衡小车之家 
淘宝店铺:http://shop114407458.taobao.com/
**************************************************************************/
unsigned char i;          //计数变量
unsigned char Send_Count; //串口需要发送的数据个数
/**************************************************************************
函数功能:OLED显示
入口参数:无
返回  值:无
作    者:平衡小车之家
**************************************************************************/
static u32 Count;



void oled_show(void)
{
		int disSHow;
		int turSHow;
			
	
	  Count=0;
		OLED_Display_On();  //显示屏打开


		//turSHow = turning*1.087613;
		turSHow = turning;
		disSHow = -distance*2.176542;
			
						
			if(ThreeSecFlag==1)
			{
				
				if(disSHow<300)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<85)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;}
				else if(disSHow<900)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<175)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;}	
				else if(disSHow<1300)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<274)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;}	
				else if(disSHow<2000)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<373)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;}	
				else if(disSHow<2700)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<472)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;}	
				else if(disSHow<3300)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<540)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;}	
				else if(disSHow<3900)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<625)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;}	
				else if(disSHow<4400)
				{	Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;}
				else if(turSHow<715)
				{	Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;}	
				else						
					Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0;
				
				
			}

		if(disSHow<0)
		{
			OLED_ShowString(0,0,"Dist -");
			OLED_ShowNumber(45,0, (-disSHow),5,12);
		}
		else
		{
			OLED_ShowString(0,0,"Dist  ");
			OLED_ShowNumber(45,0, disSHow,5,12);
		}		
		
		if(turSHow<0)
		{
			OLED_ShowString(0,10,"Turn -");
			OLED_ShowNumber(45,10, (-turSHow),5,12);
		}
		else
		{
			OLED_ShowString(0,10,"Turn  ");
			OLED_ShowNumber(45,10, turSHow,5,12);
		}
		
		
		//=============显示温度=======================//	
		/*
		                      OLED_ShowString(00,10,"Wendu");
		                      OLED_ShowNumber(45,10,Temperature/10,2,12);
		                      OLED_ShowNumber(68,10,Temperature%10,1,12);
		                      OLED_ShowString(58,10,".");
		                      OLED_ShowString(80,10,"`C");
													*/
		//=============显示编码器1=======================//	
		                      OLED_ShowString(00,20,"Enco1");
		if( Encoder_Left<0)		OLED_ShowString(45,20,"-"),
		                      OLED_ShowNumber(65,20,-Encoder_Left,5,12);
		else                 	OLED_ShowString(45,20,"+"),
		                      OLED_ShowNumber(65,20, Encoder_Left,5,12);
  	//=============显示编码器2=======================//		
		                      OLED_ShowString(00,30,"Enco2");
		if(Encoder_Right<0)		OLED_ShowString(45,30,"-"),
		                      OLED_ShowNumber(65,30,-Encoder_Right,5,12);
		else               		OLED_ShowString(45,30,"+"),
		                      OLED_ShowNumber(65,30,Encoder_Right,5,12);	
		//=============显示电压=======================//
		                      OLED_ShowString(00,40,"Volta");
		                      OLED_ShowString(58,40,".");
		                      OLED_ShowString(80,40,"V");
		                      OLED_ShowNumber(45,40,Voltage/100,2,12);
		                      OLED_ShowNumber(68,40,Voltage%100,2,12);
		 if(Voltage%100<10) 	OLED_ShowNumber(62,40,0,2,12);
		//=============显示角度=======================//
		                      OLED_ShowString(0,50,"Angle");
		if(Angle_Balance<0)		OLED_ShowNumber(45,50,Angle_Balance+360,3,12);
		else					        OLED_ShowNumber(45,50,Angle_Balance,3,12);
		//=============刷新=======================//
		OLED_Refresh_Gram();	
	}
/**************************************************************************
函数功能:虚拟示波器往上位机发送数据 关闭显示屏
入口参数:无
返回  值:无
作    者:平衡小车之家
**************************************************************************/
void DataScope(void)
{   
	  if(++Count==1)
		{	
		OLED_Clear();  
		OLED_Display_Off();		
		}	
		DataScope_Get_Channel_Data( Angle_Balance, 1 );
		DataScope_Get_Channel_Data( Encoder_Right, 2 );
		DataScope_Get_Channel_Data( Encoder_Left, 3 ); 
		DataScope_Get_Channel_Data( Voltage , 4 );   
		DataScope_Get_Channel_Data(0, 5 ); //用您要显示的数据替换0就行了
		DataScope_Get_Channel_Data(0 , 6 );//用您要显示的数据替换0就行了
		DataScope_Get_Channel_Data(0, 7 );
		DataScope_Get_Channel_Data( 0, 8 ); 
		DataScope_Get_Channel_Data(0, 9 );  
		DataScope_Get_Channel_Data( 0 , 10);
		Send_Count = DataScope_Data_Generate(10);
		for( i = 0 ; i < Send_Count; i++) 
		{
		while((USART1->SR&0X40)==0);  
		USART1->DR = DataScope_OutPut_Buffer[i]; 
		}
		delay_ms(50); //20HZ
}

 




//MiniBalance.c
#include "MiniBalance.h"
#include "math.h"
#include "led.h"
#include "mpu6050.h"
#define PI 3.14159265
/**************************************************************************
作者:平衡小车之家 
淘宝店铺:http://shop114407458.taobao.com/
**************************************************************************/

/**************************************************************************
函数功能:5MS定时中断函数 5MS控制周期
入口参数:无
返回  值:无
作    者:平衡小车之家
**************************************************************************/
int Balance_Pwm,Velocity_Pwm,Turn_Pwm;
int FiveMSCount=0;
int ThreeSecFlag=0;

		
void TIM1_UP_TIM16_IRQHandler(void)  
{    
	
	

	
	if(TIM1->SR&0X0001)//5ms定时中断
	{   
			
			FiveMSCount++;
			if(FiveMSCount==600)
				ThreeSecFlag=1;
		
			if(ThreeSecFlag==1)
			{
			distance = distance + (float)Encoder_Left*0.005;
			
			if((Encoder_Left-Encoder_Right)>0)
				turning = turning + (float)(Encoder_Left-Encoder_Right)*0.005;
			else
				turning = turning + (float)(Encoder_Right-Encoder_Left)*0.005;
			}
			
		  TIM1->SR&=~(1<<0);                                       //===清除定时器1中断标志位		 
			readEncoder();                                           //===读取编码器的值
  		Led_Flash(400);                                          //===LED闪烁;	
  		Get_battery_volt();                                      //===获取电池电压	          
			key(100);                                                //===扫描按键状态
		  Get_Angle(Way_Angle);                                    //===更新姿态	
 			Balance_Pwm =balance(Angle_Balance,Gyro_Balance);        //===平衡PID控制	
 			Velocity_Pwm=velocity(Encoder_Left,Encoder_Right);       //===速度环PID控制
 	    Turn_Pwm    =turn(Encoder_Left,Encoder_Right,Gyro_Turn); //===转向环PID控制     
 		  Moto1=Balance_Pwm+Velocity_Pwm-Turn_Pwm;                 //===计算左轮电机最终PWM
 	  	Moto2=Balance_Pwm+Velocity_Pwm+Turn_Pwm;                 //===计算右轮电机最终PWM
   		Xianfu_Pwm();                                            //===PWM限幅
      if(Turn_Off(Angle_Balance,Voltage)==0)                   //===如果不存在异常
 			Set_Pwm(Moto1,Moto2);                                    //===赋值给PWM寄存器    		
	}       
} 

/**************************************************************************
函数功能:直立PD控制
入口参数:角度、角速度
返回  值:直立控制PWM
作    者:平衡小车之家
**************************************************************************/
int balance(float Angle,float Gyro)
{  
   float Bias;
	 int balance;
	 Bias=Angle+0;              //===求出平衡的角度中值 和机械相关 +0意味着身重中心在0度附近 如果身重中心在5度附近 那就应该减去5
	 balance=35*Bias+Gyro*0.125;//===计算平衡控制的电机PWM  PD控制 
	 return balance;
}

/**************************************************************************
函数功能:速度PI控制
入口参数:左轮编码器、右轮编码器
返回  值:速度控制PWM
作    者:平衡小车之家
**************************************************************************/
int velocity(int encoder_left,int encoder_right)
{  
	  static int Velocity,Encoder_Least,Encoder,Movement;
	  static int Encoder_Integral;
	  //=============遥控前进后退部分=======================//
		if(1==Flag_Qian)	Movement=-700;	             //===如果前进标志位置1 位移为负
		else if(1==Flag_Hou)	  Movement=700;          //===如果后退标志位置1 位移为正
	  else  Movement=0;	
   //=============速度PI控制器======================//	
		Encoder_Least =Encoder_Left+Encoder_Right;     //===获取最新速度偏差
		Encoder *= 0.8;		                             //===一阶低通滤波器       
		Encoder += Encoder_Least*0.2;	                 //===一阶低通滤波器    
	  if(Turn_Off(Angle_Balance,Voltage)==0)         //为了防止积分影响用户体验,只有电机开启的时候才开始积分
		{	
  	Encoder_Integral +=Encoder;                     //===积分出位移 积分时间:5ms
		Encoder_Integral=Encoder_Integral-Movement;     //===接收遥控器数据,控制前进后退
		}
		if(Encoder_Integral>360000)  	Encoder_Integral=360000;          //===积分限幅
		if(Encoder_Integral<-360000)	Encoder_Integral=-360000;         //===积分限幅	
		Velocity=Encoder*4+Encoder_Integral/140;                        //===速度PI控制器	
		if(Turn_Off(Angle_Balance,Voltage)==1)   Encoder_Integral=0;    //===电机关闭后清除积分
	  return Velocity;
}

/**************************************************************************
函数功能:转向PD控制
入口参数:左轮编码器、右轮编码器、Z轴陀螺仪
返回  值:转向控制PWM
作    者:平衡小车之家
**************************************************************************/
int turn(int encoder_left,int encoder_right,float gyro)//转向控制
{
  	static int Turn_Target,Turn,Encoder_temp,Turn_Convert=3,Turn_Count;
	  //int Turn_Bias,Turn_Amplitude=1500/Way_Angle+800;     //===Way_Angle为滤波方法,当是1时,即由DMP获取姿态,Turn_Amplitude取大,卡尔曼和互补是,取小,因为这两种滤波算法效果稍差。
	  int Turn_Bias,Turn_Amplitude=700;
	static long Turn_Bias_Integral;
	  //=============遥控左右旋转部分=======================//
  	if(1==Flag_Left||1==Flag_Right)                      //这一部分主要是根据旋转前的速度调整速度的起始速度,增加小车的适应性
		{
			if(++Turn_Count==1)
			Encoder_temp=myabs(encoder_left+encoder_right);
			Turn_Convert=2000/Encoder_temp;
			if(Turn_Convert<3)Turn_Convert=3;
			if(Turn_Convert>10)Turn_Convert=10;
		}	
	  else
		{
			Turn_Convert=3;
			Turn_Count=0;
			Encoder_temp=0;
		}			
		if(1==Flag_Left)	           Turn_Target+=Turn_Convert; //左转
		else if(1==Flag_Right)	     Turn_Target-=Turn_Convert; //右转
		else Turn_Target=0;                                     //停止
    if(Turn_Target>Turn_Amplitude)  Turn_Target=Turn_Amplitude;    //===转向速度限幅
	  if(Turn_Target<-Turn_Amplitude) Turn_Target=-Turn_Amplitude;
  	//=============转向PD控制器=======================//
		 Turn_Bias=Encoder_Left-Encoder_Right;         //===计算转向速度偏差  
		if(Turn_Off(Angle_Balance,Voltage)==0)         //为了防止积分影响用户体验,只有电机开启的时候才开始积分
		{	
		Turn_Bias_Integral+=Turn_Bias;                //转向速度偏差积分得到转向偏差
		Turn_Bias_Integral-=Turn_Target;              //获取遥控器数据
		}
		if(Turn_Bias_Integral>1800)  	Turn_Bias_Integral=1800;          //===积分限幅
		if(Turn_Bias_Integral<-1800)	Turn_Bias_Integral=-1800;         //===积分限幅	
	  Turn=Turn_Bias_Integral*2+gyro/12;                              //===结合Z轴陀螺仪进行PD控制
	  return Turn;
}

/**************************************************************************
函数功能:赋值给PWM寄存器
入口参数:左轮PWM、右轮PWM
返回  值:无
作    者:平衡小车之家
**************************************************************************/
void Set_Pwm(int moto1,int moto2)
{
			if(moto1<0)			AIN2=1,			AIN1=0;
			else 	          AIN2=0,			AIN1=1;
			PWMA=myabs(moto1);
		  if(moto2<0)	BIN1=0,			BIN2=1;
			else        BIN1=1,			BIN2=0;
			PWMB=myabs(moto2);	
}
/**************************************************************************
函数功能:读取编码器的数据并进行数据类型转换
入口参数:无
返回  值:无
作    者:平衡小车之家
**************************************************************************/
void readEncoder(void)
{
	  u16 Encoder_L,Encoder_R;       //===左右编码器的脉冲计数
		Encoder_R = TIM4 -> CNT;       //===获取正交解码1数据	
		TIM4 -> CNT=0;                 //===计数器清零  
	  Encoder_L= TIM2 -> CNT;        //===获取正交解码2数据	
	  TIM2 -> CNT=0;	               //===计数器清零
		if(Encoder_L>32768)  Encoder_Left=Encoder_L-65000; else  Encoder_Left=Encoder_L;  //===数据类型转换
  	Encoder_Left=-Encoder_Left;
	  if(Encoder_R>32768)  Encoder_Right=Encoder_R-65000; else  Encoder_Right=Encoder_R;//===数据类型转换
}

/**************************************************************************
函数功能:限制PWM赋值 
入口参数:无
返回  值:无
作    者:平衡小车之家
**************************************************************************/
void Xianfu_Pwm(void)
{	
	  int Amplitude=3500;    //===PWM满幅是3600 限制在3500
    if(Moto1<-Amplitude) Moto1=-Amplitude;	
		if(Moto1>Amplitude)  Moto1=Amplitude;	
	  if(Moto2<-Amplitude) Moto2=-Amplitude;	
		if(Moto2>Amplitude)  Moto2=Amplitude;		
	
}

/**************************************************************************
函数功能:异常关闭电机
入口参数:倾角和电压
返回  值:1:异常  0:正常
作    者:平衡小车之家
**************************************************************************/
u8 Turn_Off(float angle, int voltage)
{
	    u8 temp;
			if(angle<-40||angle>40||1==Flag_Stop||Voltage<1110)//===电压低于11.1V 关闭电机
			{	                                                 //===倾角大于40度关闭电机
      temp=1;                                            //===Flag_Stop置1关闭电机
			AIN1=0;                                            //===可自行增加主板温度过高时关闭电机
			AIN2=0;
			BIN1=0;
			BIN2=0;
      }
			else
      temp=0;
      return temp;			
}
	
/**************************************************************************
函数功能:获取角度
入口参数:获取角度的算法 1:无  2:卡尔曼 3:互补滤波
返回  值:无
作    者:平衡小车之家
**************************************************************************/
void Get_Angle(u8 way)
{ 
	    float Accel_Y,Accel_X,Accel_Z,Gyro_Y,Gyro_Z;
	    if(way==1)                                      //DMP没有涉及到严格的时序问题,在主函数读取
			{	
			}			
      else
      {
			Gyro_Y=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_L);    //读取Y轴陀螺仪
			Gyro_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_L);    //读取Z轴陀螺仪
		  Accel_X=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_L); //读取X轴加速度记
	  	Accel_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_L); //读取Z轴加速度记
		  if(Gyro_Y>32768)  Gyro_Y-=65536;     //数据类型转换
			if(Gyro_Z>32768)  Gyro_Z-=65536;     //数据类型转换
	  	if(Accel_X>32768) Accel_X-=65536;    //数据类型转换
		  if(Accel_Z>32768) Accel_Z-=65536;    //数据类型转换
			Gyro_Balance=-Gyro_Y;                                  //更新平衡角速度
	   	Accel_Y=atan2(Accel_X,Accel_Z)*180/PI;                 //计算与地面的夹角	
		  Gyro_Y=Gyro_Y/16.4;                                    //陀螺仪量程转换	
      if(Way_Angle==2)		  	Kalman_Filter(Accel_Y,-Gyro_Y);//卡尔曼滤波	
			else if(Way_Angle==3)   Yijielvbo(Accel_Y,-Gyro_Y);    //互补滤波
	    Angle_Balance=angle;                                   //更新平衡倾角
			Gyro_Turn=Gyro_Z;                                      //更新转向角速度
	  	}
}

 

而后在MiniBalance.h中放入extern int ThreeSecFlag;即可。



实验视频在审核,估计得在下一楼再 放出。。



菜鸟
2015-08-20 16:51:59     打赏
15楼

本贴试验十视频:

首先说明,暂时只能实现小车走1个8字。并不持续走。。



视频地址:http://player.youku.com/player.php/sid/XMTMxNDY3NTk4OA==/v.swf
ps第31楼中的代码,特别是参数,估计若在另外的平台上,要改变相应的数字。。欢迎大家讨论!

菜鸟
2015-09-07 16:59:42     打赏
16楼

我跟同事将平衡小车改为寻迹小车part1


我跟同事合作。他软件编程能力不足,有意将我的车子改装成寻迹小车;我不想改硬件。于是他负责将小车进行硬件改造,我做软件代码修改。。。


小车需要5个IO口进行红外模块输入,另外一个IO口作为车头撞车的检测(硬件中就只是一个按键输入电路)。









好在我的板子不配24L01无线模块,模块的接口IO我全部用来配置为输入。

模仿开源程序,

我加入了头文件,命名为trailing.h,

#ifndef __TRAILING_H
#define __TRAILING_H	 

#include "main.h"

#define trailingStrike PAin(6)
#define trailingFrontR PBin(4)
#define trailingFrontM PAin(5)
#define trailingFrontL PAin(7)
#define trailingLeft PAin(15)
#define trailingRight PBin(3)


void trailing_Init(void);

#endif

 

在main.h内加入#include "trailing.h"后,新建一个源文件trailing.c

//trailing.c





#include "main.h"

/*
#define trailingStrike PAin(6)
#define trailingFrontR PBin(4)		//front_right
#define trailingFrontM PAin(5)		//front_middle
#define trailingFrontL PAin(7)		//front_left
#define trailingLeft PAin(15)
#define trailingRight PBin(3)
*/

void trailing_Init(void)
{
	RCC->APB2ENR|=1<<3;    //使能PORTB时钟	   	 
	GPIOB->CRL&=0XFFF00FFF; 
	GPIOB->CRL|=0X00088000;//PB3 PB4 
	//GPIOB->ODR|=0<<3;
	GPIOB->ODR|=0<<4;
	
	RCC->APB2ENR|=1<<2;    //使能PORTA口时钟 
	GPIOA->CRL&=0X000FFFFF; 
	GPIOA->CRL|=0X88800000;//PA5 PA6 PA7 PA12 PA15
	//GPIOA->ODR|=0<<5;
	//GPIOA->ODR|=0<<6;
	//GPIOA->ODR|=0<<7;
	
	GPIOA->CRH&=0X0FF0FFFF; 
	GPIOA->CRH|=0X80080000;
	//GPIOA->ODR|=0<<12;
	GPIOA->ODR|=0<<15;
} 

 

以上就是简单的硬件介绍,以及GPIO的初始化。。



共16条 2/2 1 2 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]