Jetson Nano I2C说明及Python案例

1、Nano I2C 硬件

Nano 七组硬件I2C 总线及对应关系: (分别有1.8V和3.3V)

Nano I2C Bus.png
2、Jetson Nano + IMU (MPU6050)

惯性测量单元(IMU)是一种电子设备,它使用加速度计和陀螺仪(有些搭配磁场传感器件)组合来测量和报告当前设备的速度、方向和重力信息。

硬件连接 (Nano+MPU6050)

1.VCC ---接Nano引脚第17脚; (接 5V 非3.3V)
2.GND ---接Nano 引脚第25脚;
3.SCL ---接Nano 引脚第5脚(GEN2_I2C_SCL),电平已转换成3.3V电平;
4.SDA ---接Nano 引脚第3脚(GEN2_I2C_SDA),电平已转换成3.3V电平;

软件配置
默认Nano L4T 的内核Kernel Image已包含了MPU6050 I2C Driver,不需要修改Kernel 代码, 但需要修改DTS 以支持MPU6050对应I2C和寄存器配置!

hardware\nvidia\platform\t210\porg\kernel-dts\tegra210-porg-p3448-common.dtsi
根据硬件连接修改如下:
1、去掉/*#include "porg-platforms/tegra210-porg-super-module-e2614.dtsi"*/
2、添加
    i2c@7000c400 {  /*bus1 */
        tw_icm20628: icm20628@68 {
            compatible = "invensense,mpu6xxx";
            reg = <0x68>;
            interrupt-parent = <&gpio>;
            interrupts = <TEGRA_GPIO(Z, 0) 0x01>;
            accelerometer_matrix= [01 00 00 00 01 00 00 00 01];
            gyroscope_matrix    = [01 00 00 00 01 00 00 00 01];
            geomagnetic_rotation_vector_disable = <1>;
            gyroscope_uncalibrated_disable = <1>;
            quaternion_disable = <1>;
            status = "okay";
            
            #address-cells = <1>;
            #size-cells = <0>;
            vcc-supply = <&p3448_vdd_3v3_sys>;
            vcc-pullup-supply = <&p3448_vdd_3v3_sys>;
        };
    };
3、烧录dtb 文件
sudo ./flash.sh -r -k DTB jetson-nano-qspi-sd mmcblk0p1

4、检查I2C 设备 (可查看dmesg log), 若显示UU,则参考附录中的说明,检查I2C 的DTS 配置是否有地址相同设备!
nvidia@tw-nano:~$ sudo i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

5、i2c-tools 读写是否成功以验证设备连接
sudo i2cdump -f -y 1 0x86
sudo i2cget -y 1 0x68 0x41   (TEMPERATURE-OUT0)

PS: MPU6050 特性及应用

  • MPU-6000(6050)的角速度测量范围为±250、±500、±1000与±2000°/sec (dps),可准确追踪快速与慢速动作;
  • MPU-6000(6050)的加速器测量范围为±2g、±4g±8g与±16g;
  • 以数字输出6轴或9轴的旋转矩阵、四元数(quaternion)、欧拉角格式(Euler Angle forma)的融合演算数据
  • 数字运动处理(DMP: Digital Motion Processing)引擎可减少复杂的融合演算数据、感测器同步化、姿势感应等的负荷
  • 通讯采用400kHz的IIC或最高达20MHz的SPI(MPU-6050没有SPI);
  • MPU-6000(6050)可在不同电压下工作,VDD供电为2.5V±5%、3.0V±5%或3.3V±5%,逻辑接口VDDIO供电为1.8V± 5%(MPU6000仅用VDD);
  • 应用场景:姿势识别、遥控拍摄、画面放大缩小、滚动、快速下降中断、high-G中断、零动作感应、触击感应、摇动感应功能等等
3、 Python + I2C 读取陀螺仪数据

Step1. 获取用户对I2C总线操作权限 (ex. nvidia as the login name)

$sudo usermod -aG i2c $USER
sudo usermod -aG i2c nvidia
--- 重启系统确保更改I2C等有效 ---

Step2. MPU6050 运行结果如下:

jetbot@jetbot:~/work/mpu-6050-Python$ python3 example.py 
Accelerometer data
x: -7.570465649414062
y: 6.232106921386718
z: 0.7589619262695312
Gyroscope data
x: 1.5801526717557253
y: 3.2900763358778624
z: 0.6793893129770993
Temp: 44.906470588235294oC
Accelerometer data
x: -7.615955480957031
y: 6.241683728027343
z: 0.6344634399414062
Gyroscope data
x: 1.6870229007633588
y: 3.3969465648854964
z: 1.1984732824427482
Temp: 45.00058823529412oC

Step3. Python example案例代码

"""This program handles the communication over I2C
between a Jetson Nano and a MPU-6050 Gyroscope / Accelerometer combo.
Made by: Dennis/TW
Released under the MIT License
Copyright 2019
"""

import smbus

class mpu6050:

    # Global Variables
    GRAVITIY_MS2 = 9.80665
    address = None
    bus = smbus.SMBus(1)

    # Scale Modifiers
    ACCEL_SCALE_MODIFIER_2G = 16384.0
    ACCEL_SCALE_MODIFIER_4G = 8192.0
    ACCEL_SCALE_MODIFIER_8G = 4096.0
    ACCEL_SCALE_MODIFIER_16G = 2048.0

    GYRO_SCALE_MODIFIER_250DEG = 131.0
    GYRO_SCALE_MODIFIER_500DEG = 65.5
    GYRO_SCALE_MODIFIER_1000DEG = 32.8
    GYRO_SCALE_MODIFIER_2000DEG = 16.4

    # Pre-defined ranges
    ACCEL_RANGE_2G = 0x00
    ACCEL_RANGE_4G = 0x08
    ACCEL_RANGE_8G = 0x10
    ACCEL_RANGE_16G = 0x18

    GYRO_RANGE_250DEG = 0x00
    GYRO_RANGE_500DEG = 0x08
    GYRO_RANGE_1000DEG = 0x10
    GYRO_RANGE_2000DEG = 0x18

    # MPU-6050 Registers
    PWR_MGMT_1 = 0x6B
    PWR_MGMT_2 = 0x6C

    SELF_TEST_X = 0x0D
    SELF_TEST_Y = 0x0E
    SELF_TEST_Z = 0x0F
    SELF_TEST_A = 0x10

    ACCEL_XOUT0 = 0x3B
    ACCEL_XOUT1 = 0x3C
    ACCEL_YOUT0 = 0x3D
    ACCEL_YOUT1 = 0x3E
    ACCEL_ZOUT0 = 0x3F
    ACCEL_ZOUT1 = 0x40

    TEMP_OUT0 = 0x41
    TEMP_OUT1 = 0x42

    GYRO_XOUT0 = 0x43
    GYRO_XOUT1 = 0x44
    GYRO_YOUT0 = 0x45
    GYRO_YOUT1 = 0x46
    GYRO_ZOUT0 = 0x47
    GYRO_ZOUT1 = 0x48

    ACCEL_CONFIG = 0x1C
    GYRO_CONFIG = 0x1B

    def __init__(self, address):
        self.address = address

        # Wake up the MPU-6050 since it starts in sleep mode
        self.bus.write_byte_data(self.address, self.PWR_MGMT_1, 0x00)

    # I2C communication methods

    def read_i2c_word(self, register):
        """Read two i2c registers and combine them.

        register -- the first register to read from.
        Returns the combined read results.
        """
        # Read the data from the registers
        high = self.bus.read_byte_data(self.address, register)
        low = self.bus.read_byte_data(self.address, register + 1)

        value = (high << 8) + low

        if (value >= 0x8000):
            return -((65535 - value) + 1)
        else:
            return value

    # MPU-6050 Methods

    def get_temp(self):
        """Reads the temperature from the onboard temperature sensor of the MPU-6050.

        Returns the temperature in degrees Celcius.
        """
        # Get the raw data
        raw_temp = self.read_i2c_word(self.TEMP_OUT0)

        # Get the actual temperature using the formule given in the
        # MPU-6050 Register Map and Descriptions revision 4.2, page 30
        actual_temp = (raw_temp / 340) + 36.53

        # Return the temperature
        return actual_temp

    def set_accel_range(self, accel_range):
        """Sets the range of the accelerometer to range.

        accel_range -- the range to set the accelerometer to. Using a
        pre-defined range is advised.
        """
        # First change it to 0x00 to make sure we write the correct value later
        self.bus.write_byte_data(self.address, self.ACCEL_CONFIG, 0x00)

        # Write the new range to the ACCEL_CONFIG register
        self.bus.write_byte_data(self.address, self.ACCEL_CONFIG, accel_range)

    def read_accel_range(self, raw = False):
        """Reads the range the accelerometer is set to.

        If raw is True, it will return the raw value from the ACCEL_CONFIG
        register
        If raw is False, it will return an integer: -1, 2, 4, 8 or 16. When it
        returns -1 something went wrong.
        """
        # Get the raw value
        raw_data = self.bus.read_byte_data(self.address, self.ACCEL_CONFIG)

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.ACCEL_RANGE_2G:
                return 2
            elif raw_data == self.ACCEL_RANGE_4G:
                return 4
            elif raw_data == self.ACCEL_RANGE_8G:
                return 8
            elif raw_data == self.ACCEL_RANGE_16G:
                return 16
            else:
                return -1

    def get_accel_data(self, g = False):
        """Gets and returns the X, Y and Z values from the accelerometer.

        If g is True, it will return the data in g
        If g is False, it will return the data in m/s^2
        Returns a dictionary with the measurement results.
        """
        # Read the data from the MPU-6050
        x = self.read_i2c_word(self.ACCEL_XOUT0)
        y = self.read_i2c_word(self.ACCEL_YOUT0)
        z = self.read_i2c_word(self.ACCEL_ZOUT0)

        accel_scale_modifier = None
        accel_range = self.read_accel_range(True)

        if accel_range == self.ACCEL_RANGE_2G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_2G
        elif accel_range == self.ACCEL_RANGE_4G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_4G
        elif accel_range == self.ACCEL_RANGE_8G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_8G
        elif accel_range == self.ACCEL_RANGE_16G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_16G
        else:
            print("Unkown range - accel_scale_modifier set to self.ACCEL_SCALE_MODIFIER_2G")
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_2G

        x = x / accel_scale_modifier
        y = y / accel_scale_modifier
        z = z / accel_scale_modifier

        if g is True:
            return {'x': x, 'y': y, 'z': z}
        elif g is False:
            x = x * self.GRAVITIY_MS2
            y = y * self.GRAVITIY_MS2
            z = z * self.GRAVITIY_MS2
            return {'x': x, 'y': y, 'z': z}

    def set_gyro_range(self, gyro_range):
        """Sets the range of the gyroscope to range.

        gyro_range -- the range to set the gyroscope to. Using a pre-defined
        range is advised.
        """
        # First change it to 0x00 to make sure we write the correct value later
        self.bus.write_byte_data(self.address, self.GYRO_CONFIG, 0x00)

        # Write the new range to the ACCEL_CONFIG register
        self.bus.write_byte_data(self.address, self.GYRO_CONFIG, gyro_range)

    def read_gyro_range(self, raw = False):
        """Reads the range the gyroscope is set to.

        If raw is True, it will return the raw value from the GYRO_CONFIG
        register.
        If raw is False, it will return 250, 500, 1000, 2000 or -1. If the
        returned value is equal to -1 something went wrong.
        """
        # Get the raw value
        raw_data = self.bus.read_byte_data(self.address, self.GYRO_CONFIG)

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.GYRO_RANGE_250DEG:
                return 250
            elif raw_data == self.GYRO_RANGE_500DEG:
                return 500
            elif raw_data == self.GYRO_RANGE_1000DEG:
                return 1000
            elif raw_data == self.GYRO_RANGE_2000DEG:
                return 2000
            else:
                return -1

    def get_gyro_data(self):
        """Gets and returns the X, Y and Z values from the gyroscope.

        Returns the read values in a dictionary.
        """
        # Read the raw data from the MPU-6050
        x = self.read_i2c_word(self.GYRO_XOUT0)
        y = self.read_i2c_word(self.GYRO_YOUT0)
        z = self.read_i2c_word(self.GYRO_ZOUT0)

        gyro_scale_modifier = None
        gyro_range = self.read_gyro_range(True)

        if gyro_range == self.GYRO_RANGE_250DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_250DEG
        elif gyro_range == self.GYRO_RANGE_500DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_500DEG
        elif gyro_range == self.GYRO_RANGE_1000DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_1000DEG
        elif gyro_range == self.GYRO_RANGE_2000DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_2000DEG
        else:
            print("Unkown range - gyro_scale_modifier set to self.GYRO_SCALE_MODIFIER_250DEG")
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_250DEG

        x = x / gyro_scale_modifier
        y = y / gyro_scale_modifier
        z = z / gyro_scale_modifier

        return {'x': x, 'y': y, 'z': z}

    def get_all_data(self):
        """Reads and returns all the available data."""
        temp = get_temp()
        accel = get_accel_data()
        gyro = get_gyro_data()

        return [accel, gyro, temp]

if __name__ == "__main__":
    mpu = mpu6050(0x68)
    print(mpu.get_temp())
    accel_data = mpu.get_accel_data()
    print(accel_data['x'])
    print(accel_data['y'])
    print(accel_data['z'])
    gyro_data = mpu.get_gyro_data()
    print(gyro_data['x'])
    print(gyro_data['y'])
    print(gyro_data['z'])


I2C Python 方法可以参考:
MPU-6050-Python
Raspberry-Pi-I2C-Python
NV Forum TX2-MPU6050

附录:
  • I2C 相关
nvidia@tw-nano:~$ sudo i2cdetect -F 2
Functionalities implemented by /dev/i2c-2:
I2C                              yes
SMBus Quick Command              no
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 no
SMBus Block Process Call         no
SMBus PEC                        yes
I2C Block Write                  yes

案例:
GEN3_I2C_SDA/SCL (PIN232/234) 连接的是EEPROM(AT24C02D)

nvidia@tw-nano:~$ sudo i2cdetect -y -r 2
Warning: Can't use SMBus Quick Write command, will skip some addresses
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                                                 
10:                                                 
20:                                                 
30: -- -- -- -- -- -- -- --                         
40:                                                 
50: 50 -- -- -- -- -- -- 57 -- -- -- -- -- -- -- -- 
60:                                                 
70:                                                 

From the i2cdetect man page:

INTERPRETING THE OUTPUT
       Each  cell  in  the  output  table  will  contain  one of the following
       symbols:

       · "--". The address was probed but no chip answered.

       · "UU". Probing was skipped, because this address is currently  in  use
         by  a  driver.  This  strongly  suggests that there is a chip at this
         address.

       · An address number in hexadecimal, e.g. "2d" or "4e". A chip was found
         at this address.
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-0/name 
7000c000.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-1/name 
7000c400.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-2/name 
7000c500.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-3/name 
7000c700.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-4/name 
7000d000.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-5/name 
7000d100.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-6/name 
Tegra I2C adapter
nvidia@tw-nano:~$ ls /sys/firmware/devicetree/base/i2c@7000*
/sys/firmware/devicetree/base/i2c@7000c000:
'#address-cells'   clock-names   compatible   dmas         iommus          name      reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'   temp-sensor@4c

/sys/firmware/devicetree/base/i2c@7000c400:
'#address-cells'   clock-frequency   clocks       compatible   dmas      i2cmux@70     interrupts   iqs263@44       name      reg           resets            '#size-cells'
 ak8963@0d         clock-names       cm32180@48   dma-names    gpio@20   icm20628@68   iommus       linux,phandle   phandle   reset-names   rt5659.1-001a@1a   status

/sys/firmware/devicetree/base/i2c@7000c500:
'#address-cells'      battery-gauge@55   clock-names   compatible   dmas         iommus          name      reg           resets         status
 battery-charger@6b   clock-frequency    clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'

/sys/firmware/devicetree/base/i2c@7000c700:
'#address-cells'   clock-names   compatible   dmas         iommus          name                         phandle            reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   nvidia,restrict-clk-change   print-rate-limit   reset-names  '#size-cells'

/sys/firmware/devicetree/base/i2c@7000d000:
'#address-cells'   clocks       dmas         linux,phandle   max77621@1c                      nvidia,require-cldvfs-clock   reset-names   sda-gpio
 clock-frequency   compatible   interrupts   max77620@3c     name                             phandle                       resets       '#size-cells'
 clock-names       dma-names    iommus       max77621@1b     nvidia,bit-bang-after-shutdown   reg                           scl-gpio      status

/sys/firmware/devicetree/base/i2c@7000d100:
'#address-cells'   clock-names   compatible   dmas         iommus          name      reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'

root@jetbot:~/work/mpu-6050-Python# i2cdump -y 1 0x68
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 83 01 00 4d d4 e1 02 ce 0d 61 04 0c 28 52 52 b7    ??.M?????a??(RR?
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 01 f1 0c 1c cc c6    ..........??????
40: 74 f5 50 00 00 00 00 00 00 00 00 00 00 00 00 00    t?P.............
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 3f 00 00 72    ............?..r
70: 00 00 00 00 00 68 00 00 00 00 00 00 00 00 00 00    .....h..........
80: 83 01 00 4d d4 e1 02 ce 0d 61 04 0c 28 52 52 b7    ??.M?????a??(RR?
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 01 f1 0c 1c cc c6    ..........??????
c0: 74 f5 60 00 00 00 00 00 00 00 00 00 00 00 00 00    t?`.............
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 3f 00 03 df    ............?.??
f0: 00 00 00 00 00 68 00 00 00 00 00 00 00 00 00 00    .....h..........

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容