Hi everyone, I’m back :) Another year has come with lots of opportunities and challenges presented to me as I now become a fresh Ph.D. candidate at Nanyang Technological University (NTU) Singapore. Being occupied by all the courses and my research, I hardly had time to continue with the STM32F0 tutorial series I started almost two years ago. However, we have come a long way to finish almost all the basic aspects of the STM32F0 chip including the GPIO, Interrupts, Timer, Counter, PWM, UART and ADC. Only a few peripherals including I2C, SPI, DAC, TSC and WDG are left to be discovered. Hence, I thought that I need to spend some time to continue with this tutorial series to finish all the basic peripherals. Moreover, I have recently received free samples from ICStation, which are some I2C sensors and OLED screens that gave me more motivation to do this STM32F0 I2C tutorial.
Therefore, in this tutorial, I will be covering the following parts:
- Some basic ideas of the I2C and when we need to use it.
- Overview of the I²C peripheral of the STM32F051 on the STM32F0 Discovery kit.
- An example showing how to connect and read data from a temperature and humidity sensor (HDC1080) through I²C port and then, display the temperature and relative humidity on an OLED screen (SSD1306 controller), also through the same I²C port.
- Finally, another example to show how to set up two I²C modules of the same STM32F0 (1 master, 1 slave) to transfer data with each other. Let’s get started.
Basic I²C – What should we notice?
I found this article from Sparkfun describe the fundamentals of I2C in a very intuitive and easy way to understand. If you are not familiar with I2C, I suggest that you take a look at the tutorial on Sparkfun website and come back here with this information in mind:
- The basic hardware connection for I2C communication (SCL, SDA)
- Start condition: the master device leaves SCL high and pulls SDA low, which informs all slave devices that a transmission is about to start. Start condition can be issued multiple times (repeat start) in case a master wants to retrieve more data from slaves.
- Address frame: Each device class has a fixed 7-bit ‘device address’ which is used to identify itself from other devices in the same I²C network followed by a R/W bit indicating whether this is a read (1) or write (0) operation. The 9th bit of the frame is the NACK/ACK bit which is the case for all frames (data or address). Only the slave which has the same address sends the ACK back to the master. Since this is just a basic tutorial on I²C, we only consider 7-bit address case from now on in this tutorial.
- Data frame(s): after the address frame has been transmitted by the master and acknowledged ‘ACK’ by the corresponding slave, it is followed by data frames. Depending on the value of the R/W bit in the address frame, data frame direction will be from slave to master and vice versa.
- Stop condition: Stop conditions are defined by a low to high transition on SDA after a low to high transition on SCL, with SCL remaining high.
Once you get familiar with these ideas, we can continue with our STM32 applications.
STM32F0 I2C functions
- For STM32F0 Discovery kit equipped with STM32F051R8, we have 2 I2C modules: I2C1 and I2C2 that can run simultaneously. Some of the differences between these two modules are extracted from the datasheet of the F051 chip and presented in the table below.
As indicated in Table 9 above, STM32F051 supports three-speed mode (frequency mode) for the I2C communication: 100kHz, 400kHz and 1MHz. Based on the specifications of the targeted I2C sensor, the frequency is chosen accordingly.
- I²C mode: Master mode – STM32F0 acts as a master to communicate and acquire data from other slaves (sensors); or Slave mode – provide data to other microcontrollers.
- Analog and Digital noise filters: to suppress spikes on the SDA and SCL lines. The benefits and drawbacks of each filter are presented in Table 87 below.
STM32F0 I2C Master Mode Example
In the HAL library, there are several functions that provide us an easy solution to program I2C communication, particularly for Master mode. In this tutorial, I will only mention normal communication without using interrupts and DMA. Hence, for master transmitting and receiving, we can classify the functions into 2 groups as follows:
- Group 1: functions used to communicate with those devices that do not have secondary addresses (such as the sensor we’ll use in the later part):
HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
- Group 2: functions used to communicate with those devices that have secondary addresses (such as memory chip AT24Cxxx):
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Since I don’t have any I2C memory chip with me, we’ll first explore group 1 functions with two following examples.
a. HDC1080 Temperature and Humidity sensor
Normally, a simple I2C memory chip like AT24C02 is used to demonstrate the I2C protocol. However, I don’t have any of these memory chips but another I2C temperature and humidity sensor, which has a fairly simple reading protocol, I decided to first try it. The sensor name is HDC1080 from Texas Instrument. I found it to be fairly good in humidity and temperature accuracy (±2% and ±0.2°C accordingly).
You can take a look at the tutorial video below where I did pretty much all the explanations for this sensor and how to connect and read data from it through an I2C connection.
And this is the code I used in the video:
- Variables declaration:
/* Private variables ---------------------------------------------------------*/ unsigned char buffer; unsigned int rawT, rawH; float Temperature; float Humidity; /* USER CODE END PV */
sprintf(buf,"BRIGHTNESS LEVEL:%i",i++); /* USER CODE BEGIN 2 */ //Config the HDC1080 to perform acquisition separately HAL_Delay(15); buffer=0x02; //Pointer buffer =0; //MSB byte buffer=0; //LSB byte HAL_I2C_Master_Transmit(&hi2c1,0x40<<1,buffer,3,100); /* USER CODE END 2 */
- Triggering measurement and data reading:
/* USER CODE BEGIN 3 */ //Trigger Temperature measurement buffer=0x00; HAL_I2C_Master_Transmit(&hi2c1,0x40<<1,buffer,1,100); HAL_Delay(20); HAL_I2C_Master_Receive(&hi2c1,0x40<<1,buffer,2,100); //buffer : MSB data //buffer : LSB data rawT = buffer<<8 | buffer; //combine 2 8-bit into 1 16bit Temperature = ((float)rawT/65536)*165.0 -40.0; //Trigger Humidity measurement buffer=0x01; HAL_I2C_Master_Transmit(&hi2c1,0x40<<1,buffer,1,100); HAL_Delay(20); HAL_I2C_Master_Receive(&hi2c1,0x40<<1,buffer,2,100); //buffer : MSB data //buffer : LSB data rawH = buffer<<8 | buffer; //combine 2 8-bit into 1 16bit Humidity = ((float)rawH/65536)*100.0; HAL_Delay(100); } /* USER CODE END 3 */
b. SSD1306 OLED screen
Similar to the HDC1080, we'll connect the OLED screen to the STM32F0 discovery board via the I2C1 peripheral to display the temperature and humidity readings. The OLED I got features a 0.91" display area with a resolution of 128x32 and white color pixels. You can find the datasheet for this display here.
Since this OLED is very common and different libraries for this screen could be found online, we won't go into details of how to code everything from the scratch. Indeed, I found this library specifically written for STM32 HAL library and decided to use it with just a little modification. The following video will show you how to import the library in Keil and make use of it. You can download the entire project folder, including the modified library for this OLED here. Happy coding!