Skip to main content
Associate II
December 22, 2024
Solved

I2C DMA endless while loop.

  • December 22, 2024
  • 1 reply
  • 797 views

Hi

 

I am having some issues where my MCU ( stm32f030C8T6 ) seems to get stuck in a while loop when using I2C1 and a AS5600 IC.
I will paste the code below but give some more detail on the issue here.
In the function named "void I2C1_Write_DMA(uint8_t slave_address, uint8_t *data, uint16_t size)" AND "void I2C1_Read_DMA(uint8_t slave_address, uint8_t *data, uint16_t size)" the line "while (!dma_transfer_complete);" makes the MCU get stuck in an Endless loop.

When I use the BusPirate to sniff on the transaction I see that it gives the following (" [0x6C+ ")
(Which ended on ACK) before getting stuck on in the loop.

You can compile and run this code if you happen to have this board laying around.

 

#include <stdint.h>
#include "stm32f030x8.h"

#if !defined(__SOFT_FP__) && defined(__ARM_FP)
 #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif

// Function Prototypes
void Clock_Setup(void);
void DMA_Init(void);
void I2C1_Init(void);
void I2C1_Write_DMA(uint8_t slave_address, uint8_t *data, uint16_t size);
void I2C1_Read_DMA(uint8_t slave_address, uint8_t *data, uint16_t size);
void DMA1_Channel2_3_IRQHandler(void);

// Global Variables
volatile uint8_t dma_transfer_complete = 0;


void GPIO_Setup(void) {
 // Enable GPIOC clock
 RCC->AHBENR |= RCC_AHBENR_GPIOCEN;

 // Set PC13 as output
 GPIOC->MODER &= ~(3U << (13 * 2)); // Clear MODER13[1:0]
 GPIOC->MODER |= (1U << (13 * 2)); // Set MODER13[1:0] to 01 (General-purpose output mode)

 // Optional: Configure output type and speed
 GPIOC->OTYPER &= ~(1U << 13); // Set output type to push-pull
 GPIOC->OSPEEDR |= (3U << (13 * 2)); // Set to high speed
}

void Clock_Setup(void) {
 RCC->CR |= RCC_CR_HSION; // Enable High-Speed Internal Clock
 while (!(RCC->CR & RCC_CR_HSIRDY)); // Wait for HSI to stabilize

 RCC->CFGR &= ~RCC_CFGR_SW; // Set HSI as SYSCLK
 RCC->CFGR |= RCC_CFGR_SW_HSI;
 while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI);

 RCC->CFGR &= ~RCC_CFGR_HPRE; // AHB Prescaler: Divide by 1
 RCC->CFGR &= ~RCC_CFGR_PPRE; // APB Prescaler: Divide by 1
}

void DMA_Init(void) {
 RCC->AHBENR |= RCC_AHBENR_DMAEN; // Enable DMA clock

 // Enable DMA interrupts in NVIC
 NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); // Enable DMA1 Channel 2 and 3 interrupt
 NVIC_SetPriority(DMA1_Channel2_3_IRQn, 1); // Set interrupt priority
}

void I2C1_Init(void) {
 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable I2C1 clock
 RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // Enable GPIOB clock

 // Configure PB6 (SCL) and PB7 (SDA) as Alternate Function
 GPIOB->MODER &= ~(GPIO_MODER_MODER6 | GPIO_MODER_MODER7); // Clear mode bits
 GPIOB->MODER |= (GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1); // Set to Alternate Function
 GPIOB->AFR[0] |= (1 << GPIO_AFRL_AFRL6_Pos) | (1 << GPIO_AFRL_AFRL7_Pos); // AF1 for I2C1
 GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7; // Open-drain for I2C
 GPIOB->PUPDR |= GPIO_PUPDR_PUPDR6_0 | GPIO_PUPDR_PUPDR7_0; // Pull-up resistors

 // Configure I2C1
 I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 for configuration
 I2C1->TIMINGR = 0x2000090E; // Configure timing (100kHz @ 48MHz PCLK)
 I2C1->CR1 |= I2C_CR1_PE; // Enable I2C1
}

void I2C1_Write_DMA(uint8_t slave_address, uint8_t *data, uint16_t size) {
 // Configure DMA for transmission
 DMA1_Channel2->CMAR = (uint32_t)data; // Memory address
 DMA1_Channel2->CNDTR = size; // Number of bytes
 DMA1_Channel2->CPAR = (uint32_t)&I2C1->TXDR; // Peripheral address
 DMA1_Channel2->CCR = 0; // Reset DMA channel configuration
 DMA1_Channel2->CCR |= DMA_CCR_MINC; // Memory increment mode
 DMA1_Channel2->CCR |= DMA_CCR_DIR; // Memory-to-peripheral
 DMA1_Channel2->CCR |= DMA_CCR_TCIE; // Transfer complete interrupt
 DMA1_Channel2->CCR |= DMA_CCR_EN; // Enable DMA channel

 // Configure I2C for write
 I2C1->CR2 = 0;
 I2C1->CR2 |= (slave_address << 1); // Slave address, write mode
 I2C1->CR2 |= (size << I2C_CR2_NBYTES_Pos); // Number of bytes
 I2C1->CR2 |= I2C_CR2_START; // Generate START condition

 while (!dma_transfer_complete); // Wait for DMA transfer to complete
 dma_transfer_complete = 0;
}

void I2C1_Read_DMA(uint8_t slave_address, uint8_t *data, uint16_t size) {
 // Configure DMA for reception
 DMA1_Channel3->CMAR = (uint32_t)data; // Memory address
 DMA1_Channel3->CNDTR = size; // Number of bytes
 DMA1_Channel3->CPAR = (uint32_t)&I2C1->RXDR; // Peripheral address
 DMA1_Channel3->CCR = 0; // Reset DMA channel configuration
 DMA1_Channel3->CCR |= DMA_CCR_MINC; // Memory increment mode
 DMA1_Channel3->CCR |= DMA_CCR_TCIE; // Transfer complete interrupt
 DMA1_Channel3->CCR |= DMA_CCR_EN; // Enable DMA channel

 // Configure I2C for read
 I2C1->CR2 = 0;
 I2C1->CR2 |= (slave_address << 1) | I2C_CR2_RD_WRN; // Slave address, read mode
 I2C1->CR2 |= (size << I2C_CR2_NBYTES_Pos); // Number of bytes
 I2C1->CR2 |= I2C_CR2_START; // Generate START condition

 while (!dma_transfer_complete); // Wait for DMA transfer to complete
 dma_transfer_complete = 0;
}

void DMA1_Channel2_3_IRQHandler(void) {
 if (DMA1->ISR & DMA_ISR_TCIF2) { // DMA transfer complete for Channel 2 (TX)
 DMA1->IFCR |= DMA_IFCR_CTCIF2; // Clear transfer complete flag
 DMA1_Channel2->CCR &= ~DMA_CCR_EN; // Disable DMA channel

 while (!(I2C1->ISR & I2C_ISR_TC)); // Wait for I2C transfer complete
 I2C1->CR2 |= I2C_CR2_STOP; // Generate STOP condition

 dma_transfer_complete = 1;
 }

 if (DMA1->ISR & DMA_ISR_TCIF3) { // DMA transfer complete for Channel 3 (RX)
 DMA1->IFCR |= DMA_IFCR_CTCIF3; // Clear transfer complete flag
 DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable DMA channel

 while (!(I2C1->ISR & I2C_ISR_TC)); // Wait for I2C transfer complete
 I2C1->CR2 |= I2C_CR2_STOP; // Generate STOP condition

 dma_transfer_complete = 1;
 }
}

int main(void) {
 Clock_Setup(); // Set up system clock
 GPIO_Setup();
 DMA_Init(); // Initialize DMA
 I2C1_Init(); // Initialize I2C1

 uint8_t command[1] = { 0x0C }; // Example command to send to Bus Pirate
 uint8_t response[4]; // Buffer to store response from Bus Pirate

 while (1) {
 // Write command to Bus Pirate
 I2C1_Write_DMA(0x36, command, 1); // 0x08 is the Bus Pirate's I2C slave address

 // Read response from Bus Pirate
 I2C1_Read_DMA(0x36, response, 4);
 GPIOC->ODR ^= (1U << 13);
 // Response processing (add your logic here)

 for (volatile int i = 0; i < 100000; i++); // Simple delay
 }
}

 

Best answer by TDK

The reference manual has a description of the registers. You can find the RM on the Documentation page, or in CubeMX:

STM32F0 Series - PDF Documentation

https://www.st.com/resource/en/reference_manual/rm0360-stm32f030x4x6x8xc-and-stm32f070x6xb-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

 

From the RM, you can see the register is write-only:

TDK_0-1734902784620.png

 

> After changing this in both instances of setting DMA1->IFCR it still did the same.

I figured.

 

The reference manual also has instructions on how to do things with direct register access. Doesn't look like you enable RXDMAEN (or TXDMAEN) in the CR1 register anywhere.

TDK_1-1734903031386.png

1 reply

TDK
Super User
December 22, 2024

> DMA1->IFCR |= DMA_IFCR_CTCIF2; // Clear transfer complete flag

This clears all flags, not just TCIF2. You want:

 

DMA1->IFCR = DMA_IFCR_CTCIF2;

 

 Presumably the other flag got cleared by one of these statements causing the mcu to wait forever.

Edit: not so sure of this anymore, but in any case, IFCR should not be read, only written.

"If you feel a post has answered your question, please click ""Accept as Solution""."
tableAuthor
Associate II
December 22, 2024

Do you perhaps mean "IFCR should not be 'Written", only "Read"?
I am not sure I understand the issue otherwise?

I am actually having a hard time finding this datasheet that actually shows the registers and not the short version of it.

So I can not see which flag in the register I actually have to reset.

Also the DMA_IFCR_CTCIF2 translates into "(0x1UL << (5U))"
Also the DMA_IFCR_CTCIF3 translates into "(0x1UL << (9U))"

If I = it then it will overwrite all of the register... Is that what I want?

After changing this in both instances of setting DMA1->IFCR it still did the same.

DMA1->IFCR = DMA_IFCR_CTCIF2; // Clear transfer complete flag
DMA1->IFCR = DMA_IFCR_CTCIF3; // Clear transfer complete flag

Thanks

TDK
TDKBest answer
Super User
December 22, 2024

The reference manual has a description of the registers. You can find the RM on the Documentation page, or in CubeMX:

STM32F0 Series - PDF Documentation

https://www.st.com/resource/en/reference_manual/rm0360-stm32f030x4x6x8xc-and-stm32f070x6xb-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

 

From the RM, you can see the register is write-only:

TDK_0-1734902784620.png

 

> After changing this in both instances of setting DMA1->IFCR it still did the same.

I figured.

 

The reference manual also has instructions on how to do things with direct register access. Doesn't look like you enable RXDMAEN (or TXDMAEN) in the CR1 register anywhere.

TDK_1-1734903031386.png

"If you feel a post has answered your question, please click ""Accept as Solution""."