Getting Started with STM32F103: I2C Bus Scanner

n 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 STM32F103

In this guide we will cover the following:

  • What is I2C
  • I2C setup for stm32f1
  • I2C bus scanner code.
  • 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.

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 STM32F10: locating pins

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

From Table 55 I2C remaping of STM32F103 reference manual, we can find that PB6 is SCL and PB7 os SDA:

Hence, we shall configure PB6 and PB7.

2.2: I2C setup for STM32F103: Pin Configuration

We start off by setting up the pins as following:

  • Set the mode to be output 50MHz.
  • Set the configuration alternate open drain.
  • Enable clock access to alternate mode of GPIO.

We start off by creating new source and header file with name i2c.c and i2c.h respectively

Within header, the following is declared:

#ifndef I2C_H_
#define I2C_H_
#include "stdint.h"
#include "stdio.h"
void i2c_init();
void i2c1_scan_bus(void);

#endif /* I2C_H_ */

Within source file, include the following:

#include "i2c.h"
#include "stm32f1xx.h"

For i2c_init function:

First, enable clock access to GPIOB:

/*Enable clock access to GPIOB*/
	RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;

Then set PB6 and PB7 to the requirements:

/*Set PB6 to output 50MHz*/
	GPIOB->CRL|=GPIO_CRL_MODE6;
	/*Set PB6 to ALternate Open drain*/
	GPIOB->CRL|=GPIO_CRL_CNF6;

	/*Set PB7 to output 50MHz*/
	GPIOB->CRL|=GPIO_CRL_MODE7;
	/*Set PB7 to ALternate Open drain*/
	GPIOB->CRL|=GPIO_CRL_CNF7;

Finally enable clock access to AFIO:

	/*Enable clock access to alternate function of the pins*/
	RCC->APB2ENR|=RCC_APB2ENR_AFIOEN;

Thats all for GPIO settings.

2.3 I2C setup for STM32F103: I2C configuration

We start off by finding which bus is I2C1 is connected to.

From the datasheet we can find that I2C1 is connected to APB1:

Hence, we can enable clock access to I2C1 as following:

	/*Enable clock access to I2C1*/
	RCC->APB1ENR|=RCC_APB1ENR_I2C1EN;

Now, we shall tell the i2c the frequency of the bus. Since the MCU is running by default at 8MHz, se shall set it to 8:

	/*Tell the peripheral that the clock is 8MHz*/
	I2C1->CR2&=~(I2C_CR2_FREQ);
	I2C1->CR2|=(8<<I2C_CR2_FREQ_Pos);

Now, we shall set the rise time and we shall use the maximum recommended value of 9.

	/*Set the rise time*/
	I2C1->TRISE=9;

Now for the CCR value:

The calculation as following:

I2C1->CCR|=0x28;

Finally enable the peripheral:

I2C1->CR1|=I2C_CR1_PE;

That all for the initializing sequence.

Hence, the source file as following:

void i2c_init()
{
	/*Enable clock access to GPIOB*/
	RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;

	/*Set PB6 to output 50MHz*/
	GPIOB->CRL|=GPIO_CRL_MODE6;
	/*Set PB6 to ALternate Open drain*/
	GPIOB->CRL|=GPIO_CRL_CNF6;

	/*Set PB7 to output 50MHz*/
	GPIOB->CRL|=GPIO_CRL_MODE7;
	/*Set PB7 to ALternate Open drain*/
	GPIOB->CRL|=GPIO_CRL_CNF7;

	/*Enable clock access to alternate function of the pins*/
	RCC->APB2ENR|=RCC_APB2ENR_AFIOEN;

	/*Enable clock access to I2C1*/
	RCC->APB1ENR|=RCC_APB1ENR_I2C1EN;


	/*Tell the peripheral that the clock is 8MHz*/
	I2C1->CR2&=~(I2C_CR2_FREQ);
	I2C1->CR2|=(8<<I2C_CR2_FREQ_Pos);
	/*Set the rise time*/
	I2C1->TRISE=9;

	I2C1->CCR|=0x28;

	I2C1->CR1|=I2C_CR1_PE;
}

3. I2C bus scan:

To perform bus scann, the following step shall be done:

  • Send the start condition and wait until the start is generated.
  • Send the address with write operation and wait for SR1 or SR2 to be any value.
  • Generate stop condition.
  • wait for about 100uS.
  • If the address is set, the print the address.
  • Loop for all 127 address.

Hence, the scan code as following:

void i2c1_scan_bus(void)
{        int a=0;
         for (uint8_t i=0;i<128;i++)
   {
            I2C1->CR1 |= I2C_CR1_START;
            while(!(I2C1->SR1 & I2C_SR1_SB));
            I2C1->DR=(i<<1|0);
            while(!(I2C1->SR1)|!(I2C1->SR2)){};
            I2C1->CR1 |= I2C_CR1_STOP;
            delay(100);//minium wait time is 40 uS, but for sure, leave it 100 uS
            a=(I2C1->SR1&I2C_SR1_ADDR);
            if (a==2)
         {
                printf("Found I2C device at adress 0x%X (hexadecimal), or %d (decimal)\n\r",i,i);
         }
     }
}

Within the main.c file, initialize the function and in while 1 loop, call the scan bus function as following:

#include "stm32f1xx.h"
#include "i2c.h"
#include "uart.h"


int main(void)
{
	uart2_init();
	i2c_init();
	while(1)
	{
		i2c1_scan_bus();

	}
}

4. Code:

You may download the code from here:

5. Results

Compile, upload the code and open your favourite terminal, set the baudrate to 115200 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 *