目录
一、 IIC总线概述
1. IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线主要用于近距离、低速的芯片之间的通信;IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;IIC总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用
2. IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器;
3.IIC总线通信过程
- 主机发送起始信号启用总线 (告诉其他设备,总线被占用了)
- 主机发送一个字节数据指明从机地址(主机与哪个从机通信)和后续字节的传送方 (从机地址7位,最后一位表示传输方向:0主发给从,1从发给主)
- 被寻址的从机发送应答信号回应主机
- 发送器发送一个字节数据
- 接收器发送应答信号回应发送器 … … (循环步骤4、5)
- n.通信完成后主机发送停止信号释放总线
4.IIC总线寻址方式
- IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据**
- 主机在发送起始信号后必须先发送一个字节的数据(地址+方向),该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0’表示主机发送数据,'1’表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器
二、IIC总线信号实现
1.起始信号和停止信号
两根线SDA 和 SCL 正常均为高电平
SCL为高电平时,SDA由高变低表示起始信号
SCL为高电平时,SDA由低变高表示停止信号
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态停止信号产生后总线处于空闲状态
2.字节传达与应答
IC总线通信时每个字节为8位长度(串口通信可以为5-8位),数据传送时,先传送最高位,后传送低位(串口先发低位),发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器,即一帧共有9位
3.同步信号
如何区分发送了10还是1100:时钟线SCL保证收发双方时钟同步(串口通过波特率来区分)
IIC总线在进行数据传送时,时钟线SCL为低电平期间,发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间是为了给接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定
三、IIC典型时序
阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送;A表示应答, A非表示非应答,S表示起始信号,P表示终止信号
1.主机向从机发送数据
停止发送:① 主机不想发了:主机发送停止信号;② 从机不想接受了:回复拒绝应答信号,然后主机发送停止
2.从机向主机发送数据(第一个字节一定是主机,这个是寻址完后)
停止发送:主机不想接收,主机发送停止接收信号,再发送停止信号
3.主机先向从机发送数据,然后从机再向主机发送数据(不能一次完成)
四、Exynos4412下的IIC控制器
要控制多主I2C总线操作,必须将值写入这些寄存器:
- 多主控I2C总线控制寄存器-I2CCON:控制IIC
- 多主控器I2C总线控制/状态寄存器-I2CSTAT:IIC状态
- 多主I2C总线Tx/Rx数据移位寄存器-I2CDS:发送和接收数据
- 多主I2C总线地址寄存器-I2CADD:设置IIC设备的地址
I2C总线接口的特点:
- 9通道多主、从I2C总线接口(8路通用,1路高清多媒体接口(HDMI)专用)
- 7位寻址模式
- 串行,8位导向,双向数据传输
- 在标准模式下支持最高100kbit /s
- 在Fast模式下支持最高400kbit /s。
- 支持主发送、主接收、从发送和从接收操作
- 支持中断或轮询事件
1.工作逻辑
2.控制流程
2.1主机发送模式
2.2主机接收模式
2.3从机发送数据
2.4从机接收模式
4412中很少使用主机变从机使用
五、IIC寄存器详解
5.1IICCON
5.2 IICSTATn寄存器(n = 0 to 7) :控制IIC功能和显示IIC状态
5.3 IICADD :设置IIC设备地址,一般当4412当成从机的时候才会用到
5.4 I2CDSn寄存器(n = 0 to 7)
5.5 I2CLCn寄存器(n = 0 to 7) :滤波相关的寄存器,这里用不到
六、MPU6050原理
(4412与MPU6050通信)MPU6050是一个运动处理传感器,其内部集成了3轴加速度传感器 和3轴陀螺仪(角速度传感器),以及一个可扩展数字运动处理器(测量内部陀螺仪的阻力来测的加速度)
1.MPU6050主要参数
- 可测量X、Y、Z轴三个方向的角速度
- 可编程设置角速度测量范围为±250、±500、±1000、±2000°/sec
- 可测量X、Y、Z轴三个方向的加速度
- 可编程设置加速度测量范围为±2g、±4g、±8g、±16g
- 可编程设置低功耗模式
- 可编程设置采样频率
2.MPU6050通信接口
MPU6050可以使用IIC总线和其他器件进行数据交互,我们可以使用IIC总线向MPU6050中的控制寄存器写入数据来设置MPU6050的工作参数;也可以使用IIC总线从MPU6050中的数据寄存器读取数据来获取加速度、角速度等信息
3.常用寄存器
/****************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地址
通信时,防止地址冲突,设计了多个地址。当ADO接地,即为0,此时6050的地址为1101000,即为0x68
七、MPU6050寄存器读写时序
7.1 向MPU6050的一个寄存器写一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(确定目标寄存器的地址)
- 从机发送应答信号
- 主机发送一个字节数据(要写到寄存器的数据)
- 从机发送应答信号
- 主机发送停止信号
7.2 从MPU6050的一个寄存器读一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(要写的寄存器的地址)
- 从机发送应答信号
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(读)
- 从机(MPU6050)发送应答信号
- 从机发送一个字节数据(要读的寄存器中的数据)
- 主机发送非应答信号(不再接收更多的数据)
- 主机发送停止信号
八、IIC编程-陀螺仪实验代码分析
#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
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储读取结果
short int zvalue;
/*设置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
printf("\n********** I2C test!! ***********\n");
while(1)
{
zvalue_h = iic_read(SlaveAddress, GYRO_ZOUT_H); //获取MPU6050-Z轴角速度高字节
zvalue_l = iic_read(SlaveAddress, GYRO_ZOUT_L); //获取MPU6050-Z轴角速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴角速度
printf(" GYRO--Z :Hex: %d \n", zvalue); //打印MPU6050-Z轴角速度
mydelay_ms(100);
}
return 0;
}