通过测量飞思卡公司生产的一系列加速度传感器芯片XYZ可以检测三轴的加速度等一系列运动状态。 我用14位精度。。单片机型号为。
通过读取获得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(); ///读取寄存器数据 MMA845_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 <math.h>
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<abs(z))
{
temp=temp/z;
res=atan(temp);
}
else
{
temp= z/temp;
res=atan(temp);
if(res>=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 <math.h>
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<abs(z))
{
temp=temp/z;
res=atan(temp);
}
else
{
temp= z/temp;
res=atan(temp);
if(res>=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例程。可以根据自己的外设环境改变配置。