【连载】STM32开发指南--第三十四章 ADXL345三轴加速度传感器

[复制链接]
16117|22
手机看帖
扫描二维码
随时随地手机跟帖
正点原子|  楼主 | 2013-3-22 23:39 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2013-3-22 23:40 编辑

   第三十四章 三轴加速度传感器实验
自从有了Iphone,各种新技术的普及程度越来越快,人们喜欢的不再是摔不坏的诺基亚,而是用户体验极佳的Iphone。
本章,我们介绍一种当今智能手机普遍具有的传感器:加速度传感器。在手机上,这个功能可以用来:自动切换横竖屏、玩游戏和切歌等。ALIENTEK战舰STM32开发板自带了加速度传感器:ADXL345。本章我们将使用STM32来驱动ADXL345,读取3个方向的重力加速度值,并转换为角度,显示在TFTLCD模块上。本章分为如下几个部分:
34.1 ADXL345简介
34.2 硬件设计
34.3 软件设计
34.4 下载验证

34.1 ADXL345简介
ADXL345是ADI公司的一款3轴、数字输出的加速度传感器。ADXL345是ADI公司推出的基于iMEMS技术的3轴、数字输出加速度传感器。该加速度传感器的特点有:
l  分辨率高。最高13位分辨率。
l  量程可变。具有+/-2g,+/-4g,+/-8g,+/-16g可变的测量范围。
l  灵敏度高。最高达3.9mg/LSB,能测量不到1.0°的倾斜角度变化。
l  功耗低。40~145uA的超低功耗,待机模式只有0.1uA。
l  尺寸小。整个IC尺寸只有3mm*5mm*1mm,LGA封装。
ADXL支持标准的I2C或SPI数字接口,自带32级FIFO存储,并且内部有多种运动状态检测和灵活的中断方式等特性。ADXL345传感器的检测轴如图34.1.1所示:

34.1.1 ADXL345的三个检测轴
当ADXL345沿检测轴正向加速时,它对正加速度进行检测。在检测重力时用户需要注意,当检测轴的方向与重力的方向相反时检测到的是正加速度。图33.1.2所示为输出对重力的响应。


34.1.2 ADXL345输出对重力的响应
       图34.1.2列出了ADXL345在不同摆放方式时的输出,以便后续分析。接下来我们看看ADXL345的引脚图,如图34.1.3所示:


34.1.3 ADXL345引脚图
       ADXL345支持SPI和IIC两种通信方式,为了节省IO口,战舰STM32开发板采用的是IIC方式连接,官方推荐的IIC连接电路如图34.1.4所示:



34.1.4 ADXL345 IIC模式连接电路
       从上图可看出,ADXL345的连接十分简单,外围需要的器件也极少(就2个电容),如上连接(SDO/ALT ADDRESS接地),则ADXL345的地址为0X53(不含最低位),如果SDO/ALT ADDRESS接高,那么ADXL345的地址将变为0X1D(不含最低位)。IIC通信的时序我们在之前已经介绍过(第二十七章,IIC实验),这里就不再细说了。
       最后,我们介绍一下ADXL345的初始化步骤。ADXL345的初始化步骤如下:
1) 上电
2) 等待1.1ms
3) 初始化命令序列
4) 结束
其中上电这个动作发生在开发板第一次上电的时候,在上电之后,等待1.1ms左右,就可以开始发送初始化序列了,初始化序列一结束,ADXL345就开始正常工作了。这里的初始化序列,最简单的只需要配置3个寄存器,如表34.1.1所示:

步骤

寄存器地址

寄存器名字

寄存器值

功能描述

1

0X31

DATA_FORMAT

0X0B

±16g,13位模式

2

0X2D

POWER_CTL

0X08

测量模式

3

0X2E

INT_ENABLE

0X80

使能DATA_READY中断
表34.1.1 ADXL345最简单的初始化命令序列
       发送以上序列给ADXL345以后,ADXL345即开始正常工作。
       ADXL345我们就介绍到这里,详细的介绍,请参考ADXL345的数据手册。
34.2 硬件设计
本实验采用STM32的3个普通IO连接ADXL345,本章实验功能简介:主函数不停的查询ADXL345的转换结果,得到x、y和z三个方向的加速度值(读数值),然后将其转换为与自然系坐标的角度,并将结果在LCD模块上显示出来。DS0来指示程序正在运行,通过按下WK_UP按键,可以进行ADXL345的自动校准(DS1用于提示正在校准)。
所要用到的硬件资源如下:
1)  指示灯DS0、DS1
2)  WK_UP按键
3) TFTLCD模块
4)  ADXL345
    前3个,在之前的实例已经介绍过了,这里我们仅介绍ADXL345与战舰STM32开发板的连接。该接口与MCU的连接原理图如34.2.1所示:


                  图34.2.1 ADXL345与STM32的连接电路图
从上图可以看出,ADXL345通过三根线与STM32开发板连接,其中IIC总线时和24C02以及RDA5820共用,接在PB10和PB11上面。ADXL345的两个中断输出,这里我们只用了一个,连接在STM32的PF11脚,另外这里的地址线是接3.3V,所以ADXL345的地址是0X1D,转换为0X3A写入,0X3B读取。


34.3 软件设计
打开上一章的工程,首先在HARDWARE文件夹下新建一个ADXL345的文件夹。然后新建一个adxl345.c和adxl345.h的文件保存在JOYPAD文件夹下,并将这个文件夹加入头文件包含路径。
打开adxl345.c文件,输入如下代码:
#include "adxl345.h"
#include "sys.h"
#include "delay.h"
#include "math.h"   
//初始化ADXL345.
//返回值:0,初始化成功;1,初始化失败.
u8 ADXL345_Init(void)
{                           
       IIC_Init();                                                       //初始化IIC总线  
       if(ADXL345_RD_Reg(DEVICE_ID)==0XE5)    //读取器件ID
       {  
              ADXL345_WR_Reg(DATA_FORMAT,0X2B);   
//低电平中断输出,13位全分辨率,输出数据右对齐,16g量程
              ADXL345_WR_Reg(BW_RATE,0x0A);             //数据输出速度为100Hz
              ADXL345_WR_Reg(POWER_CTL,0x28);       //链接使能,测量模式
              ADXL345_WR_Reg(INT_ENABLE,0x00);        //不使用中断         
             ADXL345_WR_Reg(OFSX,0x00);
              ADXL345_WR_Reg(OFSY,0x00);
              ADXL345_WR_Reg(OFSZ,0x00);      
              return 0;
       }                  
       return 1;                                                         
}   
//写ADXL345寄存器
//addr:寄存器地址
//val:要写入的值
//返回值:无
void ADXL345_WR_Reg(u8 addr,u8 val)
{
       IIC_Start();                        
       IIC_Send_Byte(ADXL_WRITE);     //发送写器件指令
       IIC_Wait_Ack();      
    IIC_Send_Byte(addr);                      //发送寄存器地址
       IIC_Wait_Ack();                                                                                       
       IIC_Send_Byte(val);                       //发送值                                 
       IIC_Wait_Ack();                        
    IIC_Stop();                                        //产生一个停止条件      
}
//读ADXL345寄存器
//addr:寄存器地址
//返回值:读到的值
u8 ADXL345_RD_Reg(u8 addr)         
{
       u8 temp=0;            
       IIC_Start();                        
       IIC_Send_Byte(ADXL_WRITE);  //发送写器件指令  
       temp=IIC_Wait_Ack();     
    IIC_Send_Byte(addr);               //发送寄存器地址
       temp=IIC_Wait_Ack();                                                                                             
       IIC_Start();                    //重新启动
       IIC_Send_Byte(ADXL_READ);   //发送读器件指令  
       temp=IIC_Wait_Ack();     
    temp=IIC_Read_Byte(0);             //读取一个字节,不继续再读,发送NAK                  
    IIC_Stop();                                 //产生一个停止条件        
       return temp;                         //返回读到的值
}  
//读取ADXL的平均值
//x,y,z:读取10次后取平均值
void ADXL345_RD_Avval(short *x,short *y,short *z)
{
       short tx=0,ty=0,tz=0;        
       u8 i;  
       for(i=0;i<10;i++)
       {
              ADXL345_RD_XYZ(x,y,z);
              delay_ms(10);
              tx+=(short)*x; ty+=(short)*y; tz+=(short)*z;      
       }
       *x=tx/10; *y=ty/10; *z=tz/10;
}
//自动校准
//xval,yval,zval:x,y,z轴的校准值
void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval)
{
       short tx,ty,tz;
       u8 i;
       short offx=0,offy=0,offz=0;
       ADXL345_WR_Reg(POWER_CTL,0x00);       //先进入休眠模式.
       delay_ms(100);
       ADXL345_WR_Reg(DATA_FORMAT,0X2B);   
//低电平中断输出,13位全分辨率,输出数据右对齐,16g量程
       ADXL345_WR_Reg(BW_RATE,0x0A);             //数据输出速度为100Hz
       ADXL345_WR_Reg(POWER_CTL,0x28);       //链接使能,测量模式
       ADXL345_WR_Reg(INT_ENABLE,0x00);        //不使用中断                                     `             ADXL345_WR_Reg(OFSX,0x00);
       ADXL345_WR_Reg(OFSY,0x00);
       ADXL345_WR_Reg(OFSZ,0x00);
       delay_ms(12);
       for(i=0;i<10;i++)
       {
              ADXL345_RD_Avval(&tx,&ty,&tz);
              offx+=tx; offy+=ty; offz+=tz;
       }                 
       offx/=10; offy/=10; offz/=10;
       *xval=-offx/4; *yval=-offy/4; *zval=-(offz-256)/4;
      ADXL345_WR_Reg(OFSX,*xval);
       ADXL345_WR_Reg(OFSY,*yval);
       ADXL345_WR_Reg(OFSZ,*zval);      
}
//读取3个轴的数据
//x,y,z:读取到的数据
void ADXL345_RD_XYZ(short *x,short *y,short *z)
{
       u8 buf[6],i;
       IIC_Start();                        
       IIC_Send_Byte(ADXL_WRITE);  //发送写器件指令  
       IIC_Wait_Ack();      
    IIC_Send_Byte(0x32);               //发送寄存器地址(数据缓存的起始地址为0X32)
       IIC_Wait_Ack();           
      IIC_Start();                           //重新启动
       IIC_Send_Byte(ADXL_READ);   //发送读器件指令
       IIC_Wait_Ack();
       for(i=0;i<6;i++)
       {
              if(i==5)buf=IIC_Read_Byte(0);       //读取一个字节,不继续再读,发送NACK  
              else buf=IIC_Read_Byte(1);      //读取一个字节,继续读,发送ACK
      }                       
    IIC_Stop();                                        //产生一个停止条件
       *x=(short)(((u16)buf[1]<<8)+buf[0]);            
       *y=(short)(((u16)buf[3]<<8)+buf[2]);            
       *z=(short)(((u16)buf[5]<<8)+buf[4]);           
}

实验29 三轴加速度传感器实验.rar (153.39 KB)
正点原子|  楼主 | 2013-3-22 23:39 | 显示全部楼层
本帖最后由 正点原子 于 2013-3-22 23:41 编辑

//读取ADXL345的数据times,再取平均
//x,y,z:读到的数据
//times:读取多少次
void ADXL345_Read_Average(short *x,short *y,short *z,u8 times)
{
       u8 i;
       short tx,ty,tz;
       *x=0; *y=0; *z=0;
       if(times)//读取次数不为0
       {
              for(i=0;i<times;i++)//连续读取times
              {
                     ADXL345_RD_XYZ(&tx,&ty,&tz);
                     *x+=tx; *y+=ty; *z+=tz;
                     delay_ms(5);
              }
              *x/=times; *y/=times; *z/=times;
       }
}
//得到角度
//x,y,z:x,y,z方向的重力加速度分量(不需要单位,直接数值即可)
//dir:要获得的角度.0,Z轴的角度;1,X轴的角度;2,Y轴的角度.
//返回值:角度值.单位0.1°.
short ADXL345_Get_Angle(float x,float y,float z,u8 dir)
{
       float temp,res=0;
       switch(dir)
       {
              case 0://与自然Z轴的角度
                    temp=sqrt((x*x+y*y))/z;
                    res=atan(temp);
                    break;
              case 1://与自然X轴的角度
                    temp=x/sqrt((y*y+z*z));
                    res=atan(temp);
                    break;
             case 2://与自然Y轴的角度
                    temp=y/sqrt((x*x+z*z));
                    res=atan(temp);
                    break;
      }
       return res*1800/3.14;
}
该部分代码总共有8个函数,这里我们仅介绍其中4个。首先是ADXL345_Init函数,该函数用来初始化ADXL345,和前面我们提到的步骤差不多,不过本章我们而是采用查询的方式来读取数据的,所以在这里并没有开启中断。另外3个偏移寄存器,都默认设置为0。
其次,我们介绍ADXL345_RD_XYZ函数,该函数用于从ADXL345读取数据,通过该函数可以读取ADXL345的转换结果,得到三个轴的加速度值(仅是数值,并没有转换单位)。
接着,我们介绍ADXL345_AUTO_Adjust函数,该函数用于ADXL345的校准,ADXL345有偏移校准的功能,该功能的详细介绍请参考ADXL345数据手册的第29页,偏移校准部分。这里我们就不细说了,如果不进行校准的话,ADXL345的读数可能会有些偏差,通过校准,我们可以讲这个偏差减少甚至消除。
最后,我们看看ADXL345_Get_Angle函数,该函数根据ADXL345的读值,转换为与自然坐标系的角度。计算公式如下:


其中Ax,Ay,Az分别代表从ADXL345读到的X,Y,Z方向的加速度值。通过该函数,我们只需要知道三个方向的加速度值,就可以将其转换为对应的弧度值,再通过弧度角度转换,就可以得到角度值了。
其他函数,我们就不介绍了,也比较简单。保存adxl345.c,然后把该文件加入HARDWARE组下。接下来打开adxl345.h在该文件里面加入如下代码:
#ifndef __ADXL345_H
#define __ADXL345_H
#include "myiic.h"   
#define DEVICE_ID            0X00      //器件ID,0XE5
#define THRESH_TAP         0X1D          //敲击阀值
……省略部分寄存器定义
#define FIFO_STATUS         0X39

//0X0B TO OX1F Factory Reserved     
//如果ALT ADDRESS(12)接地,IIC地址为0X53(不包含最低位).
//如果接V3.3,IIC地址为0X1D(不包含最低位).
//开发板接V3.3,所以转为读写地址后,0X3B0X3A(如果接GND,则为0XA70XA6)  
#define ADXL_READ    0X3B
#define ADXL_WRITE   0X3A
u8 ADXL345_Init(void);                                                              //初始化ADXL345
void ADXL345_WR_Reg(u8 addr,u8 val);                                      //ADXL345寄存器
u8 ADXL345_RD_Reg(u8 addr);                                                   //ADXL345寄存器
void ADXL345_RD_XYZ(short *x,short *y,short *z);                      //读取一次值
void ADXL345_RD_Avval(short *x,short *y,short *z);                    //读取平均值
void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval);     //自动校准
void ADXL345_Read_Average(short *x,short *y,short *z,u8 times);//连续读取times,取平均
short ADXL345_Get_Angle(float x,float y,float z,u8 dir);
#endif
上面的代码省略了部分寄存器的定义,其他部分比较简单,我们不作介绍。保存adxl345.h,然后在test.c里面修改代码如下:
//x,y:开始显示的坐标位置
//num:要显示的数据
//mode:0,显示加速度值;1,显示角度值;
void Adxl_Show_Num(u16 x,u16 y,short num,u8 mode)
{
       if(mode==0)   //显示加速度值
       {
              if(num<0)
              {
                     LCD_ShowChar(x,y,'-',16,0);               //显示负号
                     num=-num;                                       //转为正数
              }else LCD_ShowChar(x,y,' ',16,0);                     //去掉负号
              LCD_ShowNum(x+8,y,num,4,16);                     //显示值            
      }else             //显示角度值
       {
              if(num<0)
              {
                     LCD_ShowChar(x,y,'-',16,0);               //显示负号
                     num=-num;                                       //转为正数
              }else LCD_ShowChar(x,y,' ',16,0);                     //去掉负号
             LCD_ShowNum(x+8,y,num/10,2,16);         //显示整数部分               
              LCD_ShowChar(x+24,y,'.',16,0);                //显示小数点
              LCD_ShowNum(x+32,y,num%10,1,16);     //显示小数部分               
       }
}                                                         
int main(void)
{            
       u8 key;
       u8 t=0;   
       short x,y,z;        
       short angx,angy,angz;      
      Stm32_Clock_Init(9);           //系统时钟设置
       uart_init(72,9600);             //串口初始化为9600
       delay_init(72);                         //延时初始化
       LED_Init();                        //初始化与LED连接的硬件接口
       LCD_Init();                       //初始化LCD
       usmart_dev.init(72);             //初始化USMART        
      KEY_Init();                         //按键初始化
       POINT_COLOR=RED;//设置字体为红色
       LCD_ShowString(60,50,200,16,16,"WarShip STM32");   
       LCD_ShowString(60,70,200,16,16,"3D TEST");
       LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
       LCD_ShowString(60,110,200,16,16,"2012/9/12");
      LCD_ShowString(60,130,200,16,16,"KEY0:Auto Adjust");
      while(ADXL345_Init()) //3D加速度传感器初始化   
       {
              LCD_ShowString(60,150,200,16,16,"ADXL345 Error");
              delay_ms(200);
              LCD_Fill(60,150,239,150+16,WHITE);
             delay_ms(200);
       }                                                         
       LCD_ShowString(60,150,200,16,16,"ADXL345 OK");
      LCD_ShowString(60,170,200,16,16,"X VAL:");
       LCD_ShowString(60,190,200,16,16,"Y VAL:");
       LCD_ShowString(60,210,200,16,16,"Z VAL:");
      LCD_ShowString(60,230,200,16,16,"X ANG:");
       LCD_ShowString(60,250,200,16,16,"Y ANG:");
       LCD_ShowString(60,270,200,16,16,"Z ANG:");
       POINT_COLOR=BLUE;//设置字体为红色
       while(1)
       {                     
             if(t%10==0)//100ms读取一次
              {
                     //得到X,Y,Z轴的加速度值(原始值)
                     ADXL345_Read_Average(&x,&y,&z,10);   //读取X,Y,Z三个方向的加速度值
                     Adxl_Show_Num(60+48,170,x,0);             //显示加速度原始值
                     Adxl_Show_Num(60+48,190,y,0);
                     Adxl_Show_Num(60+48,210,z,0);
                    //得到角度值,并显示
                     angx=ADXL345_Get_Angle(x,y,z,1);   
                     angy=ADXL345_Get_Angle(x,y,z,2);   
                     angz=ADXL345_Get_Angle(x,y,z,0);
                     Adxl_Show_Num(60+48,230,angx,1);        //显示角度值
                     Adxl_Show_Num(60+48,250,angy,1);
                     Adxl_Show_Num(60+48,270,angz,1);
              }
              key=KEY_Scan(0);
              if(key==KEY_UP)
              {
                     LED1=0;//绿灯亮,提示校准中
                     ADXL345_AUTO_Adjust((char*)&x,(char*)&y,(char*)&z);//自动校准
                     LED1=1;//绿灯灭,提示校准完成
              }
             delay_ms(10);
              t++;
              if(t==20)
              {
                     t=0;
                     LED0=!LED0;
              }
       }
}
此部分代码除了main函数,还有一个Adxl_Show_Num函数,该函数用于数据显示,因为在ILI93xx.c里面,没有提供可以显示小数和负数的函数,所以我们这里编写了该函数,来实现小数和负数的显示,以满足本章要求。
其他部分,我们就不多说了。至此,我们的软件设计部分就结束了。
34.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,可以看到LCD显示如图34.4.1所示的内容:


34.4.1 程序运行时LCD显示内容
可以看到,X方向和Z方向的角度有些大(最佳值是0),所以我们按下WK_UP键,进行一次校准(注意校准的时候保持开发板水平,并且稳定),校准后如图34.4.2所示:


34.4.2 校准后
可以看到,校准后,比未校准前好了很多,此时我们移动开发板到不同角度,可以看到XYZ的数值和角度也跟着变化。      

使用特权

评论回复
罪恶之城| | 2013-4-9 09:06 | 显示全部楼层
mini板可以用这个吗?

使用特权

评论回复
正点原子|  楼主 | 2013-4-9 23:22 | 显示全部楼层
罪恶之城 发表于 2013-4-9 09:06
mini板可以用这个吗?

mini板没有板载ADXL345,你自己做一个小板,也是可以用的。

使用特权

评论回复
与时俱进| | 2013-7-2 22:46 | 显示全部楼层
这个程序验证过吗?

使用特权

评论回复
正点原子|  楼主 | 2013-7-9 00:40 | 显示全部楼层
与时俱进 发表于 2013-7-2 22:46
这个程序验证过吗?

当然

使用特权

评论回复
outstanding| | 2013-10-18 20:44 | 显示全部楼层

使用特权

评论回复
一般首席| | 2013-10-20 14:46 | 显示全部楼层
没有弄明白INT1这个引脚 为什么要接,感觉程序中也没用到这个引脚

使用特权

评论回复
正点原子|  楼主 | 2013-11-2 12:39 | 显示全部楼层
一般首席 发表于 2013-10-20 14:46
没有弄明白INT1这个引脚 为什么要接,感觉程序中也没用到这个引脚

是没用到

使用特权

评论回复
yinhanghoudu| | 2013-11-17 22:33 | 显示全部楼层
跟随
正点原子

使用特权

评论回复
summer0729| | 2014-11-11 20:41 | 显示全部楼层
学习了

使用特权

评论回复
lpdengsh| | 2015-4-6 16:19 | 显示全部楼层
刚接触ADXL345,用的这个程序测试出X,Y轴的直接读出数据偏差有100多而Z轴的偏差有一千多,是硬件问题还是我的软件有问题了

使用特权

评论回复
小浣熊| | 2015-4-6 20:49 | 显示全部楼层
看看。。

使用特权

评论回复
小浣熊| | 2015-4-6 21:20 | 显示全部楼层
太受益了。。

使用特权

评论回复
lanjackg2003| | 2015-4-7 10:08 | 显示全部楼层
mark

使用特权

评论回复
miss_lion| | 2015-4-27 15:36 | 显示全部楼层

为什么器件的id一开始定为了0x00

使用特权

评论回复
正点原子|  楼主 | 2015-4-28 23:38 | 显示全部楼层
miss_lion 发表于 2015-4-27 15:36
为什么器件的id一开始定为了0x00

哪里?

使用特权

评论回复
yangwenguan| | 2015-4-29 08:06 | 显示全部楼层
好像adl345比mpu6050的精度和稳定性要低一点

使用特权

评论回复
臭臭博| | 2015-11-24 10:34 | 显示全部楼层
你好,看了受益匪浅,可还是有好多不懂,能加你QQ更加仔细的咨询你吗,

使用特权

评论回复
wxhzzz| | 2016-2-18 17:46 | 显示全部楼层
有没有关于测空间移动距离。。。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝