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所示:
表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]);
}
//读取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次
{
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,所以转为读写地址后,为0X3B和0X3A(如果接GND,则为0XA7和0XA6)
#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 校准后