I2C同步串行通信
SPI通信虽然功能更强大了,但是需要4根线做连接,还是有点复杂,接下来I2C的连线就简单很多了。

I2C通信原理

I2C也是一种常用的串行通信方式,和SPI一样可以连接多个设备,重点是它只需要两根线就可以完成
不过他的两根线和UART不同,不全是传输数据用的,I2C中的一根线是时钟线,另一根才是传输数据的,这根线可以双向的传输数据。

I2C通信中可以有多个主设备或者从设备,只要能通过地址找到彼此的位置即可。

主器件用于启动总线传送数据,并产生时钟给各种从机,此时任何被寻址的器件均被认为是从机。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。

如果主机要发送数据给从机,主机首先得找到从机的地址,然后主动发送数据过去,最后也得由主机终止数据传送;

如果主机要接收从机的数据,同样由主机得找到从机的地址,然后主机接收从器件发送的数据,最后由主机终止接收过程。

因为I2C的特性,使用I2C的设备比SPI多很多,比如图中的紫外线传感器,陀螺仪之类的,都是使用I2C进行通信的。

硬件接线

我们找来一个常用的陀螺仪模块,按照这里的接线图连接到旭日X3派的40PIN接口上,这里除了I2C通信的两根线之外,另外两根是电源线,负责给这个模块供电的,让它正常工作起来。

运行示例程序

接线是挺简单的,我们继续来运行这个例程,看下能否收到数据。
bash
$ sudo python3 mpu6500_i2c.py
在终端启动例程,很快就可以看到通过I2C读取到了大量传感器的数据,这些就是陀螺仪模块的原始数据,收到之后我们就可以进行结算处理啦,这就是我们后续机器人开发的需要解决的问题了。


代码解析

mpu6500_i2c.py:

python
#!/usr/bin/env python3

import smbus,time

def MPU6050_start():
    # alter sample rate (stability)
    samp_rate_div = 0 # sample rate = 8 kHz/(1+samp_rate_div)
    bus.write_byte_data(MPU6050_ADDR, SMPLRT_DIV, samp_rate_div)
    time.sleep(0.1)
    # reset all sensors
    bus.write_byte_data(MPU6050_ADDR,PWR_MGMT_1,0x00)
    time.sleep(0.1)
    # power management and crystal settings
    bus.write_byte_data(MPU6050_ADDR, PWR_MGMT_1, 0x01)
    time.sleep(0.1)
    #Write to Configuration register
    bus.write_byte_data(MPU6050_ADDR, CONFIG, 0)
    time.sleep(0.1)
    #Write to Gyro configuration register
    gyro_config_sel = [0b00000,0b010000,0b10000,0b11000] # byte registers
    gyro_config_vals = [250.0,500.0,1000.0,2000.0] # degrees/sec
    gyro_indx = 0
    bus.write_byte_data(MPU6050_ADDR, GYRO_CONFIG, int(gyro_config_sel[gyro_indx]))
    time.sleep(0.1)
    #Write to Accel configuration register
    accel_config_sel = [0b00000,0b01000,0b10000,0b11000] # byte registers
    accel_config_vals = [2.0,4.0,8.0,16.0] # g (g = 9.81 m/s^2)
    accel_indx = 0                            
    bus.write_byte_data(MPU6050_ADDR, ACCEL_CONFIG, int(accel_config_sel[accel_indx]))
    time.sleep(0.1)
    # interrupt register (related to overflow of data [FIFO])
    bus.write_byte_data(MPU6050_ADDR, INT_ENABLE, 1)
    time.sleep(0.1)
    return gyro_config_vals[gyro_indx],accel_config_vals[accel_indx]
    
def read_raw_bits(register):
    # read accel and gyro values
    high = bus.read_byte_data(MPU6050_ADDR, register)
    low = bus.read_byte_data(MPU6050_ADDR, register+1)

    # combine higha and low for unsigned bit value
    value = ((high << 8) | low)
    
    # convert to +- value
    if(value > 32768):
        value -= 65536
    return value

def mpu6050_conv():
    # raw acceleration bits
    acc_x = read_raw_bits(ACCEL_XOUT_H)
    acc_y = read_raw_bits(ACCEL_YOUT_H)
    acc_z = read_raw_bits(ACCEL_ZOUT_H)

    # raw temp bits
##    t_val = read_raw_bits(TEMP_OUT_H) # uncomment to read temp
    
    # raw gyroscope bits
    gyro_x = read_raw_bits(GYRO_XOUT_H)
    gyro_y = read_raw_bits(GYRO_YOUT_H)
    gyro_z = read_raw_bits(GYRO_ZOUT_H)



    #convert to acceleration in g and gyro dps
    a_x = (acc_x/(2.0**15.0))*accel_sens
    a_y = (acc_y/(2.0**15.0))*accel_sens
    a_z = (acc_z/(2.0**15.0))*accel_sens

    w_x = (gyro_x/(2.0**15.0))*gyro_sens
    w_y = (gyro_y/(2.0**15.0))*gyro_sens
    w_z = (gyro_z/(2.0**15.0))*gyro_sens

##    temp = ((t_val)/333.87)+21.0 # uncomment and add below in return
    return a_x,a_y,a_z,w_x,w_y,w_z
    
# MPU6050 Registers
MPU6050_ADDR = 0x68
PWR_MGMT_1   = 0x6B
SMPLRT_DIV   = 0x19
CONFIG       = 0x1A
GYRO_CONFIG  = 0x1B
ACCEL_CONFIG = 0x1C
INT_ENABLE   = 0x38
ACCEL_XOUT_H = 0x3B
ACCEL_YOUT_H = 0x3D
ACCEL_ZOUT_H = 0x3F
TEMP_OUT_H   = 0x41
GYRO_XOUT_H  = 0x43
GYRO_YOUT_H  = 0x45
GYRO_ZOUT_H  = 0x47

# start I2C driver
bus = smbus.SMBus(0) # start comm with i2c bus
gyro_sens,accel_sens = MPU6050_start() # instantiate gyro/accel
while True:
    print(mpu6050_conv())
这里我们看一下怎么使用I2C和陀螺仪通信来获取信息。主体上有三个大的函数,而这三个函数的功能都比较单一,第一个函数通过给陀螺仪发送数据来进行初始化的设置,比如配置陀螺仪的电源寄存器,加速度寄存器等;第二个函数则是从陀螺仪读取数据;第三个函数则是将读取到的函数进行计算,从而变成真正的加速度等信息。

具体的读写操作可以看下面的代码片段,也是直接调用函数指定地址和数据就好了。

这里我们也能看到几个关键的参数,比如陀螺仪MPU6050的地址是0x68,通过这个地址才能确认到陀螺仪的存在,然后进行初始化,初始化完成之后进入循环,不停的将数据打印出来。