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 😀
Add Comment