esp-rs / esp-idf-hal

embedded-hal implementation for Rust on ESP32 and ESP-IDF
https://docs.esp-rs.org/esp-idf-hal/
Apache License 2.0
442 stars 169 forks source link

I2C NoAcknowledge on ESP32c3 #73

Closed Gazedo closed 2 years ago

Gazedo commented 2 years ago

Hi All, I'm trying to get a esp32c3 (m5stack c3u board) to communicate with a mpu9250, its connected with:

But no matter what I keep getting the error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: I2cError { kind: NoAcknowledge(Unknown), cause: EspError(-1) }', src/main.rs:62:37

I've tried using the write_read function and just directly using a mpu9250 crate so I think the issue lays with the esp_idf_hal crate. I was initially using version 0.37.4 and I tried to go back to version 0.35.2 which also did not work. I've confirmed the mpu9250 is functional by downloading a demo arduino program to the esp32c3 and everything reads just fine with the exact same circuit and hardware.

Below is the test code I've been using. Am I doing something wrong?

use embedded_hal::blocking::i2c::{Read, Write};
use esp_idf_hal::i2c;
use esp_idf_hal::prelude::*;
use esp_idf_hal::units::FromValueType;
use esp_idf_sys as _;
use std::thread;
use std::time::Duration;

fn main() {
    // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
    // or else some patches to the runtime implemented by esp-idf-sys might not link properly.
    esp_idf_sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();
    let per = Peripherals::take().unwrap();
    let sda = per.pins.gpio4.into_input_output().unwrap();
    let scl = per.pins.gpio5.into_output().unwrap();
    let i2c = per.i2c0;

    let config = <i2c::config::MasterConfig as Default>::default().baudrate(400.kHz().into());
    let mut i2cdev =
        i2c::Master::<i2c::I2C0, _, _>::new(i2c, i2c::MasterPins { sda, scl }, config).unwrap();

    loop {
        let mut buff: [u8; 6] = [0; 6];
        i2cdev.write(0x68, &[0x75]).unwrap();
        i2cdev.read(0x68, &mut buff).unwrap();
        log::info!("wai value is {:?}", buff);
        thread::sleep(Duration::from_millis(100));
    }
}

P.S. the line numbers are off because I eliminated a ton of comments. The line that is failing is: i2cdev.write(0x68, &[0x75]).unwrap();

ivmarkov commented 2 years ago

Shouldn't you be using write_read? Have you tried this crate?

Gazedo commented 2 years ago

Shouldn't you be using write_read? Have you tried this crate?

I've tried all of those options, I was trying to reduce the code to the bare minimum to show the error. I get the same error on all of those options because at the base level they all use the write function at some point.

Dominaezzz commented 2 years ago

What does the working Arduino code look like?

Gazedo commented 2 years ago

This is the Arduino code that works. It is the example from Bolder Flight Systems:

#include "mpu9250.h"

/* Mpu9250 object */
bfs::Mpu9250 imu;

void setup() {
  /* Serial to display data */
  Serial.begin(115200);
  while(!Serial) {}
  /* Start the I2C bus */
  Wire.begin();
  Wire.setClock(400000);
  /* I2C bus,  0x68 address */
  imu.Config(&Wire, bfs::Mpu9250::I2C_ADDR_PRIM);
  /* Initialize and configure IMU */
  if (!imu.Begin()) {
    Serial.println("Error initializing communication with IMU");
    while(1) {}
  }
  /* Set the sample rate divider */
  if (!imu.ConfigSrd(19)) {
    Serial.println("Error configured SRD");
    while(1) {}
  }
}

void loop() {
  /* Check if data read */
  if (imu.Read()) {
    Serial.print(imu.new_imu_data());
    Serial.print("\t");
    Serial.print(imu.new_mag_data());
    Serial.print("\t");
    Serial.print(imu.accel_x_mps2());
    Serial.print("\t");
    Serial.print(imu.accel_y_mps2());
    Serial.print("\t");
    Serial.print(imu.accel_z_mps2());
    Serial.print("\t");
    Serial.print(imu.gyro_x_radps());
    Serial.print("\t");
    Serial.print(imu.gyro_y_radps());
    Serial.print("\t");
    Serial.print(imu.gyro_z_radps());
    Serial.print("\t");
    Serial.print(imu.mag_x_ut());
    Serial.print("\t");
    Serial.print(imu.mag_y_ut());
    Serial.print("\t");
    Serial.print(imu.mag_z_ut());
    Serial.print("\t");
    Serial.print(imu.die_temp_c());
    Serial.print("\n");
  }
}
MabezDev commented 2 years ago

According to this article, the default sda and scl are pins 8 & 9. So the first question is, does it work if you use pins 8 & 9? Secondly, if it still doesn't work, and you are convinced it really is on pins 4 & 5, what happens if you set Wire.setPins(4, 5); in your arduino example?

Gazedo commented 2 years ago

I've verified via multimeter that the pins are 4=sda and 5=scl. I also checked the power input to the imu. Unfortunately I don't currently have a multimeter to watch the hardware signals but I may be able to borrow a cheap one. I forgot to modify the example again as I copied and pasted the generic example included with the library. In order to get the pasted example to work, I had to modify Wire.begin(); to be Wire.begin(4, 5);

Things I've tried:

Things I'd like to confirm:

Dominaezzz commented 2 years ago

Command link is necessary for transactions. There's a strict contract that embedded hal requires for i2c, as to when to receive and send ACKs, which is easy to do with command link.

I currently use the i2c implementation to drive a touch screen so it definitely works.

You should just set clock yourself.

Have a look at the embedded hal i2c contract and see if your device cam comply. .i.e. does it respond with ACKs at the write time?

Gazedo commented 2 years ago

What versions of esp-idf-hal and esp-idf-sys are you using? According to the docs for the mpu9250 the device does an ack after each write.

This is the MPU 9250 Datasheet and I read the details of the i2c communication on page 34.

Gazedo commented 2 years ago

Figured it out, you can't do into_output or into_input_output on the pins before passing them into the Master struct. That is confusing and there are no warnings to prevent that.