I2C device discovered, but later NACK

Hi. I am building currently a weather station based on few sensors and ESPHome. I use BME680 as temperature, humidity and pressure sensor (and many others, as it deliveres) and AS5600 magnetometer as sensor for wind direction. Both are connected by I2C.
Lately I managed to solder everything together and put it on the pole in the garden. The BME started with no problems. The AS5600 gets discovered in logs with address 0x36, but later sends no data, and there is no communication with sensor. Logs say:

[10:00:40.854][D][esp-idf:000]: E (2693632) i2c.master: I2C hardware NACK detected
[10:00:40.863][D][esp-idf:000]: E (2693635) i2c.master: I2C transaction unexpected nack detected
[10:00:40.866][D][esp-idf:000]: E (2693643) i2c.master: s_i2c_synchronous_transaction(945): I2C transaction failed
[10:00:40.883][D][esp-idf:000]: E (2693648) i2c.master: i2c_master_execute_defined_operations(1366): I2C transaction failed

I really have no idea what is going on. If there was any problem with cables it would not be discovered, am I right?

You don’t show a lot…
How long is your wiring?
Default I2C settings?

Here is my yaml:

esphome:
  name: weatherstation
  friendly_name: WeatherStation
  # turn on mosfet on startup to run sensors


esp32:
  board: esp32dev
  framework:
    type: esp-idf



# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: hidden

ota:
  - platform: esphome
    

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Weatherstation Fallback Hotspot"
    password: hidden

captive_portal:

globals:
  - id: pulse_count
    type: unsigned long
    restore_value: yes
    initial_value: '0'

  - id: pulse_count_last
    type: unsigned long
    restore_value: no
    initial_value: '0'
 
  - id: rain_count
    type: unsigned long
    restore_value: yes
    initial_value: '0.0'

  - id: rain_last_hour
    type: unsigned long
    restore_value: yes
    initial_value: '0.0'

i2c:
  #  sda: GPIO21
  #  scl: GPIO22
  scan: true
  id: i2c_bus
  frequency: 100kHz
  timeout: 1ms



bme68x_bsec2_i2c:
  id: bme680_internal
  model: bme680
  address: 0x77
  temperature_offset: 0
  sample_rate: LP
  state_save_interval: 4h
  

as5600: #magnetometer device definition
  id: my_as5600
  start_position: 90deg #for later correction
  direction: clockwise
  address: 0x36

#one_wire:
#  - platform: gpio
#    pin: GPIO12

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO18
      mode: INPUT_PULLUP
    name: "Hall Digital"
    id: hall_digital
    filters:
      - delayed_on: 2ms
      - delayed_off: 2ms
    on_press:
      then:
        - lambda: |-
            id(pulse_count) += 1;
  
  - platform: gpio
    pin:
      number: 25        # any pin ESP32, with reed switch
      mode:
        input: true
        pullup: true
    name: "Deszczomierz - impuls"
    id: rain_pulse
    filters:
      - delayed_on_off: 50ms   # prosty debounce (odfiltrowanie drgań styków)
    on_state:
      then:
        - lambda: |-
            id(rain_count) += 1;


sensor:
- platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
  name: "WiFi Signal dB"
  id: wifi_signal_db
  update_interval: 60s
  entity_category: "diagnostic"

- platform: copy # Reports the WiFi signal strength in %
  source_id: wifi_signal_db
  name: "WiFi Signal Percent"
  filters:
   - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
  unit_of_measurement: "Signal %"
  entity_category: "diagnostic"
  device_class: ""


- platform: bme68x_bsec2
  
  #i2c_id:
  ##address: 0x76  #Sometimes it's 0x77
  temperature:
    name: "BME680 Temperature"
    filters: 
      - median
  pressure:
    name: "BME680 Pressure"
    filters: 
      - median
  humidity:
    id: "humidity"
    name: "BME680 Humidity"
    filters: 
      - median
  gas_resistance:
    id: "gas_resistance"
    name: "BME680 Gas Resistance"
    filters: 
      - median
  co2_equivalent:
    id: co2_equivalent 
    name: "CO2 Equivalent"
    filters: 
      - median
  breath_voc_equivalent:
    id: breath_voc_equivalent 
    name: "Breath VOC Equivalent"
    filters: 
      - median
  iaq: 
    name: "IAQ"
    id: iaq_bme
    filters: 
      - median

- platform: template
#air quality
  name: "BME680 Air Quality"
  id: iaq
  icon: "mdi:gauge"
  # calculation: comp_gas = log(R_gas[ohm]) + 0.04 log(Ohm)/%rh * hum[%rh]
  lambda: |-
    return log(id(gas_resistance).state) + 0.04 *  id(humidity).state;
  state_class: "measurement"

#pluvio will work on i2c and have its own counter in attiny85

- platform: template
  name: "Deszczomierz (impulsy)"
  id: rain_impulses
  unit_of_measurement: "imp"
  accuracy_decimals: 0
  update_interval: never

- platform: template
  name: "Opad deszczu (mm)"
  id: rain_mm
  unit_of_measurement: "mm"
  accuracy_decimals: 2
  update_interval: 10s
  lambda: |-
    return id(rain_count) *6;

- platform: template
  name: "Opad deszczu na godzinę"
  unit_of_measurement: "mm/h"
  update_interval: 10min
  lambda: |-
    float current = id(rain_mm).state;
    float diff = current - id(rain_count);
    id(rain_last_hour) += diff;  // dodaj przyrost
    id(rain_count) = current;
    static unsigned long last_reset = millis();
    float result = id(rain_last_hour);
    if (millis() - last_reset >= 3600000) {  // reset co godzinę
      last_reset = millis();
      id(rain_last_hour) = 0;
    }
     return result;   // musi zwracać float

#- platform: dallas_temp #additional dallas thermometer as bme680 heats up and become inacurrate 
#  address: 0x1234567812345628
#  name: temperature_outside_ws
#  update_interval: 60s

- platform: as5600 #magnetometer wind direction
  update_interval: 1s
  name: Angle
  unit_of_measurement: '°'
  accuracy_decimals: 2
  icon: mdi:rotate-right
  filters:
    - delta: 1
    - lambda: 'return x * as5600::RAW_TO_DEGREES * id(my_as5600).get_range_scale();'

- platform: template
  name: "RPM (raw)"
  id: rpm_raw
  unit_of_measurement: "RPM"
  accuracy_decimals: 2
  update_interval: 0.5s
  lambda: |-
    const float pulses_per_rev = 1.0; // 1 impuls na obrót
    float delta = id(pulse_count) - (id(pulse_count_last) ? id(pulse_count_last) : 0);
    id(pulse_count_last) = id(pulse_count);
    return delta * 60.0 / 0.5; // RPM z delta w oknie 0.5s

- platform: template
  name: "RPM (EMA)"
  id: rpm_ema
  unit_of_measurement: "RPM"
  accuracy_decimals: 2
  update_interval: 0.5s
  lambda: |-
    static float filtered = 0.0;
    static bool first = true;
    const float alpha = 0.3; // deklarujemy tutaj
    float raw = id(rpm_raw).state;
    if (first) {
      filtered = raw;
      first = false;
    }
    filtered = alpha * raw + (1.0 - alpha) * filtered;
    return filtered;


# --- PRĘDKOŚĆ WIATRU ---
- platform: template
  name: "Wind Speed (m/s)"
  id: wind_speed_ms
  unit_of_measurement: "m/s"
  accuracy_decimals: 2
  update_interval: 0.5s
  lambda: |-
    const float meters_per_rev = 0.553; // obwód skrzydełka w metrach
    float rpm = id(rpm_ema).state;
    if (rpm <= 0) return 0;
    float rps = rpm / 60.0; // obroty na sekundę
    return rps * meters_per_rev;

- platform: template
  name: "Wind Speed (km/h)"
  id: wind_speed_kmh
  unit_of_measurement: "km/h"
  accuracy_decimals: 2
  update_interval: 0.5s
  lambda: |-
    return id(wind_speed_ms).state * 3.6;

# --- PORYWY WIATRU ---
- platform: template
  name: "Wind Gust (10s)"
  id: wind_gust_10s
  unit_of_measurement: "m/s"
  accuracy_decimals: 2
  update_interval: 0.2s
  lambda: |-
    static float max_value = 0;
    static unsigned long last_reset = 0;
    unsigned long now = millis();
    float current = id(wind_speed_ms).state;
    if (current > max_value) max_value = current;
    if (now - last_reset > 10000) {
      float gust = max_value;
      max_value = current;
      last_reset = now;
      return gust;
    }
    return id(wind_gust_10s).state;



  
text_sensor: # BME sensor output, I left them all for checking.

- platform: template
  name: "BME680 IAQ Classification"
  icon: "mdi:checkbox-marked-circle-outline"
  lambda: |-
      if ( int(id(iaq).state) <= 50) {
      return {"Excellent"};
      }
      else if (int(id(iaq).state) >= 51 && int(id(iaq).state) <= 100) {
      return {"Good"};
      }
      else if (int(id(iaq).state) >= 101 && int(id(iaq).state) <= 150) {
      return {"Lightly polluted"};
      }
      else if (int(id(iaq).state) >= 151 && int(id(iaq).state) <= 200) {
      return {"Moderately polluted"};
      }
      else if (int(id(iaq).state) >= 201 && int(id(iaq).state) <= 250) {
      return {"Heavily polluted"};
      }
      else if (int(id(iaq).state) >= 251 && int(id(iaq).state) <= 350) {
      return {"Severely polluted"};
      }
      else if (int(id(iaq).state) >= 351) {
      return {"Extremely polluted"};
      }
      else {
      return {"error"};
      }


My wiring is about 2,5 meter

Length of I2C bus? That’s a LOT.
Can’t you make it shorter?

The pole is about 3 meters and ESP is hanged down in the waterproof box. So you think that’s the matter of length?

I2C is not designed for 3m wiring.
Best option would be to locate esp next to sensors, ~20cm .
If not possible, consider active terminator like LTC4311.

If you want to play with your actual setup, add 3k3 external pullups, use twisted pair cable, both data lines paired with GND.
Try lower i2c frequency and higher timeout.

Allright then, I will think about the active terminator and adding pullups, if that won’t work, I will have to hang the box higher (although I don’t like this idea, as I planned to have it down the pole just to have an opportunity to do some fixes or change the batteries if need be).
Thanks for your advices.

Battery cables do not mind the length :slight_smile:

Also make sure you use the latest esphome version. There have been some fixes to i2c lately.

Knowing the risks I did have success with 2.5m twisted pair wiring on an optical rain sensor, so I hope that will do the trick for you too. I made no extra precautions, but I have no idea what the internals of my rain sensor did to improve my success rate. It is a well designed industrial piece.


My rain sensor is dead simple and bases on reed switch. Hopefully it will work :slightly_smiling_face:

If you only have access to Esp, add pullups and give it a try.

1 Like

They will, but tend to keep wet longer. Also they degrade over time. My piece of kit is rather expensive, but very fast responding and has a level 0-7. The one I have does not have mm’s (I have a tipping bucket too) but that isn’t fast enough to save my awning from getting wet. I own the RG-9:

Whoah, that’s a really high-end sensor :wink:

Oh, oops, that is serial. The I2C’s with long cables where thermometers…

Well, gentlemen. I have to congratulate you for the diagnosis. I put 4,7kohm pullups and now I have data. Thank you.
It turned out one pullup on the data line is enough.

Nice you got it working!
But remember it’s very fragile. Fine for wind sensor, but don’t do wiring like that if reliability is absolutely needed.
I2C was designed for device to device communication on same PCB or few cm wired connections.