Skip to main content
Graduate
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
 }
}

 

    This topic has been closed for replies.
    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

    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.

    tableAuthor
    Graduate
    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

    TDKAnswer
    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