Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C write_read() not woring correctly #25

Closed
sdc-andy opened this issue Nov 1, 2019 · 4 comments · Fixed by #26
Closed

I2C write_read() not woring correctly #25

sdc-andy opened this issue Nov 1, 2019 · 4 comments · Fixed by #26

Comments

@sdc-andy
Copy link

sdc-andy commented Nov 1, 2019

@eldruin has created a Rust driver for the VEML60300 ambient light sensor that uses the embedded-hal traits.

At the lowest level of the driver are two functions write_register() and read_register() defined as follows:

fn write_register(&mut self, register: u8, value: u16) -> Result<(), Error<E>> {
    self.i2c
        .write(self.address, &[register, value as u8, (value >> 8) as u8])
        .map_err(Error::I2C)
}

fn read_register(&mut self, register: u8) -> Result<u16, Error<E>> {
    let mut data = [0; 2];
    self.i2c
        .write_read(self.address, &[register], &mut data)
        .map_err(Error::I2C)
        .and(Ok(u16::from(data[0]) | u16::from(data[1]) << 8))
}

In testing the code the function that writes to the registers in the VEML6030 is working as it is configuring the ambient light sensor and enabling it to collect samples.

When we try to read the raw data from the ambient light sensor using a function called read_raw() which simply calls the read_register() function with the correct register offset it always returns 0.

Within the test application if we replace the call to the drivers read_raw() function with the following:

let mut dev = LinuxI2CDevice::new("/dev/i2c-2", 0x10).unwrap();
let raw = dev.smbus_read_word_data(0x04).unwrap();

then correct data is being returned. If we split the write then read as:

dev.write(&[0x04]).unwrap();
let mut data = [0; 2];
dev.read(&mut data).unwrap();
let raw = u16::from(data[0]) | (u16::from(data[1]) << 8); 

then the value of raw is, again, always 0.

The code is all cross compiled for an NXP i.MX6 running a custom Yocto Linux build with kernel version 4.14.98.

-Andy.

@eldruin
Copy link
Member

eldruin commented Nov 1, 2019

To add a bit of background, we call the smbus_read_word_data function because after the configuration of the device it is possible to read data with: i2cget -y 2 0x10 0x04 w and that function is what i2cget uses. This appears to be further lowered in the linux kernel to a read with the register address as a command here.

@eldruin
Copy link
Member

eldruin commented Nov 3, 2019

I did some testing with an I2C AT24C256 EEPROM and a logic analyzer. I wrote a simple program to read from an address which uses write_read() and ran it on an STM32F3 Discovery board and on a Raspberry Pi.
The code is basically:

let mut data = [0];
i2c.write_read(address, &[0x00, 0x12], &mut data).unwrap();

(The memory addresses are two bytes here, not one as for the veml6030's "command" but that is not my point right now)
Here are the two sequences:
Using write_read() implementation from the stm32f30x-hal crate:
stm32f3xx-hal-write_read

Using the write_read() implementation from linux-embedded-hal:
linux-embedded-hal-write_read

One can see clearly how the first one does not issue a STOP and does only a START repeat and in the second one the write and read are independent (as implemented here).

The AT24C256 EEPROM accepts it either way, but VEML6030 and probably others do not.

@sdc-andy
Copy link
Author

sdc-andy commented Nov 4, 2019

Here is the snippet from the VEML6030 datasheet regarding I2C transactions:

image

So, yes the additional STOP in the write_read() from the linux-embedded-hal will most likely be causing the VEML6030 to abort mid-transaction.

-Andy.

@eldruin
Copy link
Member

eldruin commented Nov 4, 2019

Yes, that is the classical combined write/read, which is what the stm32f30x-haluses. It is worth noting that I while there are functions to write exactly one byte (often called "command") and then read (e.g. smbus_read_word_data), No methods to write several bytes and then read several bytes are exposed, as allowed in the I2C bus specification: See Figure 13 in section 3.1.10 of UM10204.
I will have a look at building and sending messages with the I2C_RDWR ioctl() syscall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants