diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..2542322 --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +AlignAfterOpenBracket: Align +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +IndentCaseLabels: true +SpacesBeforeTrailingComments: 2 +PointerAlignment: Left +AlignEscapedNewlines: Left +ForEachMacros: ['TEST_GROUP', 'TEST'] \ No newline at end of file diff --git a/.github/workflows/check_and_build.yml b/.github/workflows/check_and_build.yml new file mode 100644 index 0000000..50429b4 --- /dev/null +++ b/.github/workflows/check_and_build.yml @@ -0,0 +1,33 @@ +name: Quality check +on: + pull_request: + branches: + - main + push: + branches: + - main +jobs: + clang_format_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RafikFarhad/clang-format-github-action@v4 + with: + sources: "esp32-different-i2c-buses-example/*.ino" + style: file + build_sketch: + runs-on: ubuntu-latest + env: + CLI_PATH: ./cli + steps: + - name: checkout + uses: actions/checkout@v4 + - name: install arduino-cli + run: | + mkdir -p $CLI_PATH + export PATH=$PATH:$CLI_PATH + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=$CLI_PATH sh + + - name: build sketch + run: | + ${CLI_PATH}/arduino-cli compile --profile arduino-uno-r4 ./differentI2cBusesExample \ No newline at end of file diff --git a/README.md b/README.md index 9f3d7a3..21b9311 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,305 @@ -# arduino-i2c-different-buses-example -Tutorial how to set up different I2C buses on Arduino platform +# Introduction + +You have a micro controller and two identical sensors, meaning that they have the same I2C address. Without a multiplexer +or the possibility to configure the I2C address of the sensor, you cannot attach them to the same I2C bus. However, on a +board that provides the ability to configure any GPIO pin pairs as an I2C bus, you can connect the two sensors to their +own individual I2C buses. In this article, we will explain how to set up separate I2C buses for each sensor for a few different boards. + +# General working principle + +The two SCD41 sensors we want to connect have an I2C address of 0x62, which cannot be changed. Therefore, to communicate +with both sensors from the micro controller, we will use a separate I2C bus for each. Each I2C bus requires one pin for +the SDA line and one for the SCL line. Depending on the board and library implementation, we can either use pre-configured +instances of the `Wire` library or we need to create our own instances of the `TwoWire` class and assign the pins used for +SDA and SCL lines. As a final step, we will create two instances of our sensor driver class and +initialize one with the first I2C bus and the other with the second I2C bus. + +# ESP32 DevKitC + +## Wiring + +First, we need to define the pins we will use for the two I2C buses. For I2C bus A, we can use the default I2C pins +of the board. For the ESP32 DevKitC, these are pin 21 (SCL) and pin 22 (SDA). For I2C bus B, we can choose any two +GPIO (General Purpose Input Output) pins. + +If you are using a different board, it is important to check the specifications to determine if any pins have special +configurations that prevent them from being used as GPIO pins. In our case, we have selected pins 17 and 16 for +I2C bus B. + +### Pull-up resistors + +Having pull-up resistors in place on the I2C data (SDA) and clock (SCL) lines is important to have a good signal quality and robust communication. You can read more about it on [I2C Pull-Up Resistors Intro](i2c-pull-up-resistors-intro.md) + +The ESP32 DevKitC Board ensures that GPIO lines are automatically pulled to a high state. Therefore, there is no need to +manually wire or configure pull-up resistors for the pins you intend to use. + +### Wiring diagram + +Since the SCD41 sensor is compatible with both 3.3V and 5V, we can connect one sensor to the 3.3V pin and the other to +the 5V pin. If both sensors require the same voltage, they can be connected through a breadboard. + +For this example, the wiring should be carried out as follows: + +![Wiring diagram SEK SCD41 to ESP32 DevKitC](images/wiringTwoSCD41ToESP.png) + +- SEK-SCD41 A - Pin 1 to ESP32 Pin 22 (SCL, yellow cable) +- SEK-SCD41 A - Pin 2 to ESP32 GND (Ground, black cable) +- SEK-SCD41 A - Pin 3 to ESP32 3V3 (Sensor supply voltage, red cable) +- SEK-SCD41 A - Pin 4 to ESP32 Pin 21 (SDA, green cable) + +- SEK-SCD41 B - Pin 1 to ESP32 Pin 17 (SCL, yellow cable) +- SEK-SCD41 B - Pin 2 to ESP32 GND (Ground, black cable) +- SEK-SCD41 B - Pin 3 to ESP32 5V (Sensor supply voltage, red cable) +- SEK-SCD41 B - Pin 4 to ESP32 Pin 16 (SDA, green cable) + +When configuring the software later on, it is important to remember the pins allocated for the second I2C bus. +Specifically, we used pin 17 for the I2C clock (SCL) and pin 16 for the I2C data (SDA). + +## Software setup + +First, you need to include the Wire library: + +``` +#include +``` + +We are using the Arduino ESP32 platform, which includes the `Wire` library, that is already configured for +the default I2C bus on pins 21/22. +We can use the `Wire` instance without any modification for the sensor attached to the "I2C bus A" (default I2C bus). +We just need to initialize the bus with: + +``` +Wire.begin(); +``` + +For the "I2C bus B" we need to configure a custom `TwoWire` instance. There is a predefined instance named `Wire1` we +can configure to use the pins we defined with the following lines of code within the `setup()` function: + +``` +const int sda_B = 16; +const int scl_B = 17; +Wire1.begin(sda_B, scl_B); +``` + +Then, the code sending the commands to the sensors over the I2C bus needs to know which bus to use for which sensor. +Thus, you need to configure the sensor instances accordingly. First, create a driver instance per sensor. +Their scope should be global, such that those can be referred to from within `setup()` and `loop()`. + +``` +SensirionI2cScd4x sensorA; +SensirionI2cScd4x sensorB; +``` + +Then, in the `setup()` function, assign the I2C buses to the sensors: + +``` +sensorA.begin(Wire, SCD41_I2C_ADDR_62); +sensorB.begin(Wire1, SCD41_I2C_ADDR_62); +``` + +Look out that you really have `Wire1` assigned for sensorB, so that it uses the custom set-up I2C bus. + +You can now send any I2C command to the sensor, such as initiating the measurement and retrieving values. + +``` +sensorA.startMeasurement(); +sensorB.startMeasurement(); +... +``` + +You can find more details and options how to configure several I2C buses on the ESP32 platform using the Arduino IDE under [ESP32 I2C Tutorial](https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/) + + +## Example sketch + +You find a complete example under [differentI2cBusesExample.ino](differentI2cBusesExample/differentI2cBusesExample.ino). +Make sure to only have the define for your board `#define ESP32_DEVKITC_V4 1` uncommented, which you find at the beginning of the sketch. + +# STM32 Nucleo 64 Board + +## Wiring + +The STM32 Nucleo 64 board has pre-defined I2C pins. We use I2C1 (SDA on pin 14, SCL on pin 15) and +I2C2 (SDA on pin 3, SCL on pin 6). + +### Pull Ups +The Nucleo board nor the development kit board has pull-up resistors built in. +Thus, we need to wire a resistor into each of the I2C communication lines. Four 8.26kOhm resistors were used in +this example and the wiring was done using a bread board so that no soldering was needed. + +### Wiring diagram +![Wiring diagram SEK SCD41 to STM32 Nucleo 64 DevKitC](images/wiringTwoSCD41ToSTM32Nucleo64.png) + +Names R.1 to R.4 stand for resistors with a value of 8.26kOhm. + +- SEK-SCD41 A Pin 1 to R.1 (SCL, yellow) +- R.1 to Nucleo Pin 15 (SCL, yellow) +- R.1 to Nucleo 3V3 +- SEK-SCD41 A Pin 2 to Nucleo GND +- SEK-SCD41 A Pin 3 to Nucleo 3V3 +- SEK-SCD41 A Pin 4 to R.2 (SDA, green) +- R.2 to Nucleo Pin 14 (SDA, green) +- R.2 to Nucleo 3V3 + +- SEK-SCD41 B Pin 1 to R.3 (SCL, yellow) +- R.3 to Nucleo Pin 6 (SCL, yellow) +- R.3 to Nucleo 3V3 +- SEK-SCD41 B Pin 2 to Nucleo GND (Ground, black cable) +- SEK-SCD41 B Pin 3 to Nucleo 5V (Sensor supply voltage, red cable) +- SEK-SCD41 B Pin 4 to R.4 (SDA, green) +- R.4 to Nucleo Pin 3 (SDA, green) +- R.4 to Nucleo 3V3 + +What we have to remember for the configuration in the software later is the pins we used for the I2C buses. + +## Software setup + +First, you need to include the Wire library: + +``` +#include +``` + +For configuring the I2C buses with the correct pins, we need to instantiate two TwoWire instances and pass +the pins to be used for the I2C communication. +Their scope should be global, thus the definition is outside `setup()` and `loop()`. + +``` +// I2C Bus A on Pins 14 (SDA) / 15 (SCL) +const int sda_A = 14; +const int scl_A = 15; +TwoWire i2cBusA(sda_A, scl_A); + +// I2C Bus B on Pins 3 (SDA) / 6 (SCL) +const int sda_B = 3; +const int scl_B = 6; +TwoWire i2cBusB(sda_B, scl_B); +``` + +Then, the code sending the commands to the sensors over the I2C Bus needs to know which bus to use for which sensor. +Thus, you need to configure the sensors instances accordingly. First, create a driver instance per sensor. +Their scope should be global, such that they can be referred to from within `setup()` and `loop()`. + +``` +SensirionI2cScd4x sensorA; +SensirionI2cScd4x sensorB; +``` + +Then, in the `setup()` function, assign the I2C Buses to the sensors: + +``` +sensorA.begin(i2cBusA, SCD41_I2C_ADDR_62); +sensorB.begin(i2cBusB, SCD41_I2C_ADDR_62); +``` + +You can now send any I2C command to the sensor, such as initiating the measurement and retrieving values. +The complete example code is provided in the link. + +``` +sensorA.startMeasurement(); +sensorB.startMeasurement(); +... +``` + +## Example sketch + +You find a complete example under [differentI2cBusesExample.ino](differentI2cBusesExample/differentI2cBusesExample.ino). +Make sure to only have the define for your board `#define STM32_NUCLEO_64 1` uncommented, which you find at the beginning of the sketch. + +# Arduino Uno R4 WIFI + +# Wiring + +The Arduino Uno R4 WIFI provides one I2C bus on the pin header, which has no pull-ups on the board. +Thus, we need to connect a pull up resistor between SDA and VDD and SCL and VDD. +In the example two 2.2kOhm resistors were used. + +The second I2C bus is on the Qwiic. We use here the breakout board from Adafruit. +The board includes 10K pull-up on SDA and SCL. + +### Pull Ups + +The Arduino Uno R4 WIFI provides no pull-ups on the board. + +The SEK SCD41 board we are going to connect to the I2C Bus on the pin header has no pull-up resistors built in. +Thus, we need to connect a pull-up resistor between SDA and VDD and SCL and VDD. Two 8.26kOhm resistors were used in +this example and the wiring was done using a bread board so that no soldering was needed. + +The second sensor is on a Adafruit breakout board. This one includes a pull-up resistor. Thus, +no pull-up resistors have to be wired into this connection, for which a Qwiic cable is used. + +### Wiring diagram + +![Wiring diagram SEK SCD41 and Adafruit SCD41 to Arduino Uno R4](images/wiringTwoSCD41ToArduinoUnoR4.png) + +The list below describes the wiring, where R.1 and R.2 are resistors with a value of 8.26kOhm. + +- SEK-SCD41 Pin 1 to R.1 (SCL, yellow) +- R.1 to Arduino Pin SCL (yellow) +- R.1 to Arduino 3V3 +- SEK-SCD41 Pin 2 to Arduino GND +- SEK-SCD41 Pin 3 to Arduino 3V3 +- SEK-SCD41 Pin 4 to R.2 (SDA, green) +- R.2 to Arduino Pin 14 (green) +- R.2 to Arduino 3V3 + +- Adafruit SCD41 to Arduino Qwiic + + +## Software setup + +First, you need to include the Wire library: + +``` +#include +``` + +For the Arduino Uno R4, this library defines two I2C buses. The first, "Wire" is configured +for the SDA/SCL pins on the pin header, where "Wire1" is configured for the Qwiic connector. +We can use those two instances without any further configuration. You just need to initialize them: + +``` +Wire.begin(); +Wire1.begin(); +``` + +Then, the code sending the commands to the sensors over the I2C bus needs to know which bus to use for which sensor. +Thus, you need to configure the sensor instances accordingly. First, create a driver instance per sensor. +Their scope should be global, such that those can be referred to from within `setup()` and `loop()`. + +``` +SensirionI2cScd4x sensorOnPins; +SensirionI2cScd4x sensorOnQwiic; +``` + +Next, in the `setup()` function, assign the I2C buses to the sensors: + +``` +sensorOnPins.begin(Wire, SCD41_I2C_ADDR_62); +sensorOnQwiic.begin(Wire1, SCD41_I2C_ADDR_62); +``` + +Look out that you really have `Wire1` assigned for `sensorOnQwiic`. + +You can now send any I2C command to the sensor, such as initiating the measurement and retrieving values. +The complete example code is provided in the link. + +``` +sensorOnPins.startMeasurement(); +sensorOnQwiic.startMeasurement(); +... +``` + + +## Example sketch + +You find a complete example under [differentI2cBusesExample.ino](differentI2cBusesExample/differentI2cBusesExample.ino). +Make sure to only have the define for your board `#define ARDUINO_UNO_R4_WIFI 1` uncommented, which you find at the beginning of the sketch. + +# Other Arduino Boards + +Documentation for Arduino boards can be found under [Arduino Wire Library](https://docs.arduino.cc/language-reference/en/functions/communication/wire/). + +Note that not all boards support multiple I2C buses and that it is recommended to use a custom pull-up resistor configuration, +as the built in resistors are likely not strong enough (resistor value is too big). + diff --git a/differentI2cBusesExample/differentI2cBusesExample.ino b/differentI2cBusesExample/differentI2cBusesExample.ino new file mode 100644 index 0000000..ea59abd --- /dev/null +++ b/differentI2cBusesExample/differentI2cBusesExample.ino @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2025, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include + +// macro definitions +// make sure that we use the proper definition of NO_ERROR +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#define NO_ERROR 0 + + +// CHOOSE YOUR BOARD HERE + +//#define ESP32_DEVKITC_V4 1 +//#define STM32_NUCLEO_64 1 +#define ARDUINO_UNO_R4_WIFI 1 + +#if STM32_NUCLEO_64 + // I2C Bus on Pins 14 (SDA) / 15 (SCL) + const int sda_A = 14; + const int scl_A = 15; + TwoWire i2cBusA(sda_A, scl_A); + // I2C Bus on Pins 3 (SDA) / 6 (SCL) + const int sda_B = 3; + const int scl_B = 6; + TwoWire i2cBusB(sda_B, scl_B); +#endif + +static char errorMessage[64]; +static int16_t error; + +SensirionI2cScd4x sensorA; +SensirionI2cScd4x sensorB; + +bool sensorAOk; +bool sensorBOk; + +void PrintUint64(uint64_t& value) { + Serial.print("0x"); + Serial.print((uint32_t)(value >> 32), HEX); + Serial.print((uint32_t)(value & 0xFFFFFFFF), HEX); +} + +bool initSensor(SensirionI2cScd4x sensor) { + uint64_t serialNumber = 0; + delay(30); + // Ensure sensor is in clean state + error = sensor.wakeUp(); + if (error != NO_ERROR) { + Serial.print("Error trying to execute wakeUp(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + } + error = sensor.stopPeriodicMeasurement(); + if (error != NO_ERROR) { + Serial.print("Error trying to execute stopPeriodicMeasurement(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + } + + error = sensor.reinit(); + if (error != NO_ERROR) { + Serial.print("Error trying to execute reinit(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + } + // Read out information about the sensor + error = sensor.getSerialNumber(serialNumber); + if (error != NO_ERROR) { + Serial.print("Error trying to execute getSerialNumber(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return false; + } + Serial.print("serial number: "); + PrintUint64(serialNumber); + Serial.println(); + return true; +} + +bool startMeasurement(SensirionI2cScd4x sensor) { + error = sensor.startPeriodicMeasurement(); + if (error != NO_ERROR) { + Serial.print("Error trying to execute startPeriodicMeasurement(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return false; + } + return true; +} + +// Read measurements from the sensor and print to the console +// The method blocks until measurements are ready +void readAndPrintMeasurement(SensirionI2cScd4x sensor) { + bool dataReady = false; + uint16_t co2Concentration = 0; + float temperature = 0.0; + float relativeHumidity = 0.0; + + error = sensor.getDataReadyStatus(dataReady); + if (error != NO_ERROR) { + Serial.print("Error trying to execute getDataReadyStatus(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + while (!dataReady) { + delay(100); + error = sensor.getDataReadyStatus(dataReady); + if (error != NO_ERROR) { + Serial.print("Error trying to execute getDataReadyStatus(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + } + error = + sensor.readMeasurement(co2Concentration, temperature, relativeHumidity); + if (error != NO_ERROR) { + Serial.print("Error trying to execute readMeasurement(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + // + // Print results in physical units. + Serial.print("CO2 concentration [ppm]: "); + Serial.print(co2Concentration); + Serial.println(); + Serial.print("Temperature [°C]: "); + Serial.print(temperature); + Serial.println(); + Serial.print("Relative Humidity [RH]: "); + Serial.print(relativeHumidity); + Serial.println(); +} + +void initI2c() { + #if ESP32_DEVKITC_V4 + + // initialize the first sensor on default I2C pins SDA Pin 21, SCL Pin 22 + Wire.begin(); + sensorA.begin(Wire, SCD41_I2C_ADDR_62); + + // initialize the second sensor on custom I2C pins, here we use for SDA Pin + // 16 and for SCl Pin 17. you should be able to choose any GPIO Pin + const int sda_B = 16; + const int scl_B = 17; + Wire1.begin(sda_B, scl_B); + sensorB.begin(Wire1, SCD41_I2C_ADDR_62); + + Serial.println("I2C Buses configured for ESP32 DevKitC V4 Board."); + Serial.printf("I2C Bus A SDA Pin 21, SCL Pin 22; I2C Bus B SDA Pin %i, SCL PIN %i\n", sda_B, scl_B); + + #elif STM32_NUCLEO_64 + + i2cBusA.begin(); + sensorA.begin(i2cBusA, SCD41_I2C_ADDR_62); + + i2cBusB.begin(); + sensorB.begin(i2cBusB, SCD41_I2C_ADDR_62); + + Serial.println("I2C Buses configured for STM32 Nucleo 64 Board."); + Serial.printf("I2C Bus A SDA Pin %i, SCL Pin %i; I2C Bus B SDA Pin %i, SCL PIN %i\n", sda_A, scl_A, sda_B, scl_B); + + #elif ARDUINO_UNO_R4_WIFI + // initialize the first sensor (SEK-SCD41) on default I2C pins SDA (D18), SCL (D19) + Wire.begin(); + sensorA.begin(Wire, SCD41_I2C_ADDR_62); + + // initialize the second sensor (Adafruit SCD41 breakout board) on QWIIC connector + Wire1.begin(); + sensorB.begin(Wire1, SCD41_I2C_ADDR_62); + + Serial.println("I2C Buses configured for Arduino Uno R4 WIFI."); + Serial.print("I2C Bus A on pins SDA (D18), SCL (D19); I2C Bus B Qwiic Connector."); + + #endif +} + + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + // CONFIGURE YOUR BOARD AT THE BEGINNING OF THIS FILE + + initI2c(); + + Serial.println("----SENSOR A-----"); + sensorAOk = initSensor(sensorA); + if (sensorAOk) { + sensorAOk = startMeasurement(sensorA); + } + + Serial.println("----SENSOR B-----"); + sensorBOk = initSensor(sensorB); + if (sensorBOk) { + sensorBOk = startMeasurement(sensorB); + } +} + +void loop() { + // + // Slow down the sampling to 0.2Hz. + // + delay(5000); + + if (sensorAOk) { + Serial.println("----SENSOR A-----"); + readAndPrintMeasurement(sensorA); + } + if (sensorBOk) { + Serial.println("----SENSOR B-----"); + readAndPrintMeasurement(sensorB); + } +} \ No newline at end of file diff --git a/differentI2cBusesExample/sketch.yaml b/differentI2cBusesExample/sketch.yaml new file mode 100644 index 0000000..2deb4a2 --- /dev/null +++ b/differentI2cBusesExample/sketch.yaml @@ -0,0 +1,8 @@ +profiles: + arduino-uno-r4: + fqbn: arduino:renesas_uno:unor4wifi + platforms: + - platform: arduino:renesas_uno (1.4.1) + libraries: + - Sensirion I2C SCD4x (1.0.0) + - Sensirion Core (0.7.0) \ No newline at end of file diff --git a/i2c-pull-up-resistors-intro.md b/i2c-pull-up-resistors-intro.md new file mode 100644 index 0000000..1275a64 --- /dev/null +++ b/i2c-pull-up-resistors-intro.md @@ -0,0 +1,58 @@ +# Pull Up Resistors on I2C Lines + +When examining the I2C protocol, you will observe that the I2C SCL and SDA lines remain in a high state when idle. +When the I2C leader or follower communicate, they pull the line to a low state. To return the lines to a +high state, external components known as pull-up resistors are required. Without these pull-ups, the lines would be left +floating, resulting in an undefined state. This leads to incorrect detection of the low state and introduces +errors in your communication. + +Having pull-up resistors in place is essential for ensuring good signal quality and reliable data exchange. If you are +experiencing unreliable communication, such as sporadic data dropouts or only receiving part of the data, it is +advisable to check the pull-up resistor configuration. + +Where can pull-up resistors be placed: + +1. On the development board you are connecting. +2. On the microcontroller. +3. Manually wired on the SDA and SCL lines between the microcontroller and development board, for example, by using a + breadboard. + +So if your development board or microcontroller has pull-ups built-in, you should be good to go. +For example, the ESP32 DevKit 4 has pull-ups built-in, but the STM32 Nucleo does not. + +Pull-up resistors are connected from the SDA and SCL lines to the positive supply voltage. Standard resistor values are +4.7 kΩ or 10 kΩ. You may also find an optimal pull-up resistor value for your sensor in the sensor or development board +datasheet. + +If there are pull-up resistors on both the board and the microcontroller, the pull-up resistances are in parallel, +resulting in a lower overall value. This could potentially lead to a pull-up resistance that is too small, causing your +devices to fail to drive the lines to the low level reliably. + +The pull-up resistance value depends on various factors. If you can log the signals with a logic analyzer or +oscilloscope, you can check that the waveform of your I2C signals has sharp edges, indicating that your resistor setup +is appropriate. + +For example, you observe the signal of a SEK SCD41 connected to a Nucleo 64 board. Neither the SEK SCD41 nor the STM +Nucleo 64 board includes pull-up resistors. Therefore, we need to connect a resistor between VDD and each SDA/SCL line, +requiring one resistor per line. + +**Too low** If you have resistance that is too low or has no pull-ups, the lines will be floating. +In the setup here, you can observe that both lines are low (0V) in the idle state. When the I2C leader attempts to +communicate, you may see some signal, but the SDA line is essentially just following the clock signal. + +Trace of setup with no resistors: +![LogicAnalyzer Snapshot with NO resistors](images/Nucleo64_I2c_No_PullUps.png) + +**Good** If your resistors are properly dimensioned, the signal shape will appear more rectangular. The lower the +resistor value, the sharper the rectangular shape should be. +Also, note that at the beginning of the trace, you can observe that both the SDA and SCL lines are in a high state (~ +3.3V). + +Trace of setup with 2.2kOhm resistors: +![LogicAnalyzer Snapshot with 2.2kOhm resistors](images/Nucleo64_I2c_2p2kOhm_PullUps.png) + +**Too big** If your resistor values are too large, the signal may take too long to recover to the high state and may not +reach the maximum voltage anymore (the maximum voltage reached in the trace during communication is approximately 3.1V). + +Trace of setup with 18kOhm resistors: +![LogicAnalyzer Snapshot with 18kOhm resistors](images/Nucleo64_I2c_18kOhm_PullUps.png) \ No newline at end of file diff --git a/images/Nucleo64_I2c_18kOhm_PullUps.png b/images/Nucleo64_I2c_18kOhm_PullUps.png new file mode 100644 index 0000000..80c927e Binary files /dev/null and b/images/Nucleo64_I2c_18kOhm_PullUps.png differ diff --git a/images/Nucleo64_I2c_2p2kOhm_PullUps.png b/images/Nucleo64_I2c_2p2kOhm_PullUps.png new file mode 100644 index 0000000..70a9d73 Binary files /dev/null and b/images/Nucleo64_I2c_2p2kOhm_PullUps.png differ diff --git a/images/Nucleo64_I2c_No_PullUps.png b/images/Nucleo64_I2c_No_PullUps.png new file mode 100644 index 0000000..f05cb2f Binary files /dev/null and b/images/Nucleo64_I2c_No_PullUps.png differ diff --git a/images/wiringTwoSCD41ToArduinoUnoR4.fzz b/images/wiringTwoSCD41ToArduinoUnoR4.fzz new file mode 100644 index 0000000..36fe505 Binary files /dev/null and b/images/wiringTwoSCD41ToArduinoUnoR4.fzz differ diff --git a/images/wiringTwoSCD41ToArduinoUnoR4.png b/images/wiringTwoSCD41ToArduinoUnoR4.png new file mode 100644 index 0000000..c6f19ac Binary files /dev/null and b/images/wiringTwoSCD41ToArduinoUnoR4.png differ diff --git a/images/wiringTwoSCD41ToESP.fzz b/images/wiringTwoSCD41ToESP.fzz new file mode 100644 index 0000000..7842095 Binary files /dev/null and b/images/wiringTwoSCD41ToESP.fzz differ diff --git a/images/wiringTwoSCD41ToESP.png b/images/wiringTwoSCD41ToESP.png new file mode 100644 index 0000000..d54a283 Binary files /dev/null and b/images/wiringTwoSCD41ToESP.png differ diff --git a/images/wiringTwoSCD41ToSTM32Nucleo64.fzz b/images/wiringTwoSCD41ToSTM32Nucleo64.fzz new file mode 100644 index 0000000..1349809 Binary files /dev/null and b/images/wiringTwoSCD41ToSTM32Nucleo64.fzz differ diff --git a/images/wiringTwoSCD41ToSTM32Nucleo64.png b/images/wiringTwoSCD41ToSTM32Nucleo64.png new file mode 100644 index 0000000..a3ccbdc Binary files /dev/null and b/images/wiringTwoSCD41ToSTM32Nucleo64.png differ