/* p13_2.c Acquire data from ADC by DMA * * The program sets up the timer TIM2 channel 2 to trigger ADC1 to * convert the analog input channel 0. The output of the ADC1 is transferred * to the buffer in memory by DMA. Once the buffer if full, the DMA is * stopped. That data are converted to decimal ASCII numbers and sent * to USART2 to be displayed on the console. * * A global variable, done, is used by the DMA transfer complete interrupt * handler to signal the other part of the program that a buffer full of * data conversion is done. * * This program was tested with Keil uVision v5.24a with DFP v2.11.0 */ #include "stm32f4xx.h" #include "stdio.h" #define ADCBUFSIZE 64 void USART2_init (void); /* Initialize UART pins, Baudrate */ void DMA2_init(void); /* Initialize DMA2 controller */ void DMA2_Stream0_setup(unsigned int src, unsigned int dst, int len); /* set up a DMA transfer for ADC1 */ void TIM2_init(void); /* initialize TIM2 */ void ADC1_init(void); /* setup ADC */ int done = 1; char adcbuf[ADCBUFSIZE]; /* buffer to receive DMA data transfers from ADC conversion results */ char uartbuf[ADCBUFSIZE * 5]; /* buffer to hold ASCII numbers for display */ int main(void) { int i; char* p; USART2_init(); DMA2_init(); TIM2_init(); ADC1_init(); while(1) { done = 0; /* clear done flag */ /* start a DMA of ADC data transfer */ DMA2_Stream0_setup((uint32_t)adcbuf, (uint32_t)&(ADC1->DR), ADCBUFSIZE); while (done == 0) {} /* wait for ADC DMA transfer complete */ /* convert the ADC data into decimal ASCII numbers for display */ p = uartbuf; for (i = 0; i < ADCBUFSIZE; i++) { sprintf(p, "%3d ", adcbuf[i]); p += 4; } /* send the ADC numbers through USART2 to the console terminal */ for (i = 0; i < p - uartbuf; i++) { while (!(USART2->SR & 0x40)) {} USART2->DR = uartbuf[i]; } } } /* Initialize ADC ADC1 is configured to do 8-bit data conversion and triggered by the rising edge of timer TIM2 channel 2 output. */ void ADC1_init(void) { RCC->AHB1ENR |= 1; /* enable GPIOA clock */ GPIOA->MODER |= 3; /* PA0 analog */ RCC->APB2ENR |= 0x0100; /* enable ADC1 clock */ ADC1->CR1 = 0x2000000; /* 8-bit conversion */ ADC1->CR2 = 0x13000000; /* exten rising edge, extsel 3 = tim2.2 */ ADC1->CR2 |= 0x400; /* enable setting EOC bit after each conversion */ ADC1->CR2 |= 1; /* enable ADC1 */ } /* Initialize TIM2 Timer TIM2 channel 2 is configured to generate PWM at 1 kHz. The output of the timer signal is used to trigger ADC conversion. */ void TIM2_init(void) { RCC->AHB1ENR |= 2; /* enable GPIOB clock */ GPIOB->MODER |= 0x80; /* PB3 timer2.2 out */ GPIOB->AFR[0] |= 0x1000; /* set pin for timer output mode */ RCC->APB1ENR |= 1; /* enable TIM2 clock */ TIM2->PSC = 160 - 1; /* divided by 160 */ TIM2->ARR = 100 - 1; /* divided by 100, sample at 1 kHz */ TIM2->CNT = 0; TIM2->CCMR1 = 0x6800; /* pwm1 mode, preload enable */ TIM2->CCER = 0x10; /* ch2 enable */ TIM2->CCR2 = 50 - 1; } /* Initialize DMA2 controller * DMA2 controller's clock is enabled and also the DMA interrupt is * enabled in NVIC. */ void DMA2_init(void) { RCC->AHB1ENR |= 0x00400000; /* DMA2 controller clock enable */ DMA2->HIFCR = 0x003F; /* clear all interrupt flags of Stream 0 */ NVIC_EnableIRQ(DMA2_Stream0_IRQn); /* DMA interrupt enable at NVIC */ } /* Set up a DMA transfer for ADC * The ADC1 is connected to DMA2 Stream 0. This function sets up the * peripheral register address, memory address, number of transfers, * data size, transfer direction, and DMA interrupts are enabled. * At the end, the DMA controller is enabled, the ADC conversion * complete is used to trigger DMA data transfer, and the timer * used to trigger ADC is enabled. */ void DMA2_Stream0_setup(unsigned int src, unsigned int dst, int len) { DMA2_Stream0->CR &= ~1; /* disable DMA2 Stream 0 */ while (DMA2_Stream0->CR & 1) {} /* wait until DMA2 Stream 0 is disabled */ DMA2->HIFCR = 0x003F; /* clear all interrupt flags of Stream 0 */ DMA2_Stream0->PAR = dst; DMA2_Stream0->M0AR = src; DMA2_Stream0->NDTR = len; DMA2_Stream0->CR = 0x00000000; /* ADC1_0 on DMA2 Stream0 Channel 0 */ DMA2_Stream0->CR |= 0x00000400; /* data size byte, mem incr, peripheral-to-mem */ DMA2_Stream0->CR |= 0x16; /* enable interrupts DMA_IT_TC | DMA_IT_TE | DMA_IT_DME */ DMA2_Stream0->FCR = 0; /* direct mode, no FIFO */ DMA2_Stream0->CR |= 1; /* enable DMA2 Stream 0 */ ADC1->CR2 |= 0x0100; /* enable ADC conversion complete DMA data transfer */ TIM2->CR1 = 1; /* enable timer2 */ } /* DMA2 Stream0 interrupt handler This function handles the interrupts from DMA2 controller Stream0. The error interrupts have a placeholder for error handling code. If the interrupt is from DMA data transfer complete, the DMA controller is disabled, the interrupt flags are cleared, the ADC conversion complete DMA trigger is turned off and the timer that triggers ADC conversion is turned off too. */ void DMA2_Stream0_IRQHandler(void) { if (DMA2->HISR & 0x000C) /* if an error occurred */ while(1) {} /* substitute this by error handling */ DMA2_Stream0->CR = 0; /* disable DMA2 Stream 0 */ DMA2->LIFCR = 0x003F; /* clear DMA2 interrupt flags */ ADC1->CR2 &= ~0x0100; /* disable ADC conversion complete DMA */ TIM2->CR1 &= ~1; /* disable timer2 */ done = 1; } /* Initialize UART pins, Baudrate The USART2 is configured to send output to pin PA2 at 9600 Baud. This is used to display the ADC conversion results on the console terminal. */ void USART2_init (void) { RCC->AHB1ENR |= 1; /* enable GPIOA clock */ RCC->APB1ENR |= 0x20000; /* enable USART2 clock */ /* Configure PA2 for USART2_TX */ GPIOA->AFR[0] &= ~0x0F00; GPIOA->AFR[0] |= 0x0700; /* alt7 for USART2 */ GPIOA->MODER &= ~0x0030; GPIOA->MODER |= 0x0020; /* enable alternate function for PA2 */ USART2->BRR = 0x0683; /* 9600 baud @ 16 MHz */ USART2->CR1 = 0x0008; /* enable Tx, 8-bit data */ USART2->CR2 = 0x0000; /* 1 stop bit */ USART2->CR3 = 0x0000; /* no flow control */ USART2->CR1 |= 0x2000; /* enable USART2 */