cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F407 DMA and ADC bare metal

AAnth.1
Senior

Hi

I am trying to get my ADC measurements of the interal chip temperature and data transfer to a global variable via DMA working properly. The ADC1 is triggered every second via timer 4. Configuration is stream0, channel channel 0. I do get several error bits in the DMA ISR:

FEIFO (FIFO error interrupt flag)

DMEIFO (Direct mode error interrupt flag)

TCIFO (Stream transfer complete interrupt flag)

I seem to misunderstand how I can have a stream complete flag set, but I cannot see the ADC value in the actual variable when using the watch window.

Also, when leaving the DMA ISR, I monitor the ADC status register, and see the overrun flag being set meaning that there is data lost along the way. What am I missing here? :\

Thank you.

/*
 * STM32F407 DMA setup
 * Uses internal temperature measurement and transfers data to SRAM via DMA
 */
 
#include "stm32f4xx.h"
#include <stdio.h>
 
void funcADCInit(void);
void funcGPIOInit(void);
void funcTIM4Init(void);
void funcISRInit(void);
void funcDMAInit(void);
 
uint32_t temp_sensor = 0;
 
int main(void)
{
	funcGPIOInit();
	funcTIM4Init();
	funcISRInit();
	funcADCInit();
	funcDMAInit();
    /* Loop forever */
	for(;;);
}
 
void funcDMAInit(void)
{
	// Enable DMA1 bus
	RCC->AHB1ENR |= (1<<RCC_AHB1RSTR_DMA2RST_Pos);
	// ADC1 is channel 0, stream 0
	DMA2_Stream0->CR &= ~(3<<DMA_SxCR_CHSEL_Pos);	// Channel 0
 
	// Interrupt enable
	DMA2_Stream0->CR |= (1<<DMA_SxCR_TCIE_Pos);		// Interrupt enable when transfer complete
	DMA2_Stream0->CR |= (1<<DMA_SxCR_DMEIE_Pos);
	DMA2_Stream0->CR |= (1<<DMA_SxCR_TEIE_Pos);
	DMA2_Stream0->CR |= (1<<DMA_SxCR_HTIE_Pos);
	DMA2_Stream0->CR |= (1<<DMA_SxCR_TCIE_Pos);
 
	// Source address
	DMA2_Stream0->M0AR = (uint32_t) &ADC1->DR;
 
	// Destination address
	DMA2_Stream0->PAR = (uint32_t) &temp_sensor;
 
	// Number of data items
	DMA2_Stream0->NDTR = 1;
 
	// Enable circular mode
	DMA2_Stream0->CR |= (1<<DMA_SxCR_CIRC_Pos);		// Circular mode enabled
 
	// Direction
	DMA2_Stream0->CR &= ~(3<<DMA_SxCR_DIR_Pos);	// Data direction from peripheral to memory (P2M)
 
	// Peripheral data width
	DMA2_Stream0->CR &= ~(3<<DMA_SxCR_PSIZE_Pos);
	DMA2_Stream0->CR |= (1<<DMA_SxCR_PSIZE_Pos);	// Half word
 
	// Memory data width
	DMA2_Stream0->CR &= ~(3<<DMA_SxCR_MSIZE_Pos);
	DMA2_Stream0->CR |= (1<<DMA_SxCR_MSIZE_Pos);	// Half word
 
	// FIFO or Direct Mode
	DMA2_Stream0->FCR &= ~(1<<DMA_SxFCR_DMDIS_Pos);	// Direct mode enabled
 
	// Enable the stream
	DMA2_Stream0->CR |= (1<<DMA_SxCR_EN_Pos);		// DMA enable
 
	ADC1->CR2 |= (1<<ADC_CR2_DMA_Pos);
 
}
 
void funcISRInit(void)
{
	// Enable interrupt for ADC1
	__NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
 
void funcADCInit(void)
{
	RCC->APB2ENR |= (1<<RCC_APB2ENR_ADC1EN_Pos);	// Enable ADC clock
	ADC1->CR1 &= ~(1<<ADC_CR1_SCAN_Pos);			// SCAN mode disabled
	ADC1->CR1 &= ~(3<<ADC_CR1_RES_Pos);				// 12bit resolution
	ADC1->SQR3 &= ~(0xFFFFFFFF<<ADC_SQR3_SQ1_Pos);	// Clears whole 32bit register
	ADC1->SQR3 |= (18<<ADC_SQR3_SQ1_Pos);			// First conversion in regular sequence: Temperature on ADC1_In18
	ADC1->CR2 &= ~(1<<ADC_CR2_CONT_Pos);			// Single conversion
	ADC1->CR2 |= (9<<ADC_CR2_EXTSEL_Pos);			// TIM4 CC4 event as trigger source
	ADC1->CR2 |= (1<<ADC_CR2_EXTEN_Pos);			// Trigger detection on rising edge
	ADC1->CR2 &= ~(1<<ADC_CR2_ALIGN_Pos);			// Right alignment
	ADC1->SMPR2 |= (7<<ADC_SMPR2_SMP0_Pos);			// 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us
 
	ADC->CCR |= (1<<ADC_CCR_TSVREFE_Pos);			// Temp sensor and Vrefint enabled
 
	ADC1->CR1 |= (1<<ADC_CR1_EOCIE_Pos);			// Interrupt enable
 
	ADC1->CR2 |= (1<<ADC_CR2_ADON_Pos);				// Turns ADC on
}
 
void funcGPIOInit(void)
{
	RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIODEN_Pos);		// Enables AHB1 to communicate with GPIOD
 
	GPIOD->MODER &= ~(2<<GPIO_MODER_MODER15_Pos);	// PD15 as output	[01]
 
	GPIOD->OTYPER &= ~(1<<GPIO_OTYPER_OT15_Pos);	// Push pull
	GPIOD->OSPEEDR &= ~(3<<GPIO_OSPEEDR_OSPEED15_Pos); // Sets speed on PD12 to low
}
 
void funcTIM4Init(void)
{
	// Sets PD12 to TIM4 CH4
	RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIODEN_Pos);		// Enables AHB1 to communicate with GPIOD
	GPIOD->MODER |= (2<<GPIO_MODER_MODER15_Pos);	// Sets PD15 as Alternate function
 
	GPIOD->AFR[1] |= (2<<GPIO_AFRL_AFSEL7_Pos);	// Sets PD15 to AF2 [0010]
 
	// Use PD12 to toggle LED in PWM mode. Use alternate function and TIM4
	RCC->APB1ENR |= (1<<RCC_APB1ENR_TIM4EN_Pos);	// Enables TIM4 on APB1 bus
	TIM4->CR1 |= (1<<TIM_CR1_CEN_Pos);		// CEN: Counter enabled
	TIM4->CR1 &= ~(1<<TIM_CR1_UDIS_Pos);	// UDIS: Update event enabled
	TIM4->CR1 &= ~(1<<TIM_CR1_URS_Pos);		// URS: Update request source enabled
	TIM4->CR1 &= ~(1<<TIM_CR1_OPM_Pos);		// OPM: One pulse mode disabled
	TIM4->CR1 &= ~(1<<TIM_CR1_DIR_Pos);		// DIR: Up count
	TIM4->CR1 &= ~(3<<TIM_CR1_CMS_Pos);		// CMS: Edge aligned mode enabled
	TIM4->CR1 &= ~(1<<TIM_CR1_ARPE_Pos);	// ARPE: Auto reload register not buffered
	TIM4->CR1 &= ~(3<<TIM_CR1_CKD_Pos);		// CKD: Clock division and digital filters set to tdts=tck
 
	TIM4->DIER &= ~(1<<TIM_DIER_UIE_Pos);	// UIE: Update interrupt disabled
 
	TIM4->EGR |= (1<<TIM_EGR_UG_Pos);		// UG: Update generation enabled
	TIM4->SMCR |= (4<<TIM_SMCR_TS_Pos);		// TI1F trigger selection
 
	TIM4->CCMR2 &= ~(3<<TIM_CCMR2_CC4S_Pos);	// CC4S: CC4 set as output
	TIM4->CCMR2 |= (2<<TIM_CCMR2_OC4FE_Pos);	// Pre-load register enabled
	TIM4->CCMR2 |= (6<<TIM_CCMR2_OC4M_Pos);		// OC1M: PWM1 mode [6:4] 110
 
	TIM4->CCER |= (1<<TIM_CCER_CC4E_Pos);	// CC4E: Capture compare 4 output enable
	TIM4->CCER &= ~(1<<TIM_CCER_CC4P_Pos);	// CC4P: Active high
	TIM4->CCER &= ~(1<<TIM_CCER_CC4NP_Pos);	// CC4NP: Cleared because CC1 channel is set to output
 
	TIM4->PSC = 0xF9FF;		// Sets pre-scaler to 63999. Timer clock is now 16MHz/(63999+1)=250Hz
 
	TIM4->ARR = 0x00F9;		// Counter goes up to 249 to have a 1s timer
 
	TIM4->CCR4 = 0x007C;	// Sets compare match to half of ARR: 50% duty cycle
}
 
void DMA2_Stream0_IRQHandler()
{
	// Handles the IRQ of DMA2 stream 0.
	if(DMA2->LISR & (1<<DMA_LISR_FEIF0_Pos))
	{
		DMA2->LIFCR |= (1<<DMA_LIFCR_CFEIF0_Pos);
	}
	else if(DMA2->LISR & (1<<DMA_LISR_DMEIF0_Pos))
	{
		DMA2->LIFCR |= (1<<DMA_LIFCR_CDMEIF0_Pos);
	}
	else if(DMA2->LISR & (1<<DMA_LISR_TEIF0_Pos))
	{
		DMA2->LIFCR |= (1<<DMA_LIFCR_CTEIF0_Pos);
	}
	else if(DMA2->LISR & (1<<DMA_LISR_HTIF0_Pos))
	{
		DMA2->LIFCR |= (1<<DMA_LIFCR_CHTIF0_Pos);
	}
	else if(DMA2->LISR & (1<<DMA_LISR_TCIF0_Pos))
	{
		DMA2->LIFCR |= (1<<DMA_LIFCR_CTCIF0_Pos);
	}
}

1 ACCEPTED SOLUTION

Accepted Solutions

More harmless stuff:

> DMA2_Stream0->CR &= ~(3<<DMA_SxCR_CHSEL_Pos); // Channel 0

Why 3?

> DMA2_Stream0->CR |= (1<<DMA_SxCR_TCIE_Pos);

You have it twice.

I don't like the piecewise "building up" of the registers' content using RMW operations. Why don't you write them all at once, directly? Example:

LCD_DMA_STREAM->CR = (0x2 << DMA_SxCR_CHSEL_Pos)
			| (0x0 << DMA_SxCR_MBURST_Pos)
			| (0x0 << DMA_SxCR_PBURST_Pos)
			| (0x0 << DMA_SxCR_PL_Pos)
			| (0x0 << DMA_SxCR_PINCOS_Pos)
			| (0x0 << DMA_SxCR_MSIZE_Pos)
			| (0x0 << DMA_SxCR_PSIZE_Pos)
			| (0x1 << DMA_SxCR_MINC_Pos)
			| (0x0 << DMA_SxCR_PINC_Pos)
			| (0x0 << DMA_SxCR_CIRC_Pos)
			| (0x1 << DMA_SxCR_DIR_Pos)
			| (0x1 << DMA_SxCR_TCIE_Pos)
			;

Now for the showstopper:

> // Source address

> DMA2_Stream0->M0AR = (uint32_t) &ADC1->DR;

> // Destination address

> DMA2_Stream0->PAR = (uint32_t) &temp_sensor;

You want to perform P2M DMA, so this transfers from temp_sensor into ADC1-DR.

I'm not sure why do you get the error flags, but am not very interested to investigate pathologic cases.

JW

View solution in original post

5 REPLIES 5
TDK
Guru

RCC->AHB1ENR |= (1<<RCC_AHB1RSTR_DMA2RST_Pos);

This does not enable the DMA2 clock.

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

Hi

Thank you very much. You are absolutely correct, I used the wrong #define. Lucky me, they have the same value 22UL, so this does not explain the mistake. Let's cut the pizza into smaller slices:

  1. How do I understand the following statement regarding the FIFO error flag from the reference manual?: "In direct mode, the FIFO error flag can also be set under the following conditions: In the peripheral-to-memory mode, the FIFO can be saturated (overrun) if the memory bus is not granted for several peripheral requests"
  2. How do I understand the following statement regarding the DMEIF error flag from the reference manual?: "This flag is set when a DMA request occurs while the previous data have not yet been fully transferred into the memory (because the memory bus was not granted). In this case, the flag indicates that 2 data items were be transferred successively to the same destination address, which could be an issue if the destination is not able to manage this situation"

So it seems that it is somehow working, but not really. And hence the ADC1 OVR error flag is set.

Does anyone have a clue how I can solve the FIFO and DMEIF error flag?

Thank you,

More harmless stuff:

> DMA2_Stream0->CR &= ~(3<<DMA_SxCR_CHSEL_Pos); // Channel 0

Why 3?

> DMA2_Stream0->CR |= (1<<DMA_SxCR_TCIE_Pos);

You have it twice.

I don't like the piecewise "building up" of the registers' content using RMW operations. Why don't you write them all at once, directly? Example:

LCD_DMA_STREAM->CR = (0x2 << DMA_SxCR_CHSEL_Pos)
			| (0x0 << DMA_SxCR_MBURST_Pos)
			| (0x0 << DMA_SxCR_PBURST_Pos)
			| (0x0 << DMA_SxCR_PL_Pos)
			| (0x0 << DMA_SxCR_PINCOS_Pos)
			| (0x0 << DMA_SxCR_MSIZE_Pos)
			| (0x0 << DMA_SxCR_PSIZE_Pos)
			| (0x1 << DMA_SxCR_MINC_Pos)
			| (0x0 << DMA_SxCR_PINC_Pos)
			| (0x0 << DMA_SxCR_CIRC_Pos)
			| (0x1 << DMA_SxCR_DIR_Pos)
			| (0x1 << DMA_SxCR_TCIE_Pos)
			;

Now for the showstopper:

> // Source address

> DMA2_Stream0->M0AR = (uint32_t) &ADC1->DR;

> // Destination address

> DMA2_Stream0->PAR = (uint32_t) &temp_sensor;

You want to perform P2M DMA, so this transfers from temp_sensor into ADC1-DR.

I'm not sure why do you get the error flags, but am not very interested to investigate pathologic cases.

JW

Jan found your real issue.

Note also that instead of (for example)

RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIODEN_Pos);

you can do

RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

A bit less typing and IMO more readable.

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

Hi Jan

the piecewise building up of the registers is purely for myself to memorize a step-by-step procedure as of which bits need to be set and which to be cleared. I agree with you, that your approach is more elegant. If that way of writing to the registers increases your motivation for support, I will gladly adapt to that. I would even be willing to try the LL library from ST if that increases your motivation to help me even more 🙂 I always truly appreciated when you helped me out, and I hope I can count on you in the future.

To your actual comment:

MOAR is the memory address, so this should be (uint32_t) &temp_sensor, wheres PAR is the peripheral address, which is my ADC, which is (uint32_t) &ADC1-DR.

I changed that and the error flags disappear. So that solved the actual problem. From there, I could debug my code further and managed to figure out, that I also need to set the DDS bit in my ADC1->CR2 register in order to generate a new DMA request after the last DMA transfer occurred.

This being said, my code seems to work fine now, and I can trigger my ISR on a regular basis and not only once.

Thank you all for your support.

Cheers,