
In the previous guides, we took a look how to scan the i2c bus for peripherals (here), read single byte (here), write single byte (here), read multiple-bytes (here) and multiple-bytes(here). In this guide, we shall use DMA to transfer the data from/to peripheral using DMA.
In this guide, we shall cover the following:
- Configure I2C in DMA
- I2C write using DMA
- I2C read using DMA
- Connection diagram
- Code
- Demo
1. Configure the I2C for DMA
We start off by some defines states that will help us later
/** DMA1_Stream5_channel 1 is I2C1_RX DMA1_Stream6_channel 1 is I2C1_TX */ #define PB8_ALT (1<<17) #define PB9_ALT (1<<19) #define I2C_AF4 (0x04) #define ch1 (1<<25)
We also need to check which DMA, stream and channel that has the I2C1_TX and I2C1_RX
From the DMA section of F411 reference manual,

We can see that DMA1 Stream5 channel 1 is for I2C1_RX and DMA1 Stream 6 Channel 1 is for I2C1_TX
Now we can configure the I2C1
void i2c1_init(void)
{
RCC->AHB1ENR|=RCC_AHB1ENR_GPIOBEN; //enable gpiob clock
RCC->APB1ENR|=RCC_APB1ENR_I2C1EN; //enable i2c1 clock
GPIOB->MODER|=PB8_ALT|PB9_ALT; //set PB8 and PB9 to alternative function
GPIOB->AFR[1]|=(I2C_AF4<<0)|(I2C_AF4<<4);
GPIOB->OTYPER|=GPIO_OTYPER_OT8|GPIO_OTYPER_OT9;
I2C1->CR1=I2C_CR1_SWRST;//reset i2c
I2C1->CR1&=~I2C_CR1_SWRST;// release reset i2c
I2C1->CR1 &=~ I2C_CR1_NOSTRETCH;//disable clock strech
I2C1->CR1 &= ~I2C_CR1_ENGC;//diable generaral callback
I2C1->CR2 |= I2C_CR2_LAST;//set next DMA EOT is last transfer
I2C1->CR2 |= I2C_CR2_DMAEN; //enable DMA
I2C1->CR2|=16;//set clock source to 16MHz
I2C1->CCR=80; //based on calculation
I2C1->TRISE=17; //output max rise
I2C1->CR1 |=I2C_CR1_PE;
}Initialize the DMA1 for both stream
/**
* @brief Initialize DMA1_Stream5
* @note CH3 for I2C1
* @param None
* @retval None
*/
void i2c_rx_dma_init(void)
{
RCC->AHB1ENR|=RCC_AHB1ENR_DMA1EN;
DMA1_Stream5->CR=0x00;//reset everything
while((DMA1_Stream5->CR)&DMA_SxCR_EN){;}
DMA1_Stream5->CR|=ch1|DMA_SxCR_MINC|DMA_SxCR_TCIE|DMA_SxCR_HTIE|DMA_SxCR_TEIE;
NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}
/**
* @brief Initialize DMA1_Stream6
* @note CH3 for I2C1
* @param None
* @retval None
*/
void i2c_tx_dma_init(void)
{
RCC->AHB1ENR|=RCC_AHB1ENR_DMA1EN;
DMA1_Stream6->CR=0x00;//reset everything
while((DMA1_Stream6->CR)&DMA_SxCR_EN){;}
DMA1_Stream6->CR|=ch1|DMA_SxCR_MINC|DMA_SxCR_DIR_0|DMA_SxCR_TCIE|DMA_SxCR_HTIE|DMA_SxCR_TEIE;
NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}
2. I2C write using DMA:
For the write function, we need two functions, first one is for setting address and start transfer which similar to writing a single byte. After the address has set, we can start with DMA transfer.
void I2C_write(uint8_t SensorAddr,
uint8_t * pWriteBuffer, uint16_t NumByteToWrite)
{
/*Wait until the bus is free*/
while(I2C1->SR2&I2C_SR2_BUSY){;}
/* Generate START */
I2C1->CR1 |= I2C_CR1_START;
/* Wait SB flag is set */
while(!(I2C1->SR1&I2C_SR1_SB)){;}
/* Read SR1 */
(void)I2C1->SR1;
/* Send slave address with write */
I2C1->DR = (SensorAddr<<1);
/* Wait ADDR flag is set */
while(((I2C1->SR1)&I2C_SR1_ADDR)==0){;}
/* Start DMA */
DMA_Transmit(pWriteBuffer, NumByteToWrite);
/* Read SR1 */
(void)I2C1->SR1;
/* Read SR2 */
(void)I2C1->SR2;
} Now for DMA transfer from memory to peripheral
static void DMA_Transmit(const uint8_t * pBuffer, uint8_t size)
{
/* Check null pointers */
if(NULL != pBuffer)
{
DMA1_Stream6->CR&=~DMA_SxCR_EN;
while((DMA1_Stream6->CR)&DMA_SxCR_EN){;}
/* Set memory address */
DMA1_Stream6->M0AR = (uint32_t)pBuffer;
DMA1_Stream6->PAR=(uint32_t)&I2C1->DR;
/* Set number of data items */
DMA1_Stream6->NDTR = size;
/* Clear all interrupt flags */
DMA1->HIFCR = (DMA_HIFCR_CDMEIF6 | DMA_HIFCR_CTEIF6
| DMA_HIFCR_CHTIF6 | DMA_HIFCR_CTCIF6);
/* Enable DMA1_Stream4 */
DMA1_Stream6->CR |= DMA_SxCR_EN;
}
else
{
/* Null pointers, do nothing */
}
}3. I2C Read using DMA:
For reading, we start off normally similar to reading a single byte guide:
void I2C_Read(uint8_t SensorAddr, uint8_t ReadAddr,
uint8_t * pReadBuffer, uint16_t NumByteToRead)
{
//wait until the bus is free
while(I2C1->SR2&I2C_SR2_BUSY){;}
/* Generate START */
I2C1->CR1 |= I2C_CR1_START;
/* Wait SB flag is set */
while(!(I2C1->SR1&I2C_SR1_SB)){;}
/* Read SR1 */
(void)I2C1->SR1;
/* Send slave address with write */
I2C1->DR=(SensorAddr<<1|0);
/* Wait ADDR flag is set */
while(((I2C1->SR1)&I2C_SR1_ADDR)==0){;}
/* Read SR1 */
(void)I2C1->SR1;
/* Read SR2 */
(void)I2C1->SR2;
/* Wait TXE flag is set */
while(I2C_SR1_TXE != (I2C_SR1_TXE & I2C1->SR1))
{
/* Do nothing */
}
if(2 <= NumByteToRead)
{
/* Acknowledge enable */
I2C1->CR1 |= I2C_CR1_ACK;
/* Send register address to read with increment */
I2C1->DR = (ReadAddr);
}
else
{
/* Acknowledge disable */
I2C1->CR1 &= ~I2C_CR1_ACK;
/* Send register address to read (single) */
I2C1->DR = ReadAddr;
}
/* Wait BTF flag is set */
while(!(I2C_SR1_BTF & I2C1->SR1))
{
/* Do nothing */
}
/* Generate ReSTART */
I2C1->CR1 |= I2C_CR1_START;
/* Wait SB flag is set */
while(I2C_SR1_SB != (I2C_SR1_SB & I2C1->SR1))
{
/* Do nothing */
}
/* Read SR1 */
(void)I2C1->SR1;
/* Send slave address with read */
I2C1->DR = (SensorAddr<<1 | (uint8_t)0x01);
/* Wait ADDR flag is set */
while(((I2C1->SR1)&I2C_SR1_ADDR)==0){;}
/* Start DMA */
DMA_Receive(pReadBuffer, NumByteToRead);
/* Read SR1 */
(void)I2C1->SR1;
/* Read SR2 */
(void)I2C1->SR2;
}
Then rather using while loop, we can use DMA to receive the data as following:
static void DMA_Receive(const uint8_t * pBuffer, uint8_t size)
{
/* Check null pointers */
if(NULL != pBuffer)
{
DMA1_Stream5->CR&=~DMA_SxCR_EN;
while((DMA1_Stream5->CR)&DMA_SxCR_EN){;}
/* Set memory address */
DMA1_Stream5->M0AR = (uint32_t)pBuffer;
DMA1_Stream5->PAR=(uint32_t)&I2C1->DR;
/* Set number of data items */
DMA1_Stream5->NDTR = size;
/* Clear all interrupt flags */
DMA1->HIFCR = ( DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CTEIF5
| DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTCIF5);
/* Enable DMA1_Stream2 */
DMA1_Stream5->CR |= DMA_SxCR_EN;
}
else
{
/* Null pointers, do nothing */
}
} In our header file,
#ifndef __wire__h #define __wire__h #include "stm32f4xx.h" // Device header #include <stddef.h> void i2c1_init(void); void i2c_rx_dma_init(void); void i2c_tx_dma_init(void); void I2C_write(uint8_t SensorAddr,uint8_t * pWriteBuffer, uint16_t NumByteToWrite); void I2C_Read(uint8_t SensorAddr, uint8_t ReadAddr,uint8_t * pReadBuffer, uint16_t NumByteToRead); #endif
for testing purposes, we shall randomize the minutes and hours every 5 seconds as following:
#include "debug.h"
#include "wire.h"
#include "stdlib.h"
volatile int finished=0;
int read_finish();
void reset_finish();
uint8_t data[3], data_r[3];
uint8_t data_s[4];
//note: Index zero shall always contains the starting memory address
uint8_t data_write[4]={0x00,0x10,0x12,0x00};
int bcd_to_decimal(unsigned char x) {
return x - 6 * (x >> 4);
}
int main(void)
{
i2c1_init();
i2c_rx_dma_init();
i2c_tx_dma_init();
while(1)
{
I2C_Read(0x68,0x00,data,3);
while(read_finish()==0){;}
reset_finish();
//for(volatile int i=0;i<100000;i++);
for (int i=0;i<3;i++)
{
data_r[i]=bcd_to_decimal(data[i]);
}
if(data_r[0]==5)
{
data_s[0]=0x00;
data_s[1]=0;
data_s[2]=rand()%20;
data_s[3]=rand()%10;
I2C_write(0x068,data_s,4);
}
}
}
int read_finish()
{
return finished;
}
void reset_finish()
{
finished=0;
}
void DMA1_Stream5_IRQHandler(void)
{
if((DMA1->HISR)&DMA_HISR_TCIF5)
{
finished=1;
log_debug("I2C finished receiving using DMA1_Stream5");
I2C1->CR1 |= I2C_CR1_STOP;
DMA1->HIFCR=DMA_HIFCR_CTCIF5;
}
if((DMA1->HISR)&DMA_HISR_HTIF5)
{
log_debug("DMA1 stream5 half transfer interrupt");
DMA1->HIFCR=DMA_HIFCR_CHTIF5;
}
if((DMA1->HISR)&DMA_HISR_TEIF5)
{
log_debug("DMA1 stream5 error");
DMA1->HIFCR=DMA_HIFCR_CTEIF5;
}
}
void DMA1_Stream6_IRQHandler(void)
{
if((DMA1->HISR)&DMA_HISR_TCIF6)
{
log_debug("I2C finished transmiting using DMA1_Stream6");
finished=1;
I2C1->CR1 |= I2C_CR1_STOP;
DMA1->HIFCR=DMA_HIFCR_CTCIF6;
}
if((DMA1->HISR)&DMA_HISR_HTIF6)
{
log_debug("DMA1 stream6 half transfer interrupt");
DMA1->HIFCR=DMA_HIFCR_CHTIF6;
}
if((DMA1->HISR)&DMA_HISR_TEIF6)
{
log_debug("DMA1 stream6 error");
DMA1->HIFCR=DMA_HIFCR_CTEIF6;
}
}
4. Connection diagram:
In this guide, we shall use DS3231 for I2C experiment and connected to our STM32F411RE Nucleo as following:

5. Code:
You can download the code from here:
6. Demo
Happy coding š
2 Comments
stuck on while(((I2C3->SR1)&I2C_SR1_ADDR)==0);
Hi,
thats means either, the slave is not responding r sending incorrect slave address.
Add Comment