Working with STM32 and I2C (Inter Integrated Circuit): I2C bus scanner

official i2c logo

In this guide, we shall discuss what is I2C and how to make an i2c bus scanner to scan the slave devices that connected to the i2c1 bus of STM32F411

In this guide we will cover the following:

  • What is I2C
  • I2C setup for stm32f4
  • I2C bus scanner code
  • Results

1.1: What is I2C

I2C or Inter Integrated Circuit is type of synchronous serial communication that capable to communicate with up to 127 slave devices as show in figure below.

I2C bus

Like UART communication, I2C only uses two wires to transmit data between devices:

  • SDA (Serial Data):  The line for the master and slave to send and receive data.
  • SCL (Serial Clock): The line that carries the clock signal.

1.2: HOW I2C WORKS

With I2C, data is transferred in messages. Messages are broken up into frames of data. Each message has an address frame that contains the binary address of the slave, and one or more data frames that contain the data being transmitted. The message also includes start and stop conditions, read/write bits, and ACK/NACK bits between each data frame:

Introduction to I2C - Message, Frame, and Bit

Start Condition: The SDA line switches from a high voltage level to a low voltage level before the SCL line switches from high to low.

Stop Condition: The SDA line switches from a low voltage level to a high voltage level after the SCL line switches from low to high.

Address Frame: A 7 or 10 bit sequence unique to each slave that identifies the slave when the master wants to talk to it.

Read/Write Bit: A single bit specifying whether the master is sending data to the slave (low voltage level) or requesting data from it (high voltage level).

ACK/NACK Bit: Each frame in a message is followed by an acknowledge/no-acknowledge bit. If an address frame or data frame was successfully received, an ACK bit is returned to the sender from the receiving device.

For more information and detailed explanations refer to this NXP documentation (here).

2.1: I2C setup for STM32F4: locating pins

First we need to find which pins are for I2C1 bus.

From Table 9 of STM32F411 datasheet we find the pins for I2C1

From the table, we can use PB6 & PB7 or PB8 and PB9 for I2C1. In this guide, we will PB8 and PB9 and the alternate function is AF04.

Now We find which bus provides the clock to I2C1. This can be found in datasheet of STM32F411

From the block diagram, the I2C1 is connected to APB1.

2.2: I2C setup for STM32F4: Configuration

Now We need to enable clock access to I2C1 and GPIOB as following:

RCC->AHB1ENR|=RCC_AHB1ENR_GPIOBEN; //enable gpiob clock
RCC->APB1ENR|=RCC_APB1ENR_I2C1EN; //enable i2c1 clock

Then configure the pins as alternate function and AF4:

GPIOB->MODER|=0xA0000; //set pb8and9 to alternative function
GPIOB->AFR[1]|=0x44;

Now we need to set the most important feature of I2C which the pins must be open drain as following

GPIOB->OTYPER|=GPIO_OTYPER_OT8|GPIO_OTYPER_OT9; //set pb8 and pb9 as open drain

Now we need to configure the I2C as following

I2C1->CR1=I2C_CR1_SWRST;//reset i2c
I2C1->CR1&=~I2C_CR1_SWRST;// release reset i2c	
I2C1->CR2|=16;//set clock source to 16MHz
I2C1->CCR=80;  //based on calculation
I2C1->TRISE=17; //output max rise 
I2C1->CR1|=I2C_CR1_PE; //enable I2C

Set up for I2C is completed.

2.3: I2C setup for STM32F4: I2C scan bus

void i2c_bus_scan(void)
{        
					int a=0; //address variable
         for (uint8_t i=0;i<128;i++) //go through all 127 address
   {
            I2C1->CR1 |= I2C_CR1_START; //generate start 
            while(!(I2C1->SR1 & I2C_SR1_SB)); // wait to start to be generated
            I2C1->DR=(i<<1|0); // transmit the address
            while(!(I2C1->SR1)|!(I2C1->SR2)){}; //clear status register
            I2C1->CR1 |= I2C_CR1_STOP; //generate stop condition
            delay(100);//minium wait time is 40 uS, but for sure, leave it 100 uS
            a=(I2C1->SR1&I2C_SR1_ADDR); //read the status register address set
            if (a==2)//if the address is valid
         {
					 //print the address
					 sprintf(data,"Found I2C device at adress 0x%X (hexadecimal), or %d (decimal)\r\n",i,i);
                UART_Write_String(data);
         }
     }
}

The UART_Write_String function can be obtained from here (link)

or

Simply download the entire project from here:

3. Results

Compile, upload the code and open your favourite terminal and you shall see the address of connected devices (Note: I got only 1 device connected)

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *