In this tutorial, I will explain about I2C protocol and how to use it on STM32F103 microcontroller. I2C (Inter-Integrated Circuit) or sometimes called TWI (Two Wire
Interface) is a synchronous serial protocol that only need 2 wire for
communication. These wire are SCL for clock line and SDA for data line.
Using I2C bus, you can connect devices like temperature sensor, EEPROM,
RTC, etc (up to 112 device) just using two wire (plus GND wire). For accessing these
device, I2C use 7-bit address. Theoretically 7-bit address space allows
128 addresses, however some addresses are reserved for special purpose.
Thus, only 112 address can be used. There is also special method to use
I2C with 10-bit address.
I2C is multi-master and multi-slave bus. I2C bus can consists of one or more
master device, but only one master device can have an access to I2C bus
each time. To communicate with a slave device, master is responsible for
sending clock to the slave device. Clock signal is always generated by master.
I2C drivers are open-drain,
means that they can pull the signal low, but can't drive it high. On
each signal line (SCL and SDA) must have a pull-up resistor to restore
the signal high when no device is asserting it low.
The image above is explain how the I2C bus works. START and STOP condition are defined as rising or falling edges on the SDA line while the SCL line is kept high. After master send a START condition, then it will followed by sending slave address and R/W bit. Then slave device is responsible to generate ACK. ACK is defined as low logic on SDA line.
After that, if master wants to write data to slave (indicate by R/W bit = 0), then master send the data byte to slave. The MSB of data byte will be sent first to slave. Every data byte received, slave is responsible to generate ACK.
If master wants to read data from slave (indicate 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 high logic on SDA line.
From the image below, you can see the detail of timing diagram for I2C bus.
In this tutorial, I will share how to use I2C on STM32F103 microcontroller as a master and for the slave I will use Arduino. The slave is very simple, when master write data byte 0x01 to it, then the LED on Arduino board will blinking every 250ms. To turn off LED blinking, master must write data byte 0x00. Master also can 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:
#include <Wire.h> #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); } }
For the I2C master on STM32F103, I have wrote the code, that consist of several functions:
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 use i2c_init() functions to initialize the I2C peripheral. In the main loop, I will write a command to turn on and turn off LED blinking on Arduino board every 2500ms. Every write command is followed by a command for reading the current LED blinking status, then that status is displayed on LCD 16x2.
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); } }
Hei, I have a problem about I2C at stm32f103. I have copied your github repositories about access hmc5883l compass. But the data of hmc5883l not show up at my serial terminal. what should i do??
ReplyDeletethanks..
Okay now i already knew why if i activated the i2c, usart won't send any data to my terminal. i2c interuption running continuously without give any chance usart to do interuption data. I think that is the problem :D :D
ReplyDeleteWhat is the value of pull-up resistor?
ReplyDeleteklo inisialiasi stm32 via register langsung bagaimana gan bwt chip stm32F103 mohon pencerahannya gan
ReplyDeleteanyone can give some example of initialize i2c through registers all suggestions are greatly appreciated
ReplyDeleteHello sir , thanks for your tutorials
ReplyDeleteI ask you if you can send the hardware connections for I2C with lcd as image