MMA845xQ系列是飞思卡公司出品的一系列加速度传感器芯片,通过测量XYZ三轴的加速度可以检测倾角,自由落体,翻转,脉冲,振动等一系列运动状态。
我用的是14位精度的MMA8451Q。单片机型号为STC8A8K64。
通过读取倾角寄存器获得X,Y,Z三个方向的重力加速度向量值,根据三角函数公式,计算传感器与自然坐标轴的夹角。
另外初步测试了偏差校正功能。
I2C读写模块 MMA8451.c/MMA8451.h
MMA8451.h
#ifndef __MMA8451_H
#define __MMA8451_H
extern unsigned char BUF[6];
void Init_MMA8451();
void Multiple_read_MMA8451();
unsigned char Single_Read_MMA8452(unsigned char REG_Address);
void Single_Write_MMA8451(unsigned char REG_Address,unsigned char REG_data);
#endif
MMA8451.c
#include "MMA8451.h"
#include "intrins.h"
#include "config.h"
#define uchar unsigned char
#define uint unsigned int
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long int LWORD;
sbit SCL=P1^1; //IIC时钟引脚定义 根据自己线路板连接
sbit SDA=P1^0; //IIC数据引脚定义
#define SlaveAddress 0x38
BYTE BUF[6]; //数据区
/**************************************
延时5微秒(STC90C52RC@12M)
不同的工作环境,需要调整此函数,注意时钟过快时需要修改
当改用1T的MCU时,请调整此延时函数
**************************************/
void Delay5us()
{
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
}
/**************************************
延时5毫秒(STC90C52RC@12M)
不同的工作环境,需要调整此函数
当改用1T的MCU时,请调整此延时函数
**************************************/
void Delay5ms()
{
WORD n = 560;
while (n--);
}
/**************************************
起始信号
**************************************/
void MMA8451_Start()
{
SDA = 1; //拉高数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 0; //产生下降沿
Delay5us(); //延时
SCL = 0; //拉低时钟线
}
/**************************************
停止信号
**************************************/
void MMA8451_Stop()
{
SDA = 0; //拉低数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 1; //产生上升沿
Delay5us(); //延时
}
/**************************************
发送应答信号
入口参数:ack (0:ACK 1:NAK)
**************************************/
void MMA8451_SendACK(bit ack)
{
SDA = ack; //写应答信号
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
/**************************************
接收应答信号
**************************************/
bit MMA8451_RecvACK()
{
SCL = 1; //拉高时钟线
Delay5us(); //延时
CY = SDA; //读应答信号
SCL = 0; //拉低时钟线
Delay5us(); //延时
return CY;
}
/**************************************
向IIC总线发送一个字节数据
**************************************/
void MMA8451_SendByte(BYTE dat)
{
BYTE i;
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1; //移出数据的最高位
SDA = CY; //送数据口
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
MMA8451_RecvACK();
}
/**************************************
从IIC总线接收一个字节数据
**************************************/
BYTE MMA8451_RecvByte()
{
BYTE i;
BYTE dat = 0;
SDA = 1; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL = 1; //拉高时钟线
Delay5us(); //延时
dat |= SDA; //读数据
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
return dat;
}
//******单字节写入*******************************************
void Single_Write_MMA8451(uchar REG_Address,uchar REG_data)//2a 01
{
MMA8451_Start(); //起始信号
MMA8451_SendByte(SlaveAddress); //发送设备地址+写信号
MMA8451_SendByte(REG_Address); //内部寄存器地址
MMA8451_SendByte(REG_data); //内部寄存器数据
MMA8451_Stop(); //发送停止信号
}
//********单字节读取*****************************************
uchar Single_Read_MMA8452(uchar REG_Address)
{ uchar REG_data;
MMA8451_Start(); //起始信号
MMA8451_SendByte(SlaveAddress); //发送设备地址+写信号
MMA8451_SendByte(REG_Address); //发送存储单元地址,从0开始
MMA8451_Start(); //起始信号
MMA8451_SendByte(SlaveAddress+1); //发送设备地址+读信号
REG_data=MMA8451_RecvByte(); //读出寄存器数据
MMA8451_SendACK(1);
MMA8451_Stop(); //停止信号
return REG_data;
}
//*********************************************************
//
//连续读出MMA8451内部加速度数据,地址范围0x01~0x06
//
//*********************************************************
void Multiple_read_MMA8451()//Multiple_Read_MMA8451();
{ uchar i;
MMA8451_Start(); //起始信号
MMA8451_SendByte(SlaveAddress); //发送设备地址+写信号
MMA8451_SendByte(0x01); //发送存储单元地址,从0x01开始
MMA8451_Start(); //起始信号
MMA8451_SendByte(SlaveAddress+1); //发送设备地址+读信号
for (i=0; i<6; i++) //连续读取6个地址数据,存储中BUF
{
BUF[i] = MMA8451_RecvByte(); //BUF[0]存储0x32地址中的数据
if (i == 5)
{
MMA8451_SendACK(1); //最后一个数据需要回NOACK
}
else
{
MMA8451_SendACK(0); //回应ACK
}
}
MMA8451_Stop(); //停止信号
Delay5ms();
}
//*****************************************************************
//初始化MMA8451,根据需要请参考pdf进行修改************************
void Init_MMA8451()
{
Single_Write_MMA8451(0x2a,0x01); //
Single_Write_MMA8451(0x2b,0x02); //
}
V1:获取倾角 计算atan值
main.c
/*********************************************
P10 数据口 P11时钟口
P0,P2,接数码管
倾角寄存器0x01-0x06
读取数据为重力加速度在传感器坐标轴上分量
通过计算过程转换为自然XYZ坐标轴夹角tan值
当tan值在极值(无穷大)一侧时,改用cot值计算
***********************************************/
#include "config.h"
//#include "MPU6050.h"
#include "MMA8451.h"
#include "display.h"
#include
void delay_ms(u8 ms);
int xa,ya,za;
//*********************************************************************
//****************角度计算*********************************************
//*********************************************************************
int MMA8451_Get_Angle(long x,long y,long z,u8 dir);
void MMA8451_Add_Angle2(int x,int y,int z);
void MMA8451_Get_Angle3(int x,int y,int z);
// ===================== 主函数 =====================
void main(void)
{
int x,y,z;
//所有I/O口全设为准双向,弱上拉模式
P0M0=0xff; P0M1=0xff;
P1M0=0xff; P1M1=0xff;
P2M0=0xff; P2M1=0xff;
P3M0=0xff; P3M1=0xff;
P4M0=0xff; P4M1=0xff;
P5M0=0xff; P5M1=0xff;
P6M0=0xff; P6M1=0xff;
P7M0=0xff; P7M1=0xff;
//P1=0x00;
P1M0=0xfc; P1M1=0xfc; //时钟口1.1 数据口1.0 11111100 11111100
P0 = 0xFF;
P0M0=0x00; P0M1=0x00; //数码管数据通道 00000000 00000000 常规
P2 = 0xFF;
P2M0=0x00; P2M1=0x00; //数码管位选通道 00000000 00000000 常规
initLedDisplay();
delay_ms(100);
Init_MMA8451(); //初始化MPU-6050
delay_ms(100);
EA = 1; //允许总中断
while(1)
{
// Init_MMA8451();
// delay_ms(100);
delay_ms(100);
Multiple_read_MMA8451();
x=(unsigned int)BUF[0]<<8|BUF[1];
y=(unsigned int)BUF[2]<<8|BUF[3];
z=(unsigned int)BUF[4]<<8|BUF[5];
//hexstr[7]=(BUF[0]&0xf0)>>4;
//hexstr[6]=(BUF[0]&0x0f);
//hexstr[5]=16;
//hexstr[4]=(BUF[2]&0xf0)>>4;
//hexstr[3]=(BUF[2]&0x0f);
//hexstr[2]=18;
//hexstr[1]=(BUF[4]&0xf0)>>4;
//hexstr[0]=(BUF[4]&0x0f);
//hexstr[1]=(BUF[5]&0xf0)>>4;
//hexstr[0]=(BUF[5]&0x0f);
// x=(char)BUF[0];
// y=(char)BUF[2];
// z=(char)BUF[4];
xa=MMA8451_Get_Angle(x,y,z,1);
ya=MMA8451_Get_Angle(x,y,z,2);
za=MMA8451_Get_Angle(x,y,z,0);
hexstr[7]=ya/1000;
// if(ya<0)
// hexstr[7]=16,ya=-ya;
// else
// hexstr[7]=17;
hexstr[6]=(ya%1000)/100;
hexstr[5]=(ya%100)/10;
hexstr[4]=ya%10;
hexstr[3]=xa/1000;
// if(za<0)
// hexstr[3]=16,za=-za;
// else
// hexstr[3]=17;
hexstr[2]=(xa%1000)/100;
hexstr[1]=(xa%100)/10;
hexstr[0]=xa%10;
showLedDisplay();
delay_ms(3000);
}
}
int abs_round(double v){
return (int)(v>=0?(v+0.5):(v-0.5));
}
void MMA8451_Get_Angle3(int x,int y,int z)
{
double temp,xd,yd,zd;
temp = sqrt((double)(x*x+y*y+z*z));
yd = y/temp;
if(yd<=0.707 && yd>=-0.707){
yd = asin(yd);
ya = yd*180/3.1416+0.5;
}else{
yd = acos(yd);
ya = yd*180/3.1416-0.5;
ya=90-ya;
}
zd = z/temp;
if(zd<=0.707 && zd>=-0.707){
zd = asin(zd);
za = zd*180/3.1416+0.5;
}else{
zd = acos(zd);
za = zd*180/3.1416-0.5;
za=90-za;
}
xd = x/temp;
if(xd<=0.707 && xd>=-0.707){
xd = asin(xd);
xa = xd*180/3.1416+0.5;
}else{
xd = acos(xd);
xa = xd*180/3.1416-0.5;
xa=90-xa;
}
}
void MMA8451_Add_Angle2(int x,int y,int z)
{
double temp,xd,yd,zd;
temp = sqrt((double)(x*x+y*y+z*z));
zd = asin(z/temp)*180/3.1416; //与自然Z轴的角度
xd = asin(x/temp)*180/3.1416; //与自然X轴的角度
yd = asin(y/temp)*180/3.1416; //与自然Y轴的角度
za += (int)(zd+0.5);
ya += (int)(yd+0.5);
xa += (int)(xd+0.5);
}
int MMA8451_Get_Angle(long x,long y,long z,u8 dir)
{
double temp;
double res=0;
switch(dir)
{
case 0://与自然Z轴的角度
temp = sqrt((double)(x*x+y*y));
if(temp=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
res=atan(temp);
break;
case 1://与自然X轴的角度
temp = sqrt((double)(y*y+z*z));
if(temp>=abs(x))
{
temp=x/temp;
res=atan(temp);
}
else
{
temp= temp/x;
res=atan(temp);
if(res>=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
break;
case 2://与自然Y轴的角度
temp = sqrt((double)(x*x+z*z));
if(temp>=abs(y))
{
temp=y/temp;
res=atan(temp);
}
else
{
temp= temp/y;
res=atan(temp);
if(res>=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
break;
}
// if(res<0)
res = 1.5708+res;
return res*1800/3.1416;//把弧度转换成角度
}
void delay_ms(u8 ms)
{
u16 i;
do
{
i = MAIN_Fosc / 13000;
while(--i) ; //13T per loop
}while(--ms);
}
V2:加上偏差校正,可抵消因机械误差引起的14度以内的微小角度偏移。
main.c
/*********************************************
P10 数据口 P11时钟口
P0,P2,接数码管
P30 P31 P32 P33接触点开关
P30 清零校准寄存器重新计算校正值
P31 获取当下倾角值
P32 清零校准计算器
P33 将最近一次计算的校正值写到校准寄存器
***********************************************/
#include "config.h"
//#include "MPU6050.h"
#include "MMA8451.h"
#include "display.h"
#include
int MMA8451_Get_Angle(long x,long y,long z,u8 dir);
void displayAngle();
/* ============================================= */
void delay_ms(u8 ms);
unsigned char hexstr_cp[8];
int xa,ya,za;
// ===================== 主函数 =====================
void main(void)
{
unsigned char reg;
unsigned int ux,uy,uz;
int sx,sy,sz;
//所有I/O口全设为准双向,弱上拉模式
P0M0=0xff; P0M1=0xff;
P1M0=0xff; P1M1=0xff;
P2M0=0xff; P2M1=0xff;
P3M0=0xff; P3M1=0xff;
P4M0=0xff; P4M1=0xff;
P5M0=0xff; P5M1=0xff;
P6M0=0xff; P6M1=0xff;
P7M0=0xff; P7M1=0xff;
//P1=0x00;
P1M0=0xfc; P1M1=0xfc; //时钟口1.1 数据口1.0 11111100 11111100
P0 = 0xFF;
P0M0=0x00; P0M1=0x00; //数码管数据通道 00000000 00000000 常规
P2 = 0xFF;
P2M0=0x00; P2M1=0x00; //数码管位选通道 00000000 00000000 常规
P2 = 0xFF;
P3M0=0x00; P3M1=0x00;
initLedDisplay();
delay_ms(100);
Init_MMA8451(); //初始化MPU-6050
delay_ms(100);
EA = 1; //允许总中断
while(1)
{
// Init_MMA8451();
// delay_ms(100);
if(!P30){
delay_ms(10);
if(!P30){
while(!P30);
showdot = 0;
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg &= 0xFE;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
Single_Write_MMA8451(0x2F,0x00);
Single_Write_MMA8451(0x30,0x00);
Single_Write_MMA8451(0x31,0x00);
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg |= 0x01;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
Multiple_read_MMA8451();
/*
hexstr[7]=(BUF[0]&0xf0)>>4;
hexstr[6]=(BUF[0]&0x0f);
hexstr[5]=(BUF[1]&0xf0)>>4;
hexstr[4]=(BUF[1]&0x0f);
hexstr[3]=(BUF[2]&0xf0)>>4;
hexstr[2]=(BUF[2]&0x0f);
hexstr[1]=(BUF[3]&0xf0)>>4;
hexstr[0]=(BUF[3]&0x0f);
*/
displayAngle();
showLedDisplay();
hexstr_cp[0]=hexstr[0];
hexstr_cp[1]=hexstr[1];
hexstr_cp[2]=hexstr[2];
hexstr_cp[3]=hexstr[3];
hexstr_cp[4]=hexstr[4];
hexstr_cp[5]=hexstr[5];
hexstr_cp[6]=hexstr[6];
hexstr_cp[7]=hexstr[7];
ux=((unsigned int)BUF[0]<<8)|BUF[1];
uy=((unsigned int)BUF[2]<<8)|BUF[3];
uz=((unsigned int)BUF[4]<<8)|BUF[5];
/*
if(BUF[0]>0x7f){
ux=(~ux+1)>>2;
sx=ux/8;
}else{
sx=((~ux+1)>>2)/8;
}
if(BUF[2]>0x7f){
uy=(~uy+1)>>2;
sy=uy/8;
}else{
sy=((~uy+1)>>2)/8;
}
if(BUF[4]>0x7f){
uz=(~uz+1)>>2;
sz=(4069+uz)/8;
}else{
sz=(int)(4069-(uz>>2))/8;
if(sz<0){
sz+=256;
}
}
*/
if(BUF[0]>0x7f){
ux=(~ux+1)>>2;
sx=ux/8;
}else{
sx=~(ux>>2)+1;
sx=sx/8;
}
if(BUF[2]>0x7f){
uz=(~uz+1)>>2;
sz=uz/8;
}else{
sz=~(uz>>2)+1;
sz=sz/8;
}
if(BUF[4]>0x7f){
uy=(~uy+1)>>2;
sy=(4069+uy)/8;
}else{
sy=~(uy>>2)+1;
sy=(4069+sy)/8;
}
/*
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg &= 0xFE;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
Single_Write_MMA8451(0x2F,(char)sx);
Single_Write_MMA8451(0x30,(char)sy);
Single_Write_MMA8451(0x31,(char)sz);
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg |= 0x01;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
reg = Single_Read_MMA8452(0x2F);
hexstr[7]=(reg&0xf0)>>4;
hexstr[6]=(reg&0x0f);
hexstr[5]=0;
reg = Single_Read_MMA8452(0x30);
hexstr[4]=(reg&0xf0)>>4;
hexstr[3]=(reg&0x0f);
hexstr[2]=18;
reg = Single_Read_MMA8452(0x31);
hexstr[1]=(reg&0xf0)>>4;
hexstr[0]=(reg&0x0f);
showLedDisplay();
*/
delay_ms(3000);
}
}
if(!P31){
delay_ms(10);
if(!P31){
while(!P31);
delay_ms(100);
Multiple_read_MMA8451();
/*
hexstr[7]=(BUF[2]&0xf0)>>4;
hexstr[6]=(BUF[2]&0x0f);
hexstr[5]=(BUF[3]&0xf0)>>4;
hexstr[4]=(BUF[3]&0x0f);
hexstr[3]=(BUF[4]&0xf0)>>4;
hexstr[2]=(BUF[4]&0x0f);
hexstr[1]=(BUF[5]&0xf0)>>4;
hexstr[0]=(BUF[5]&0x0f);
*/
displayAngle();
showLedDisplay();
delay_ms(3000);
}
}
if(!P32){
delay_ms(10);
if(!P32){
while(!P32);
showdot = 0;
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg &= 0xFE;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
Single_Write_MMA8451(0x2F,0x00);
Single_Write_MMA8451(0x30,0x00);
Single_Write_MMA8451(0x31,0x00);
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg |= 0x01;
Single_Write_MMA8451(0x2A, reg);
/*
delay_ms(100);
reg = Single_Read_MMA8452(0x2F);
hexstr[7]=(reg&0xf0)>>4;
hexstr[6]=(reg&0x0f);
hexstr[5]=0;
reg = Single_Read_MMA8452(0x30);
hexstr[4]=(reg&0xf0)>>4;
hexstr[3]=(reg&0x0f);
hexstr[2]=16;
reg = Single_Read_MMA8452(0x31);
hexstr[1]=(reg&0xf0)>>4;
hexstr[0]=(reg&0x0f);
*/
hexstr[0]=0;
hexstr[1]=0;
hexstr[2]=0;
hexstr[3]=0;
hexstr[4]=0;
hexstr[5]=0;
hexstr[6]=0;
hexstr[7]=0;
showLedDisplay();
delay_ms(3000);
}
}
if(!P33){
delay_ms(10);
if(!P33){
while(!P33);
showdot = 1;
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg &= 0xFE;
Single_Write_MMA8451(0x2A, reg);
delay_ms(100);
Single_Write_MMA8451(0x2F,0x00);
Single_Write_MMA8451(0x30,0x00);
Single_Write_MMA8451(0x31,0x00);
delay_ms(100);
Single_Write_MMA8451(0x2F,(char)sx);
Single_Write_MMA8451(0x30,(char)sy);
Single_Write_MMA8451(0x31,(char)sz);
delay_ms(100);
reg = Single_Read_MMA8452(0x2A);
reg |= 0x01;
Single_Write_MMA8451(0x2A, reg);
/*
delay_ms(100);
reg = Single_Read_MMA8452(0x2F);
hexstr[7]=(reg&0xf0)>>4;
hexstr[6]=(reg&0x0f);
hexstr[5]=0;
reg = Single_Read_MMA8452(0x30);
hexstr[4]=(reg&0xf0)>>4;
hexstr[3]=(reg&0x0f);
hexstr[2]=17;
reg = Single_Read_MMA8452(0x31);
hexstr[1]=(reg&0xf0)>>4;
hexstr[0]=(reg&0x0f);
*/
hexstr[0]=hexstr_cp[0];
hexstr[1]=hexstr_cp[1];
hexstr[2]=hexstr_cp[2];
hexstr[3]=hexstr_cp[3];
hexstr[4]=hexstr_cp[4];
hexstr[5]=hexstr_cp[5];
hexstr[6]=hexstr_cp[6];
hexstr[7]=hexstr_cp[7];
showLedDisplay();
delay_ms(3000);
}
}
}
}
void displayAngle(){
int x,y,z;
x=(unsigned int)BUF[0]<<8|BUF[1];
y=(unsigned int)BUF[2]<<8|BUF[3];
z=(unsigned int)BUF[4]<<8|BUF[5];
xa=MMA8451_Get_Angle(x,y,z,1);
ya=MMA8451_Get_Angle(x,y,z,2);
za=MMA8451_Get_Angle(x,y,z,0);
//hexstr[7]=ya/1000;
if(ya<0)
hexstr[7]=16,ya=-ya;
else
hexstr[7]=17;
hexstr[6]=(ya%1000)/100;
hexstr[5]=(ya%100)/10;
hexstr[4]=ya%10;
//hexstr[3]=xa/1000;
if(xa<0)
hexstr[3]=16,xa=-xa;
else
hexstr[3]=17;
hexstr[2]=(xa%1000)/100;
hexstr[1]=(xa%100)/10;
hexstr[0]=xa%10;
/*
//hexstr[3]=za/1000;
if(za<0)
hexstr[3]=16,za=-za;
else
hexstr[3]=17;
hexstr[2]=(za%1000)/100;
hexstr[1]=(za%100)/10;
hexstr[0]=za%10;
*/
}
int MMA8451_Get_Angle(long x,long y,long z,u8 dir)
{
double temp;
double res=0;
switch(dir)
{
case 0://与自然Z轴的角度
temp = sqrt((double)(x*x+y*y));
if(temp=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
res=atan(temp);
break;
case 1://与自然X轴的角度
temp = sqrt((double)(y*y+z*z));
if(temp>=abs(x))
{
temp=x/temp;
res=atan(temp);
}
else
{
temp= temp/x;
res=atan(temp);
if(res>=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
break;
case 2://与自然Y轴的角度
temp = sqrt((double)(x*x+z*z));
if(temp>=abs(y))
{
temp=y/temp;
res=atan(temp);
}
else
{
temp= temp/y;
res=atan(temp);
if(res>=0)
res = 1.5708 - res;
else
res = - 1.5708 - res;
}
break;
}
// if(res<0)
//res = 1.5708+res;
return res*1800/3.1416;//把弧度转换成角度
}
void delay_ms(u8 ms)
{
u16 i;
do
{
i = MAIN_Fosc / 13000;
while(--i) ; //13T per loop
}while(--ms);
}
PS:display.c是数码管显示模块,config.h来自STC例程。可以根据自己的外设环境改变配置。