MPU6050
MPU6050是一个运动处理传感器,其内部集成了3轴加速度传感器和3轴陀螺仪(角速度传感器),以及一个可扩展数字运动处理器
MPU6050主要参数
可测量X、Y、Z轴三个方向的角速度
可编程设置角速度测量范围为±250、±500、±1000、±2000°/sec
可测量X、Y、Z轴三个方向的加速度
可编程设置加速度测量范围为±2g、±4g、±8g、±16g(g = 9.8m/s^2)数值也低精度比较高
可编程设置低功耗模式
可编程设置采样频率
… …
MPU6050通信接口
MPU6050可以使用IIC总线和其他器件进行数据交互,我们可以使用IIC总线向MPU6050中的控制寄存器写入数据来设置MPU6050的工作参数也可以使用IIC总线从MPU6050中的数据寄存器读取数据来获取加速度、角速度等信息
MPU6050内部常用寄存器地址
/****************MPU6050内部常用寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000°/s)
#define ACCEL_CONFIG 0x1C //加速计自检及测量范围及高通滤波频率,典型值:0x0(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define SlaveAddress 0x68 //MPU6050-I2C地址
向MPU6050的一个寄存器写一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(要写的寄存器的地址)
- 从机发送应答信号
- 主机发送一个字节数据(要写到寄存器的数据)
- 从机发送应答信号
- 主机发送停止信号
从MPU6050的一个寄存器读一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(要写的寄存器的地址)
- 从机发送应答信号
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(读)
- 从机(MPU6050)发送应答信号
- 从机发送一个字节数据(要读的寄存器中的数据)
- 主机发送非应答信号(不再接收更多的数据)
- 主机发送停止信号
IIC编程下例子
陀螺仪实验:
实时监测开发板的放置状态,当监测到开发板水平放置时,每隔一分钟向终端上打印一次当前的时间以及开发板的状态
如:“2023-04-05 23:45:00 Status: Normal”
当监测到开发板发生倾斜时,每隔一秒钟向终端上打印一次当前的时间以及开发板的状态 如:“2023-04-05 23:45:00 Status:
Warning” 同时让蜂鸣器产生“滴滴”的警报声,在警报状态下,若按下Key2按键,解除蜂鸣器的警报声 提示:
开发板水平静止放置时MPU6050的Z轴上的加速度应该等于重力加速度的值(9.8m/s2),而其X轴和Y轴上的加速度应该等于0
当开发板发生倾斜时MPU6050的Z轴上的加速度的分量会减小,而其X轴和Y轴上的加速度分量会增大 我们可以以此来判断开发板是否发生倾斜
I2C_SCL5
GPB_2 / GPB_3
对时钟源进行512倍预分频 打开IIC中断
按着这个流程图来设计编程的,主机向从机写数据
设置IIC模式为主机发送模式 使能IIC发送和接收
将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)
设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收
从机接收完一个字节以后,中断挂起位自动置1,每次接收一个字节以后都要判断是否为1
主机向从机将要发送的第二个字节数据
清除中断挂起标志位 开始下一个字节的发送
代码实现:
interface.c
#include "exynos_4412.h"
/****************MPU6050内部寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x18(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress 0x68 //MPU6050-I2C地址
/************************延时函数************************/
void mydelay_ms(int time)
{
int i,j;
while(time--)
{
for(i=0;i<5;i++)
for(j=0;j<514;j++);
}
}
/**********************************************************************
* 函数功能:I2C向特定地址写一个字节
* 输入参数:
* slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* data:写入的数据
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第三个字节数据(即要写入到MPU6050内部指定的寄存器中的数据)写入发送寄存器*/
I2C5.I2CDS = data;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*发送停止信号 结束本次通信*/
I2C5.I2CSTAT = 0xD0;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时*/
mydelay_ms(10);
}
/**********************************************************************
* 函数功能:I2C从特定地址读取1个字节的数据
* 输入参数: slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* 返回参数: unsigned char: 读取的数值
**********************************************************************/
unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
unsigned char data = 0;
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即要读取的MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*清除中断挂起标志位 重新开始一次通信 改变数据传送方向*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+读位1)*/
I2C5.I2CDS = slave_addr << 1 | 0x01;
/*设置IIC为主机接收模式 发送起始信号 使能IIC收发*/
I2C5.I2CSTAT = 0xb0;
/*等待从机接收到数据后应答*/
while(!(I2C5.I2CCON & (1<<4)));
/*禁止主机应答信号(即开启非应答 因为只接收一个字节) 清除中断标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<7))&(~(1<<4));
/*等待接收从机发来的数据*/
while(!(I2C5.I2CCON & (1<<4)));
/*将从机发来的数据读取*/
data = I2C5.I2CDS;
/*直接发起停止信号结束本次通信*/
I2C5.I2CSTAT = 0x90;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时等待停止信号稳定*/
mydelay_ms(10);
return data;
}
/**********************************************************************
* 函数功能:MPU6050初始化
**********************************************************************/
void MPU6050_Init ()
{
iic_write(SlaveAddress, PWR_MGMT_1, 0x00); //设置使用内部时钟8M
iic_write(SlaveAddress, SMPLRT_DIV, 0x07); //设置陀螺仪采样率
iic_write(SlaveAddress, CONFIG, 0x06); //设置数字低通滤波器
iic_write(SlaveAddress, GYRO_CONFIG, 0x18); //设置陀螺仪量程+-2000度/s
iic_write(SlaveAddress, ACCEL_CONFIG, 0x0); //设置加速度量程+-2g
}
/**********************************************************************
* 函数功能:初始化系统时间
**********************************************************************/
void RTC_Init() {
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息*/
RTC.BCDYEAR = 0x023;
RTC.BCDMON = 0x8;
RTC.BCDDAY = 0x1;
RTC.BCDWEEK = 0x15;
RTC.BCDHOUR = 0x16;
RTC.BCDMIN = 0x46;
RTC.BCDSEC = 0x50;
/*禁止RTC控制*/
RTCCON = RTCCON & (~(1));
}
/**********************************************************************
* 函数功能:初始化报警器功能函数PWM
**********************************************************************/
void PWM_Init() {
/*1. 将GPD0_0引脚设置成PWM0的输出引脚*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2. 设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | (99);
/*3. 设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器频率 = PLCK / (99+1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4. 设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5. 设置PWM0的频率为500HZ (1/1000) / (1/1M) = 1000*/
PWM.TCNTB0 = 1000;
/*6. 设置PWM0的占空比为60%*/
PWM.TCMPB0 = 600;
/*7. 将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8. 关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9. 使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
}
/**********************************************************************
* 函数功能:IRO异常处理
**********************************************************************/
//IRQ异常处理
void do_irq(void)
{
static int flag = 0;
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*警报关闭*/
PWM.TCON = PWM.TCON & (~(1));
mydelay_ms(100);
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
/**********************************************************************
* 函数功能:把KEY2按键设置成中断模式,在警报状态下,若按下Key2按键,解除蜂鸣器的警报声
**********************************************************************/
void KEY2_Init() {
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_2的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned int Oldmin = 0, Newmin = 0, Oldsec = 0, Newsec = 0; //存储时间
unsigned char zvalue_h,zvalue_l, xvalue_h, xvalue_l, yvalue_h, yvalue_l; //存储读取结果
short int zvalue, xvalue, yvalue;
/*设置GPB_2引脚和GPB_3引脚功能为I2C传输引脚*/
GPB.CON = (GPB.CON & ~(0xF<<12)) | 0x3<<12; //设置GPB_3引脚功能为I2C_5_SCL
GPB.CON = (GPB.CON & ~(0xF<<8)) | 0x3<<8; //设置GPB_2引脚功能为I2C_5_SDA
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
RTC_Init(); //初始化RTC
PWM_Init(); //初始化PWM
KEY2_Init(); //初始化KEY2按键中断功能
printf("\n********** I2C test!! ***********\n");
while(1)
{
zvalue_h = iic_read(SlaveAddress, ACCEL_ZOUT_H); //获取MPU6050-Z轴加速度高字节
zvalue_l = iic_read(SlaveAddress, ACCEL_ZOUT_L); //获取MPU6050-Z轴加速度低字节
zvalue = (zvalue_h << 8) | zvalue_l; //获取MPU6050-Z轴加速度
xvalue_h = iic_read(SlaveAddress, ACCEL_XOUT_H); //获取MPU6050-X轴加速度高字节
xvalue_l = iic_read(SlaveAddress, ACCEL_XOUT_L); //获取MPU6050-X轴加速度低字节
xvalue = (xvalue_h << 8) | xvalue_l; //获取MPU6050-X轴加速度
yvalue_h = iic_read(SlaveAddress, ACCEL_YOUT_H); //获取MPU6050-Y轴加速度高字节
yvalue_l = iic_read(SlaveAddress, ACCEL_YOUT_L); //获取MPU6050-Y轴加速度低字节
yvalue = (yvalue_h << 8) | yvalue_l; //获取MPU6050-Y轴加速度
printf("ACCEL - Z : %d\n", zvalue);
printf("ACCEL - X : %d\n", xvalue);
printf("ACCEL - Y : %d\n", yvalue);
if(xvalue == 0 && yvalue == 0) {
/*水平*/
/*打印当前时间 实现每隔一分钟打印一次*/
Newmin = RTC.BCDMIN;
if(Oldmin != Newmin) {
printf("20%x-%x-%x %x:%x:%x Status: Normal\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
Oldmin = Newmin;
}
/*警报关闭*/
PWM.TCON = PWM.TCON & (~(1));
mydelay_ms(100);
} else {
/*倾斜*/
/*打印当前时间 实现每隔一秒打印一次*/
Newsec = RTC.BCDSEC;
if(Oldsec != Newsec) {
printf("20%x-%x-%x %x:%x:%x Status: Warning\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
printf("xvalue = %d, yvalue = %d\n", xvalue, yvalue);
Oldsec = Newsec;
}
/*警报响起 滴滴的声音*/
PWM.TCON = PWM.TCON & (~(1));
mydelay_ms(100);
PWM.TCON = PWM.TCON | 1;
mydelay_ms(100);
}
mydelay_ms(100);
}
return 0;
}
start.S
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
b irq_handler
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
/* 将_start的地址给了r0 */
ldr r0, =_start
/* 将r0寄存器通过协处理寄存器指令,给了协处理器p15里面的c12寄存器,c12寄存器的作用就是设置异常向量表位置的 */
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
// 处理IRQ异常程序
irq_handler:
//因为产生IRQ异常后自动保存到LR中的返回地址是被IRQ打断的
//下一条下一条的指令地址,所以需要我们人为的修正一下
sub lr, lr , #4
//因为IRQ模式下和USER模式下R0-R12寄存器是同一组
//所以执行异常程序之前需要把USER模式下的R0-R12寄存器压栈保护现场
stmfd sp!, {r0-r12}
//处理异常
bl do_irq
//异常返回
//1. 将r0-r12寄存器的值出栈,使其恢复到被异常打断之前的值
//2. 将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前的状态
//3. 将栈中LR寄存器中的值出栈给PC,实现程序的返回
ldmfd sp!, {r0-r12, pc}
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512