Working with STM32F7 and I2C: Write Mode with DMA

In the previous guide (here), we took a look at I2C read mode to read set of registers from DS3231 RTC module using DMA mode. In this guide, we shall use DMA to write those register.

In this guide, we shall cover the following:

  • I2C configuration.
  • I2C write using DMA.
  • Code.
  • Results.

1. I2C configuration:

Continuing from the previous guide, we shall configure the DMA to write to I2C slave device.

First, we need to figure which DMA, Stream and channel responsible for I2C1_TX. We can find that from the reference manual.

From the reference manual Table 27 which describes the DMA1 requests map, we can see that you have Stream6 and Stream7 for I2C1_TX. We shall use Stream 6 in this guide.

After selecting the Stream6, we shall disable the Stream and wait until it is disabled:

		DMA1_Stream6->CR &=~DMA_SxCR_EN;
		while((DMA1_Stream6->CR & DMA_SxCR_EN)==1);

Then configure the DMA as following:

  • Set channel to 3.
  • Memory increment.
  • Transfer complete interrupt.
  • Direction Memory to peripheral.
DMA1_Stream6->CR|=(CH1<<25)|DMA_SxCR_MINC|DMA_SxCR_TCIE|DMA_SxCR_DIR_0;

Enable DMA1_Stream6 interrupt in NVIC:

NVIC_EnableIRQ(DMA1_Stream6_IRQn);

Set the peripheral address to I2C1->TXDR:

DMA1_Stream6->PAR=(uint32_t )&I2C1->TXDR;

2. I2C Write using DMA:

First declare a function that takes three arguments:

  • Slave address.
  • buffer that hold the data to be written.
  • Length of the data to be written.
void i2c_write_dma(uint8_t slav_add, uint8_t *data, uint8_t length)

We start with enabling the I2C and DMA for write mode:

			/*Enable I2C*/
			I2C1->CR1 |=I2C_CR1_PE;
			/*Enable DMA TX*/
			I2C1->CR1|=I2C_CR1_TXDMAEN;

Then set the slave address in CR2 register:

I2C1->CR2=(slav_add<<1);

Select 7-bit addressing mode:

I2C1->CR2&=~I2C_CR2_ADD10;

Set the length as the parameter set by the user:

/*Set number to transfer to length for write operation*/
			I2C1->CR2|=(length<<I2C_CR2_NBYTES_Pos);

Set to write mode:

I2C1->CR2&=~I2C_CR2_RD_WRN;

Set auto end.

I2C1->CR2|=I2C_CR2_AUTOEND;

Set the memory address of DMA1_Stream6 to be the buffer:

DMA1_Stream6->M0AR=(uint32_t )data;

Number of transfer to be the length:

DMA1_Stream6->NDTR=length;

Enable the Stream:

DMA1_Stream6->CR |=DMA_SxCR_EN;

Launch the I2C communication:

I2C1->CR2|=I2C_CR2_START;

Wait until the transfer completed (depends on your application):

			while(tx_finished==0);
			tx_finished=0;

For interrupt handler of DMA1_Stream6:


void DMA1_Stream6_IRQHandler(void)
	{
	if(DMA1->HISR & DMA_HISR_TCIF6){
		tx_finished=1;
		DMA1->HIFCR=DMA_HIFCR_CTCIF6;
		}

	}

Hence the entire code as following:

i2c_dma.c

volatile uint8_t tx_finished;
void i2c_write_dma(uint8_t slav_add, uint8_t *data, uint8_t length)
	{

			/*Enable I2C*/
			I2C1->CR1 |=I2C_CR1_PE;
			/*Enable DMA TX*/
			I2C1->CR1|=I2C_CR1_TXDMAEN;
			/*Set slave address*/
			I2C1->CR2=(slav_add<<1);
			/*7-bit addressing*/
			I2C1->CR2&=~I2C_CR2_ADD10;
			/*Set number to transfer to length for write operation*/
			I2C1->CR2|=(length<<I2C_CR2_NBYTES_Pos);
			/*Set the mode to write mode*/
			I2C1->CR2&=~I2C_CR2_RD_WRN;
			/*hardware end*/
			I2C1->CR2|=I2C_CR2_AUTOEND;

			DMA1_Stream6->M0AR=(uint32_t )data;
			DMA1_Stream6->NDTR=length;
			DMA1_Stream6->CR |=DMA_SxCR_EN;
			/*Generate start*/
			I2C1->CR2|=I2C_CR2_START;
			while(tx_finished==0);
			tx_finished=0;
	}

i2c_dma.h:

void i2c_write_dma(uint8_t slav_add, uint8_t *data, uint8_t length);

You may download the source code from here (includes read/write mode):

3. Code:

In main.c:

#include "uart.h"
#include "i2c_dma.h"
#include "stdlib.h"
#include "stm32f7xx.h"
#define slave_add (0x68)
uint8_t data_rec[3];
uint8_t data_send[4];



void delay(int ms)
{
	int i;
	SysTick->LOAD=16000-1;
	SysTick->VAL=0;
	SysTick->CTRL=0x5;
		for ( i=0;i<ms;i++)
		{
			while(!(SysTick->CTRL &0x10000)){}
		}
	SysTick->CTRL=0;

}


int bcd_to_decimal(unsigned char x) {
    return x - 6 * (x >> 4);
}


int main(void)
	{

		uart3_tx_init();
		i2c_dma_init(0x00303D5B);

		printf("init\r\n");
		while(1)
			{
				i2c_read_dma(slave_add,0x00,data_rec,3);
				for (volatile int i=0;i<3;i++)
					{
						data_rec[i]=bcd_to_decimal(data_rec[i]);
					}
				if(data_rec[0]!=0){
				if(data_rec[0]%5==0){
						data_send[0]=0x00;
						data_send[1]=0x00;
						data_send[2]=(rand() % 5);
						data_send[3]=(rand() % 5);
						i2c_write_dma(slave_add,data_send,4);
						}}
				printf("rtc data %d %d %d\r\n",data_rec[2],data_rec[1],data_rec[0]);
				delay(100);


			}


	}

4. Results:

Open your favourite Serial terminal and set the baudrate to 115200 and you should get this:

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *