Using an I2C sensor (BMP180) with Heltec WiFi Kit 32 V3

I am trying to use a BMP180 barometer I2C sensor with my Heltec WiFi Kit 32 V3. I am using the Arduino IDE to write and upload sketches and have run the I2C scanner and it sees the BMP180 at address 0x77.

I am aware that the OLED is using the Wire() and so have tried to initiate the BMP like so:

#include "BMP085.h"
Adafruit_BMP085 bmp;
#define PIN_SDA 41
#define PIN_SCL 42
#define BMP085_ADDRESS 0x77 

//extern TwoWire Wire1;

void setup() {
  Serial.begin(9600);
  Wire1.begin(PIN_SDA,PIN_SCL);
  delay(50);
 if (!bmp.begin(0x77, &Wire1)) {
	Serial.println("Could not find a valid BMP085 sensor, check wiring!");
	while (1) {}
  }
}

I have used several different libraries ( SparkFun BMP180, Adafruit BMP3XX, BMP180MI, Adafruit BMP085 unified and several others) but cannot get the Heltec WiFi Kit 32 V3 to recognize the BMP189 on Wire(1).

My research has shown that several people have the same issue. If the Heltec WiFi Kit 32 V3’s OLED uses the first I2C to itself and the second I2C cannot be used by common sensors, then the Heltec WiFi Kit 32 V3 is practically useless, so I’m really hoping someone can show me how to get around this issue.

Is your sensor rated for battery voltage (3.3v)? If so, are you powering it from a 3.3v power pin?
5v Bosch parts will work but you have to be powered from the USB and using a 5v pin.

I’m running a BME280 with a Mestastic Heltec V3 on pins 41 & 42 like your cfg.

Has there been any progress on this? I am struggling with the same thing on the Heltec LoRa 32(v3) with the several Adafruit sensors. I was able to get the BME680 working, but then following the same general aproach has not worked with other sensors. I’m at a loss. This seems to be a common issue, but I have to find a comprehensive solution or explanation.

I have written a short sketch which I use as a library in other sketches to switch on the OLED and I2C bus. Here it is:

#include <Wire.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/18, /* data=*/17);

//Define IIC2 pins
#define PIN_SDA 41
#define PIN_SCL 42
#define Vext 36        // External power control
#define OLED_RESET 21  // Reset pin for the OLED

extern TwoWire Wire1;

//use IIC2
TwoWire WIRE1(1);
#define Wire WIRE1

void OLEDi2cSetup() {
  pinMode(Vext, OUTPUT);
  digitalWrite(Vext, LOW);  // Enable external power **before I2C initialization**
  analogWrite(35, 15);  // Not so bright
  delay(500);

  // Initialize Wire for OLED on default Heltec pins (17,18)
  Wire.begin(17, 18);
  //Reset OLED
  pinMode(OLED_RESET, OUTPUT);
  digitalWrite(OLED_RESET, LOW);
  delay(50);  // Wait for OLED to fully power down
  digitalWrite(OLED_RESET, HIGH);
  delay(50);  // Give time to initialize

  u8g2.begin();

  // Initialize Wire1 on custom pins
  Wire1.begin(PIN_SDA, PIN_SCL);
1 Like

Cool!

The student becomes the master - wax on, wax off!

Thanks for the reply, I have kept working on it and discovered something that is likley obvious to others, but was not to me. I was on YouTube at the ShotokuTech channel and ran across this video (https://youtu.be/sBYHlvLupxw?si=eGCNm7QklhmY4slC), which reminded me that often pull-up or pull-down resistors are needed. I’m not sure why they were not needed for the BME680, but they are required for the CCS811 board. Anyway, with that and then a bit more tweaking, I was able to get the CCS811 to initialize, debug to serial, and transmit over LoRa using the following sketch:

/**
 * This sketch was written starting with the "LoRAWAN_TTN" example 
 * from the "Heltec_ESP32_LoRa_V3" Library. It also uses portions of the 
 * "CCS811_test" example from the "Adafruit CCS811 Library". Modifications 
 * have been made to both contributions by Brandon J. Winters.
 * 
 * 
 * FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
 * THE LIBRARY MANAGER IN THE ARDUINO IDE.
 * 
 * This code will send a two-byte LoRaWAN message every 15 minutes. The first
 * byte is a simple 8-bit counter, the second is the ESP32 chip temperature
 * directly after waking up from its 15 minute sleep in degrees celsius + 100.
 *
 * If your NVS partition does not have stored TTN / LoRaWAN provisioning
 * information in it yet, you will be prompted for them on the serial port and
 * they will be stored for subsequent use.
 *
 * See https://github.com/ropg/LoRaWAN_ESP32
*/

// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 900

#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_CCS811.h>

LoRaWANNode* node;

extern TwoWire Wire1; //Setup Wire1 as second I2C bus

//create CCS811 sensor and give it the nickname "ccs"
Adafruit_CCS811 ccs;

//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging

void setup() {
  // put your setup code here, to run once:
 
 Wire1.begin(41, 42); //SDA is pin 41 and SCL is pin 42 for second I2C bus

 Serial.begin(115200); //begin serial communication at the defined baud rate

 ccs.begin(0x5A, &Wire1); //start the ccs sensor at the indicated address on Wire1

 Serial.println("CCS811 Test");

  if(!ccs.begin()){
    Serial.println("Failed to start sensor! Please check your wiring.");
    while(1);
  }

  // Wait for the sensor to be ready
  while(!ccs.available());

  Serial.println("CCS811 found after wait");

 // Obtain directly after deep sleep
 // May or may not reflect room temperature, some correction needed 
  float temp = heltec_temperature();
  Serial.println();  
  Serial.println(F("-------------------------"));
  Serial.print("Consecutive Transmissions: ");
  Serial.print(trans++);
  Serial.println();
  Serial.printf("Heltec Temperature: %.1f °C\n", temp);
 
if(ccs.available()){
    if(!ccs.readData()){
      Serial.print("CO2: ");
      Serial.print(ccs.geteCO2());
      Serial.print(" ppm");
      Serial.println();
      Serial.print("TVOC: ");
      Serial.print(ccs.getTVOC());
      Serial.print(" TVOC Units");
      Serial.println();
       Serial.println(F("-------------------------"));
    }
    else{
      Serial.println("ERROR!");
      while(1);
    }
  }

 heltec_setup(); //for some reason this needs to be called after Wire1 and Serial for this board. I assume there is another way, but this seems to work also.

 // initialize radio
  Serial.println("Radio init");
  int16_t state = radio.begin();
  if (state != RADIOLIB_ERR_NONE) {
    Serial.println("Radio did not initialize. We'll try again later.");
    goToSleep();
  }

  node = persist.manage(&radio);

  if (!node->isActivated()) {
    Serial.println("Could not join network. We'll try again later.");
    goToSleep();
  }

// If we're still here, it means we joined, and we can send something

// Manages uplink intervals to the TTN Fair Use Policy
  node->setDutyCycle(true, 1250);

//create LoRa data uplink structure and packet
  uint8_t uplinkData[4];
  uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
  uplinkData[1] = (ccs.geteCO2() / 5); //max value to multiply by and keep under cap of 256 for uint8_t value
  uplinkData[2] = (ccs.getTVOC() * 10); //max value to multiply by and keep under cap of 256 for uint8_t value
  uplinkData[3] = (ccs.calculateTemperature() + 273.15); //zeros out the temperature, this may not be functioning correctly

//define LoRa downlink size
  uint8_t downlinkData[256];
  size_t lenDown = sizeof(downlinkData);

//Send of the LoRa data and establish wait for downlink
  state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);

  if(state == RADIOLIB_ERR_NONE) {
    Serial.println("Message sent, no downlink received.");
  } else if (state > 0) {
    Serial.println("Message sent, downlink received.");
  } else {
    Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
  }

  goToSleep();    // Does not return, program starts over next round

}

void loop() 
{
  // put your main code here, to run repeatedly:

}

void goToSleep() {
  Serial.println("Going to deep sleep now");
  // allows recall of the session after deepsleep
  persist.saveSession(node);
  // Calculate minimum duty cycle delay (per FUP & law!)
  uint32_t interval = node->timeUntilUplink();
  // And then pick it or our MINIMUM_DELAY, whichever is greater
  uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
  Serial.printf("Next TX in %i s\n", delayMs/1000);
    Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
  Serial.println();
  delay(100);  // So message prints
  // and off to bed we go
  heltec_deep_sleep(delayMs/1000);
}

This sketch sends data over LoRa to The Things Network and can be added as an end device there to any application. I am using the following JavaScript payload decoder in my application:

function Decoder(bytes, port) {
  var decoded = {};

  // Decode bytes to int
  var testShort0 = (bytes[0]);
  var testShort1 = (bytes[1] * 5);
  var testShort2 = (bytes[2] / 10);
  var testShort3 = (bytes[3]);
  // Decode int 
  decoded.count = testShort0;  
  decoded.eCO2 = testShort1;
  decoded.TVOC = testShort2;
  decoded.temperature = testShort3;


  return decoded;
}

The I2C spec requires a pull-up resistor, period. However you can often get away with the MCU GPIO internal pull-ups and many many dev boards come with PU resistors on them so it’s become less & less obvious / clear.

And then there is the mess of having multiple pull-ups from all the dev boards, which most of the time aren’t an issue.

And if you really want to get in to the weeds, if you have longer I2C SDA/SCL lines or wires, in theory you need to adjust the pull-up values accordingly - longer lines need lower values to bring the voltage back up in the face of the line capacitance.

1 Like

@nmcc thank you for this description. It may be clear to you already, but I have not learned any of this stuff (Arduino, HomeAssistant, LoRa,…) in a conventional fashion, I assume I’ve spent way more time trying to wade through the weeds on these topics than if I’d simply started in a more systematic way. Regardless, I appreciate your comments about the need for pull-up resistors. It appears as though the Adafruit BME680 has them integrated, but the other boards I’ve been using do not.

LOL, does anyone anymore, if ever, with this computery / electronics stuff.

With much of this we hack our way through the learning curve and then one day stumble upon the real reasons why some things are a magic art!