STM32 Adventures – Communication using I2C

When working with STM32 using registries only it is important that you have REFERENCE MANUAL and DATASHEET available. We will use them a lot 🙂


Today we start with I2C communication protocol. Configuration of it might be a bit of tricky at the beginning gut I hope to explain to you what I have came across of when trying to get the protocol to work. For more details about I2C itself check out wiki .

Device address

Since I have worked with it I usually like to start off with defining address of the device that I’m working with ( or devices if you work with more than one ).

#define DEVICE_ADDRESS 0x01

This has saved me many times from problems with wrong addressing and spending time troubleshooting something which is easy as above 🙂

Transmission flow

So now its time to dive back into RM. We need to find part about I2C. Since we will be building a master device we focus there (at least for now ). As you can see on image below its shows really nice what we must program. Specific events at specific point of transmissions ( for transmitter and receiver ). This makes it really easy to code against.



Configuration of peripherial

Let’s move on to configuration. What is important for you is to know that the code presented here is not using interrupts or DMA. Reason for this is slow build up of skills. I will document those later on and update the links in this post accordingly.

Let’s start by finding which PINs we need to configure. For that one I jump to DS and search for I2C1 (that one I want to use ) and checkout Alternate Function Mappings



So now I can choose even what suits me more 🙂 I will stick with PB6 and PB7. Below function will set those pins as alternate function, high speed , open drain and finally configure that we use AF4 for those pins.

 * Function responsbile for configuration
 * of GPIO pins
void i2c_setup_gpio(void)
			GPIO_MODER_MODER7_1; 	// AF: PB7 => I2C1_SDA




	 * Alternate functions are configured in ARFL for PINS 0..7
	 * and in ARFH for PINS 8..15
	 * Based on DS we will select appropiate AF0..AF7
	GPIOB->AFR[0] |= ( 1 << 30 ) | ( 1 << 26); // P6/P7 => AF4

If you have by any chance forgotten about RCC here is my routine which I used to enable all peripherals

void rcc_init(void)

			RCC_AHB1ENR_GPIOCEN;	// Enable clock: GPIOC
	RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;	// Enable clock: I2C1

	__DSB();	// Data Synchronization Barrier


Once hardware is ready we can configure the I2C registries.

void i2c_init(void)

	 *  Reset I2C from lock state
	//I2C1->CR1 |= I2C_CR1_SWRST;

	 * FREQ: Set frequencey based on APB1 clock
	 * 8MHz
	I2C1->CR2 &= ~(I2C_CR2_FREQ);
	I2C1->CR2 |= 0x08;

	 * Depending on the frequency of said prescaler must be installed in
	 * accordance with the selected data rate.
	 * We choose the maximum, for standard mode - 100 kHz:
	 *  8MHz / 100 = 80 kHz;
	I2C1->CCR &= ~I2C_CCR_CCR;
	I2C1->CCR |= 80;

	 * clock period is equal to (1 / 8 MHz = 125 ns), therefore the maximum rise time:
	 * 1000 ns / 125 ns = 8 + 1 (plus one - a small margin) = 3
	I2C1->TRISE = 9;

	 * Enable perifpherial at the END
	I2C1->CR1 = I2C_CR1_ACK|
	I2C_CR1_PE;     // PE : Peripherial enable

Its quite easy here – we set freq of peripherial ( in my case it was 8Mhz ) then set clock rate ( which has simple formula ) and then we setup time rise. All of this is documented in RM and clearly explained.

The only thing that is left now is to read and write from the device.

Read using I2C

uint8_t i2c_read(uint8_t address, uint8_t registry)
	uint32_t timeout = TIMEOUT_MAX;

	while(I2C1->SR2 & I2C_SR2_BUSY);		// Wait for BUSY line
	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = address<<1;					// Write device address (W)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    (void)I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_TXE));		// Wait for EV8_1
	I2C1->DR = registry;

	I2C1->CR1 |= I2C_CR1_STOP;				// Generate STOP condition

	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = (address << 1 ) | 1;			// Write device address (R)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    I2C1->CR1 &= ~I2C_CR1_ACK;              // No ACK
    (void)I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_RXNE));	// Wait for EV7_1
    uint8_t value = (uint8_t)I2C1->DR;      // Read value

    I2C1->CR1 |= I2C_CR1_STOP;			    // Generate STOP condition

	return value;

Writing using I2C

void i2c_write(uint8_t address, uint8_t registry, uint8_t data) 

	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = address<<1;					// Write device address (W)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    reg2 = I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_TXE));		// Wait for EV8_1
	I2C1->DR = registry;					// Write registry address

	while (!(I2C1->SR1 & I2C_SR1_BTF));	    // Wait for BTF
	I2C1->DR = data;

	I2C1->CR1 |= I2C_CR1_STOP;			    // Generate STOP condition


Code presented above has still work in progress as I need to add error control and timeouts – otherwise if something would go wrong now it would block whole uC




Code for this exercise can be of course found at github https://github.com/RafPe/STM32F4DISCOVERY-I2C













STM32 Adventures – output clock to pin

When working with STM32 using registries only it is important that you have REFERENCE MANUAL and DATASHEET available. We will use them a lot 🙂


In today’s post we will be outputting SYSCLK to external pin of our STM32F407 discovery board. As mentioned we dive into our RM and find part responsible for RCC configurations. From there it is easy to see which bits we need to output SYSCLK to our pin.



As you can see on the above reset value shows that desired SYSCLK and default no prescaler will be selected. Then in this instance let’s find out on which pin we have to enable our alternate function.

To do this we need to find it in DS.



Great – it is default system function! So the only thing we should do is just set this PIN as output

int main(void)


	 *  Enable SYSCLK output to PC9


let’s see it if works by looking into our logic level analyzer



We will defenitely be able to use this functionality in interacting with external hardware – but thats for future posts


STM32 – minimal libraries project

Hey! So you most likely here because you would like to challenge yourself and try to program 32bit microcontrollers using minimal amount of libraries ( HAL/CMSIS ). On the internet you will find multiple discussions which approach to use – I will not be touching that – I guess everyone has his own way 😉

We will starting by shortly discussing environment used and then we will move on to configuring our project. I will be using for this STM324F407 discovery board – but with the right changes you will be able to adjust it to your own needs.

I will cover in this post:

  • Creating new project in Eclipse
  • Setting all libraries
  • Setting project & compilator settings

Before you start you should have


This project will use several files i.e. startup file / linker script and several header files.


Setting up project

Go ahead and create new ARM project. Here we will not be using any ‘Hello world’ or prepared examples – we start blank!



Here just go through the wizard and finish. When done we will move on getting extra files we need for our project.


Create folders inside the project folder so the structure will look as follow


Download firmware using STM32Cube

Now we need to start our STM32Cube to get all files we will need to move on with our project. Go to help -> install new libraries and then select the firmware for your cortex. For me it will be for STM32F4



Get following files into the project directories:

├── cmsis
│   ├── include
│   │   ├── cmsis_gcc.h
│   │   ├── core_cm0plus.h
│   │   ├── core_cm4.h ** THIS IS BASED ON YOUR CORTEX!! If you have different take different lib file !! **
│   │   ├── core_cmFunc.h
│   │   ├── core_cmInstr.h
│   │   └── core_cmSimd.h
│   └── src
├── include
├── src
│   └── main.c ( this is file created by you )
└── system
    ├── include
    │   ├── stm32f407xx.h
    │   ├── stm32f4xx.h
    │   └── system_stm32f4xx.h
    └── src
        ├── startup_stm32f407xx.s
        └── system_stm32f4xx.c


Modify project settings

Now since we have all the files we need your project should look similar to the following



We will now go and modify several project settings. Open project preferences and make required modifications

  • Change extension used by assembler source file to be *.s ( it’s project specific setting )stm32_minimallibs_06
  • Change to appropriate cortex family modelstm32_minimallibs_07
  • Modify preprocessor definitions to match your processor ( this settings in image below are for STM32F407VGx )stm32_minimallibs_08
  • Modify includes in Assembler and Compiler so it contains “${ProjDirPath}/cmsis/include” and “${ProjDirPath}/system/include
  • Under Linker -> General have the following ${ProjDirPath}/STM32F407VGTx_FLASH.ld ( if you are using different MCU then you will need to change this )
  • Modify paths which include source codestm32_minimallibs_09


Blink the diode

So now you are in position to just blink the diode using the following code in main.c

 * main.c
 *  Created on: 12 Nov 2016
 *      Author: rafpe
#include "stm32f4xx.h"
#include "stm32f407xx.h"

int main(void)
	volatile uint32_t delay;

	RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // enable the clock to GPIOD


	GPIOD->MODER = (1 << 26); // set pin 13 to be general purpose output

	while (1)
		for (delay = 1000000; delay; delay--)
			GPIOD->ODR ^= (1 << 13);


Compile and you are ready to flash your device.




Given all setup here we are HSI is used as system clock source. We will definitely come back to this as we will need more than i.e. 16 Mhz! Happy coding!