树莓派5模拟I2C通信读取MPU6050数据

树莓派有I2C通信接口,需要开启并使用特定引脚,然后使用smbus进行通信使用。

为了加深对于I2C通信过程中的信号的处理,通过使用GPIO模拟I2C通信,完成MPU6050的数据读取。

from gpiozero import DigitalOutputDevice, DigitalInputDevice
from time import sleep


class I2C:
    def __init__(self, scl_pin, sda_pin):
        self.scl_pin = scl_pin
        self.sda_pin = sda_pin
        # init output default high
        self.scl = DigitalOutputDevice(self.scl_pin)
        self.sda = DigitalOutputDevice(self.sda_pin)
    
    def start(self):
        '''
        每一次完成通信过程,需要以start方式开始
        '''
        self.chang_write_mode()
        self.scl.value = 1
        self.sda.value = 1
        self.sda.value = 0
        self.scl.value = 0

    def stop(self):
        '''
        每一次完成通信过程,需要以stop方式结束
        '''
        self.chang_write_mode()
        self.sda.value = 0
        self.scl.value = 1
        self.sda.value = 1

    def send_data(self, address, reg_add, *datas):
        '''
        向指定i2c设备、以及i2c设备的寄存器地址,发送数据
        '''
        address_w = (address << 1) | 0x00
        address_r = address << 1 | 0x01
        self.start()
        self.send_byte(address_w)
        self.send_byte(reg_add)
        for data in datas:
            self.send_byte(data)
        i2c.stop()

    def read_data(self, address, reg_add, data_len=1):
        '''
        从指定i2c设备、以及i2c设备的寄存器地址,读取数据
        '''
        address_w = (address << 1) | 0x00
        address_r = address << 1 | 0x01
        self.start()
        self.send_byte(address_w)
        self.send_byte(reg_add)
        self.start()
        self.send_byte(address_r)
        datas = self.read_bytes(data_len)
        self.stop()
        if len(datas) == 1:
            return datas[0]
        else:
            return datas

    def read_ack(self, func=None):
        self.chang_read_mode()
        ack = self.read_bit()
        if ack != 0:
            self.stop()
            if func is None:
                raise Exception("ack is not 0")
            else:
                func()

    def send_ack(self):
        self.chang_write_mode()
        self.send_bit(0)

    def send_nack(self):
        self.chang_write_mode()
        self.send_bit(1)

    def send_byte(self, value):
        for i in range(0, 8):
            bit = value & (0x80 >> i)
            self.send_bit(bit)
        self.read_ack()

    def send_bit(self, value):
        self.chang_write_mode()
        self.sda.value = value
        sleep(0.001)
        self.scl.value = 1
        self.scl.value = 0

    def read_bytes(self, len=1):
        values = []
        for index in range(0, len):
            value = 0
            for i in range(0, 8):
                bit = self.read_bit()
                value = (value << 1) | bit
            values.append(value)
            if index < len - 1:
                self.send_ack()
        self.send_nack()
        return values

    def read_byte(self):
        value = 0
        for i in range(0, 8):
            bit = self.read_bit()
            value = (value << 1) | bit
        self.send_nack()
        return value

    def read_bit(self):
        self.chang_read_mode()
        self.scl.value = 1
        sleep(0.001)
        value = self.sda.value
        self.scl.value = 0
        return value

    def chang_read_mode(self):
        if type(self.sda) != DigitalInputDevice:
            self.sda.close()
            self.sda = DigitalInputDevice(self.sda_pin)

    def chang_write_mode(self):
        if type(self.sda) != DigitalOutputDevice:
            self.sda.close()
            self.sda = DigitalOutputDevice(self.sda_pin)

# mpu6050的地址
address = 0x68
# 电源管理寄存器地址
power_mgmt_1 = 0x6B
power_mgmt_2 = 0x6C
i2c = I2C(21, 20)
i2c.send_data(address, power_mgmt_1, 0)
# 获取温度数据
word = i2c.read_data(address, 0x41, 2)
value = (word[0] << 8) + word[1]
if value >= 0x8000:
    value = -((65535 - value) + 1)
temperature = value / 340 + 36.53

 

因为是树莓派5,RPi.GPIO无法使用,最后使用的是gpiozero。

如果要读取角速度和加速度信息,read_data中的寄存器地址修改为对应的即可,别忘了比例转换得到实际值。

 

I2C具体交互过程参考了MPU-6000-Datasheet1.pdf (tdk.com)其中的时序图内容。

 

posted @ 2024-01-19 02:05  mahuan2  阅读(336)  评论(0编辑  收藏  举报