锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

平衡小车总结

时间:2023-03-04 02:00:00 3055d1t加速度传感器

一、设计思路

使用stm32f103zet6作为主控芯片使用0.96OLED显示当前两个轮子的速度和俯仰角,模块初始化阶段显示各模块的初始化进度mpu6050获得当前位置的俯仰角度;使用L298N通过PWM控制电机速度;使用霍尔编码器测量角速,进一步计算实际速度;使用降压模块和12V向各模块和主控芯片供电。使用位置式PID与增量式PID系列控制平衡车静止直立。

二、模块选择

1.stm32f103zet6

图1.stm32f103zet6

2.0.96OLED

图2.0.96OLED

3.mpu6050

图3.mpu6050

4.L298N

图4.L298N

5.霍尔编码器减速电机

图5.霍尔编码器减速电机

6.降压模块

图6.降压模块

7.12V电源

图7.12V电源

三、硬件建设

图8.硬件构建1

图9.硬件构建2

如图8、图9所示,mpu6050与0.96oled插入顶部的面包板,主控芯片固定在面包板下,然后是降压模块和L298N固定在主控芯片下,降压模块下12V带编码盘的直流减速电机对称分布在孔板两侧,分别安装轮胎。

四、驱动编写

引脚接口:

TIM2__PA15 && PB3__left ——>PD4,5
TIM4__PD12 && PD13__right ——>PD6,7

MPU6050 I2C
PB10:IIC_SCL,PB11:IIC_SDA

OLED 4线SPI
D0:PA5(SCL),D1:PA7(SDA),RES :PB0 ,DC:PB1,CS:PA4

1.OLED

#include "oled.h" #include "stdlib.h" #include "oledfont.h"     #include "delay.h"  u8 OLED_GRAM[144][8];  ///反显函数 void OLED_ColorTurn(u8 i) {  if(i==0)   {    OLED_WR_Byte(0xA6,OLED_CMD);//正常显示   }  if(i==1)   {    OLED_WR_Byte(0xA7,OLED_CMD);//反色显示   } }  ///屏幕旋转180度 void OLED_DisplayTurn(u8 i) {  if(i==0)   {    OLED_WR_Byte(0xC8,OLED_CMD);//正常显示    OLED_WR_Byte(0xA1,OLED_CMD);   }  if(i==1)   {    OLED_WR_Byte(0xC0,OLED_CMD);//反转显示    OLED_WR_Byte(0xA0,OLED_CMD);   } }   void OLED_WR_Byte(u8 dat,u8 cmd) {   u8 i;       if(cmd)    OLED_DC_Set();  else    OLED_DC_Clr();  OLED_CS_Clr();  for(i=0;i<8;i  )  {   OLED_SCLK_Clr();   if(dat&0x80)      OLED_SDIN_Set();   else       OLED_SDIN_Clr();   OLED_SCLK_Set();   dat<<=1;     }           OLED_CS_Set();  OLED_DC_Set();       }  //开启OLED显示  void OLED_DisPlay_On(void) {  OLED_WR_Byte(0x8D,OLED_CMD);///电荷泵使能  OLED_WR_Byte(0x14,OLED_CMD);///打开电荷泵  OLED_WR_Byte(0xAF,OLED_CMD);///点亮屏幕 }  //关闭OLED显示  void OLED_DisPlay_Off(void) {  OLED_WR_Byte(0x8D,OLED_CMD);///电荷泵使能  OLED_WR_Byte(0x10,OLED_CMD);///关闭电荷泵  OLED_WR_Byte(0xAF,OLED_CMD);///关闭屏幕 }  ///更新显存到OLED  void OLED_Refresh(void) {  u8 i,n;  for(i=0;i<8;i  )  {     OLED_WR_Byte(0xb0 i,OLED_CMD); //设置起始地址     OLED_WR_Byte(0x00,OLED_CMD);   ///设置低列起始地址     OLED_WR_Byte(0x10,OLED_CMD);   ////设置高列起始地址     for(n=0;n<128;n  )    OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);   } } ///清屏函数 void OLED_Clear(void) {  u8 i,n;  for(i=0;i<8;i  )  {     for(n=0;n<128;n  )    {     OLED_GRAM[n][i]=0.///清除所有数据    }   }  OLED_Refresh();//更新显示 }  //画点  //x:0~127 //y:0~63 void OLED_DrawPoint(u8 x,u8 y) {  u8 i,m,n;  i=y/8;  m=y%8;  n=1<128)||(y1<0)||(y2>64)||(x1>x2)||(y1>y2))return;  if(x1==x2)    //画竖线  {    for(i=0;i<(y2-y1);i  )    {     OLED_DrawPoint(x1,y1 i);    }   }  else if(y1==y2)   //画横线  {    for(i=0;i<(x2-x1);i  )    {     OLED_DrawPoint(x1 i,y1);    }   }  else      //画斜线  {   k1=y2-y1;   k2=x2-x1;   k=k1*10/k2;   for(i=0;i<(x2-x1);i  )    {      OLED_DrawPoint(x1 i,y1 i*k/10);    }  } } //x,y:圆心坐标 //r:圆的半径 void OLED_DrawCircle(u8 x,u8 y,u8 r) {  int a, b,num;     a = 0;     b = r;     while(2 * b * b >= r * r)           {         OLED_DrawPoint(x   a,y - b);
        OLED_DrawPoint(x - a, y - b);
        OLED_DrawPoint(x - a, y + b);
        OLED_DrawPoint(x + a, y + b);
 
        OLED_DrawPoint(x + b, y + a);
        OLED_DrawPoint(x + b, y - a);
        OLED_DrawPoint(x - b, y - a);
        OLED_DrawPoint(x - b, y + a);
        
        a++;
        num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
        if(num > 0)
        {
            b--;
            a--;
        }
    }
}



//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size:选择字体 12/16/24
//取模方式 逐列式
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{
	u8 i,m,temp,size2,chr1;
	u8 y0=y;
	size2=(size1/8+((size1%8)?1:0))*(size1/2);  //得到字体一个字符对应点阵集所占的字节数
	chr1=chr-' ';  //计算偏移后的值
	for(i=0;i=' ')&&(*chr<='~'))//判断是不是非法字符!
	{
		OLED_ShowChar(x,y,*chr,size1);
		x+=size1/2;
		if(x>128-size1)  //换行
		{
			x=0;
			y+=2;
    }
		chr++;
  }
}

//m^n
u32 OLED_Pow(u8 m,u8 n)
{
	u32 result=1;
	while(n--)
	{
	  result*=m;
	}
	return result;
}

显示2个数字
x,y :起点坐标	 
len :数字的位数
size:字体大小
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1)
{
	u8 t,temp;
	for(t=0;t>=1;
								y++;
							}
							x++;
							if((x-x0)==size1)
							{x=x0;y0=y0+8;}
							y=y0;
			 }
	}
}

//num 显示汉字的个数
//space 每一遍显示的间隔
void OLED_ScrollDisplay(u8 num,u8 space)
{
	u8 i,n,t=0,m=0,r;
	while(1)
	{
		if(m==0)
		{
	    OLED_ShowChinese(128,24,t,16); //写入一个汉字保存在OLED_GRAM[][]数组中
			t++;
		}
		if(t==num)
			{
				for(r=0;r<16*space;r++)      //显示间隔
				 {
					for(i=0;i<144;i++)
						{
							for(n=0;n<8;n++)
							{
								OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
							}
						}
           OLED_Refresh();
				 }
        t=0;
      }
		m++;
		if(m==16){m=0;}
		for(i=0;i<144;i++)   //实现左移
		{
			for(n=0;n<8;n++)
			{
				OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
			}
		}
		OLED_Refresh();
	}
}

//配置写入数据的起始位置
void OLED_WR_BP(u8 x,u8 y)
{
	OLED_WR_Byte(0xb0+y,OLED_CMD);//设置行起始地址
	OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
	OLED_WR_Byte((x&0x0f),OLED_CMD);
}

//x0,y0:起点坐标
//x1,y1:终点坐标
//BMP[]:要写入的图片数组
void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[])
{
	u32 j=0;
	u8 x=0,y=0;
	if(y%8==0)y=0;
	else y+=1;
	for(y=y0;y

2.MPU6050

#include "mpu6050.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"   

/**********************************************
函数名称:MPU_Init
函数功能:初始化MPU6050
函数参数:无
函数返回值:0,初始化成功  其他,初始化失败
**********************************************/
u8 MPU_Init(void)
{ 
	u8 res;
	
	MPU_IIC_Init();			//初始化IIC总线
	MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80);	//复位MPU6050
  delay_ms(100);
	MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00);	//唤醒MPU6050 
	MPU_Set_Gyro_Fsr(3);										//陀螺仪传感器,±2000dps
	MPU_Set_Accel_Fsr(0);										//加速度传感器,±2g
	MPU_Set_Rate(200);												//设置采样率50Hz
	MPU_Write_Byte(MPU_INT_EN_REG,0X00);		//关闭所有中断
	MPU_Write_Byte(MPU_USER_CTRL_REG,0X00);	//I2C主模式关闭
	MPU_Write_Byte(MPU_FIFO_EN_REG,0X00);		//关闭FIFO
	MPU_Write_Byte(MPU_INTBP_CFG_REG,0X80);	//INT引脚低电平有效
	
	res=MPU_Read_Byte(MPU_DEVICE_ID_REG);
	if(res==MPU_ADDR)												//器件ID正确,即res = MPU_ADDR = 0x68
	{
		MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01);		//设置CLKSEL,PLL X轴为参考
		MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00);		//加速度与陀螺仪都工作
		MPU_Set_Rate(500);													//设置采样率为50Hz
 	}else return 1;    //地址设置错误,返回1
	return 0;					 //地址设置正确,返回0
}

/**********************************************
函数名称:MPU_Set_Gyro_Fsr
函数功能:设置MPU6050陀螺仪传感器满量程范围
函数参数:fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps
函数返回值:0,设置成功  其他,设置失败
**********************************************/
u8 MPU_Set_Gyro_Fsr(u8 fsr)
{
	return MPU_Write_Byte(MPU_GYRO_CFG_REG,fsr<<3); //设置陀螺仪满量程范围
}

/**********************************************
函数名称:MPU_Set_Accel_Fsr
函数功能:设置MPU6050加速度传感器满量程范围
函数参数:fsr:0,±2g;1,±4g;2,±8g;3,±16g
函数返回值:0,设置成功  其他,设置失败
**********************************************/
u8 MPU_Set_Accel_Fsr(u8 fsr)
{
	return MPU_Write_Byte(MPU_ACCEL_CFG_REG,fsr<<3); //设置加速度传感器满量程范围  
}

/**********************************************
函数名称:MPU_Set_LPF
函数功能:设置MPU6050的数字低通滤波器
函数参数:lpf:数字低通滤波频率(Hz)
函数返回值:0,设置成功  其他,设置失败
**********************************************/
u8 MPU_Set_LPF(u16 lpf)
{
	u8 data=0;
	
	if(lpf>=188)data=1;
	else if(lpf>=98)data=2;
	else if(lpf>=42)data=3;
	else if(lpf>=20)data=4;
	else if(lpf>=10)data=5;
	else data=6; 
	return MPU_Write_Byte(MPU_CFG_REG,data);//设置数字低通滤波器  
}

/**********************************************
函数名称:MPU_Set_Rate
函数功能:设置MPU6050的采样率(假定Fs=1KHz)
函数参数:rate:4~1000(Hz)  初始化中rate取50
函数返回值:0,设置成功  其他,设置失败
**********************************************/
u8 MPU_Set_Rate(u16 rate)
{
	u8 data;
	if(rate>1000)rate=1000;
	if(rate<4)rate=4;
	data=1000/rate-1;
	data=MPU_Write_Byte(MPU_SAMPLE_RATE_REG,data);	//设置数字低通滤波器
 	return MPU_Set_LPF(rate/2);											//自动设置LPF为采样率的一半
}

/**********************************************
函数名称:MPU_Get_Temperature
函数功能:得到温度传感器值
函数参数:无
函数返回值:温度值(扩大了100倍)
**********************************************/
short MPU_Get_Temperature(void)
{
   u8 buf[2]; 
   short raw;
	 float temp;
	
	 MPU_Read_Len(MPU_ADDR,MPU_TEMP_OUTH_REG,2,buf); 
   raw=((u16)buf[0]<<8)|buf[1];
   temp=36.53+((double)raw)/340;
   return temp*100;
}

/**********************************************
函数名称:MPU_Get_Gyroscope
函数功能:得到陀螺仪值(原始值)
函数参数:gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
函数返回值:0,读取成功  其他,读取失败
**********************************************/
u8 MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
  u8 buf[6],res;
	
	res=MPU_Read_Len(MPU_ADDR,MPU_GYRO_XOUTH_REG,6,buf);
	if(res==0)
	{
		*gx=((u16)buf[0]<<8)|buf[1];
		*gy=((u16)buf[2]<<8)|buf[3];
		*gz=((u16)buf[4]<<8)|buf[5];
	} 	
  return res;
}

/**********************************************
函数名称:MPU_Get_Accelerometer
函数功能:得到加速度值(原始值)
函数参数:ax,ay,az:加速度传感器x,y,z轴的原始读数(带符号)
函数返回值:0,读取成功  其他,读取失败
**********************************************/
u8 MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
    u8 buf[6],res;  
	res=MPU_Read_Len(MPU_ADDR,MPU_ACCEL_XOUTH_REG,6,buf);
	if(res==0)
	{
		*ax=((u16)buf[0]<<8)|buf[1];  
		*ay=((u16)buf[2]<<8)|buf[3];  
		*az=((u16)buf[4]<<8)|buf[5];
	} 	
    return res;
}

/**********************************************
函数名称:MPU_Write_Len
函数功能:IIC连续写(写器件地址、寄存器地址、数据)
函数参数:addr:器件地址      reg:寄存器地址
				 len:写入数据的长度  buf:数据区
函数返回值:0,写入成功  其他,写入失败
**********************************************/
u8 MPU_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
	u8 i;
	
	MPU_IIC_Start();
	MPU_IIC_Send_Byte((addr<<1)|0);      //发送器件地址+写命令(0为写,1为读)	
	if(MPU_IIC_Wait_Ack())							 //等待应答
	{
		MPU_IIC_Stop();
		return 1;
	}
    MPU_IIC_Send_Byte(reg);						 //写寄存器地址
    MPU_IIC_Wait_Ack();		             //等待应答
	for(i=0;i
#include "mpuiic.h"
#include "delay.h"
 
/**********************************************
函数名称:MPU_IIC_Delay
函数功能:MPU IIC延时函数,延时2ms
函数参数:无
函数返回值:无
**********************************************/
void MPU_IIC_Delay(void)
{
	delay_us(2);
}

/**********************************************
函数名称:MPU_IIC_Init
函数功能:MPU IIC初始化
函数参数:无
函数返回值:无
**********************************************/
void MPU_IIC_Init(void)
{					     
  GPIO_InitTypeDef  GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);			//先使能外设IO PORTB时钟 
		
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;	  //端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 				  //推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;				  //IO口速度为50MHz
  GPIO_Init(GPIOB, &GPIO_InitStructure);					 					//根据设定参数初始化GPIO 
	
  GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);						  //PB10,PB11 输出高	
 
}

/**********************************************
函数名称:MPU_IIC_Start
函数功能:MPU IIC发送起始信号
函数参数:无
函数返回值:无
**********************************************/
void MPU_IIC_Start(void)
{
	MPU_SDA_OUT();     //SDA线 输出
	MPU_IIC_SDA=1;	  	  
	MPU_IIC_SCL=1;
	MPU_IIC_Delay();
 	MPU_IIC_SDA=0;     //START:当SCL线处于高电平时,SDA线突然从高变低,发送起始信号
	MPU_IIC_Delay();
	MPU_IIC_SCL=0;		 //钳住I2C总线,准备发送或接收数据 
}

/**********************************************
函数名称:MPU_IIC_Stop
函数功能:MPU IIC发送停止信号
函数参数:无
函数返回值:无
**********************************************/
void MPU_IIC_Stop(void)
{
	MPU_SDA_OUT();		 //SDA线输出
	MPU_IIC_SCL=0;
	MPU_IIC_SDA=0;		 //STOP:当SCL线处于高电平时,SDA线突然从低变高,发送停止信号
 	MPU_IIC_Delay();
	MPU_IIC_SCL=1; 
	MPU_IIC_SDA=1;		 //发送I2C总线结束信号
	MPU_IIC_Delay();							   	
}

/**********************************************
函数名称:MPU_IIC_Wait_Ack
函数功能:MPU IIC等待信号到来
函数参数:无
函数返回值:1:接收应答信号成功  0:接收应答信号失败
**********************************************/
u8 MPU_IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	MPU_SDA_IN();  //SDA设置为输入  
	MPU_IIC_SDA=1;MPU_IIC_Delay();
	MPU_IIC_SCL=1;MPU_IIC_Delay();
	while(MPU_READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			MPU_IIC_Stop();
			return 1;
		}
	}
	MPU_IIC_SCL=0;//时钟输出0
	return 0;
} 

/**********************************************
函数名称:MPU_IIC_Ack
函数功能:MPU IIC产生应答信号
函数参数:无
函数返回值:无
**********************************************/
void MPU_IIC_Ack(void)
{
	MPU_IIC_SCL=0;
	MPU_SDA_OUT();
	MPU_IIC_SDA=0;
	MPU_IIC_Delay();
	MPU_IIC_SCL=1;
	MPU_IIC_Delay();
	MPU_IIC_SCL=0;
}

/**********************************************
函数名称:MPU_IIC_NAck
函数功能:MPU IIC不产生应答信号
函数参数:无
函数返回值:无
**********************************************/   
void MPU_IIC_NAck(void)
{
	MPU_IIC_SCL=0;
	MPU_SDA_OUT();
	MPU_IIC_SDA=1;
	MPU_IIC_Delay();
	MPU_IIC_SCL=1;
	MPU_IIC_Delay();
	MPU_IIC_SCL=0;
}

/**********************************************
函数名称:MPU_IIC_Send_Byte
函数功能:MPU IIC发送一个字节
函数参数:txd:要发送的数据
函数返回值:无
注意:IIC发送字节是一个一个位发送的,发送一个字节需要发送八次
**********************************************/
void MPU_IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
		MPU_SDA_OUT(); 	    
    MPU_IIC_SCL=0;		//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        MPU_IIC_SDA=(txd&0x80)>>7;
        txd<<=1; 	  
		    MPU_IIC_SCL=1;
		    MPU_IIC_Delay(); 
		    MPU_IIC_SCL=0;	
		    MPU_IIC_Delay();
    }	 
} 	    

/**********************************************
函数名称:MPU_IIC_Read_Byte
函数功能:MPU IIC读取一个字节
函数参数:ack: 1,发送ACK   0,发送NACK 
函数返回值:接收到的数据
注意:IIC读取字节是一个一个位读取的,读取一个字节需要读取八次
**********************************************/ 
u8 MPU_IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
  MPU_SDA_IN();              //SDA设置为输入
    for(i=0;i<8;i++)
	  {
        MPU_IIC_SCL=0;
        MPU_IIC_Delay();
				MPU_IIC_SCL=1;
        receive<<=1;
        if(MPU_READ_SDA)receive++;   //如果读到了数据
				MPU_IIC_Delay(); 
    }					 
    if (!ack)
        MPU_IIC_NAck();   //发送nACK
    else
        MPU_IIC_Ack();    //发送ACK   
    return receive;
}
//
//添加的代码部分 
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK精英STM32开发板V3
//MPU6050 DMP 驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2015/1/17
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
// 

//q30格式,long转float时的除数.
#define q30  1073741824.0f

//陀螺仪方向设置
static signed char gyro_orientation[9] = { 1, 0, 0,
                                           0, 1, 0,
                                           0, 0, 1};
//MPU6050自测试
//返回值:0,正常
//    其他,失败
u8 run_self_test(void)
{
	int result;
	//char test_packet[4] = {0};
	long gyro[3], accel[3]; 
	result = mpu_run_self_test(gyro, accel);
	if (result == 0x3) 
	{
		/* Test passed. We can trust the gyro data here, so let's push it down
		* to the DMP.
		*/
		float sens;
		unsigned short accel_sens;
		mpu_get_gyro_sens(&sens);
		gyro[0] = (long)(gyro[0] * sens);
		gyro[1] = (long)(gyro[1] * sens);
		gyro[2] = (long)(gyro[2] * sens);
		dmp_set_gyro_bias(gyro);
		mpu_get_accel_sens(&accel_sens);
		accel_sens = 0;      //设置为基础值
		accel[0] *= accel_sens;
		accel[1] *= accel_sens;
		accel[2] *= accel_sens;
		dmp_set_accel_bias(accel);
		return 0;
	}else return 1;
}
//陀螺仪方向控制
unsigned short inv_orientation_matrix_to_scalar(
    const signed char *mtx)
{
    unsigned short scalar; 
    /*
       XYZ  010_001_000 Identity Matrix
       XZY  001_010_000
       YXZ  010_000_001
       YZX  000_010_001
       ZXY  001_000_010
       ZYX  000_001_010
     */

    scalar = inv_row_2_scale(mtx);
    scalar |= inv_row_2_scale(mtx + 3) << 3;
    scalar |= inv_row_2_scale(mtx + 6) << 6;


    return scalar;
}
//方向转换
unsigned short inv_row_2_scale(const signed char *row)
{
    unsigned short b;

    if (row[0] > 0)
        b = 0;
    else if (row[0] < 0)
        b = 4;
    else if (row[1] > 0)
        b = 1;
    else if (row[1] < 0)
        b = 5;
    else if (row[2] > 0)
        b = 2;
    else if (row[2] < 0)
        b = 6;
    else
        b = 7;      // error
    return b;
}
//空函数,未用到.
void mget_ms(unsigned long *time)
{

}
//mpu6050,dmp初始化
//返回值:0,正常
//    其他,失败
u8 mpu_dmp_init(void)
{
	u8 res=0;
	MPU_IIC_Init(); 	//初始化IIC总线
	if(mpu_init()==0)	//初始化MPU6050
	{	 
		res=mpu_set_sensors(INV_XYZ_GYRO|INV_XYZ_ACCEL);//设置所需要的传感器
		if(res)return 1; 
		res=mpu_configure_fifo(INV_XYZ_GYRO|INV_XYZ_ACCEL);//设置FIFO
		if(res)return 2; 
		res=mpu_set_sample_rate(DEFAULT_MPU_HZ);	//设置采样率
		if(res)return 3; 
		res=dmp_load_motion_driver_firmware();		//加载dmp固件
		if(res)return 4; 
		res=dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));//设置陀螺仪方向
		if(res)return 5; 
		res=dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT|DMP_FEATURE_TAP|	//设置dmp功能
		    DMP_FEATURE_ANDROID_ORIENT|DMP_FEATURE_SEND_RAW_ACCEL|DMP_FEATURE_SEND_CAL_GYRO|
		    DMP_FEATURE_GYRO_CAL);
		if(res)return 6; 
		res=dmp_set_fifo_rate(DEFAULT_MPU_HZ);	//设置DMP输出速率(最大不超过200Hz)
		if(res)return 7;   
		res=run_self_test();		//自检
		if(res)return 8;    
		res=mpu_set_dmp_state(1);	//使能DMP
		if(res)return 9;     
	}else return 10;
	return 0;
}
//得到dmp处理后的数据(注意,本函数需要比较多堆栈,局部变量有点多)
//pitch:俯仰角 精度:0.1°   范围:-90.0° <---> +90.0°
//roll:横滚角  精度:0.1°   范围:-180.0°<---> +180.0°
//yaw:航向角   精度:0.1°   范围:-180.0°<---> +180.0°
//返回值:0,正常
//    其他,失败
u8 mpu_dmp_get_data(float *pitch,float *roll,float *yaw)
{
	float q0=1.0f,q1=0.0f,q2=0.0f,q3=0.0f;
	unsigned long sensor_timestamp;
	short gyro[3], accel[3], sensors;
	unsigned char more;
	long quat[4]; 
	if(dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors,&more))return 1;	 
	/* Gyro and accel data are written to the FIFO by the DMP in chip frame and hardware units.
	 * This behavior is convenient because it keeps the gyro and accel outputs of dmp_read_fifo and mpu_read_fifo consistent.
	**/
	/*if (sensors & INV_XYZ_GYRO )
	send_packet(PACKET_TYPE_GYRO, gyro);
	if (sensors & INV_XYZ_ACCEL)
	send_packet(PACKET_TYPE_ACCEL, accel); */
	/* Unlike gyro and accel, quaternions are written to the FIFO in the body frame, q30.
	 * The orientation is set by the scalar passed to dmp_set_orientation during initialization. 
	**/
	if(sensors&INV_WXYZ_QUAT) 
	{
		q0 = quat[0] / q30;	//q30格式转换为浮点数
		q1 = quat[1] / q30;
		q2 = quat[2] / q30;
		q3 = quat[3] / q30; 
		//计算得到俯仰角/横滚角/航向角
		*pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;	// pitch
		*roll  = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3;	// roll
		*yaw   = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;	//yaw
	}else return 2;
	return 0;
}
/**
 *  @brief      Get one packet from the FIFO.
 *  If @e sensors does not contain a particular sensor, disregard the data
 *  returned to that pointer.
 *  \n @e sensors can contain a combination of the following flags:
 *  \n INV_X_GYRO, INV_Y_GYRO, INV_Z_GYRO
 *  \n INV_XYZ_GYRO
 *  \n INV_XYZ_ACCEL
 *  \n INV_WXYZ_QUAT
 *  \n If the FIFO has no new data, @e sensors will be zero.
 *  \n If the FIFO is disabled, @e sensors will be zero and this function will
 *  return a non-zero error code.
 *  @param[out] gyro        Gyro data in hardware units.
 *  @param[out] accel       Accel data in hardware units.
 *  @param[out] quat        3-axis quaternion data in hardware units.
 *  @param[out] timestamp   Timestamp in milliseconds.
 *  @param[out] sensors     Mask of sensors read from FIFO.
 *  @param[out] more        Number of remaining packets.
 *  @return     0 if successful.
 */
int dmp_read_fifo(short *gyro, short *accel, long *quat,
    unsigned long *timestamp, short *sensors, unsigned char *more)
{
    unsigned char fifo_data[MAX_PACKET_LENGTH];
    unsigned char ii = 0;

    /* TODO: sensors[0] only changes when dmp_enable_feature is called. We can
     * cache this value and save some cycles.
     */
    sensors[0] = 0;

    /* Get a packet. */
    if (mpu_read_fifo_stream(dmp.packet_length, fifo_data, more))
        return -1;

    /* Parse DMP packet. */
    if (dmp.feature_mask & (DMP_FEATURE_LP_QUAT | DMP_FEATURE_6X_LP_QUAT)) {
#ifdef FIFO_CORRUPTION_CHECK
        long quat_q14[4], quat_mag_sq;
#endif
        quat[0] = ((long)fifo_data[0] << 24) | ((long)fifo_data[1] << 16) |
            ((long)fifo_data[2] << 8) | fifo_data[3];
        quat[1] = ((long)fifo_data[4] << 24) | ((long)fifo_data[5] << 16) |
            ((long)fifo_data[6] << 8) | fifo_data[7];
        quat[2] = ((long)fifo_data[8] << 24) | ((long)fifo_data[9] << 16) |
            ((long)fifo_data[10] << 8) | fifo_data[11];
        quat[3] = ((long)fifo_data[12] << 24) | ((long)fifo_data[13] << 16) |
            ((long)fifo_data[14] << 8) | fifo_data[15];
        ii += 16;
#ifdef FIFO_CORRUPTION_CHECK
        /* We can detect a corrupted FIFO by monitoring the quaternion data and
         * ensuring that the magnitude is always normalized to one. This
         * shouldn't happen in normal operation, but if an I2C error occurs,
         * the FIFO reads might become misaligned.
         *
         * Let's start by scaling down the quaternion data to avoid long long
         * math.
         */
        quat_q14[0] = quat[0] >> 16;
        quat_q14[1] = quat[1] >> 16;
        quat_q14[2] = quat[2] >> 16;
        quat_q14[3] = quat[3] >> 16;
        quat_mag_sq = quat_q14[0] * quat_q14[0] + quat_q14[1] * quat_q14[1] +
            quat_q14[2] * quat_q14[2] + quat_q14[3] * quat_q14[3];
        if ((quat_mag_sq < QUAT_MAG_SQ_MIN) ||
            (quat_mag_sq > QUAT_MAG_SQ_MAX)) {
            /* Quaternion is outside of the acceptable threshold. */
            mpu_reset_fifo();
            sensors[0] = 0;
            return -1;
        }
        sensors[0] |= INV_WXYZ_QUAT;
#endif
    }

    if (dmp.feature_mask & DMP_FEATURE_SEND_RAW_ACCEL) {
        accel[0] = ((short)fifo_data[ii+0] << 8) | fifo_data[ii+1];
        accel[1] = ((short)fifo_data[ii+2] << 8) | fifo_data[ii+3];
        accel[2] = ((short)fifo_data[ii+4] << 8) | fifo_data[ii+5];
        ii += 6;
        sensors[0] |= INV_XYZ_ACCEL;
    }

    if (dmp.feature_mask & DMP_FEATURE_SEND_ANY_GYRO) {
        gyro[0] = ((short)fifo_data[ii+0] << 8) | fifo_data[ii+1];
        gyro[1] = ((short)fifo_data[ii+2] << 8) | fifo_data[ii+3];
        gyro[2] = ((short)fifo_data[ii+4] << 8) | fifo_data[ii+5];
        ii += 6;
        sensors[0] |= INV_XYZ_GYRO;
    }

    /* Gesture data is at the end of the DMP packet. Parse it and call
     * the gesture callbacks (if registered).
     */
    if (dmp.feature_mask & (DMP_FEATURE_TAP | DMP_FEATURE_ANDROID_ORIENT))
        decode_gesture(fifo_data + ii);

    get_ms(timestamp);
    return 0;
}

/**
 *  @brief      Register a function to be executed on a tap event.
 *  The tap direction is represented by one of the following:
 *  \n TAP_X_UP
 *  \n TAP_X_DOWN
 *  \n TAP_Y_UP
 *  \n TAP_Y_DOWN
 *  \n TAP_Z_UP
 *  \n TAP_Z_DOWN
 *  @param[in]  func    Callback function.
 *  @return     0 if successful.
 */
int dmp_register_tap_cb(void (*func)(unsigned char, unsigned char))
{
    dmp.tap_cb = func;
    return 0;
}

/**
 *  @brief      Register a function to be executed on a android orientation event.
 *  @param[in]  func    Callback function.
 *  @return     0 if successful.
 */
int dmp_register_android_orient_cb(void (*func)(unsigned char))
{
    dmp.android_orient_cb = func;
    return 0;
}

/**
 *  @}
 */

3.霍尔编码器

#include "encoder.h"
#include "stm32f10x_gpio.h"
/****************************************************************
函数功能:把TIM2初始化为编码器接口模式 PA15, PB3
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM2(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
	TIM_ICInitTypeDef TIM_ICInitStructure;  
	GPIO_InitTypeDef GPIO_InitStructure;
//使能定时器4的时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//使能PA, PB端口时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	//部分重映射开启AFIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;	//端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);		//初始化GPIOA
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;	//端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);		//初始化GPIOB
	
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
//设定计数器自动重载值
	TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; 
//选择时钟分频:不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM向上计数
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, 
	TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 10;
	TIM_ICInit(TIM2, &TIM_ICInitStructure);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	//Reset counter
	TIM_SetCounter(TIM2,0);
	TIM_Cmd(TIM2, ENABLE); 
}
/****************************************************************
函数功能:把TIM4初始化为编码器接口模式 PD12, PD13
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM4(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
	TIM_ICInitTypeDef TIM_ICInitStructure;  
	GPIO_InitTypeDef GPIO_InitStructure;
	//使能定时器4的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	//使能PD端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
		//部分重映射开启AFIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);


	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13;	//端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOD, &GPIO_InitStructure);		//初始化GPIOD
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
	//设定计数器自动重载值
	TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; 
	//选择时钟分频:不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//TIM向上计数
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
	TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,
	TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 10;
	TIM_ICInit(TIM4, &TIM_ICInitStructure);
	TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除TIM的更新标志位
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	TIM_SetCounter(TIM4,0);//Reset counter
	TIM_Cmd(TIM4, ENABLE); 
}
/*****************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回值:速度值
*****************************************************************/
int Read_Encoder(u8 TIMX)
{
	int Encoder_TIM;    
	switch(TIMX)
	{
		case 2:  Encoder_TIM= (short)TIM2 -> CNT;  
						 TIM2 -> CNT=0;
						 break;
		case 4:  Encoder_TIM= (short)TIM4 -> CNT;
						 TIM4 -> CNT=0;
						 break;	
		default:  Encoder_TIM=0;
	}
	return Encoder_TIM;
}

4.电机控制

#include	"motor.h"

void TIM3_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
		//TIM1 Full remapping pins 
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
	//TIM3 CH1,2重映射PC6,7
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6|GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		    // 复用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}


void TIM3_Mode_Config(void) 	
{ 
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	/* ----------------------------------------------------------------------- 
    TIMx Channel1 duty cycle = (TIMx_CCR1/ TIMx_ARR+1)* 100% = 50%
  ----------------------------------------------------------------------- */
	//PWM频率f=TIMxCLK/{(TIM_Period+1)*(TIM_Prescaler+1)}=72M/{(999+1)*(71+1)}=1KHZ;
  //Time base configuration 		 
  TIM_TimeBaseStructure.TIM_Period = 999;       //当定时器从0计数到999,即为1000次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Prescaler = 0;	    //设置预分频:不预分频,即为72MHz
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;	//设置时钟分频系数:不分频(这里用不到)
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

  //PWM1 Mode configuration: Channel1 
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;	    //配置为PWM模式1
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  //当定时器计数值小于CCR1_Val时为高电平
	
  TIM_OC1Init(TIM3, &TIM_OCInitStructure);	 //使能通道1
  TIM_OC2Init(TIM3, &TIM_OCInitStructure);	  //使能通道2
	
	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
  TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);


  //TIM3 enable counter 
  TIM_Cmd(TIM3, ENABLE);                   //使能定时器3	
}

void GPIO_Mode_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
	
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		    // 推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOD, GPIO_Pin_4);
	GPIO_ResetBits(GPIOD, GPIO_Pin_5);
	GPIO_ResetBits(GPIOD, GPIO_Pin_6);
	GPIO_ResetBits(GPIOD, GPIO_Pin_7);
}

void TIM_PWM_Init(void)
{
	TIM3_GPIO_Config();
	TIM3_Mode_Config();	
	GPIO_Mode_Init();
}

五、PID算法

1.简介

        所谓 PID 控制,就是对系统偏差进行比例、积分以及微分的控制。PID 是闭环控制,因此需要有传感器测量我们需要控制的参数,并且反馈到我们的控制计算当中,并且参与控制。PID 由 3 个单元组成,分别是比例(Proportion)单元、积分(Integral) 单元、微分(Differential)单元。通过对这三个单元的处理计算输出给执行器,达到减小偏差最终实现收敛的过程。

2.位置式PID

        位置闭环控制就是根据编码器的脉冲累加测量电机的位置信息,并与目标值进行比较,得到控制偏差,然后通过对偏差的比例、积分、微分进行控制,使偏差趋向于零的过程。

float Position_PID (float Encoder,float Target) 
{ 
	static float Bias,Pwm,Integral_bias,Last_Bias; 
	Bias=Target- Encoder; //计算偏差 
	Integral_bias+=Bias; //求出偏差的积分 
	Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias); 
	Last_Bias=Bias; //保存上一次偏差	
//	printf("%f\r\n",Pwm);
	if (Pwm >= 999) Pwm = 999;
  else if (Pwm <= -999) Pwm = -999;	
	if (Pwm >= 0) return 999 - Pwm;
	else return -999 - Pwm;
}
        入口参数为编码器的位置测量值和位置控制的目标值,返回值为电机控制PWM(现在再看一下上面的控制框图是不是更加容易明白了)
        第一行是相关内部变量的定义。
        第二行是求出位置偏差,由测量值减去目标值。
        第三行通过累加求出偏差的积分。
        第四行使用位置式 PID 控制器求出电机 PWM
        第五行保存上一次偏差,便于下次调用。
        最后一行是返回。

3.增量式PID

        速度闭环控制就是根据单位时间获取的脉冲数(这里使用了 M 法测速)测量电机的速度信息,并与目标值进行比较,得到控制偏差,然后通过对偏差的比例、积分、微分进行控制,使偏差趋向于零的过程。
float Incremental_PI (int Encoder,int Target) 
{ static float Bias,Pwm,Last_bias; 
	Bias=Encoder-Target; //计算偏差 
	Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias; //增量式 PI 控制器 
	Last_bias=Bias; //保存上一次偏差
//	printf("%f\r\n",Pwm);	
	return Pwm; //增量输出 
}
        入口参数为编码器的速度测量值和速度控制的目标值,返回值为电机控制 PWM
        第一行是相关内部变量的定义。
        第二行是求出速度偏差,由测量值减去目标值。
        第三行使用增量 PI 控制器求出电机 PWM
        第四行保存上一次偏差,便于下次调用。
        最后一行是返回。

4.串级PID

        所谓串级 PID 就是使用两PID 控制器进行串联工作,外环控制器的输出作为内环控制器的输入,由内环控制器的输出去控制执行器

float Position_KP = 0.1;
float Position_KI = 0;
float Position_KD = 5;

float Velocity_KP = 0;
float Velocity_KI = 0;

float Position_PID (float Encoder,float Target) { 
	static float Bias,Pwm,Integral_bias,Last_Bias; 
	Bias=Target- Encoder; //计算偏差 
	Integral_bias+=Bias; //求出偏差的积分 
	Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias); 
	Last_Bias=Bias; //保存上一次偏差	
//	printf("%f\r\n",Pwm);
	if (Pwm >= 999) Pwm = 999;
  else if (Pwm <= -999) Pwm = -999;	
	if (Pwm >= 0) return 999 - Pwm;
	else return -999 - Pwm;
}
float Incremental_PI (int Encoder,int Target) 
{ static float Bias,Pwm,Last_bias; 
	Bias=Encoder-Target; //计算偏差 
	Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias; //增量式 PI 控制器 
	Last_bias=Bias; //保存上一次偏差
//	printf("%f\r\n",Pwm);	
	return Pwm; //增量输出 
}

六、心得体会

        在编码器驱动编写的时候遇到TIM2和TIM4通道1、2引脚占用问题,当使用通道3、4时编码器无法正常工作,在将TIM2和TIM4的通道完全重映射后正常工作。我们使用的这个mpu6050对输入供电要求非常严格,必须是3.3V否则就会初始化不成功,这点导致后面调试PID参数的时候非常耗费时间,可以通过购买某原子的mpu6050解决,就是贵一些,推荐这种解决方案。

        本来以为stm32学的差不多了,常用的都知道了,结果拿到编码器的时候去查阅资料,一直在讲定时器的编码器接口功能,才发现定时器还有专门为编码器准备的一个功能,很震惊,所以在以后的学习中要认真仔细一点。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章