2020-09-01 11:48 AM
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);
}
}
Solved! Go to Solution.
2020-09-01 01:16 PM
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
2020-09-01 12:36 PM
RCC->AHB1ENR |= (1<<RCC_AHB1RSTR_DMA2RST_Pos);
This does not enable the DMA2 clock.
2020-09-01 01:01 PM
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:
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,
2020-09-01 01:16 PM
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
2020-09-01 02:21 PM
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.
2020-09-02 08:04 AM
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,