简介
IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线,主要用于近距离、低速的芯片之间的通信;IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;IIC总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用
IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生; 每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器。
本质 :i2c总线本质上是基于bus_type的(platform也是),是在bus_type的基础上做了二层封装(platform做了一层封装)。
IIC总线通信过程
- 主机发送起始信号启用总线
- 主机发送一个字节数据指明从机地址(高7位)和后续字节的传送方向
- 被寻址的从机发送应答信号回应主机
- 发送器发送一个字节数据
- 接收器发送应答信号回应发送器
… … (循环步骤4、5)
n. 通信完成后主机发送停止信号释放总线
***IIC的读写流程
读操作: 从从设备的指定寄存器中读取len个字节的数据放入到buf当中
mpu6050_i2c_read(uint8 slave_addr,uint8 reg_addr,int len,uint8 buf[])
IIC初始化 发送start信号 -> (写)发送从机地址 + 0(写)--->等待ACK信号---> 发送从机寄存器地址(8位)------->等待应答信号ACK--->再次产生起始信号start(开始读数据) --->(读)发送从机地址 + 1(读) +读数据到到buf(连续接受len个长度的数据到buf[i]中 /等ACK)按字节从以reg开头的连续的地址中读取 -->发送NACK信号给从机MPU6050-->结束信号
写操作: 从buf中读取len个长度的数据写入到从设备指定的寄存器中
mpu6050_i2c_write(uint8 slave_addr,uint8 reg_addr,int len,uint8 buf[])
IIC初始化 发送start信号 -> (写)发送从机地址 + 0(写)--->等待ACK信号---> 发送从机寄存器地址(8位)------->开始写数据(遍历数组,按字节发送 (写),等待应答)按字节写入到以reg开头的连续的地址中去 -->结束信号
IIC总线寻址方式
IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据。
主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向(低1位,0:主->从,1:从->主) ,'0'表示主机发送数据,'1'表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器。
IIC总线信号实现
起始信号和停止信号
SCL为高电平时,SDA由高变低表示起始信号
SCL为高电平时,SDA由低变高表示停止信号
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态,停止信号产生后总线处于空闲状态
字节传送与应答
IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器即一帧共有9位
同步信号
IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定。
典型IIC时序
主机向从机发送数据
从机向主机发送数据
主机先向从机发送数据,然后从机再向主机发送数据
注:阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送;A表示应答, A非表示非应答,S表示起始信号,P表示终止信号
IIC框架示意图
**I2C设备驱动:**即挂接在I2C总线上的二级外设的驱动,也称客户(client)驱动,实现对二级外设的各种操作,二级外设的几乎所有操作全部依赖于对其自身内部寄存器的读写,对这些二级外设寄存器的读写又依赖于I2C总线的发送和接收
**I2C总线驱动:**即对I2C总线自身控制器的驱动,一般SOC芯片都会提供多个I2C总线控制器,每个I2C总线控制器提供一组I2C总线(SDA一根+SCL一根),每一组被称为一个I2C通道,Linux内核里将I2C总线控制器叫做适配器(adapter),适配器驱动主要工作就是提供通过本组I2C总线与二级外设进行数据传输的接口,每个二级外设驱动里必须能够获得其对应的adapter对象才能实现数据传输
**I2C核心:**承上启下,为I2C设备驱动和I2C总线驱动开发提供接口,为I2C设备驱动层提供管理多个i2c_driver、i2c_client对象的数据结构,为I2C总线驱动层提供多个i2c_algorithm、i2c_adapter对象的数据结构
四大核心对象之间的关系图
————————————————
原文链接:https://blog.csdn.net/jzxnb521/article/details/140664398
Exynos4412下的IIC控制器
概述:Exynos 4412 SCP简化指令集计算机(RISC)微处理器支持四个多主控间集成电路(I2C)总线串行接口。为了在连接到I2C总线的总线主机和外围设备之间传输信息,我们使用了一条专用的串行数据线(SDA)和串行时钟线(SCL)。SDA线和SCL线都是双向的。在多主I2c总线模式下,多个Exynos 4412 SCP RISC微处理器接收或从从设备传输串行数据。主Exynos 4412 SCP通过I2C总线启动并终止数据传输。Exynos 4412 SCP中的I2C总线采用了标准的I2C总线仲裁程序,实现了多主传输和多从传输。
要控制多主I2C总线操作,必须将值写入这些寄存器:
- 多主I2C总线控制寄存器-I2CCON
- 多主I2C总线控制/状态寄存器-I2CSTAT
- 多主I2C总线Tx/Rx数据移位寄存器-I2CDS
- 多主I2C总线地址寄存器-I2CADD
如果I2C总线空闲,SDA和SCL线都应处于高级别。SDA的从高到从低的转换启动了一个启动条件。SDA的从低到高的转变启动了一个停止条件,而SCL在高水平上保持稳定。主设备总是生成启动和停止条件。启动后,数据字节地址值7位通过SDA行传输。此地址值确定总线主设备所选择的从属设备。第8位决定了传输的方向(读或写)。放在SDA行上的每个数据字节总共应该是8位。在总线传输操作期间,没有发送或接收字节的限制。I2C主设备和从设备总是首先从最重要的位(MSB)发送数据,然后确认(ACK)位立即跟随每个字节。
I2C总线接口的特点是:
- 9通道多主通道、从属I2C总线接口(8通道通用,1通道专用高清多媒体接口(HDMI))。
- 7-位寻址模式
- 串行、8位定向和双向数据传输
- 支持在标准模式下的100kbit/秒
- 支持在快速模式下的400kbit/秒。
- 支持主传输、主接收、从传输和从接收操作
- 支持中断或轮询事件
在执行任何I2C Tx/Rx操作之前:1.如果需要,请在I2CADD寄存器上写下自己的从属地址。 2.设置I2CCON寄存器:a)启用中断。b)定义SCL周期。 3.设置I2CSTAT以启用串行输出。
下图说明了主机/发射机模式的操作
下图说明了主模式/接收器模式的操作
图29-8说明了从属机/发射机模式的操作。
图29-9说明了从属机/接收机模式的操作。
IIC寄存器详解
第7位:应答信号的开关(主机做接收)。
第6位:时钟源分频选择,影响的是IIC通信速度。
第5位:中断信号功能的开关。
第4位:中断挂起标志位。(需要第5位打开)当第四位变为1了 说明中断发生了 需要手动清零
第[3:0]位:发送时钟的值。
第[7:6]位:模式选择
第5位:繁忙信号(1:起始信号/0:终止信号)
第4位:1:使能接收和发送 0:关闭接收和发送
I2CADDn :设置4412作为从机的地址
I2CDSn (n = 0 to 7):数据移位寄存器
MPU6050原理
MPU6050是一个运动处理传感器,其内部集成了3轴加速度传感器和3轴陀螺仪(角速度传感器),以及一个可扩展数字运动处理器。
- 可测量X、Y、Z轴三个方向的角速度
- 可编程设置角速度测量范围为±250、±500、±1000、±2000°/sec
- 可测量X、Y、Z轴三个方向的加速度
- 可编程设置加速度测量范围为±2g、±4g、±8g、±16g
- 可编程设置低功耗模式
- 可编程设置采样频率
... ...
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 //存放X轴加速度结果(高8位)
#define ACCEL_XOUT_L 0x3C //存放X轴加速度结果(低8位)
#define ACCEL_YOUT_H 0x3D //y
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F //z
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41 //温度传感器(高8位)
#define TEMP_OUT_L 0x42 //温度传感器(低8位)
#define GYRO_XOUT_H 0x43 //x角速度
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45 //y角速度
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47 //z角速度
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define SlaveAddress 0x68 //MPU6050-I2C地址
MPU6050寄存器读写时序
向MPU6050的一个寄存器写一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(要写的寄存器的地址(RA))
- 从机发送应答信号
- 主机发送一个字节数据(要写到寄存器的数据)
- 从机发送应答信号
- 主机发送停止信号
从MPU6050的一个寄存器读一个字节的数据
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(写)
- 从机(MPU6050)发送应答信号
- 主机发送一个字节数据(要写的寄存器的地址(RA))
- 从机发送应答信号
- 主机(Exynos4412)发送起始信号
- 主机发送从机地址(MPU6050的地址)及读写方向(读)
- 从机(MPU6050)发送应答信号
- 从机发送一个字节数据(要读的寄存器中的数据)
- 主机发送非应答信号(不再接收更多的数据)
- 主机发送停止信号
#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;
}