BLE Beacons: an experiment with an I2C sensor 

 February 6, 2024

By  Peter

Join Our Mailing List

We publish fresh content each week. Read how-to's on Arduino, ESP32, KiCad, Node-RED, drones and more. Listen to interviews. Learn about new tech with our comprehensive reviews. Get discount offers for our courses and books. Interact with our community.

One email per week, no spam, unsubscribe at any time.

In this post, I continue the work I started in last week’s article. If you haven’t read that, please take a few minutes to do so because I’ll be building on that knowledge here.

Last week, you learned about BLE beacons and experimented with the Fermion BLE Sensor Beacon from DFRobot. You configured a beacon to take readings from an analog light sensor and broadcast the raw values from the sensor to any listening BLE device. I used my phone for that purpose.

Let’s take the next step: replacing the analog sensor with a more sophisticated I2C digital sensor. This is an opportunity to dig deeper into I2C territory and learn how to burn configuration to the beacon.

To achieve this, there are two main challenges to overcome:

  1. Unlike analog sensors, digital sensors using I2C, SPI, or other protocols are far more complicated to work with from a programming perspective. We will not use an Arduino or ESP32 MCU to take the readings but a beacon module. As we can’t run an I2C library to help reduce the programming complexity, we will need to gain a much deeper understanding of the I2C protocol to configure the beacon to read the values from the sensor directly, using raw I2C commands.
  2. The Fermion beacon device does not allow for testing of I2C configuration in RAM, as we did with the analog sensor in the previous article. Instead, we will need to test that the sensor is working and the parameters of the I2C communication using an Arduino, then copy these parameters to the beacon device, and finally “burn” the configuration to the beacon. Once a configuration is burned, it is permanent. If we make a mistake, the beacon is “bricked”, and we’ll have to use a new one to try again.

So, let’s begin with a quick tutorial on the I2C protocol.

I2C basic, a deeper dive

I2C (Inter-Integrated Circuit), pronounced “I-squared-C,” is a synchronous, multi-master, multi-slave, packet-switched, single-ended, serial communication bus invented by Philips Semiconductor (now NXP Semiconductors). It is widely used for attaching low-speed peripherals to microcontrollers in short-distance, intra-board communication.

Think of I2C as a straightforward method for electronic devices to communicate with each other. It’s like having a conversation where only two wires are needed, no matter how many devices are involved. This makes it incredibly useful for connecting various sensors, displays, or other components to a central controller, such as an Arduino or ESP32, without a mess of wires.

I2C was designed to support communication between different parts of an electronic device using just two lines: one for data (SDA) and one for timing (SCL). This simplicity is a big advantage, especially in complex projects.

For example, if you’re building a weather station with an Arduino, you might use I2C to connect temperature, humidity, and pressure sensors. The Arduino sends a signal to each sensor asking for data, and the sensors respond in turn, all over the same two wires. It’s efficient and keeps your project neat.

Similarly, in a project with an ESP32, you might use I2C to connect a small display screen. The ESP32 can send text or graphics to the display, telling it what to show, again using just those two lines.

In both cases, I2C helps manage these conversations smoothly, even if multiple devices are talking at once. It’s like having a well-organized group chat where everyone takes turns, ensuring the information is exchanged clearly and without confusion.

I2C and how it works

Let’s break down how I2C works in a way that’s easy to understand, yet detailed enough for those who are keen on the technical aspects.

At its core, I2C is a two-wire protocol, which means it uses just two wires for communication. These are:

  1. SDA (Serial Data Line): This is the line through which data is sent back and forth between devices.
  2. SCL (Serial Clock Line): This line provides the clock signal, which synchronizes data transmission between devices.

Now, let’s talk about the devices involved. In an I2C setup, you’ll hear about ‘master’ and ‘slave’ devices. The master device is the one that initiates and controls the communication. It can be something like an Arduino or Raspberry Pi. The slave devices are the ones that respond to the master’s requests. These could be sensors, displays, or other peripherals.

Here’s a step-by-step look at how a typical I2C communication might go:

  1. Start Condition: This is how the master device signals that it’s about to send data. It does this by pulling the SDA line low (to a lower voltage) while the SCL line is still high.
  2. Address Frame: The master then sends an address frame. This is basically the ‘name’ or address of the slave device it wants to talk to. Each device on the I2C bus has a unique address, so the master uses this address to specify which device it’s communicating with.
  3. Read/Write Bit: Along with the address, the master also sends a bit that tells the slave device whether it wants to read data from it or write data to it.
  4. Acknowledgment Bit (ACK): If a slave device recognizes its address and is ready to communicate, it sends back an acknowledgment bit. This is the device’s way of saying, “Yes, I’m here and ready to communicate.”
  5. Data Transfer: Data is then transferred in 8-bit chunks. After each chunk, an acknowledgment bit is sent by the receiving device.
  6. Stop Condition: Once the communication is complete, the master sends a stop condition. This is done by pulling the SDA line high (to a higher voltage) while the SCL line is still high. This signals all devices on the I2C bus that the data transfer is complete.

One of the neat features of I2C is ‘clock stretching’. This is where a slave device can hold the clock line low. It’s a way for slower devices to tell the master, “Hey, slow down, I need more time to process this.”

In the context of microcontrollers like Arduino or ESP32, I2C is incredibly useful. For instance, if you’re building a project with multiple sensors, you can connect all of them to the same two I2C wires on the Arduino. The Arduino can then communicate with each sensor one at a time using their unique addresses. It simplifies wiring and saves on input/output pins.

In summary, I2C is a versatile and efficient protocol that’s great for connecting various components in an electronic project. Its two-wire setup and address-based communication make it ideal for systems where multiple devices need to talk to a central controller.

I2C and speed

I2C supports multiple speed modes, allowing for flexibility in communication speed based on the requirements of the application and the capabilities of the devices:

  • Standard Mode: The most basic speed mode, operating up to 100 kHz. It’s suitable for many general applications where high data rates are not necessary.
  • Fast Mode: Increases the speed to 400 kHz, offering faster data transfer rates for applications that require quicker communication between devices.
  • Fast Mode Plus: An enhancement over Fast Mode, operating up to 1 MHz. This mode is useful for more demanding applications that need higher data throughput.
  • High-Speed Mode: For even higher performance, High-Speed Mode can go up to 3.4 MHz, enabling very fast data transfers. However, not all devices support this mode, so compatibility must be checked.
  • Ultra-Fast Mode: The fastest I2C mode, capable of reaching up to 5 MHz. It’s less commonly used and supported by a limited number of devices.

I2C and addresses

I2C uses addresses to identify each device on the bus. This allows the master device to communicate with specific slave devices:

  • 7-bit Addressing: The most common form of addressing, allowing for 128 possible addresses (0x00 to 0x7F). However, some addresses are reserved for special purposes, so the actual number of available addresses for user devices is fewer.
  • 10-bit Addressing: Extends the address space to allow for more devices on the same bus. It’s less commonly used but can be helpful in complex systems with many I2C devices.

I2C and other considerations

There’s a few other things to consider when working with I2C directly. Those are:

  • Pull-up Resistors: I2C lines (SDA and SCL) are open-drain, requiring pull-up resistors to function correctly. The value of these resistors can affect the speed and reliability of the communication, typically ranging from 2.2kΩ to 10kΩ based on the bus speed and capacitance.
  • Bus Capacitance: The total capacitance of the I2C bus affects the maximum speed and the value of the pull-up resistors. Keeping the bus capacitance low helps in maintaining signal integrity, especially at higher speeds.
  • Arbitration and Clock Stretching: I2C supports multi-master configurations where two or more masters can control the bus. Arbitration ensures that only one master controls the bus at a time. Clock stretching allows a slower device to delay the master by holding the SCL line low, giving it more time to process the data.

When programming I2C communications, it’s essential to consider these factors to ensure reliable and efficient communication between devices. Always refer to the datasheets of the devices you’re using for specific requirements and capabilities related to I2C communication. In the example later in this post, I will have to refer to the datasheet for my sensor, the SH40, several times.

I2C and Arduino without a library

Hopefully, you now have a better understanding of the technicalities of I2C, and might be wondering how all this comes together when its time to program an Arduino Uno board to “talk” to an I2C sensor.

I am discussing Arduino and I2C here for two reasons:

  1. I am almost 100% certain that you already have a good understanding of the Arduino, so discussing I2C adds knowledge on top of existing knowledge.
  2. We must use the Arduino to test the I2C sensor module before attaching it to the BLE beacon.

The Arduino Uno offers built-in support for I2C communication, making it a versatile choice for projects that involve connecting various sensors, displays, or other I2C-compatible devices.

On the Arduino Uno, the I2C communication is facilitated through two specific pins:

  • A4 (SDA): Serial Data Line, used for transmitting data between I2C devices.
  • A5 (SCL): Serial Clock Line, used for clocking the data transmission.

These pins are part of the Analog Input section on the Arduino Uno but are multiplexed to serve as the I2C bus when the Wire library is used in your sketches.

Wire Library

The Arduino platform provides the Wire library to implement I2C communication. This library abstracts much of the complexity of I2C communication, making it easier to send and receive data to and from I2C devices. To use I2C in your Arduino sketches, you typically start by including the Wire library at the beginning of your code:

#include <Wire.h>

Then, in your setup() function, you initiate the I2C bus with:


This command sets up the Arduino as an I2C master device. The Wire library also supports creating an Arduino I2C slave device by specifying an address for the Arduino with Wire.begin(address).

I2C Communication with Wire Library

To communicate with I2C devices, you use Wire.beginTransmission(address), Wire.write(), and Wire.endTransmission() for sending data, and Wire.requestFrom(address, quantity) followed by Wire.read() for receiving data. The address parameter is the I2C address of the target device, and quantity is the number of bytes you want to read.

I2C Speed

The Arduino Uno’s I2C bus operates in the Standard Mode, with a default speed of 100 kHz. However, it’s possible to adjust the I2C clock speed using Wire.setClock(clockFrequency) if a connected device supports faster communication.

Multi-Master Capability

While the Arduino Uno can easily function as an I2C master, it’s also capable of acting as a slave device in a multi-master I2C setup. This allows for complex communication scenarios where multiple Arduino boards or other microcontrollers might need to communicate with each other over the same I2C bus.

Limitations and Considerations

  • The Arduino Uno’s hardware I2C pins (A4 and A5) are dedicated, and while it’s possible to create a software-based I2C on other pins using software libraries, the hardware I2C offers the best performance and reliability.
  • When connecting multiple devices to the I2C bus, it’s essential to ensure that each device has a unique address and that the total bus capacitance does not exceed the limits for reliable communication.
  • Pull-up resistors are necessary for the SDA and SCL lines to function correctly. Many Arduino boards, including the Uno, have built-in pull-up resistors on the I2C lines, but when connecting multiple devices or long cables, additional external pull-up resistors might be needed.

The I2C capabilities of the Arduino Uno make it an excellent choice for projects that require communication with various sensors, displays, or other peripherals, offering a balance between ease of use and flexibility.

A complete I2C Arduino sketch

Let’s put all this knowledge into an Arduino program. This program reads raw values from an SHT40 sensor, like the one I will connect to my BLE beacon later.

This example demonstrates how to communicate with an SHT40 temperature and humidity sensor using the I2C protocol on an Arduino. The SHT40 is a digital sensor that provides accurate temperature and humidity readings.

#include <Wire.h> // Include the Wire library for I2C communication

#define SHT40_ADDRESS 0x44 // Define the I2C address of the SHT40 sensor

void setup() {
  Serial.begin(115200); // Initialize serial communication at 115200 baud rate for debugging
  Wire.begin(); // Initialize the I2C communication

void loop() {
  Wire.beginTransmission(SHT40_ADDRESS); // Begin I2C transmission to the SHT40 sensor
  Wire.write(0xFD); // Send the command to trigger a high-precision measurement
  Wire.endTransmission(); // End the I2C transmission

  delay(10); // Wait for the sensor to complete the measurement (max 10ms according to the datasheet)

  Wire.requestFrom(SHT40_ADDRESS, 6); // Request 6 bytes of data from the sensor
  if (Wire.available() == 6) { // Check if 6 bytes were received
    byte data[6]; // Create an array to store the received data
    for (int i = 0; i < 6; i++) { // Loop to read each byte
      data[i] = Wire.read(); // Read a byte and store it in the array

    // Convert the received data to temperature and humidity
    int temperatureRaw = (data[0] << 8) | data[1]; // Combine the first two bytes for temperature
    int humidityRaw = (data[3] << 8) | data[4]; // Combine the fourth and fifth bytes for humidity

    // Calculate the actual temperature and humidity values
    float temperature = -45 + 175 * (temperatureRaw / 65535.0); // Convert raw value to Celsius
    float humidity = 100 * (humidityRaw / 65535.0); // Convert raw value to relative humidity percentage

    // Print the results to the serial monitor
    Serial.print("Temperature: ");
    Serial.println(" °C");
    Serial.print("Humidity: ");
    Serial.println(" %");

  delay(1000); // Wait for 1 second before taking the next measurement

Program Functionality Explained

  • #include <Wire.h>: This line includes the Arduino Wire library, which is required for I2C communication.
  • #define SHT40_ADDRESS 0x44: Defines the I2C address of the SHT40 sensor. This address is used to communicate with the sensor.
  • void setup() { ... }: The setup function initializes serial communication for debugging purposes and starts the I2C communication.
  • Serial.begin(115200): Sets up the serial port at 115200 baud rate, which is the speed of communication between the Arduino and your computer.
  • Wire.begin(): Initializes the I2C communication protocol.
  • void loop() { ... }: The main loop of the program, where the sensor is read and the data is processed continuously.
  • Wire.beginTransmission(SHT40_ADDRESS): Starts a transmission to the SHT40 sensor.
  • Wire.write(0xFD): Sends a command to the sensor to perform a high-precision measurement.
  • Wire.endTransmission(): Ends the transmission, signaling that the command has been sent.
  • delay(10): Waits for 10 milliseconds, giving the sensor time to measure temperature and humidity.
  • Wire.requestFrom(SHT40_ADDRESS, 6): Requests 6 bytes of data from the sensor, which includes the temperature and humidity readings.
  • if (Wire.available() == 6) { ... }: Checks if 6 bytes of data are available to read.
  • byte data[6]: Declares an array to store the 6 bytes of data from the sensor.
  • The for loop reads each byte from the I2C buffer and stores it in the data array.
  • The next lines extract the temperature and humidity from the received data, convert them to human-readable values, and then print these values to the serial monitor.
  • delay(1000): Pauses the loop for 1 second before repeating, allowing for a 1-second interval between measurements.

This program demonstrates how to read temperature and humidity data from the SHT40 sensor using I2C communication, process the data, and output the results to the serial monitor.

I2C support in the IN100 SoC

Equipped for seamless I2C interactions, the IN100 chip is a top pick for diverse electronics endeavors. I2C, a go-to for hooking up multiple gadgets on just two wires, keeps things talking smoothly in the microcontroller world. The IN100 chip utilizes this protocol effectively to connect with other I2C-compatible devices.

One of the standout features of the IN100 chip’s I2C support is its ability to function as both a master and a slave device. The IN100 chip’s versatility shines as it can take charge or follow lead in electronic setups, effortlessly switching between guiding other devices and taking cues from a central controller. So, when you’ve got a ton of chips and sensors that need to talk to each other, the IN100 chip is your go-to because it makes sharing data a breeze.

The IN100 chip’s I2C interface is designed for ease of use. With the IN100 chip at the helm of I2C communications, you’re free to zip through data transfers with ease and keep your focus on the big picture of your project. So, you can ditch the tedious coding grunt work and zoom in on the big picture of your project.

Plus, the chip’s geared up to work with the usual I2C speeds, so you can hook it up with a bunch of different devices without any hiccups. So, the IN100 chip’s speed adjustment is a game-changer because it lets you find that sweet spot—fast enough for demanding tasks but still easy on the battery—for all kinds of gadgets.

Error handling is another area where the IN100 chip’s I2C support shines. It’s got built-in tricks to spot and bounce back from typical I2C hiccups, like when data crash into each other or someone else wins the signal tug-of-war. With these I2C safeguards in place, you’re essentially beefing up your project’s communication backbone, which means less messed-up data and more reliable device performance.

The IN100 chip offers comprehensive support for I2C communications, making it a valuable component for electronics enthusiasts and professionals alike. Its flexibility, ease of use, and error-handling capabilities make it a solid choice for projects requiring reliable I2C communication.

Experiment: Fermion BLE Sensor Beacon with the SHT40 sensor

If you managed to absorb all of the information in this post up to know, congratulations! I know it wasn’t easy. But now, the fun part begins. You will learn how to configure the BLE Sensor Beacon to read temperature and humidity from the SHT40 sensor.

The SHT40 sensor

The SHT40 is a digital temperature and humidity sensor from Sensirion, known for its precision, reliability, and compact form factor. It’s part of Sensirion’s SHT4x series, which represents the latest generation of temperature and humidity sensors, designed to meet the needs of a wide range of applications from consumer electronics to industrial systems.

The SHT40 sensor stands out for its ability to deliver high-accuracy measurements in a tiny footprint, making it an excellent choice for applications where space is at a premium and precision is critical. Its design reflects a commitment to performance and efficiency, incorporating Sensirion’s advanced sensor technology to provide reliable readings.

For this experiment, you will need a copy of the SHT40 datasheet.

In relation to the I2C communications for the SHT40 that you are about to use, you will need this information:

The SHT40 temperature and humidity sensor incorporates an I2C interface for communication, which allows it to easily integrate with microcontrollers and other digital systems that support the I2C protocol. Here are some technical details related to its I2C communications:

I2C Address

The SHT40 has a fixed I2C address, which is typically 0x44 (7-bit format). This address is used to initiate communication with the sensor, and it’s important to ensure that no other device on the same I2C bus has the same address to avoid conflicts.

Communication Speed

The sensor supports standard I2C speeds up to 1 MHz (Fast-mode Plus), making it compatible with most microcontrollers and systems. The actual speed used can be configured in the master device, depending on the requirements of the application and the capabilities of the I2C bus.

Data Format

The SHT40 communicates data in a digital format, providing temperature and humidity readings as 16-bit values. The data is transmitted in a specific sequence, usually starting with the most significant byte (MSB) for each measurement.


The sensor uses specific command codes sent over the I2C bus to initiate measurements, read results, and perform other functions such as soft reset or heater control. These commands are detailed in the SHT40 datasheet and are essential for proper communication and control of the sensor.

Here is a list of commands (you can find details in the datasheet):

Single Shot Data Acquisition Commands:

  • 0xFD: Measure High Precision. You will be using this command later.
  • 0xF6: Measure Medium Precision.
  • 0xE0: Measure Low Precision.

These commands are used for single-shot measurements, allowing the sensor to take a single measurement of temperature and humidity with different levels of precision.

Periodic Data Acquisition Commands:

Start periodic measurement with different repeatability settings:

  • 0x21: High repeatability with 0.5 measurements per second.
  • 0x32: Medium repeatability with 1 measurement per second.
  • 0x24: Low repeatability with 2 measurements per second.
  • 0x2F: High repeatability with 4 measurements per second.
  • 0x38: Medium repeatability with 10 measurements per second.

Use 0x30 to stop periodic data acquisition.

Read Out of Measurement Results for Periodic Mode:

  • 0xE000: Read measurement for periodic mode.

This command is used to read the measurement results from the sensor when in periodic measurement mode.

ART (Accelerated Response Time) Command:

  • 0x2B: ART command.

This command is used to enable the ART feature, which accelerates the sensor’s response time.

Heater Command:

  • 0x39: Enable heater.
  • 0x36: Disable heater.

These commands control the internal heater of the SHT40, which can be used to test the sensor or for certain applications where heating is necessary.

Soft Reset Command:

  • 0x94: Soft reset command.

This command is used to reset the sensor, which can be useful for re-initialization without needing to power cycle the sensor.

Fetch Data Command:

  • 0xE000: Fetch data command for periodic measurement mode.

This command is used to fetch the latest measurement results in periodic measurement mode.

Read Serial Number Command:

  • 0x89: Read serial number of the sensor.

This command allows reading the serial number of the SHT40 sensor for identification purposes.

Step 1: Test that the sensor works with the Arduino

We only have one shot to configure the beacon to work with the SHT40 (or any other I2C device), so we have to do two things before we do anything with the sensor:

  1. Double-check that the sensor works.
  2. Confirm and note the I2C parameters for the sensor.

To achieve these goals, you can use any I2C-compatible microcontroller. I will use an Arduino Uno R4. The same program will work with an Arduino Uno R3 or an ESP32.

Start by wiring the Arduino Uno to the sensor. Follow this process:

  • Connect the sensor GND pin to the Arduino GND pin.
  • Connect the sensor VCC pin to the Arduino 3.3V pin.
  • Connect the sensor SCL pin to the Arduino SCL pin (or A5).
  • Connect the sensor SDA pin to the Arduino SDA pin (or A4).
Arduino Uno and SHT40.

And here’s my circuit with the Arduino powered up.

My Arduino + SHT40 test circuit.

Now, start the Arduino IDE, and copy the following sketch in a new window:

  #include <Wire.h>

  #define SHT40_ADDRESS 0x44 // I2C address of the sensor, here 0x44 for SHT40
  int l = 6;// Read Byte Length

  void setup(){

  void loop() {

    Wire.endTransmission();//i2c null

    delay(10); // The programme waits 10ms for the SHT40 to get ready

    Wire.requestFrom(SHT40_ADDRESS, 6);
    if (Wire.available() >= l) {
      byte data[l];
      for (int i = 0; i < l; i++) {//Read the data output from the I2C sensor
        data[i] = Wire.read();
        Serial.print(data[i], HEX);
        Serial.print(" ");
    Wire.endTransmission();//i2c null

This Arduino sketch communicates with an SHT40 temperature and humidity sensor using the I2C protocol. The sketch initializes the I2C communication, sends a command to the sensor to take a measurement, and then reads and prints the measurement data.

The sketch is very similar to the example I provided earlier in this post. Here, I will highlight a few instructions of main interest:

  • #define SHT40_ADDRESS 0x44: Defines the I2C address of the SHT40 sensor. The SHT40 uses 0x44 as its default I2C address.
  • int l = 6;: Specifies the number of bytes to read from the sensor, which is 6 in this case. Remember that the IN100 buffer can only hold 5 bytes, so the last byte will be lost. More about this later.
  • Wire.write(0xFD);: Sends a command (0xFD) to the sensor, instructing it to perform a measurement. This command is specific to the SHT40 and triggers a high-precision, single-shot measurement.
  • Wire.requestFrom(SHT40_ADDRESS, 6);: Requests 6 bytes of data from the sensor, which is the standard data packet size for a single measurement from the SHT40, including temperature, humidity, and CRC checksums for data integrity.

Upload the sketch to your Arduino, and notice the output in the serial monitor. Here is what I see in mine:

69 61 9D 9A 50 5C 
69 65 59 9A 58 E5 
69 5F 47 9A 64 5D 
69 5B 83 9A 6B 73 
69 6E B3 9A 79 52 
69 64 68 9A 68 20 

There’s a few conclusions to note from this experiment:

  • The device is working!
  • I2C address is 0x44.
  • I2C standard speed of 100 KHz is used.
  • As per the datasheet, the six bytes returns contain these values (see page 12):
    • Two bytes for the temperature.
    • One byte for the CRC of the temperature.
    • Two bytes for the humidity.
    • One byte for the CRC of the humidity.

Let’s focus on the data. In the last line from my captured results, you can see the data 69 64 68 9A 68 20`. Let’s decipher them:

  • The temperature is 0x69 0x64.
  • The checksum for the temperature is 0x68.
  • The humidity is 0x9A 0x68.
  • The checksum for the humidity is 0x20.

You might be wondering how to convert these cryptic hexadecimal numbers into degrees Celsium or other human-friendly numbers. You can find a conversion formula on page 12 of the datasheet. To do the calculation, first convert the hexadecimal two-byte number into decimal, and then apply the formula.

The temperature hex is 6964. Using a calculator, I find that this number is decimal 26980.

The humidity hex is 9A68. In decimal, it is 39528.

Let’s calculate the temperature in °C. T = (-45 + 175 * 26980 / (2^16 – 1)) = 27.0455 °C

And the humidity: RH = (-6 + 125 * 39528 /(2^16 – 1) = 69.3948 %


Step 2: Configure the beacon using NanoBeaconfConfig

Now that I know that the I2C sensor works, and its I2C parameters, I can turn to NanoBeaconConfig to configure my beacon. If you are not familiar with this tool, go to part one of this article to read about it.

Start the configuration at the Advertising tab. Select Set #1 (1) and click Edit (2).

Under “Advertising Data “select “Custom” (3) and click “Settings” (4).

In the Custom Advertising Settings form, check the “Device Name” checkbox, and enter a reasonable name in the box (5). I named mine “SHT40”. This is the name I will look for in the BLE scanner app on my phone later. Also, check the “Manufacturer Specific Data” box (6), and click Edit (7). This is where you will configure the I2C data to be broadcasted as part of the manufacturer-specific data packet.

I plan to wire my I2C sensor to pins IO3 and IO7, which provide access to the I2C. Later, I will configure the role of each pin, that is SCL for IO7 and SDA for IO3. I chose these pins so I can simply plugin the sensor to a header on the beacon without the need for number wires. You can learn about which pins can be used with I2C in Table 1 (page 13) of the IN100 datasheet.

In the Manufacturer Specific Data form, select “I2C Slave #1 Read Data” from the drop-down (8). The offset allows you to ignore zero or more of the first few bytes of the I2C data, which is useful if these bytes don’t contain useful information. In our case, we’ll set the offset to zero since all bytes are useful. In the “Bytes” box, enter 5. Even though the sensor sends a total of six bytes, the IN100 SoC only has a 5-byte buffer, so the last byte cannot fit. This is not a problem, though, because the sixth byte is the CRC for the humidity value, so we can knowingly ingore it.

When you have entered the values in the fields, click “Append to Data” (11). Verify that the correct data is in the manufacturer-specific data field (12), and click OK (13).

Continue with the Advertising Parameters tab, still under “Advertising”. Most of the defaults here are acceptable. Only select the “Public/Static Address” (14) and click OK (15).

Next, let’s configure the I2C channel. Click on the I2C tab (16), enable I2C Slave #1 (17), and click Edit (18).

As per our findings when we did the Arduino experiment, we’ll set the I2C slave address to 0x44, the address mode to 7 bits, and I2C speed to 100 KHz. We’ll also set SCl to be pin MGPIO 7 and SDA GPIO 3. Finally, the data length is 5 bytes.

In the I2C Commands form, you must create a rudimentary program that implements the sequence of commands that control the communication between the beacon and the sensor using the I2C protocol. There are pre-defined commands at the top of the form which require custom text to be configured, and then to be added to the list of commands in the large text box using the “Add button”.


  • For a command to be added to the list, it must be selected.
  • If you make a mistake, use the Delete button to remove the error and try again.

It is important to get this right because you only have one shot at it!

Follow these instructions to the letter:

  1. Check “Execute I2C command when cold boot”.
  2. Check “Execute I2C command when warm boot”.
  3. Select “i2c_write(slave_addres…..)” and enter “0xFD” in the box. This will send the command “0xFD to the slave device”, which instructs it to take a high-precision reading. Click “Add”.
  4. Select the “delay_command” and enter “10000” in the box to add a 10ms delay. Click “Add”.
  5. Select the “i2c_read(…..)” command, and add “5” in the box. Click “Add”. This will read five bytes and send them to the master device.
  6. Check that the Commands box contents looks like my example below.
  7. Click OK.

Confirm the configuration in the Current Settings box in the bottom right corner of the configuration tool:

Step 3: Connect the beacon and sensor

The configuration is complete. Let’s turn our attention to the hardware. I have soldered a female header with four pins on my beacon to make this step easy. To connect the beacon to the sensor, simply align the two so that the sensor VCC pin is aligned with VBAT on the beacon, and the two GND pins also align. Verify that the sensor’s SCL pin connects to the beacon’s IO7, and SDA (sensor) connects to IO3 (beacon).

Here is the schematic, in which I have included the USB-to-TTL interface because we’ll need it to burn the configuration shortly:

Here is a photograph of my beacon and sensor:

Step 4: Burn the configuration to the beacon

We are now ready to burn the configuration to the beacon. For this, we’ll use the USB-to-TTL interface, as we did with the first experiment.

Connect the interface to the beacon as per the wiring diagram. To facilitate this, I soldered a four-pin header to the beacon, as you can see in the photograph below:

Plug the USB interface to your computer, and continue the process with the config tool.

With the USB interface connected, click “Probe” to update the listing of available serial ports (1), select the port that corresponds to your USB interface from the drop-down menu (2), and click “Connect” (3).

Once the connection is successful, you’ll see a message confirming this.

Now, we have reached the most critical part of this process: to burn the configuration to the beacon. This is not reversible, so take a few minutes to double-check your work so far. When you are confident that the configuration is correct, click “Burn/Program” (1). A progress bar will appear. Once the process is complete, click OK to close the progress window (2).

Step 5: Test

You can now disconnect the USB interface from your computer and from the beacon. Insert a coin cell battery to the beacon, and start the scanner app on your phone.

In the scanner app, use the filter to make it easy to find your beacon. I entered “SHT” in the device name, and the beacon appeared in the list.


The sensor data appears in the Manufacturer Data field, next to the manufacturer ID. In the screenshot above, you can see the Manufacturer Data:

  ID      T    crc    H   
0505 6A32 90 90A3

I have labelled the manufacturer ID, temperature (T), CRC for the temperature, and the humidity (H).


In this exploration of I2C communication with the SHT40 sensor, we’ve journeyed from understanding the basics of I2C protocol to practically applying this knowledge in configuring a BLE beacon for direct sensor communication. We started with a foundational overview of I2C, highlighting its simplicity and efficiency in connecting multiple devices over just two wires. By delving into the technicalities, we learned how I2C facilitates orderly communication between a ‘master’ and multiple ‘slave’ devices, ensuring clear data exchange.

Our practical application involved replacing an analog sensor with a digital I2C sensor, the SHT40, to enhance our beacon’s capabilities. This transition required a deeper dive into I2C commands and the nuances of digital sensor communication. We navigated through the challenges of programming a beacon module without the convenience of an I2C library, emphasizing the importance of understanding raw I2C commands.

The experiment with the Arduino Uno served as a crucial step, verifying the sensor’s functionality and understanding the necessary I2C parameters. This groundwork was essential before configuring the beacon, as it ensured we had a working setup before committing to the irreversible step of burning the configuration into the beacon.

The successful beacon configuration and subsequent data broadcast demonstrated the power of integrating digital sensors with BLE beacons. This not only expanded the beacon’s utility but also provided a template for incorporating various I2C devices into similar projects.

Looking ahead, there are many interesting possibilities. One could explore integrating different types of sensors, enhancing data processing on the beacon, or even developing custom applications to interact with the beacon’s data. The key takeaway is the empowerment that comes with mastering I2C communication, opening doors to a myriad of IoT applications. Whether it’s for personal projects or sophisticated industrial solutions, the skills and knowledge gained here lay a solid foundation for future innovations.


You may also like

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Understanding the power requirements is crucial for any enthusiast using the Arduino Uno in their projects. How you manage voltage, amperage, and power sources impacts the success or failure of your endeavours. In this guide,

Read More
The Ultimate Guide to Powering Your Arduino Uno Board

If you are curious about how electronic devices work, understanding the fundamentals of the operation of their basic components is essential. Every electronic device—from the smartphones in our pockets to the satellites orbiting our planet—contains

Read More
A Guide to Essential Electronic Components in Circuits