rust-embedded / rust-i2cdev

Rust library for interfacing with i2c devices under Linux
Apache License 2.0
205 stars 53 forks source link

i2c write register issue #72

Closed lucviala closed 2 years ago

lucviala commented 2 years ago

Hi,

I'm mainly experienced in C development and I saw in Rust a massive game changer in memory safety & some other things like concurrency, so I started to move to Rust for my developments. My first program was to develop a device driver using I2C bus, I'm on RPI4, but I'm facing one massive issue, I am not able to write register on the device, but I don't know why. There is program samples & strace logs:

From my working C program:

#include <stdio.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int read_status(int fd) {
    int rc = 0;
    uint8_t buf[3] = {0x6a, 0x0, 0x0};
        struct i2c_msg messages[] = {
        {.addr = 0x4b, .flags = 0, .buf = buf, .len = 1},
        {.addr = 0x4b, .flags = I2C_M_RD, .buf = buf, .len = 3},
    };

    struct i2c_rdwr_ioctl_data ioctl_data = {
        .msgs = messages,
        .nmsgs = 2,
    };

    rc = ioctl(fd, I2C_RDWR, &ioctl_data);
    if(rc != 2) {
        perror("Unable to read\n");
        return -1;
    }

    printf("Status: [%x, %x, %x]\n", buf[0], buf[1], buf[2]);
    return 0;
}

int start_fan(int fd, uint8_t start_stop) {
    uint8_t buf[2] = {0x2b, start_stop};
    int ret = 0;

    ret = i2c_smbus_write_byte_data(fd, buf[0], buf[1]);
    if(ret < 0) {
    perror("Failed to start fan");
        return -1;
    }

    return 0;
}

int main() {
    int ret = 0;

    int fd = open("/dev/i2c-1", O_RDWR);

    if (fd < 0) {
        printf("Failed to open i2c-1\n");
        return 1;
    }

    uint8_t addr = 0x4b;

    if (ioctl(fd, I2C_SLAVE, addr) < 0) {
        printf("Failed to acquire bus access/talk to slave\n");
        return 1;
    }

    ret = read_status(fd);
    if(ret < 0) {
        printf("Failed to verify status\n");
        return 1;
    }

    ret = start_fan(fd, 1);
    if(ret < 0) {
        printf("Failed to start fan\n");
        return 1;
    }

    printf("Fan started\n");

    ret = read_status(fd);
    if(ret < 0) {
        printf("Failed to verify status\n");
        return 1;
    }

    close(fd);

    return 0;
}
Status: [0, 78, f0]
Fan started
Status: [1, f1, e1]
openat(AT_FDCWD, "/dev/i2c-1", O_RDWR)  = 3
ioctl(3, _IOC(_IOC_NONE, 0x7, 0x3, 0), 0x4b) = 0
ioctl(3, _IOC(_IOC_NONE, 0x7, 0x20, 0), 0xbee68538) = 0
ioctl(3, _IOC(_IOC_NONE, 0x7, 0x7, 0), 0xbee68570) = 2
write(1, "Status: [0, 78, f0]\n", 20Status: [0, 78, f0]
)   = 20
ioctl(3, _IOC(_IOC_NONE, 0x7, 0x20, 0), 0xbee68538) = 0
write(1, "Fan started\n", 12Fan started
)           = 12
ioctl(3, _IOC(_IOC_NONE, 0x7, 0x7, 0), 0xbee68570) = 2
write(1, "Status: [1, f1, e1]\n", 20Status: [1, f1, e1]
)   = 20
close(3)                                = 0

From my non-working rust program :

device.rs

use embedded_hal::i2c::blocking::{I2c, Operation};
#[cfg(feature = "serde")]
use serde::Serialize;

pub const Devcie_I2C_ADDR: u8 = 0x4b;

#[derive(Debug)]
pub enum Error<E> {
    /// I²C bus error
    I2c(E),
    /// Failed to parse sensor data
    InvalidData,
    /// No calibration data is available (probably forgot to call or check BME280::init for failure)
    NoCalibrationData,
    /// Chip ID doesn't match expected value
    UnsupportedChip,
}

#[derive(Debug, Default)]
pub struct Device<I2C> {
    /// concrete I²C device implementation
    i2c: I2C,
    /// I²C device address
    address: u8,
}

impl<I2C> Device<I2C>
where
    I2C: I2c,
{
    /// Create a new BME280 struct using a custom I²C address
    pub fn new(i2c: I2C, address: u8) -> Self {
        Device { i2c, address }
    }

    pub fn verify_status(&mut self) -> Result<u8, I2C::Error> {
        let status = self.read_register(0x64, 3)?;
        let crc = crc16(&status[..2]);
        Ok(status[0])
    }

    pub fn start_mode(&mut self) -> Result<(), I2C::Error> {
        self.write_register(0x10, 0x2)
    }

    pub fn start_fan(&mut self) -> Result<(), I2C::Error> {
        self.write_register(0x2b, 1)
    }

    fn read_register(&mut self, register: u8, length: u8) -> Result<Vec<u8>, I2C::Error> {
        let mut data: Vec<u8> = vec![0; length as usize];
        let buf: Vec<u8> = vec![register];
        let mut ops = [Operation::Write(&buf), Operation::Read(&mut data)];
        self.i2c.transaction(self.address, &mut ops)?;
        println!("{:x?}", data);

        Ok(data)
    }

    fn write_register(&mut self, register: u8, payload: u8) -> Result<(), I2C::Error> {
        println!("Writing to register 0x{:02x}: 0x{:02x}", register, payload);
        let data: Vec<u8> = vec![register, payload];
        let mut ops = [Operation::Write(&data)];
        self.i2c.transaction(self.address, &mut ops)
    }
}

fn crc16(data: &[u8]) -> u16 {
    let mut _data: u16 = 0;
    let mut crc = 0xffff_u16;

    for i in 0..data.len() {
        _data = 0xff as u16 & data[i] as u16;
        for _ in 0..8 {
            if (crc & 0x0001_u16) ^ (_data & 0x0001_u16) != 0 {
                crc = (crc >> 1) ^ 0x8408_u16;
            } else {
                crc >>= 1;
            }
            _data >>= 1;
        }
    }
    crc = !crc;
    _data = crc;
    crc = (crc << 8) | (_data >> 8 & 0xff);
    crc
}

#[cfg(test)]
#[test]
pub fn test_crc_16() {
    // Test simple command
    let data = [0_u8, 0x78_u8, 0xf0_u8];
    let crc = crc16(&data[..data.len() - 2]);
    assert_eq!(crc, 0x78f0_u16);

    let data = [0x0_u8, 0x9_u8, 0x3a_u8, 0x80_u8, 0x1a_u8, 0xaf_u8];
    let interval = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
    assert_eq!(interval, 604800_u32);
    let crc = crc16(&data[..data.len() - 2]);
    assert_eq!(crc, 0x1aaf_u16);

    let data = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 6,
    ];
    let crc = crc16(&data[..data.len() - 2]);
    println!("{:x}", crc);
    assert_eq!(crc, 0xca06_u16);

    let data = [
        0, 0x3, 0x90, 0x4c, 0, 1, 0xf5, 0x7a, 0, 0, 0x6e, 0xb4, 0, 0, 5, 0xd5, 0, 0, 1, 0xf, 0, 0,
        0, 0, 0, 0, 0, 0, 0x11, 0xe1,
    ];
    let crc = crc16(&data[..data.len() - 2]);
    assert_eq!(crc, 0x11e1_u16);

    let data = [
        0, 2, 0x63, 0x8d, 0, 1, 0x43, 0x1d, 0, 0, 0x42, 0x29, 0, 0, 1, 0xf7, 0, 0, 0, 0x15, 0, 0,
        0, 0, 0, 0, 0, 0, 0xb4, 0x19,
    ];

    let crc = crc16(&data[..data.len() - 2]);
    assert_eq!(crc, 0xb419_u16);
}

main.rs

use linux_embedded_hal::I2cdev;

mod device;

fn main() {
    let i2c_bus = I2cdev::new("/dev/i2c-1").unwrap();
    let mut ipsx100: ips7100_v2::Device<I2cdev> =
        ips7100_v2::Device::new(i2c_bus, ips7100_v2::Devcie_I2C_ADDR);

    match ipsx100.verify_status() {
        Ok(status) => println!("Device status verified: {}", status),
        Err(e) => println!("Error: {:?}", e),
    }

    match ipsx100.start_fan() {
        Ok(_) => println!("Fan started"),
        Err(e) => println!("Error: {:?}", e),
    }

    match ipsx100.verify_status() {
        Ok(status) => println!("Device status verified: {:x}", status),
        Err(e) => println!("Error: {:?}", e),
    }
}
[0, 78, f0]
Device status verified: 0
Writing to register 0x2b: 0x01
Fan started
[0, 78, f0]
Device status verified: 0
openat(AT_FDCWD, "/dev/i2c-1", O_RDWR|O_LARGEFILE|O_CLOEXEC) = 4
ioctl(4, _IOC(_IOC_NONE, 0x7, 0x3, 0), 0x4b) = 0
ioctl(4, _IOC(_IOC_NONE, 0x7, 0x8, 0), 0) = 0
ioctl(4, _IOC(_IOC_NONE, 0x7, 0x7, 0), 0xbebc1388) = 2
write(1, "[0, 78, f0]\n", 12[0, 78, f0]
)           = 12
ioctl(4, _IOC(_IOC_NONE, 0x7, 0x7, 0), 0xbea0e3a0) = 1
write(1, "Fan started\n", 12Fan started
)           = 12
ioctl(4, _IOC(_IOC_NONE, 0x7, 0x7, 0), 0xbebc1388) = 2
write(1, "[0, 78, f0]\n", 12[0, 78, f0]
)           = 12
close(4)                                = 0

What I'm doing wrong ? Can someone help me ?

Thanks in advance, and sorry for this long post.

lucviala commented 2 years ago

OMG, I'm confused, I had read the wrong register in my rust program.

Sorry for this post !

lucviala commented 2 years ago

Just a word to say that you've made a very good job ! And I definitely need to buy a rubber duck. :smile: