Skip to main content
Matth1
Associate III
February 4, 2022
Question

Is there a guide on how to use I2C with DMA?

  • February 4, 2022
  • 4 replies
  • 25447 views

Bonjour,

I've been able to use I2C to communicate with I/O extender and display drivers with HAL. I'm now trying to use the I2C with HAL using DMA.

The problem is that UM1785 Description of STM32F0 HAL and low-layer drivers is a bit vague on explanations. STM32 offers examples, but that's not really what I'm looking for, and videos, but I'd rather have a written article (with a written article I can read at my own speed, easily skip or re-read parts, copy text, and so on).

Are their learning tools such as UMs or Application notes dedicated to the subject?

M.R.

4 replies

TDK
February 4, 2022
"If you feel a post has answered your question, please click ""Accept as Solution""."
Piyoosh
Associate III
March 18, 2025

Hi TDK,

I am also trying to integrate my IMU with stm32f103c8t6 using I2C DMA but facing a lot of issues, can you help me out or can you give me any references? 

luisjose_
Graduate
March 27, 2025

Hi Piyoosh,

Same here, i am facing a lot of trouble trying to read the MPU6050 with STM32F103C6T6 using DMA. I think that i made it work well the transmitter, because i see the flags and seems fine, but the receiver is still wrong. Here is my code if can help you.

#include "dma_i2c.h"

uint8_t g_rx_cmplt;
uint8_t g_tx_cmplt;

static void i2c_start(void)
{
	I2C1->CR1 |= CR1_START;
	while(!(I2C1->SR1 & SR1_SB)){}
}

static void send_address(uint8_t address, uint8_t mode)
{
	i2c_start();
	I2C1->DR = ((address << 1)|(mode));
	while(!(I2C1->SR1 & SR1_ADDR)){}
}

void I2C1_init(void)
{
	RCC->APB2ENR |= APB2ENR_IOPBEN;		//	Enable clock access to GPIOB Port
	RCC->APB2ENR |= APB2ENR_AFIOEN;		//	Alternate Function I/O clock enabled
	GPIOB->CRL |= (1U << 24);			//	MODE6[0] = 1	Output mode, max speed 10 MHz, PB6, I2C1_SCL
	GPIOB->CRL |= (1U << 25);			//	MODE6[1] = 0
	GPIOB->CRL |= (1U << 26);			//	CNF6[0] = 1	 	Alternate function output Open-drain
	GPIOB->CRL |= (1U << 27);			//	CNF6[1] = 1
	GPIOB->CRL |= (1U << 28);			//	MODE7[0] = 1	Output mode, max speed 10 MHz, PB7, I2C1_SDA
	GPIOB->CRL |= (1U << 29);			//	MODE7[1] = 0
	GPIOB->CRL |= (1U << 30);			//	CNF7[0] = 1	 	Alternate function output Open-drain
	GPIOB->CRL |= (1U << 31);			//	CNF7[1] = 1
	RCC->APB1ENR |= APB1ENR_I2C1EN;		//	Enable clock access to I2C1 peripheral
	I2C1->CR1 = CR1_SWRST;				//	I2C Peripheral under reset state
	I2C1->CR1 &= ~CR1_SWRST;			//	I2C Peripheral not under reset
	I2C1->CR1 &= ~CR1_NOSTRETCH;		//	Clock stretching enabled
	I2C1->CR1 &= ~CR1_ENGC;				//	General call disabled. Address 00h is NACKed.
	I2C1->CR2 |= CR2_DMAEN;				//	DMA request enabled when TxE=1 or RxNE =1
	I2C1->CR2 |= CR2_LAST;				//	Next DMA EOT is the last transfer
	I2C1->CR2 |= (FREQ << 0);			//	Peripheral Clock = APB Clock = System Clock = 8MHz
	I2C1->CCR |= (I2C_MODE << 0);		//	Set I2C1 to standard mode, 100KHz clock
	I2C1->TRISE = I2C_MAX_RISE_TIME;	//	Set I2C1 max rise time
	I2C1->CR1 |= CR1_PE;				//	Peripheral enable
}

void DMA1_Channel6_I2C1_TX_init(void)
{
	RCC->AHBENR |= AHBENR_DMA1EN;			//	Enable clock access to the DMA1 module
	DMA1_Channel6->CCR = 0;					//	Disable DMA channel
	while(DMA1_Channel6->CCR & CCR_EN){}	//	Wait until channel is disabled
	DMA1_Channel6->CCR |= CCR_MINC;			//	Memory increment mode enabled
	DMA1_Channel6->CCR |= CCR_TCIE;			//	Transfer complete interrupt enable
	DMA1_Channel6->CCR |= CCR_DIR;			//	Read from memory
	NVIC_EnableIRQ(DMA1_Channel6_IRQn);		//	Enable channel 6 TCI in NVIC
}

void DMA1_Channel7_I2C1_RX_init(void)
{
	RCC->AHBENR |= AHBENR_DMA1EN;			//	Enable clock access to the DMA1 module
	DMA1_Channel7->CCR = 0;					//	Disable DMA channel
	while(DMA1_Channel7->CCR & CCR_EN){}	//	Wait until channel is disabled
	DMA1_Channel7->CCR |= CCR_MINC;			//	Memory increment mode enabled
	DMA1_Channel7->CCR |= CCR_TCIE;			//	Transfer complete interrupt enable
	DMA1_Channel7->CCR &= ~CCR_DIR;			//	Read from peripheral
	NVIC_EnableIRQ(DMA1_Channel7_IRQn);		//	Enable channel 7 TCI in NVIC
}

void i2c_write(uint8_t slave_address, uint8_t *p_write_buff, uint16_t num_of_bytes)
{


	DMA1->IFCR |= IFCR_CTCIF6;
	DMA1_Channel6->CPAR = (uint32_t)(&(I2C1->DR));
	DMA1_Channel6->CMAR = (uint32_t)p_write_buff;
	DMA1_Channel6->CNDTR = num_of_bytes;
	I2C1->CR2 |= CR2_DMAEN;

	while(I2C1->SR2 & SR2_BUSY){}
	send_address(slave_address, WRITE);
	(void)I2C1->SR2;

 DMA1_Channel6->CCR |= CCR_EN;

 while (!g_tx_cmplt) {}
 I2C1->CR1 |= CR1_STOP;
 g_tx_cmplt = 0;
}

void i2c_read(uint8_t slave_address, uint8_t reg, uint8_t *buffer_rx, uint16_t num_of_bytes) //STILL DONT WORK
{
	DMA1->IFCR |= IFCR_CTCIF7;
	DMA1_Channel7->CPAR = (uint32_t)(&(I2C1->DR));
	DMA1_Channel7->CMAR = (uint32_t)buffer_rx;
	DMA1_Channel7->CNDTR = num_of_bytes;
	I2C1->CR2 &= ~CR2_LAST;

	while(I2C1->SR2 & SR2_BUSY){}
	send_address(slave_address, WRITE);
	(void)I2C1->SR2;
	while(!(I2C1->SR1 & SR1_TXE)){}
	I2C1->DR = reg;
	while(!(I2C1->SR1 & SR1_BTF)){}
	send_address(slave_address, READ);
	(void)I2C1->SR2;
 if (num_of_bytes > 1)
 {
 I2C1->CR1 |= CR1_ACK;
 }
 else
 {
 I2C1->CR1 &= ~CR1_ACK;
 }
 if (num_of_bytes == 2)
 {
 I2C1->CR2 |= CR2_LAST;
 }
 I2C1->CR2 |= CR2_DMAEN;
	DMA1_Channel7->CCR |= CCR_EN;

 while (!g_rx_cmplt) {}
 g_rx_cmplt = 0;
}


void DMA1_Channel6_IRQHandler(void)
{
	if(DMA1->ISR & ISR_TCIF6)
	{
		DMA1->IFCR |= IFCR_CTCIF6;
		I2C1->CR2 &= ~CR2_DMAEN;
		DMA1_Channel6->CCR &= ~CCR_EN;
		while(DMA1_Channel6->CCR & CCR_EN){}
		g_tx_cmplt = 1;
	}
}

void DMA1_Channel7_IRQHandler(void)
{
	if(DMA1->ISR & ISR_TCIF7)
	{
		I2C1->CR1 |= CR1_STOP;
		DMA1->IFCR |= IFCR_CTCIF7;
		I2C1->CR2 &= ~CR2_DMAEN;
		I2C1->CR2 &= ~CR2_LAST;
		DMA1_Channel7->CCR &= ~CCR_EN;
		while(DMA1_Channel7->CCR & CCR_EN){}
		g_rx_cmplt = 1;
	}
}

 I think that the trick is in something related to the set of the DMA enable bits at some point, exactly.

Technical Moderator
February 4, 2022

Hello @Matth​ and welcome to the Community :)

You will find a working I2C_TwoBoards_ ComDMA examples (relevant to the device that you are using) provided with STM32CubeF0 MCU package

The list of examples provided in the table 2, on the AN4735 Application note STM32Cube firmware examples for STM32F0 Series;

Have a look also at this FAQ: STM32 I2C does not work, which describes general tips related to I2C on STM32 devices.

When your question is answered, please close this topic by choosing Select as Best. This will help other users find that answer faster.

Imen

Imen

In order to give better visibility on the answered topics, please click on 'Best answer' on the reply which solved your issue or answered your question. Thanks
Matth1
Matth1Author
Associate III
February 5, 2022

Thanks for the answer.

However, I'm afraid learning directly from an example would be an exercise in cargo cult programming, where one wanting to learn will have to copy/paste the whole project and snip bit by bit what seems to be specific to the example (communication between two boards) to keep the parts relevant to the subject (I2C with DMA).

I'd rather have an article focussed on a subject, in this case I2C with DMA.

First part gives a synthetic view of the processes, such as what the https://community.st.com/s/article/stm32-i2c-does-not-work article provides.

Then the MCU configuration could be addressed, with explanations of the different setting possibilities (with pros and cons). In such a case, how to add DMA settings to I2C configuration, etc.

Then a synthetic description of the main.c code what is in main(), what is in the infinite loop. Also a part on how settings can be changed on the fly if needed (peripherals with different speeds or addressing?) and of course the proper I2C transmitting and receiving functions.

It can be helpful for programmers who are not yet familiar with MCU.

Such an article would give away to make things work, to understand how it works, and where to look for for if on wants to make the process more efficient.

Andrew Neil
Super User
February 20, 2024

Looking for the same thing just now, I came across this old thread.

I know @Matth1 didn't want videos, but these two do seem to cover how to set up a comms peripheral with DMA:

STM32CubeMX basics: 10.11 STM32Cube HAL labs UART - UART DMA: 

https://www.youtube.com/watch?v=BxFYmBYBmaU

STM32CubeMX basics: 11.3 STM32Cube HAL labs SPI - Lab SPI DMA:

https://www.youtube.com/watch?v=s9GCTLd6-AM&list=PLnMKNibPkDnGtuIl5v0CvC81Am7SKpj02&index=52

 

There doesn't seem to be one specifically for I2C, but the process should be the same?

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Visitor
June 9, 2026

Yes. Besides UM1785, ST provides several useful Application Notes covering I2C, DMA, and HAL usage in more detail. A good starting point is to review the STM32Cube firmware examples alongside the relevant I2C and DMA application notes for your STM32 family, as they explain transfer modes, callbacks, and common implementation patterns better than the HAL reference alone. The STM32 community forums and official documentation are also valuable for practical examples and troubleshooting. For unrelated apps such as Honista, always download from the official project website or trusted sources and avoid unofficial download sites to reduce security risks.