STM32F103 I2C
===========================
STM32F103 I2C (Inter-Integrated Circuit) or sometimes called TWI (Two Wire Interface) is a synchronous serial protocol that only needs 2 wires for communication.
The wires are SCL for clock line and SDA for data line. With I2C, you can connect devices like temperature sensor, EEPROM, RTC, and etc (up to 112 devices) just using 2 wires (plus GND wire).
For accessing these devices, I2C use 7-bit address. Theoretically, 7-bit address space allows 128 addresses, however some addresses are reserved for special purpose.
Thus, only 112 addresses can be used.

I2C Protocol
===========================
I2C is a multi-master and multi-slave bus.
I2C can consist of one or more master device, but only one master device can access the I2C bus each time.
To communicate with a slave device, master device is responsible for sending clock signal to the slave device.
The clock signal is generated by master.
I2C pins are [open-drain](https://en.wikipedia.org/wiki/Open_collector), so it can pull the signal to logic 0, but can’t drive it to logic 1.
Each signal line (SCL and SDA) must have a pull-up resistor to restore the signal to logic 1 when no device is asserting it to logic 0.
The figure below explains how I2C protocol works.
START and STOP conditions are defined as rising edge or falling edge on the SDA line, while the SCL line is high.
After master sends the START condition, then it will be followed by the slave address and R/W bit.
After that, slave is responsible to generate ACK (acknowledge).
ACK is defined as logic 0 on SDA line. If master wants to write data to slave (indicated by R/W bit = 0), then master will send the data byte to slave.
The MSB of data byte will be sent first to slave. Slave is responsible to generate ACK for every received data byte.

If master wants to read data from slave (indicated by R/W bit = 0), then after the first ACK of slave address and R/W bit,
master will read data byte from slave.
The MSB of data byte will be read first from slave.
If the data size is only one byte or that byte is the last data byte, then master will send NACK to slave, otherwise master will send ACK.
NACK is defined as logic 1 on SDA line.
From the figure below, you can see the detail of I2C bus timing diagram.

Example Code
===========================
In this tutorial, I will explain how to use I2C on STM32F103 as a master device and for the slave device I use Arduino.
The slave is very simple, when master write data byte 0x01 to it, then the LED on Arduino board will blinking every 250 ms.
To turn off the LED blinking, master must write data byte 0x00.
Master can also read the LED blinking status (on/off) from the slave which returns 1 or 0.
This is the code for Arduino as an I2C slave.
~~~~~~~~~~~~~~~~~~~~~~~~~~~ C linenumbers
#include
#define OWN_ADDRESS 0x08
#define LED_PIN 13
int ledBlink = 0;
void receiveEvent(int bytes)
{
// Read received data
ledBlink = Wire.read();
}
void requestEvent()
{
// Send LED blinking status
Wire.write(ledBlink);
}
void setup()
{
pinMode (LED_PIN, OUTPUT);
// Start the I2C bus as slave
Wire.begin(OWN_ADDRESS);
// Attach a function to trigger when something is received
Wire.onReceive(receiveEvent);
// Attach a function to trigger when something is requested
Wire.onRequest(requestEvent);
}
void loop()
{
// If received data is 1, then blink LED for 250 ms
if (ledBlink == 1)
{
digitalWrite(LED_PIN, HIGH);
delay(250);
digitalWrite(LED_PIN, LOW);
delay(250);
}
// If received data is 0, then turn off LED
else if (ledBlink == 0)
{
digitalWrite(LED_PIN, LOW);
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The code below is for STM32F103 as an I2C master. You can use i2c_init() function to initialize the I2C peripheral.
In the main function, I write a command to turn on and turn off LED blinking on Arduino board every 2500 ms.
Every write command is followed by a command for reading the current LED blinking status, then the status is displayed on LCD 16×2.
~~~~~~~~~~~~~~~~~~~~~~~~~~~ C linenumbers
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "delay.h"
#include "lcd16x2.h"
#define I2Cx_RCC RCC_APB1Periph_I2C2
#define I2Cx I2C2
#define I2C_GPIO_RCC RCC_APB2Periph_GPIOB
#define I2C_GPIO GPIOB
#define I2C_PIN_SDA GPIO_Pin_11
#define I2C_PIN_SCL GPIO_Pin_10
#define SLAVE_ADDRESS 0x08
void i2c_init(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_address_direction(uint8_t address, uint8_t direction);
void i2c_transmit(uint8_t byte);
uint8_t i2c_receive_ack(void);
uint8_t i2c_receive_nack(void);
void i2c_write(uint8_t address, uint8_t data);
void i2c_read(uint8_t address, uint8_t* data);
uint8_t receivedByte;
int main(void)
{
DelayInit();
lcd16x2_init(LCD16X2_DISPLAY_ON_CURSOR_OFF_BLINK_OFF);
// Initialize I2C
i2c_init();
while (1)
{
// Write 0x01 to slave (turn on LED blinking)
i2c_write(SLAVE_ADDRESS, 0x01);
DelayMs(5);
// Read LED blinking status (off/on)
i2c_read(SLAVE_ADDRESS, &receivedByte);
// Display LED blinking status
lcd16x2_clrscr();
if (receivedByte == 0)
{
lcd16x2_puts("LED Blinking Off");
}
else if (receivedByte == 1)
{
lcd16x2_puts("LED Blinking On");
}
DelayMs(2500);
// Write 0x00 to slave (turn off LED blinking)
i2c_write(SLAVE_ADDRESS, 0x00);
DelayMs(5);
// Read LED blinking status (off/on)
i2c_read(SLAVE_ADDRESS, &receivedByte);
// Display LED blinking status
lcd16x2_clrscr();
if (receivedByte == 0)
{
lcd16x2_puts("LED Blinking Off");
}
else if (receivedByte == 1)
{
lcd16x2_puts("LED Blinking On");
}
DelayMs(2500);
}
}
void i2c_init()
{
// Initialization struct
I2C_InitTypeDef I2C_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
// Step 1: Initialize I2C
RCC_APB1PeriphClockCmd(I2Cx_RCC, ENABLE);
I2C_InitStruct.I2C_ClockSpeed = 100000;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2Cx, &I2C_InitStruct);
I2C_Cmd(I2Cx, ENABLE);
// Step 2: Initialize GPIO as open drain alternate function
RCC_APB2PeriphClockCmd(I2C_GPIO_RCC, ENABLE);
GPIO_InitStruct.GPIO_Pin = I2C_PIN_SCL | I2C_PIN_SDA;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(I2C_GPIO, &GPIO_InitStruct);
}
void i2c_start()
{
// Wait until I2Cx is not busy anymore
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
// Generate start condition
I2C_GenerateSTART(I2Cx, ENABLE);
// Wait for I2C EV5.
// It means that the start condition has been correctly released
// on the I2C bus (the bus is free, no other devices is communicating))
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
}
void i2c_stop()
{
// Generate I2C stop condition
I2C_GenerateSTOP(I2Cx, ENABLE);
// Wait until I2C stop condition is finished
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_STOPF));
}
void i2c_address_direction(uint8_t address, uint8_t direction)
{
// Send slave address
I2C_Send7bitAddress(I2Cx, address, direction);
// Wait for I2C EV6
// It means that a slave acknowledges his address
if (direction == I2C_Direction_Transmitter)
{
while (!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
}
else if (direction == I2C_Direction_Receiver)
{
while (!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
}
}
void i2c_transmit(uint8_t byte)
{
// Send data byte
I2C_SendData(I2Cx, byte);
// Wait for I2C EV8_2.
// It means that the data has been physically shifted out and
// output on the bus)
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
uint8_t i2c_receive_ack()
{
// Enable ACK of received data
I2C_AcknowledgeConfig(I2Cx, ENABLE);
// Wait for I2C EV7
// It means that the data has been received in I2C data register
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
// Read and return data byte from I2C data register
return I2C_ReceiveData(I2Cx);
}
uint8_t i2c_receive_nack()
{
// Disable ACK of received data
I2C_AcknowledgeConfig(I2Cx, DISABLE);
// Wait for I2C EV7
// It means that the data has been received in I2C data register
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
// Read and return data byte from I2C data register
return I2C_ReceiveData(I2Cx);
}
void i2c_write(uint8_t address, uint8_t data)
{
i2c_start();
i2c_address_direction(address << 1, I2C_Direction_Transmitter);
i2c_transmit(data);
i2c_stop();
}
void i2c_read(uint8_t address, uint8_t* data)
{
i2c_start();
i2c_address_direction(address << 1, I2C_Direction_Receiver);
*data = i2c_receive_nack();
i2c_stop();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can get the project file from [here](https://github.com/yohanes-erwin/stm32f103-keil/tree/master/i2c-master-polling).
This is the result:
