cancel
Showing results for 
Search instead for 
Did you mean: 

I2C DMA endless while loop.

table
Associate II

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 } }
View more

 

1 ACCEPTED SOLUTION

Accepted Solutions

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".

View solution in original post

3 REPLIES 3
TDK
Guru

> 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".
table
Associate II

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

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".