cancel
Showing results for 
Search instead for 
Did you mean: 

I2C DMA endless while loop.

table
Visitor

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

 

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

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