2024-12-22 09:38 AM
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
}
}
2024-12-22 09:49 AM - edited 2024-12-22 10:05 AM
> 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.
2024-12-22 10:16 AM - edited 2024-12-22 10:25 AM
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
2024-12-22 01:35 PM
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
From the RM, you can see the register is write-only:
> 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.