上一篇文章再 C52 单片机上进行了测试,那么接下来我们就分析一下测试程序。看看其中都用到哪些寄存器?
测试代码,参看:MPU6050开发 -- 在 C52 单片机上测试
一、单片机介绍
这部分上一篇文章已经讲了,我使用的是郭天祥的STC89C52单片机,该实验板上面使用的外部晶振频率是 11.0592MHz。因为STC89C52 没有集成 I2C 控制器,那么我们就定义51单片机端口:
//****************************************
sbit SCL=P1^5; //IIC时钟引脚定义
sbit SDA=P1^4; //IIC数据引脚定义
//****************************************
二、定义MPU6050内部地址
这应该是本篇文章的重点了,找出这几个重要的寄存器。
(1)寄存器25 - 采样速率分频器(SMPRT_DIV)
参数:
SMPLRT_DIV 为8位无符号值。 采样率是通过将陀螺仪输出速率除以该值来确定的。
描述:
该寄存器指定用于产生MPU-60X0采样率的陀螺仪输出速率的分频器。传感器寄存器输出,FIFO输出和DMP采样都基于采样率。采样率是通过将陀螺仪输出速率除以 SMPLRT_DIV 产生的:
采样率=陀螺仪输出速率/(1 + SMPLRT_DIV)
当DLPF禁用(DLPF_CFG = 0或7)时,陀螺仪输出速率= 8kHz,当DLPF使能时(见寄存器26)为1kHz。
注意:加速度计输出速率是1kHz。 这意味着对于大于1kHz的采样率,同一个加速度计采样可能会不止一次输出到FIFO,DMP和传感器寄存器。
(2)寄存器26 - 配置(CONFIG)
注:位7和位6保留
参数:
EXT_SYNC_SET 3位无符号值。 配置FSYNC引脚采样。
DLPF_CFG 3位无符号值。 配置DLPF设置
描述:
该寄存器为陀螺仪和加速度计配置外部帧同步(FSYNC)引脚采样和数字低通滤波器(DLPF)设置。
连接到FSYNC引脚的外部信号可以通过配置 EXT_SYNC_SET 进行采样。
FSYNC 引脚的信号变化被锁存,以便捕获短闪光灯。 锁存的FSYNC信号将按照寄存器 25 中定义的采样速率进行采样。采样后,锁存器将复位为当前的 FSYNC 信号状态。
根据下表,取样值将被报告在由 EXT_SYNC_SET 的值确定的传感器数据寄存器中的最低有效位的位置。
DLPF由 DLPF_CFG 配置,加速度计和陀螺仪根据 DLPF_CFG 的值进行过滤,如下表所示。
(3)寄存器27 - 陀螺仪配置(GYRO_CONFIG)
注:位2到位0被保留。
参数:
XG_ST 设置此位将导致X轴陀螺仪执行自检。
YG_ST 设置此位将使Y轴陀螺仪执行自检。
ZG_ST 设置该位使Z轴陀螺仪执行自检。
FS_SEL 2位无符号值。 选择陀螺仪的全量程范围。
描述:
该寄存器用于触发陀螺仪自检并配置陀螺仪的满量程范围。
陀螺仪自检允许用户测试机械和电气部分陀螺仪。每个陀螺仪轴的自检可通过控制该寄存器的XG_ST,YG_ST和ZG_ST位来激活。每个轴的自检可以独立进行,也可以同时进行。
当自检被激活时,车载电子装置将启动适当的传感器。这种驱动将使传感器的检测质量移动一段相当于预先确定的科里奥利力的距离。这种检测质量位移导致传感器输出发生变化,这反映在输出信号中。输出信号用于观察自检响应。
自检响应定义如下:
自检响应=启用自检的传感器输出 - 未启用自检的传感器输出
每个陀螺仪轴的自检限制在MPU-6000 / MPU-6050产品规格文件。当自检的价值响应在产品规格的最小/最大范围内,零件已通过自检。当自检响应超过文档中指定的最小/最大值时,该部分被认为是自检失败。
FS_SEL根据下表选择陀螺仪输出的满量程范围。
(4)寄存器28 - 加速度计配置(ACCEL_CONFIG)
参数:
XA_ST 当设置为1时,X轴加速度计执行自检。
YA_ST 当设置为1时,Y轴加速度计执行自检。
ZA_ST 设置为1时,Z轴加速计执行自检。
AFS_SEL 2位无符号值。 选择加速度计的全量程范围。
描述:
该寄存器用于触发加速度计自检并配置加速度计满量程范围。该寄存器还配置数字高通滤波器(DHPF)。
加速度计自检允许用户测试加速度计的机械和电子部分。每个加速度计轴的自检可通过控制该寄存器的XA_ST,YA_ST和ZA_ST位来激活。每个轴的自检可以独立进行,也可以同时进行。
当自检被激活时,车载电子装置将启动适当的传感器。这种致动模拟外力。被驱动的传感器又将产生相应的输出信号。输出信号用于观察自检响应。
自测响应定义如下:
自检响应=启用自检的传感器输出 - 未启用自检的传感器输出
MPU-6000 / MPU-6050产品规格文档的电气特性表中提供了每个加速度计轴的自检限制。当自检响应值在产品规格的最小/最大范围内时,该部件已通过自检。当自测响应超过文档中指定的最小/最大值时,该部分被认为是自检失败。
AFS_SEL 根据下表选择加速度计输出的满量程范围。
(5)寄存器59到64 - 加速度计测量 ACCEL_XOUT_H,ACCEL_XOUT_L,ACCEL_YOUT_H,ACCEL_YOUT_L,ACCEL_ZOUT_H和ACCEL_ZOUT_L
参数:
ACCEL_XOUT 16位二进制补码值。存储最近的X轴加速计测量值。
ACCEL_YOUT 16位二进制补码值。存储最近的Y轴加速度计测量值。
ACCEL_ZOUT 16位二进制补码值。存储最近的Z轴加速计测量值。
描述:
这些寄存器存储最新的加速度计测量结果。
按照 寄存器25 中定义的采样率将加速度计测量值写入这些寄存器。
加速度计测量寄存器以及温度测量寄存器,陀螺仪测量寄存器和外部传感器数据寄存器由两组寄存器组成:内部寄存器组和面向用户的读取寄存器组。
加速度计传感器内部寄存器组内的数据总是以采样率更新。同时,只要串行接口空闲,面向用户的读取寄存器组就会复制内部寄存器组的数据值。这保证了传感器寄存器的突发读取将从相同的采样时刻读取测量结果。请注意,如果不使用突发读取,则用户负责通过检查数据就绪中断来确保一组单个字节的读取对应于单个采样时刻。
每个16位加速度计的测量结果都在 ACCEL_FS(寄存器28)中定义了一个满量程。对于每个满量程设置,ACCEL_xOUT 中的每个 LSB 的加速度计灵敏度如下表所示。
(6)寄存器65和66 - 温度测量(TEMP_OUT_H和TEMP_OUT_L)
参数:
TEMP_OUT 16位有符号值。存储最近的温度传感器测量值。
描述:
这些寄存器存储最新的温度传感器测量值。
温度测量值按照 寄存器25 中定义的采样率写入这些寄存器。
这些温度测量寄存器以及加速度计测量寄存器,陀螺仪测量寄存器和外部传感器数据寄存器由两组寄存器组成:内部寄存器组和面向用户的读取寄存器组。
温度传感器内部寄存器组内的数据始终以采样率进行更新。
同时,只要串行接口空闲,面向用户的读取寄存器组就会复制内部寄存器组的数据值。这保证了传感器寄存器的突发读取将从相同的采样时刻读取测量结果。请注意,如果不使用突发读取,则用户负责通过检查数据就绪中断来确保一组单个字节的读取对应于单个采样时刻。
电气规格表(MPU-6000 / MPU-6050产品规格文档第6.4节)中提供了温度传感器的比例因子和偏移量。
对于给定的寄存器值,温度(摄氏度)可以被计算为:
以℃为单位的温度=(TEMP_OUT寄存器值作为有符号数量)/ 340 + 36.53
请注意,上述公式中的数学是十进制的。
(7)寄存器67至72 - 陀螺仪测量 GYRO_XOUT_H,GYRO_XOUT_L,GYRO_YOUT_H,GYRO_YOUT_L,GYRO_ZOUT_H和GYRO_ZOUT_L
参数:
GYRO_XOUT 16位二进制补码值。存储最新的X轴陀螺仪测量。
GYRO_YOUT 16位二进制补码值。存储最新的Y轴陀螺仪测量结果。
GYRO_ZOUT 16位二进制补码值。存储最新的Z轴陀螺仪测量结果。
描述:
这些寄存器存储最近的陀螺仪测量结果。
陀螺仪测量值按寄存器25中定义的采样率写入这些寄存器。
这些陀螺仪测量寄存器以及加速度计测量寄存器,温度测量寄存器和外部传感器数据寄存器由两组寄存器组成:内部寄存器组和面向用户的读取寄存器组。
陀螺仪传感器内部寄存器组内的数据总是以采样率更新。
同时,只要串行接口空闲,面向用户的读取寄存器组就会复制内部寄存器组的数据值。这保证了传感器寄存器的突发读取将从相同的采样时刻读取测量结果。请注意,如果不使用突发读取,则用户负责通过检查数据就绪中断来确保一组单个字节的读取对应于单个采样时刻。
每个16位陀螺仪测量具有在 FS_SEL(寄存器27)中定义的满量程。对于每个满量程设置,GYRO_xOUT中陀螺仪每个LSB的灵敏度如下表所示:
(8)寄存器107 - 电源管理1 (PWR_MGMT_1)
注:位4保留。
参数:
DEVICE_RESET 设置为1时,该位将所有内部寄存器复位为默认值。一旦复位完成,该位自动清零。每个寄存器的默认值可以在第3节找到。
SLEEP 当该位置1时,该位将MPU-60X0置于睡眠模式。
CYCLE 当该位设置为1且SLEEP被禁止时,MPU-60X0将循环。在睡眠模式和唤醒之间以LP_WAKE_CTRL(寄存器108)确定的速率从活动传感器获取单个样本数据。
TEMP_DIS 设置为1时,该位禁用温度传感器。
CLKSEL 3位无符号值。 指定设备的时钟源。
描述:
该寄存器允许用户配置电源模式和时钟源。它还提供了一些重置整个设备,以及一些禁用温度传感器。
通过将 SLEEP 设置为1,MPU-60X0 可以进入低功耗睡眠模式。当 CYCLE 设置为1而睡眠模式被禁用时,MPU-60X0 将进入循环模式。在周期模式下,器件在休眠模式和唤醒之间循环,以由 LP_WAKE_CTRL(寄存器108)确定的速率从加速计获取单个采样。要配置唤醒频率,请使用 电源管理2寄存器(寄存器108)内的 LP_WAKE_CTRL。
MPU-60X0 时钟源可选择内部 8MHz 振荡器,基于陀螺仪的时钟或外部时钟源。当选择内部 8MHz 振荡器或外部时钟源作为时钟源时,MPU-60X0 可以在陀螺仪禁用的低功耗模式下工作。
上电时,MPU-60X0 时钟源默认为内部振荡器。但是,强烈建议将器件配置为使用其中一个陀螺仪(或外部时钟源)作为时钟参考,以提高稳定性。时钟源可以按照下表进行选择。
(9)寄存器117 - 我是谁(WHO_AM_I)
注:位0和7保留。 (硬编码为0)
参数:
WHO_AM_I包含MPU-60X0的6位I2C地址。
位6:位1的上电复位值为110 100。
描述:
该寄存器用于验证设备的身份。 WHO_AM_I的内容是MPU-60X0的7位I2C地址的高6位。 MPU-60X0的I2C地址的最低有效位由AD0引脚的值决定。 该寄存器不反映AD0引脚的值。
该寄存器的默认值是0x68。
到此,涉及到的几个重要寄存器讲完了。
接下来是初始化MPU6050,但是我不晓得测试程序中提供的典型值是怎么得出来的。
三、延时
该测试程序中有两个延时函数,需要了解一下。
(1)void Delay5us ()
void Delay5us()
{
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
}
这里的 _nop_(); 是什么意思?
网上查询,这个函数是延时一个机器周期的意思,它包含在头文件intrins.h中,只要程序应用到_nop_(),就需要有头文件intrins.h 的声明。
那我们查看一下头文件intrins.h:
用Keil打开工程将鼠标移动到 intrins.h,单击右键,选择 Open document <intrins.h>,即可打开该头文件。
/*--------------------------------------------------------------------------
INTRINS.H
Intrinsic functions for C51.
Copyright (c) 1988-2010 Keil Elektronik GmbH and ARM Germany GmbH
All rights reserved.
--------------------------------------------------------------------------*/
#ifndef __INTRINS_H__
#define __INTRINS_H__
#pragma SAVE
#if defined (__CX2__)
#pragma FUNCTIONS(STATIC)
/* intrinsic functions are reentrant, but need static attribute */
#endif
extern void _nop_ (void);
extern bit _testbit_ (bit);
extern unsigned char _cror_ (unsigned char, unsigned char);
extern unsigned int _iror_ (unsigned int, unsigned char);
extern unsigned long _lror_ (unsigned long, unsigned char);
extern unsigned char _crol_ (unsigned char, unsigned char);
extern unsigned int _irol_ (unsigned int, unsigned char);
extern unsigned long _lrol_ (unsigned long, unsigned char);
extern unsigned char _chkfloat_(float);
#if defined (__CX2__)
extern int abs (int);
#endif
#if !defined (__CX2__)
extern void _push_ (unsigned char _sfr);
extern void _pop_ (unsigned char _sfr);
#endif
#pragma RESTORE
#endif
说明 _nop_() 确实是在头文件intrins.h里声明的。但是它是啥意思呢?我还是不清楚。
我快疯了,我查了半天,只查到下面这个自写的 delay.c 。但是 _nop_() 语句的句数是怎么计算出来的就不晓得了。
参看:1T单片机专用延时函数
/*********************************************************************
*
* delay.h
*
*********************************************************************
* 描 述: 网上收集整理的延时函数头文件,试用源码。
* 开发平台: KEIL+HL-1/HJ-3G/HJ-C52开发板
********************************************************************/
#ifndef DELAY_H
#define DELAY_H
void delay1us();
void delay2us();
void delay5us();
void delay10us();
void delay20us();
void delay50us();
void delay100us();
void delay200us();
void delay500us();
void delay1ms();
void delay2ms();
void delay5ms();
void delay10ms();
void delay20ms();
void delay50ms();
void delay100ms();
void delay200ms();
void delay300ms();
void delay400ms();
void delay500ms();
void delay1000ms();
void delay2000ms();
void delay5000ms();
void delay1s();
void delay2s();
void delay5s();
void delay10s();
void delay8(unsigned char t);/*1.6us--232us延时函数(fosc=11.0592M时)*/
void delay(unsigned int t);/*2.2us--77ms延时函数(fosc=11.0592M时)*/
void delayms(unsigned char t);/*一个毫秒级可变延时函数*/
void delays(float t);/*一个秒级可变延时函数*/
#endif
/*********************************************************************
*
* delay.c
*
*********************************************************************
* 描 述: 网上收集整理的延时函数,试用源码。
* 开发平台: KEIL+HL-1/HJ-3G/HJ-C52开发板*/
#include "stc.h"/*包含STC单片机头文件*/
#include "hjc52.h"/*包含HJ-C52开发板头文件*/
#include "delay.h"/*包含网上收集的延时函数头文件*/
#include <intrins.h>/*包含含有_nop_()函数的头文件*/
/*一些固定延时函数*/
/*1uS延时函数*/
/*FOSC=11.0592、12、22.1184MHZ时,适用*/
/*其他晶振频率时,要调整_nop_();语句的句数,_nop_();语句的句数=FOSC-10*/
void delay1us()
{
#if FOSC==11059200
_nop_();
#elif FOSC==12000000
_nop_();_nop_();
#elif FOSC==22118400
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();
#endif
}
/*2uS延时函数*/
/*FOSC=11.0592、12、22.1184MHZ时,适用*/
/*其他晶振频率时,要调整_nop_();语句的句数,_nop_();语句的句数=2*FOSC-10*/
void delay2us()
{
#if FOSC==11059200
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();
#elif FOSC==12000000
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
#elif FOSC==22118400
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();
#endif
}
/*5uS延时函数*/
void delay5us()
{
delay8((FOSC/1000000*5-18)/10);
}
/*10uS延时函数*/
void delay10us()
{
delay8((FOSC/1000000*10-18)/10);
}
/*20uS延时函数*/
void delay20us()
{
delay8((FOSC/1000000*20-18)/10);
}
/*50uS延时函数*/
void delay50us()
{
delay8((FOSC/1000000*50-18)/10);
}
/*100uS延时函数*/
void delay100us()
{
delay((0.1*FOSC/1000-24)/13);
}
/*200uS延时函数*/
void delay200us()
{
delay((0.2*FOSC/1000-24)/13);
}
/*500uS延时函数*/
void delay500us()
{
delay((0.5*FOSC/1000-24)/13);
}
/*1mS延时函数*/
void delay1ms()
{
delay((1*FOSC/1000-24)/13);
}
/*2mS延时函数*/
void delay2ms()
{
delay((2*FOSC/1000-24)/13);
}
/*5mS延时函数*/
void delay5ms()
{
delay((5*FOSC/1000-24)/13);
}
/*10mS延时函数*/
void delay10ms()
{
delay((10*FOSC/1000-24)/13);
}
/*20mS延时函数*/
void delay20ms()
{
delayms(20);
}
/*50mS延时函数*/
void delay50ms()
{
delayms(50);
}
/*100mS延时函数*/
void delay100ms()
{
delayms(100);
}
/*200mS延时函数*/
void delay200ms()
{
delayms(200);
}
void delay300ms()
{
delayms(300);
}
void delay400ms()
{
delayms(400);
}
/*500mS延时函数*/
void delay500ms()
{
delayms(500);
}
void delay1000ms()
{
delayms(1000);
}
void delay2000ms()
{
delayms(2000);
}
void delay5000ms()
{
delayms(5000);
}
/*1S延时函数*/
void delay1s()
{
delays(1);
}
/*2S延时函数*/
void delay2s()
{
delays(2);
}
/*5S延时函数*/
void delay5s()
{
delays(5);
}
/*10S延时函数*/
void delay10s()
{
delays(10);
}
/*0.8us--116us延时函数(FOSC=22.1184M时)*/
/*1.5us--214us延时函数(FOSC=12M时)*/
/*1.6us--232us延时函数(FOSC=11.0592M时)*/
/*延时时间:(18+10t)/FOSC*/
/*最大延时:2568/FOSC*/
/*最小延时:18/FOSC*/
/*设要延时的时间为T(常量,单位为us),调用方式:delay8((FOSC/1000000*T-18)/10)*/
/*例如要延时的时间为100us,调用方式:delay8((FOSC/1000000*100-18)/10)*/
void delay8(unsigned char t)
{
unsigned char i;
for(i=t;i;i--);
}
/*1.1us--38.5ms延时函数(FOSC=22.1184M时)*/
/*2us--70.9ms延时函数(FOSC=12M时)*/
/*2.2us--77ms延时函数(FOSC=11.0592M时)*/
/*延时时间:(13t+24)/FOSC
/*最大延时:851979/FOSC
/*最小延时:24/FOSC
/*设要延时的时间为T(常量,单位为ms),调用方式:delay((T*FOSC/1000-24)/13)*/
/*例如要延时的时间为10ms,调用方式:delay((10*FOSC/1000-24)/13)*/
void delay(unsigned int t)
{
unsigned int i;
for(i=t;i;i--);
}
/*一个毫秒级可变延时函数*/
/*功 能:实现与参数直接对应的时间(单位为毫秒)的延时*/
/*参 数:范围1到255*/
/*返回值:无*/
void delayms(unsigned char t)
{
unsigned char j;
for(j=t;j;j--)
delay1ms();/*1ms延时*/
}
/*一个秒级可变延时函数*/
/*功 能:实现与参数直接对应的时间(单位为秒)的延时*/
/*参 数:范围0.01到42949672*/
/*返回值:无*/
void delays(float t)
{
unsigned int j;
j=t*100;
while(j--)
{
delay10ms();/*10ms延时*/
}
}
再有上面提到 _nop_(); 这个函数是延时一个机器周期的意思。那么接下来我们再看一下单片机几个周期的介绍:
1)时钟周期
也称为振荡周期,定义为时钟频率的倒数(外接12MHZ的晶振,其时钟周期就是1/12us),t它是单片机中最基本、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作
2) 状态周期
它是时钟周期的两倍。
3) 机器周期
在一个操作周期内,单片机完成一项基本周期,如取指令、存储器读/写等,它是由12个时钟周期(6个状态周期)组成。 我们用的单片机上时钟频率为 11.0592MHz,那么机器周期为 12x(1/11.0592) = 1.09us.
4) 指令周期
它是指CPU执行一条指令的所需要的时间,一般一个指令周期含有1~4个机器周期。
总结:
我不管怎么实现的了,看名字Delay5us,即这个函数延时 5 us。
(2)void delay(unsigned int k);
void delay(unsigned int k)
{
unsigned int i,j;
for(i=0;i<k;i++)
{
for(j=0;j<121;j++);
}
}
这个是延时函数用到两个for循环,内层for循环恒定为小于121,有外层for决定延时多少毫秒。
举例,delay(500); //上电延时 500ms (毫秒)
这个内层恒定值的怎么得出的?是测出来的...
四、I2C通信协议实现
I2C通信我们已经讲了,参看:MPU6050开发 -- 进阶之I2C/SPI通信协议
现在看一下,用程序怎么实现。
(1)I2C起始信号
SCL 为高时SDA的下降沿。
//*************************************************************************************************
//I2C起始信号
//*************************************************************************************************
void I2C_Start()
{
SDA = 1; //拉高数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 0; //产生下降沿
Delay5us(); //延时
SCL = 0; //拉低时钟线
}
(2)I2C停止信号
SCL 为高时SDA的上升沿
//*************************************************************************************************
//I2C停止信号
//*************************************************************************************************
void I2C_Stop()
{
SDA = 0; //拉低数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 1; //产生上升沿
Delay5us(); //延时
}
(3)应答
ACK:应答信号,在第 9 个时钟周期 SCL 为高时,SDA 为低
NACK:拒绝信号,在第 9 个时钟周期,SDA 一直为低
}
//**************************************************************************************************
//I2C发送应答信号
//入口参数:ack (0:ACK 1:NAK)
//**************************************************************************************************
void I2C_SendACK(bit ack)
{
SDA = ack; //写应答信号
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
//****************************************************************************************************
//I2C接收应答信号
//****************************************************************************************************
bit I2C_RecvACK()
{
SCL = 1; //拉高时钟线
Delay5us(); //延时
CY = SDA; //读应答信号
SCL = 0; //拉低时钟线
Delay5us(); //延时
return CY;
}
在接收应答中,有一个 CY = SDA; 这里的 CY 是什么?
程序状态寄存器 PSW 是计算机系统的核心部件——运算器的一部分,PSW用来存放两类信息:一类是体现当前指令执行结果的各种状态信息,称为状态标志,如有无借位进位(CY位)、有无溢出(OF位)、结果正负(SF位)、结果是否为零(ZF位)、奇偶标志位(PF位)等;另一类是存放控制信息,称为控制状态,如允许中断(IF位),跟踪标志(TF位),方向标志(DF)等。
根据上面这段话可以得出,CY是PSW的进位标志。特点是:进位、借位CY=1;否则CY=0.
(4)发送数据
8个bit 发送一个应答 ACK,进位、借位CY=1;否则CY=0.
//*****************************************************************************************************
//向I2C总线发送一个字节数据
//*****************************************************************************************************
void I2C_SendByte(uchar dat)
{
uchar i;
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1; //移出数据的最高位
SDA = CY; //送数据口
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
I2C_RecvACK();
}
(5)接收数据
//*****************************************************************************************************
//从I2C总线接收一个字节数据
//******************************************************************************************************
uchar I2C_RecvByte()
{
uchar i;
uchar dat = 0;
SDA = 1; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL = 1; //拉高时钟线
Delay5us(); //延时
dat |= SDA; //读数据
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
return dat;
}
(6)I2C设备写入/读取一个字节数据
这里有意思的就是读/写地址了,写地址:11010000,读地址:11010001
//*****************************************************************************************************
//向I2C设备写入一个字节数据
//*****************************************************************************************************
void Single_WriteI2C(uchar REG_Address,uchar REG_data)
{
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress); //发送设备地址+写信号
I2C_SendByte(REG_Address); //内部寄存器地址,
I2C_SendByte(REG_data); //内部寄存器数据,
I2C_Stop(); //发送停止信号
}
//*******************************************************************************************************
//从I2C设备读取一个字节数据
//*******************************************************************************************************
uchar Single_ReadI2C(uchar REG_Address)
{
uchar REG_data;
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress); //发送设备地址+写信号
I2C_SendByte(REG_Address); //发送存储单元地址,从0开始
I2C_Start(); //起始信号
I2C_SendByte(SlaveAddress+1); //发送设备地址+读信号
REG_data=I2C_RecvByte(); //读出寄存器数据
I2C_SendACK(1); //接收应答信号
I2C_Stop(); //停止信号
return REG_data;
}
五、初始化
(1)串口初始化
单片机这些寄存器,我懒得的搞了
//******************************************************************************************************
//串口初始化
//*******************************************************************************************************
void init_uart()
{
TMOD=0x21;
TH1=0xfd; //实现波特率9600(系统时钟11.0592MHZ)
TL1=0xfd;
SCON=0x50;
PS=1; //串口中断设为高优先级别
TR0=1; //启动定时器
TR1=1;
ET0=1; //打开定时器0中断
ES=1;
EA=1;
}
(2)初始化MPU6050
这些寄存器,我们上面已经讲了。但是它的典型值怎么来的呢?接下来我们分析一下。
//******************************************************************************************************
//初始化MPU6050
//******************************************************************************************************
void InitMPU6050()
{
Single_WriteI2C(PWR_MGMT_1, 0x00); //解除休眠状态
Single_WriteI2C(SMPLRT_DIV, 0x07);
Single_WriteI2C(CONFIG, 0x06);
Single_WriteI2C(GYRO_CONFIG, 0x18);
Single_WriteI2C(ACCEL_CONFIG, 0x01);
}
《1》PWR_MGMT_1 典型值为0x00 (正常启用)
根据上面寄存器107 - 电源管理1 (PWR_MGMT_1)的介绍可知:
SLEEP 置0,为未休眠状态
TEMP_DIS 置0,可以使用温度传感器
CLKSEL 置0,MPU-60X0 时钟源选择的是内部 8MHz 振荡器
《2》SMPLRT_DIV 典型值为0x07 (陀螺仪采用率 125Hz)
根据上面寄存器25 - 采样速率分频器(SMPRT_DIV)的介绍可知:
SMPLRT_DIV 置7,因为采样率是通过将陀螺仪输出速率除以该值来确定的。
采样率=陀螺仪输出速率/(1 + SMPLRT_DIV)
当DLPF禁用(DLPF_CFG = 0或7)时,陀螺仪输出速率= 8kHz,当DLPF启用时(见寄存器26)为1kHz。
根据下面寄存器26分析,数字低通滤波器(DLPF)启用,此时陀螺仪输出速率为 1KHz
那采样率 = 1KHz /(1+7) = 125 Hz
《3》CONFIG 典型值为0x06 (低通滤波器频率为 5Hz)
根据上面寄存器26 - 配置(CONFIG)的介绍可知:
EXT_SYNC_SET 置0,输入禁用。又因为连接到FSYNC引脚的外部信号可以通过配置 EXT_SYNC_SET 进行采样。
因此陀螺仪和加速度计配置外部帧同步(FSYNC)引脚采样无法被禁用了。
DLPF_CFG 置6 ,数字低通滤波器(DLPF)启用。
DLPF由 DLPF_CFG 配置,可知加速度计和陀螺仪的低通滤波器频率为 5Hz
《4》GYRO_CONFIG 典型值为0x18(陀螺仪不自检,输出满量程范围为 ± 2000 °/s)
根据上面寄存器27 - 陀螺仪配置(GYRO_CONFIG 的介绍可知:
XG_ST,YG_ST和ZG_ST位 置0,陀螺仪无自检功能
FS_SEL 置3,根据下表选择陀螺仪输出的满量程范围为 ± 2000 °/s
(5)ACCEL_CONFIG 典型值为0x01(加速度计不自检,输出的满量程范围为± 2g)
根据上面寄存器28 - 加速度计配置(ACCEL_CONFIG)的介绍可知:
XA_ST,YA_ST和ZA_ST位 置0,加速度计无自检功能
AFS_SEL 置0,根据下表选择加速度计输出的满量程范围为 ± 2g
该寄存器还配置数字高通滤波器(DHPF),但是寄存器手册里,并未介绍高通滤波器频率。
六、串口发送
(1)串口发送函数
//*************************************************************************************************
//串口发送函数
//*************************************************************************************************
void SeriPushSend(uchar send_data)
{
SBUF=send_data;
while(!TI);TI=0;
}
参看:SBUF -- 百度百科
这里有一个 SBUF,全称:serial data buffer,中文名为串行数据缓冲器。这个重叠的地址靠读/写指令区分:串行发送数据时,CPU向SBUF写入数据,此时99H表示发送SBUF;串行接收数据时,CPU从SBUF读出数据,此时99H表示接收SBUF。
参看:TI -- 百度百科
再有 TI,作为51系列单片机中的串行口通信发送请求中断标志位。一旦TI=1,CPU就被告知产生了一个串行通信口中断。
(2)ASCII 码
SeriPushSend(0x20); //空格
SeriPushSend(0x0d); //回车
SeriPushSend(0x0a); //换行
其中十六进制的 0x20、0x0d和0x0a 都是 ASCII码,对应的是空格、回车和换行。
参看:C语言再学习 -- ASCII码表(转)
七、超级终端显示10进制数据
(1)串口调试工具
//******************************************************************************************************
//超级终端(串口调试助手)上显示10位数据
//******************************************************************************************************
void Display10BitData(int value)
{ uchar i;
// value/=64; //转换为10位数据
lcd_printf(dis, value); //转换数据显示
for(i=0;i<6;i++)
{
SeriPushSend(dis[i]);
}
// DisplayListChar(x,y,dis,4); //启始列,行,显示数组,显示长度
}
(2)合成数据
//******************************************************************************************************
//合成数据
//******************************************************************************************************
int GetData(uchar REG_Address)
{
uchar H,L;
H=Single_ReadI2C(REG_Address);
L=Single_ReadI2C(REG_Address+1);
return ((H<<8)+L); //合成数据
}
举个例子,Display10BitData(GetData(ACCEL_XOUT_H)); //显示X轴加速度
宏定义可知:
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
则:ACCEL_XOUT_L = ACCEL_XOUT_H + 1;
因此就得出了:
H=Single_ReadI2C(REG_Address);
L=Single_ReadI2C(REG_Address+1);
合成数据则为:(H<<8)+L);
这里说明下 ACCEL_XOUT 是16位二进制补码值,存储最近的X轴加速计测量值。
什么是二进制补码?参看:C语言再学习 -- 负数
初始化MPU6050时我们讲到 ACCEL_CONFIG 典型值为0x01(加速度计不自检,输出的满量程范围为± 2g)
那么,ACCEL_xOUT 中的加速度计每个 LSB 的灵敏度应为 16384 LSB/g
初始化MPU6050时我们讲到 GYRO_CONFIG 典型值为0x18(陀螺仪不自检,输出满量程范围为 ± 2000 °/s)
同理,GYRO_xOUT中陀螺仪每个LSB的灵敏度为 16.4 LSB/°/s
八、数据分析
(1)MPU6050初始化设置总结
再讲数据分析之前,我们先总结一些初始化MPU6050寄存器设置和加速度计/陀螺仪灵敏度
PWR_MGMT_1 典型值为0x00 (正常启用)
SMPLRT_DIV 典型值为0x07 (陀螺仪采用率 125Hz)
CONFIG 典型值为0x06 (低通滤波器频率为 5Hz)
GYRO_CONFIG 典型值为0x18(陀螺仪不自检,输出满量程范围为 ± 2000 °/s)
ACCEL_CONFIG 典型值为0x01(加速度计不自检,输出的满量程范围为± 2g)
ACCEL_xOUT 中的加速度计每个 LSB 的灵敏度应为 16384 LSB/g
GYRO_xOUT中的陀螺仪每个 LSB 的灵敏度为 16.4 LSB/°/s
(2)数据分析
最后在串口调试工具上,我们来看看它得出的数据。
A 为三轴MEMS加速度计,G为三轴MEMS陀螺仪。挪动MPU6050模块,则数据变化。
那么问题来了:
得到的这些数据是什么意思呢?
我们拿到这些数据怎么来做分析?
陀螺仪和加速度计的原理又是什么?
陀螺仪和加速度计没有自检,怎么检测得到的数据是否正确?或者说怎么判断这个MPU6050传感器是不是好的?
再有现在这些数据是延时2秒后发送的(delay(2000); ),若把延时缩短那么最短能让它多长时间发送?
请听下回分解!!
这篇讲的有点多了,再开一篇文章接着讲!!