基于Cortex-A9分析Linux内核I2C架构

一口Linux
关注

3 -- I2C数据发送/接收移位寄存器

fs4412的i2c总线上挂载了mpu6050

mpu6050每次读取或者要写入数据时,必须先告知从设备要操作的内部寄存器地址(RA),然后紧跟着读取或者写入数据(DATA),内部寄存器的配置和读取一次最多1个data,交互时序如下:

【注意】上述两个时序非常重要,下面我们编写基于linux的驱动编写i2c_msg还要再依赖他。

上述简化时序的术语解释如下

【寄存器使用规则】

下面先提前讲一下具体应用中如何启动和恢复IIC的传输启动或恢复4412的I2C传输有以下两种方法。1) 当IICCON[4]即中断状态位为0时,通过写IICSTAT寄存器启动I2C操作。有以下两种情况。

1--在主机模式,令IICSTAT[5:4]等于0b11,将发出S信号和IICDS寄存器的数据(寻址),令IICSTAT[5:4]等于0b01,将发出P信号。2--在从机模式,令IICSTAT[4]等于1将等待其他主机发出S信号及地址信息。

2)当IICCON[4]即中断状态为1时,表示I2C操作被暂停。在这期间设置好其他寄存器之后,向IICCON[4]写入0即可恢复I2C操作。所谓“设置其他寄存器”,有以下三种情况:

1--对于主机模式,可以按照上面1的方法写IICSTAT寄存器,恢复I2C操作后即可发出S信号和IICDS寄存器的值(寻址),或发出P信号。2--对于发送器,可以将下一个要发送的数据写入IICDS寄存器中,恢复I2C操作后即可发出这个数据。3--对于接收器,可以从IICDS寄存器读出接收到的数据。最后向IICCON[4]写入0的同时,设置IICCON[7]以决定是否在接收到下一个数据后是否发出ACK信号。MPU6050

MPU-6000(6050)为全球首例整合性6轴运动处理组件,相较于多组件方案,免除了组合陀螺仪与加速器时间轴之差的问题,减少了大量的封装空间。当连接到三轴磁强计时,MPU-60X0提供完整的9轴运动融合输出到其主I2C或SPI端口(SPI仅在MPU-6000上可用)。

MPU-6000(6050)的角速度全格感测范围为±250、±500、±1000与±2000°/sec (dps),可准确追踪快速与慢速动作,并且,用户可程式控制的加速器全格感测范围为±2g、±4g±8g与±16g。

产品传输可透过最高至400kHz的IIC或最高达20MHz的SPI(MPU-6050没有SPI)。

电路图

【MPU6050硬件电路图】(实际板子电路图不一定和下面一样,具体问题具体分析,本例参考exynos-fs4412开发板)

1 AD0接地的  值为 0

所以从设备地址为0x68;

2 SCL、SDA连接的i2c_SCL5、i2c_SDA5

由此可得这两个信号线复用了GPIO的GPB的2、3引脚;

3 查阅exynos4412 datasheet 6.2.2 Part 1可得

所以设置GPIO 的 GPB     【15:8】= 0x33 即可。

MPU6050内部寄存器

mpu6050内部寄存器的使用,参考datasheet《MPU-6000 and MPU-6050Register Map and Descriptions Revision 4.0 》。

Mpu6050内部有100多个寄存器。比如:

这个寄存器是用来设置加速度属性的,当bit[4:3] 设置为0,表示3个轴的加速度量程最大为±2g。

mpu6050的内部寄存器非常多,并不需要每一个寄存器都需要搞懂,在如下代码实例中,我已经列举出常用的寄存器以及他们的典型值,其他的寄存器不再一一介绍。

下面是个IIC总线实例:

用IIC总线实现CPU与MPU-6050的数据查询

具体代码如下:

/***************************************
// MPU6050常用内部地址,以下地址在mpu6050内部
/***************************************
#define SMPLRT_DIV  0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG   0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG  0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,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 0xD0 //IIC写入时的地址字节数据,+1为读取
typedef struct {
       unsigned int CON;
       unsigned int DAT;
       unsigned int PUD;
       unsigned int DRV;
       unsigned int CONPDN;
       unsigned int PUDPDN;
}gpb;
#define GPB (* (volatile gpb *)0x11400040)
typedef struct {
       unsigned int I2CCON;
       unsigned int I2CSTAT;
       unsigned int I2CADD;
       unsigned int I2CDS;
       unsigned int I2CLC;
}i2c5;
#define  I2C5 (* (volatile i2c5 *)0x138B0000 )
void mydelay_ms(int time)

 int i, j;
 while(time--)
 {
   for (i = 0; i < 5; i++)
     for (j = 0; j < 514; j++);
 }

*********************************************************************
* @brief            iic read a byte program body
* @param[in]    slave_addr, addr, &data
* @return         None
*********************************************************************
void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data)

 根据mpu6050的datasheet,要读取数据必须先执行写操作:写入一个从设备地址,
 然后执行读操作,才能读取到该内部寄存器的内容
 I2C5.I2CDS = slave_addr; //将从机地址写入I2CDS寄存器中
 I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5); //设置时钟并使能中断
 I2C5.I2CSTAT = 0xf0;    //[7:6]设置为0b11,主机发送模式;
 //往[5:4]位写0b11,即产生启动信号,发出IICDS寄存器中的地址
 
 while(!(I2C5.I2CCON & (1 << 4))); // 等待传输结束,传输结束后,I2CCON [4]位为1,标识有中断发生;  
 
 // 此位为1时,SCL线被拉低,此时I2C传输停止;
 I2C5.I2CDS = addr;       //写命令值
 I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));// I2CCON [4]位清0,继续传输
 while(!(I2C5.I2CCON & (1 << 4)));// 等待传输结束
 
 I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位写0b01,发出停止信号
 I2C5.I2CDS = slave_addr | 1;  //表示要读出数据
 
 I2C5.I2CCON = (1 << 7)|(1 << 6) |(1 << 5) ; //设置时钟并使能中断
 I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主机接收模式;
 
 //往[5:4]位写0b11,即产生启动信号,发出IICDS寄存器中的地址
 //    I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));    如果强行关闭,将读取不到数据
 
 while(!(I2C5.I2CCON & (1 << 4)));//等待传输结束,接收数据
 
 I2C5.I2CCON &= ~((1<<7)|(1 << 4)); Resume the operation  & no ack
  // I2CCON [4]位清0,继续传输,接收数据,  
  // 主机接收器接收到最后一字节数据后,不发出应答信号 no ack  
 
 // 从机发送器释放SDA线,以允许主机发出P信号,停止传输;
 while(!(I2C5.I2CCON & (1 << 4)));// 等待传输结束
 
 I2C5.I2CSTAT = 0x90;
 *data = I2C5.I2CDS;
 I2C5.I2CCON &= ~(1<<4);  clean interrupt pending bit  
 mydelay_ms(10);
 *data = I2C5.I2CDS;

*************************************************************
* @brief            iic write a byte program body
* @param[in]    slave_addr, addr, data
* @return         None
************************************************************
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)

 I2C5.I2CDS = slave_addr;
 I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5) ;
 I2C5.I2CSTAT = 0xf0;
 while(!(I2C5.I2CCON & (1 << 4)));
 I2C5.I2CDS = addr;
 I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
 while(!(I2C5.I2CCON & (1 << 4)));
 
 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);

void MPU6050_Init ()

 iic_write(SlaveAddress, PWR_MGMT_1, 0x00);
 iic_write(SlaveAddress, SMPLRT_DIV, 0x07);
 iic_write(SlaveAddress, CONFIG, 0x06);
 iic_write(SlaveAddress, GYRO_CONFIG, 0x18);
 iic_write(SlaveAddress, ACCEL_CONFIG, 0x01);

读取mpu6050某个内部寄存器的内容
int get_data(unsigned char addr)

 char data_h, data_l;
 
 iic_read(SlaveAddress, addr, &data_h);
 iic_read(SlaveAddress, addr+1, &data_l);
 return (data_h<<8)|data_l;

*  裸机代码,不同于LINUX 应用层, 一定加循环控制

int main(void)

 int data;
 unsigned char zvalue;
 GPB.CON = (GPB.CON & ~(0xff<<8)) | 0x33<<8; // GPBCON[3], I2C_5_SCL GPBCON[2], I2C_5_SDAmydelay_ms(100);
 uart_init();
 
 ---------------------------------------------------------------
 I2C5.I2CSTAT = 0xD0;
 I2C5.I2CCON &= ~(1<<4);  clean interrupt pending bit  
 
 --------------------------------------------------------------
 mydelay_ms(100);
 MPU6050_Init();
 mydelay_ms(100);
 printf("********** I2C test!! ***********");
 while(1)
 {
   data = get_data(GYRO_ZOUT_H);
   
   printf(" GYRO --> Z <---:Hex: %x", data);
   data = get_data(GYRO_XOUT_H);
   printf(" GYRO --> X <---:Hex: %x", data);
   
   printf("");
   mydelay_ms(1000);
 }
 return 0;

实验结果如下:

********** I2C test!! ***********  
GYRO --> Z <---:Hex: 1c GYRO --> X <---:Hex: feda  
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6  
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fed6  
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fedc  
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda  
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6  
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda  
GYRO --> Z <---:Hex: fcf2 GYRO --> X <---:Hex: 202  
GYRO --> Z <---:Hex: ec GYRO --> X <---:Hex: faa0  
GYRO --> Z <---:Hex: 4c GYRO --> X <---:Hex: e  
GYRO --> Z <---:Hex: fe GYRO --> X <---:Hex: fed8  
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: fede  
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: feda  
读写操作代码解析:

写入一个数据流程:

读数据流程:

上图阅读注意点:

从设备地址是在用的时候应该左移一位|读写位,比如写reg=0x68<1|0,即0xD0;主设备发出S信号,需要将I2CSTATn 的bite:5设置为1;主设备发出p信号,需要将I2CSTATn 的bite:5设置为0;主机发送数据需要将寄存器I2CCONn的bit:4置0,to reume the operation;主机等待从设备发送的ack或者data,需要轮训判断I2CCONn的bit:4是否置1;代码的理解除了结合功能流程图、时序图、源代码还要结合寄存器说明;代码的编写顺序必须严格按照时序和模块流程图执行;时序中的每一个数据信号(包括ack、data、reg)的产生或者发送对应的代码都用箭头以及相同的颜色框处;对1于read操作,NACK的回复需要在接收最后一个data之前设置I2CCONn :7位为0,这样在收到从设备的data后,才会将SDA拉低。


声明: 本文由入驻OFweek维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存