cancel
Showing results for 
Search instead for 
Did you mean: 

Reading data from external ADC chip using GPIOC port and DMA2, does not work as espected.

NBogd.1
Associate II

Hello.

I am trying to read data from a 12bit ADC AD9226 connected to port GPIOC, on

STM32F407VGT6 clocked at 168MHz. On port PA8 there is a 42MHz clock for the external ADC

and also the same TIM1_CH1 I triggers the DMA2 to transfer data from pheripheral to

memory. The DMA2 uses CH6, STREAM 3.

The sistem get's the data from the external ADC when a trigger signal is on GPIOA_Pin_9,

and after DMA2_Stream3_IRQHandler is called, we stop the DMA2 and set a flag so we can know that we have data, and then we display data on a 324x240 display.

The problem is that i barely reach to see corectly a 5MHz signal from a signal generator.

The DMA2 transfers the data at 42MHz?

Please take a look at my code to see if there are any errors. Thank you!

8 REPLIES 8
NBogd.1
Associate II
/*
 *  Keil uVision 5
 *  STM32F4xx Keil packs version 2.4.0
 *  STM32F4xx Standard peripheral drivers version 1.5.0
 * 3.2" TFT LCD Display Module (HY32D)
 */
 
/* Include core modules */
#include "stm32f4xx.h"
#include "defines.h"
#include "glcd.h"
 
#define TOGGLE_GREEN_LED    GPIOD->ODR ^= GPIO_Pin_12
#define TOGGLE_ORANGE_LED   GPIOD->ODR ^= GPIO_Pin_13 
 
// Used to clear the past signal from the display
uint16_t buf2[320];
 
// ADC buffer size
#define BufferSize 320 // * 100
 
// ADC buffer
uint16_t GPIO_DATA[BufferSize];
 
volatile uint8_t data_ready = 0;
/*******************************************************************************
* Function Name  : Timer1 configurations
* Description    : Configure Timer1 in such a way that it can initiate data transfer
*                  using DMA on rising edge of clock signal received on port pin.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM1_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
 
uint16_t Period;
Period = 4; //(SystemCoreClock / 42000000);
 
// Clocks enable
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
 
// GPIOA Configuration: Channel 1 as alternate function push-pull
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);
 
// TIM1 clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);
 
// Time Base configuration
TIM_TimeBaseStructure.TIM_Prescaler = 0; // Dump 1X clock into timer
TIM_TimeBaseStructure.TIM_Period = Period - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
 
// Channel 1 Configuration in PWM mode
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = Period / 2; // 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
 
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
 
// TIM1 Main Output Enable
TIM_CtrlPWMOutputs(TIM1, ENABLE);
 
// TIM1 counter enable
TIM_Cmd(TIM1, ENABLE);
 
TIM_DMACmd( TIM1, TIM_DMA_CC1, ENABLE);
}
 
/*******************************************************************************
* Function Name  : DMA2 configuration
* Description    : Transfer Data from peripheral port (GPIOA) to RAM buffer
* Input          : None
* Output         : None
* Return         : None
* PA8 -> TIM1_CH1 - DMA2 STREAM 3, CH6 --- THIS IS USED
*******************************************************************************/
void DMA2_Configuration(void)
{
  DMA_InitTypeDef  DMA_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
 
  // Enable the DMA Stream IRQ Channel
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
 
  // Enable the DMA clock
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
 
  // Configure the DMA Stream
  DMA_Cmd(DMA2_Stream3, DISABLE);
  DMA_DeInit(DMA2_Stream3);
 
  // Check if the DMA Stream is disabled before enabling it.
  //   Note that this step is useful when the same Stream is used multiple times:
  //   enabled, then disabled then reenabled... In this case, the DMA Stream disable
  //   will be effective only at the end of the ongoing data transfer and it will
  //   not be possible to reconfigure it before making sure that the Enable bit
  //   has been cleared by hardware. If the Stream is used only once, this step might
  //   be bypassed.
  //while (DMA_GetCmdStatus(DMA2_Stream3) != DISABLE);
 
  // Set the parameters to be configured
  DMA_InitStructure.DMA_Channel = DMA_Channel_6;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOC->IDR;  // Read fron GPIO input data register
  DMA_InitStructure.DMA_Memory0BaseAddr    = (uint32_t)&GPIO_DATA[0]; // Send the data to the  RAM buffer
 
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
 
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_BufferSize = BufferSize;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; // HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(DMA2_Stream3, &DMA_InitStructure);
 
  // Enable DMA Transfer Complete interrupt
  DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
 
  DMA_Cmd(DMA2_Stream3, ENABLE);
}
 
void DMA2_Stream3_IRQHandler(void) {
  // Test on DMA Stream Transfer Complete interrupt
  if (DMA_GetITStatus(DMA2_Stream3, DMA_IT_TCIF3))  // != RESET)
  {
    // Clear DMA Stream Transfer Complete interrupt pending bit
    DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TCIF3);
 
    DMA_Cmd(DMA2_Stream3, DISABLE);
    //TIM_Cmd(TIM1, DISABLE);
 
    data_ready = 1;
  }
}
 
int main(void) {
  double Vin;
  uint16_t tmp;
  uint16_t counter = 0x0;
  uint16_t idx = 0x0;
 
  uint32_t offset = 0x0;
  GPIO_InitTypeDef GPIO_InitStructure;
 
  /* Initialize system */
  SystemInit();
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
 
  /*  STM32F4 Discovery: (STM32F407VG)
   *    - Onboard leds:
   *      - LED_GREEN   on PD12
   *      - LED_ORANGE  on PD13
   *    - Output type is push-pull
   *    - Mode is output
   *    - No pull resistor
   *    - Speed 100MHz
   */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_12;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
 
  // ADC pins
  //   D0     D1    D2    D3    D4    D5    D6    D7    D8    D9   D10   D11
  // PC11   PC10   PC9   PC8   PC7   PC6   PC5   PC4   PC3   PC2   PC1   PC0 
  // PA8 is the CLK for the ADC (TIM1_CH2)
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //GPIO_OType_OD;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 
  // Configure SYNC signal input pin
  // Signal used to start the aquisition
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  LCD_Initializtion();
  LCD_Clear(Black);
 
  TIM1_Configuration();
  DMA2_Configuration();
 
  while (1)
  { 
    // If SYNC is HIGH start DMA2 to get data
    if (GPIOA->IDR & GPIO_Pin_9)
    {
      TOGGLE_ORANGE_LED;
 
      // Start again to colect data
      DMA_Cmd(DMA2_Stream3, ENABLE);
      //TIM_Cmd(TIM1, ENABLE);
    }
 
    // If buffer is full then draw data to screen
    if (data_ready == 1)
    {
      // Clear data ready flag
      data_ready = 0;
 
      // Erase last disaplayed signal
      for (counter = 5; counter < 318; counter++)
      {
        LCD_DrawLine(240 - buf2[counter], counter, 240 - buf2[counter+1], counter+1, Black);
      }
 
      // Draw red line in middle screen
      for (counter = 0; counter < 319; counter++)
      {
        LCD_SetPoint(120, 319 - counter , Red);
      }
 
      // Convert AD9266 values to voltage
      for (counter = 0; counter < 319; counter++)
      {
        // ADC to voltage conversion
        Vin = (2048.0 - GPIO_DATA[offset + counter]) / 409.6;
        tmp = (uint16_t)(120.0 + (Vin*100.0));   // * 20 zoom to display 50mV signall
        buf2[counter] = tmp;
      }
 
      // Draw the signal
      for (counter = 5; counter < 318; counter++)
      {
        LCD_DrawLine(240 - buf2[counter], counter, 240 - buf2[counter+1], counter+1, Yellow);
      }
 
      // End of data to screen
      TOGGLE_GREEN_LED; 
    }
  }
}

TDK
Guru

The DMA can't handle transferring at 84MB/s.

Read the answers to this question:

https://community.st.com/s/question/0D50X00009XkYFc/dma-pheriperal-to-memory-max-speed

And the linked app note:

https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf

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

Hello.

I readed 3 times the PDF from above.

Why do you mean by 84MB/s ?

The timer that drives the DMA2 is at 42MB/s.

TDK
Guru

Mind your units.

Your timer that drives DMA2 is at 42 MHz. You're transferring a half-word each time (16 bits). So your desired throughput is 42 MHz * 16 bits = 84 MByte/s.

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

Ok. Now I get it right 42000000 * 16 / 8 = 84MB/s. Thank you for clarifying this.

Now a question arrise: If I will use an 8bit ADC it will give me 42MB/s.

Also what it means by data contention, I can understand this:

"Probably close to 21 MHz, but you'll get an overwhelming amount of data, and a lot of contention. I'd also expect jitter in the sample point/rate

Consider using the DCMI bus, admittedly not 16-bit wide, but could use 8-bit mode. Or the F(S)MC"

Thank yo for taking your time to answer my questions!

NBogd.1
Associate II

Someone please tell me why the STM32F429 DMA MEM2MEM can go up to 96MHz, I can't find anywere information about this.

Thank you!

Contention, the DMA and CPU both want to access the same memory unit at the same time, they can't, so they have to take turns and share the bandwidth.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Thank you!