STM32 F4 ADC DMA Temperature Sensor

Goal: detecting temperature variations using a temperature sensor, ADC with DMA and TIM3 as a trigger (ADC sampling frequency = TIM3 trigger frequency).

Note: Using TIM3 as a trigger is suited for monitoring temperature variations over time. However, this is an overkill for checking the absolute temperature once in a while; a software trigger would be a better option for that.

Description: 
The temperature sensor is connected to ADC1 input channel 16. TIM3 will trigger periodically (CK_CNT/(Autoreload value+1)) and ADC1 will sample the signal using the 3-cycle sample time. I will use the 12-bit resolution which results in the total of 15-cycle conversion time (including the 3-cycle sampling time). Now, the ADC clock frequency is 21MHz (driven by the prescaled APB2). The total conversion time is then 15/21Mhz = ~714 ns (~1.4Msps max). A DMA request is made after each conversion, which places the converted voltage value into a user-specified buffer. An end-of-conversion (EOC) flag generates an interrupt at the TIM3 trigger frequency (with a 15 cycle offset). TIM3 shouldn't trigger faster than the minimum conversion time (~714ns). In the case of measuring the temperature, sampling frequency is not an issue so I'll set up TIM3 to trigger, say, 2000 times per second.

ADC sampling frequency: 2kHz (max is 1.4MHz with a 21MHz ADC clock, 12-bit resolution and 3-cycle sampling).
ADC interrupt frequency: 2kHz (I will use ADC_IRQHandler() to toggle a pin to make sure my calculations are correct, the toggling frequency should be 1kHz).

Now, we end up with some value in the user-defied buffer (which the DMA writes into at the end of each conversion, 2000 times per second). The manual gives us a linear algebraic formula for calculating the temperature: Temperature (in °C) = {(VSENSE – V25) / Avg_Slope} + 25
VSENSE is the 12-bit value we get from ADC in our buffer (this is not the voltage value).
VSENSE should be multiplied by the ADC resolution step (Vref/4095) to get the actual voltage value.
V25 is 0.76V (voltage that sensor outputs at 25°C)
Avg_Slope is 0.0025V/°C (rate at which voltage changes when temperature changes by one degree Celsius)

According to the manual, the offset of the function can be up to 45°C due to process variation so it'll require some calibration if we plan to measure absolute temperature.
I will pass the ADC values through an averaging filter to improve the variation accuracy. The filter also removes highest and lowest samples (can add a more sophisticated mechanism here).




main.c
#include <stm32f4xx.h>
#include "mcu_init.h"



//====================================================================================
//   Global variables for temperature measurements
//====================================================================================
volatile uint16_t ADC_Raw[NS] = {0};       // Updated 2000 times per second by DMA
uint16_t Sample_ADC_Raw[NS]   = {0};       // Non-volatile copy of ADC_Raw[NS]
uint32_t ADC_Average          = 0;         // Average of the samples
float Temp                    = 0;         // Temporary register
float Temp_Celsius            = 0;         // Temperature in Celsius
float Calibration_Value       = 11.0;      // For measuring absolute temperature



//====================================================================================
//   Functions used in this module
//====================================================================================
void Sort_values(uint16_t [], uint8_t);
float Get_Temperature(void);



//====================================================================================
//   main function
//====================================================================================
int main()
{ 
 
  //ADC_Interrupt_Config();                  // Indirectly testing my calculations
  //GPIOD_Config();                          // Indirectly testing my calculations
  TIM3_Config();
  ADC_Config(); 
 
  while (1)
  {
    Temp_Celsius = Get_Temperature();        // Monitoring Temp_Celsius in debug mode
  
    // Set a threshold value, light up LEDs if Temp_Celsius goes above or below it.
    // I put it in the freezer to test :)
  
  }
 
}



//====================================================================================
//   Description: Averaging samples from ADC, calculating temperature in Celsius
//====================================================================================
float Get_Temperature(void)
{
  uint8_t i;
 
  for(i = 0; i < NS; i++)
  {
    Sample_ADC_Raw[i] = ADC_Raw[i];
  }
  
  Sort_values(Sample_ADC_Raw, NS);
  
  ADC_Average = 0;
  for(i = SR/2; i < NS-SR/2; i++)
  {
    ADC_Average += Sample_ADC_Raw[i];
  }
  ADC_Average /= (NS-SR);
    
  Temp += ADC_Average;
  Temp *= 3;
  Temp /= 4095;
  Temp -= (float)0.76;
  Temp /= (float)0.0025;
  Temp += (float)25.0;
  Temp -= Calibration_Value;
  
  return Temp;
}



//====================================================================================
// Description: Bubble sort min to max 
//====================================================================================
void Sort_values(uint16_t A[], uint8_t L)
{
  uint8_t i = 0;
  uint8_t status = 1;
 
  while(status == 1)
  {
    status = 0;
    for(i = 0; i < L-1; i++)
    {
      if (A[i] > A[i+1])
      {
        A[i]^=A[i+1];
        A[i+1]^=A[i];
        A[i]^=A[i+1];
        status = 1;    
      }
    }
  }
}



void ADC_IRQHandler(void) // (for testing)
{
  GPIO_ToggleBits(GPIOD, GPIO_Pin_9);
  ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
}

mcu_init.h
#ifndef __MCU_INIT_H
#define __MCU_INIT_H

#include <stm32f4xx.h>

#define NS       10         // Number of samples to get from ADC
#define SR       4          // Samples removed after sorting, 4=(2 highest & 2 lowest)
#define ADC1_RDR 0x4001204C // ADC1 Regular Data Register (read only)

extern volatile uint16_t ADC_Raw[NS];   // DMA writes ADC values into this buffer



//====================================================================================
//   Functions used for measuring temperature variations
//====================================================================================
void GPIOD_Config(void);            // Indirectly testing my calculations
void ADC_Interrupt_Config(void);    // Indirectly testing my calculations
void TIM3_Config(void);
void ADC_Config(void);



#endif // __MCU_INIT_H

mcu_init.c
#include "mcu_init.h"
#include <stm32f4xx.h>



//====================================================================================
//   Configuring TIM3 to trigger at 2kHz which is the ADC sampling rate
//====================================================================================
void TIM3_Config(void)
{
  TIM_TimeBaseInitTypeDef TIM3_TimeBase;

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
 
  TIM_TimeBaseStructInit(&TIM3_TimeBase); 
  TIM3_TimeBase.TIM_Period        = (uint16_t)49; // Trigger = CK_CNT/(49+1) = 2kHz
  TIM3_TimeBase.TIM_Prescaler     = 420;          // CK_CNT = 42MHz/420 = 100kHz
  TIM3_TimeBase.TIM_ClockDivision = 0;
  TIM3_TimeBase.TIM_CounterMode   = TIM_CounterMode_Up;  
  TIM_TimeBaseInit(TIM3, &TIM3_TimeBase);
  TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);

  TIM_Cmd(TIM3, ENABLE);
}



//====================================================================================
//   Configuring GPIO PD9 (for testing)
//====================================================================================
void GPIOD_Config(void)
{
  GPIO_InitTypeDef gpio_D;
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
 
  gpio_D.GPIO_Mode  = GPIO_Mode_OUT;
  gpio_D.GPIO_OType = GPIO_OType_PP;
  gpio_D.GPIO_Pin   = GPIO_Pin_9;
  gpio_D.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  gpio_D.GPIO_Speed = GPIO_Medium_Speed;
  GPIO_Init(GPIOD, &gpio_D);
 
}



//====================================================================================
//   Configuring ADC global interrupt (for testing)
//====================================================================================
void ADC_Interrupt_Config(void)
{
  NVIC_InitTypeDef NVIC_ADC1;
 
  NVIC_ADC1.NVIC_IRQChannel    = ADC_IRQn;
  NVIC_ADC1.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_ADC1);
}



//====================================================================================
//   Configuring ADC with DMA
//====================================================================================
void ADC_Config(void)
{
  ADC_InitTypeDef       ADC_INIT;
  ADC_CommonInitTypeDef ADC_COMMON;
  DMA_InitTypeDef       DMA_INIT;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

  DMA_INIT.DMA_Channel = DMA_Channel_0;  
  DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)ADC1_RDR;
  DMA_INIT.DMA_Memory0BaseAddr    = (uint32_t)&ADC_Raw[0]; 
  DMA_INIT.DMA_DIR                = DMA_DIR_PeripheralToMemory;
  DMA_INIT.DMA_BufferSize         = NS;
  DMA_INIT.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  DMA_INIT.DMA_MemoryInc          = DMA_MemoryInc_Enable;
  DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_INIT.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
  DMA_INIT.DMA_Mode               = DMA_Mode_Circular;
  DMA_INIT.DMA_Priority           = DMA_Priority_High;
  DMA_INIT.DMA_FIFOMode           = DMA_FIFOMode_Disable;         
  DMA_INIT.DMA_FIFOThreshold      = DMA_FIFOThreshold_HalfFull;
  DMA_INIT.DMA_MemoryBurst        = DMA_MemoryBurst_Single;
  DMA_INIT.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single;
  DMA_Init(DMA2_Stream4, &DMA_INIT);
  DMA_Cmd(DMA2_Stream4, ENABLE);

  ADC_COMMON.ADC_Mode             = ADC_Mode_Independent;
  ADC_COMMON.ADC_Prescaler        = ADC_Prescaler_Div2;
  ADC_COMMON.ADC_DMAAccessMode    = ADC_DMAAccessMode_Disabled;
  ADC_COMMON.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  ADC_CommonInit(&ADC_COMMON);

  ADC_INIT.ADC_Resolution           = ADC_Resolution_12b;
  ADC_INIT.ADC_ScanConvMode         = DISABLE;
  ADC_INIT.ADC_ContinuousConvMode   = DISABLE; // ENABLE for max ADC sampling frequency
  ADC_INIT.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  ADC_INIT.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T3_TRGO;
  ADC_INIT.ADC_DataAlign            = ADC_DataAlign_Right;
  ADC_INIT.ADC_NbrOfConversion      = 1;
  ADC_Init(ADC1, &ADC_INIT);

  ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_3Cycles);
  ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
  ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);      // (for testing)
  ADC_DMACmd(ADC1, ENABLE);
  ADC_Cmd(ADC1, ENABLE);
  ADC_TempSensorVrefintCmd(ENABLE);
}







Summary: 
To work with other sensors, follow these steps:
 - choose the ADC channel
 - choose the sampling rate
 - adjust the TIMx trigger frequency
 - configure DMA

A software trigger is suited for reading sensor's values on demand (turn off ADC when not using).

27 comments:

  1. I can't work with FPU in KEIL
    when it comes to the floating-point operations, it always jump to "hard fault" ??
    although USE_FPU option is checked in project options ??
    so what can I do?

    ReplyDelete
    Replies
    1. Try enabling it like that:

      #define CPACR (*((volatile unsigned long *) 0xE000ED88))
      CPACR |= 0xFu<<20;

      That's for STM32F407

      Delete
  2. Hi,

    nice example! I want to compile the code above as a project with the Keil qVision V5.
    Which other software components (e.g. TIM, DMA) do I need to add to the project to get it compiled successfully?

    Thanks!

    ReplyDelete
    Replies
    1. Hi,

      I have the whole StdPeriph library added to my projects, but in this particular example I used RCC, GPIO, TIM, DMA, and ADC (and misc.c and misc.h for testing).
      I also use Keil uVision5 so it should compile if everything else is set up correctly.

      Delete
  3. Hello Sergey,

    OK, thanks.

    Now I have quit some compiler errors. For example:
    \STM32F4xx_DFP\1.0.8\RTE_Driver\GPIO_STM32F4xx.h(57): error: #40: expected an identifier

    In the wizard OPTIONS FOR TARGET -> C/C++ I have added "USE_STDPERIPH_DRIVER" as Preprocessor Symbol.
    Do I need more additional changes/settings? Do you have some "Include Paths" defined?

    ReplyDelete
  4. Hi Sergey,

    Problem solved. The compiler error above refers to GPIO driver that comes with the Keil DFP, we don't need this driver since you are using the ST standard peripheral drivers.
    After removing this driver I was able to compile the project successfully.

    ReplyDelete
  5. Hi Sergey,

    I'm facing some issue using the example above in combination with a Keil STM32F400 evaluation board. I want to read out the analog input which is connected to the onboard potentiometer.
    According to the schematics of the board the potentiometer is connected to pin PF9. Do you know which ADC channel changes I need to make to get this potentiometer voltage as "ADC_RAW"?

    In evaluation board "demo" example the analog input is configured based on the code below:

    ***********************
    void ADC_Init (void) {
    /* Setup potentiometer pin PF9 and A/D converter ADC3 */

    RCC->APB2ENR |= (1UL << 10); /* Enable ADC3 clock */
    RCC->AHB1ENR |= (1UL << 5); /* Enable GPIOF clock */
    GPIOF->MODER |= (3UL << 2*9); /* PF9 is in Analog mode */

    ADC3->SQR1 = 0;
    ADC3->SQR2 = 0;
    ADC3->SQR3 = ( 7UL << 0); /* SQ1 = channel 7 */
    ADC3->SMPR1 = 0; /* Clear register */
    ADC3->SMPR2 = ( 7UL << 21); /* Channel 7 sample time is 480 cyc. */
    ADC3->CR1 = ( 1UL << 8); /* Scan mode on */

    ADC3->CR2 |= ( 1UL << 3); /* Initialize calibration registers */
    while (ADC3->CR2 & (1UL << 3)); /* Wait for initialization to finish */
    ADC3->CR2 |= ( 1UL << 2); /* Start calibration */
    while (ADC3->CR2 & (1UL << 2)); /* Wait for calibration to finish */

    ADC3->CR1 |= ( 1UL << 5); /* enable EOC interrupt */
    ADC3->CR2 |= ( 1UL << 0); /* ADC enable */
    }
    ***********************

    Thanks.

    ReplyDelete
    Replies
    1. Hi,

      Your channel configuration is correct (ADC3, Ch.7, sampling time is 480 cycles).

      According to the ADC block diagram, the data moves as follows:

      Potentiometer -> GPIO Block -> Analog Mux -> ADC Block -> Regular Data Register (then DMA takes it from the Regular Data Register to ADC_Raw[] or some other user-specified location in memory).

      So off the top of my head, I can think of the following:

      1). PF9 is connected to ADC3 so you will need the Regular Data Register address for ADC3.
      I used ADC1, not ADC3.
      Change this: #define ADC1_RDR 0x4001204C // ADC1 Regular Data Register (read only)
      To this: #define ADC3_RDR 0x4001224C // ADC3 Regular Data Register (read only)

      2). ADC3_RDR is where your data ends up after it's converted by the ADC. Next, DMA takes the data from ADC3_RDR and puts it into ADC_Raw[]. So the DMA peripheral base address has to be changed to ADC3_RDR.

      If you are not using the DMA, you can directly view the data in ADC3_RDR at this address 0x4001224C.

      I hope it helps.

      Delete
  6. Hello Sergey,

    Memory adderess 0x4001224C shows "00".
    It looks like the ADC initialization/conversion is not executed as it should, since no PF9 ADC-values are presented at the DMA input location.
    The "ADC_Init" function that I mention above relies on the Board Support ADC driver (MCBSTM32F400:A/D Converter) from Keil, maybe there is the problem.

    I prefer to continue with your mcu_init.c approach, since you are using the only the standard peripheral drivers from ST. So I have edit your code:

    ***********************************************************
    //====================================================================================
    // Configuring ADC with DMA
    //====================================================================================
    void ADC_Config(void)
    {
    ADC_InitTypeDef ADC_INIT;
    ADC_CommonInitTypeDef ADC_COMMON;
    DMA_InitTypeDef DMA_INIT;
    GPIO_InitTypeDef gpio_F;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); //Enable DMA2 clock
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //Enable GIOF clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE); //Enable ADC3 clock

    gpio_F.GPIO_Mode = GPIO_Mode_AN;
    gpio_F.GPIO_Pin = GPIO_Pin_9;
    gpio_F.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOF, &gpio_F);

    DMA_INIT.DMA_Channel = DMA_Channel_0;
    DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)ADC3_RDR; //0x4001224C
    DMA_INIT.DMA_Memory0BaseAddr = (uint32_t)&ADC_Raw[0];
    DMA_INIT.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_INIT.DMA_BufferSize = NS;
    DMA_INIT.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_INIT.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_INIT.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_INIT.DMA_Mode = DMA_Mode_Circular;
    DMA_INIT.DMA_Priority = DMA_Priority_High;
    DMA_INIT.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_INIT.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_INIT.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_INIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA2_Stream4, &DMA_INIT);
    DMA_Cmd(DMA2_Stream4, ENABLE);

    ADC_COMMON.ADC_Mode = ADC_Mode_Independent;
    ADC_COMMON.ADC_Prescaler = ADC_Prescaler_Div2;
    ADC_COMMON.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_COMMON.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_COMMON);

    ADC_INIT.ADC_Resolution = ADC_Resolution_12b;
    ADC_INIT.ADC_ScanConvMode = DISABLE;
    ADC_INIT.ADC_ContinuousConvMode = DISABLE; // ENABLE for max ADC sampling frequency
    ADC_INIT.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_INIT.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
    ADC_INIT.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_INIT.ADC_NbrOfConversion = 1;
    ADC_Init(ADC3, &ADC_INIT);

    ADC_RegularChannelConfig(ADC3, ADC_Channel_7, 1, ADC_SampleTime_3Cycles);
    ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);
    ADC_ITConfig(ADC3, ADC_IT_EOC, ENABLE); // (for testing)
    ADC_DMACmd(ADC3, ENABLE);
    ADC_Cmd(ADC3, ENABLE);
    ADC_TempSensorVrefintCmd(ENABLE);
    }
    ***********************************************************
    Still I get only "00" on memory location 0x4001224C. Also the ADC_Raw values are not changing in the Watch window.
    Did I miss something?

    Thanks.

    ReplyDelete
  7. Hi Sergey,

    short update, I have the ADC running.
    But now it seems that DMA is not working properly. Based on the code below I don't get ADC values on the DMA destination adres "ADC_Raw"
    When I check the ADC peripheral address (ADC3_RDR), I can see the analog values, so the ADC values are available for DMA.

    Why are the analog values not processed by DMA? Do I miss something?
    Thanks!

    *********************************

    #include "ADC_DMA.h"
    #include

    void TIM2_Config(void)
    {
    TIM_TimeBaseInitTypeDef TIM2_TimeBase;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_TimeBaseStructInit(&TIM2_TimeBase);
    TIM2_TimeBase.TIM_Period = (uint16_t)49; // Trigger = CK_CNT/(49+1) = 2kHz
    TIM2_TimeBase.TIM_Prescaler = 420; // CK_CNT = 42MHz/420 = 100kHz
    TIM2_TimeBase.TIM_ClockDivision = 0;
    TIM2_TimeBase.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM2_TimeBase);
    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
    }

    void GPIOH_Config(void)
    {
    GPIO_InitTypeDef gpio_H;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH, ENABLE);

    gpio_H.GPIO_Mode = GPIO_Mode_OUT;
    gpio_H.GPIO_OType = GPIO_OType_PP;
    gpio_H.GPIO_Pin = GPIO_Pin_3;
    gpio_H.GPIO_PuPd = GPIO_PuPd_NOPULL;
    gpio_H.GPIO_Speed = GPIO_Medium_Speed;
    GPIO_Init(GPIOH, &gpio_H);
    }

    void ADC_Config(void)
    {
    ADC_InitTypeDef ADC_INIT;
    ADC_CommonInitTypeDef ADC_COMMON;
    DMA_InitTypeDef DMA_INIT;
    GPIO_InitTypeDef gpio_F;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOF, ENABLE); //Enable DMA2 clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE); //Enable ADC3 clock

    DMA_INIT.DMA_Channel = DMA_Channel_0;
    DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)ADC3_RDR; //0x4001224C
    DMA_INIT.DMA_Memory0BaseAddr = (uint32_t)&ADC_Raw[0];
    DMA_INIT.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_INIT.DMA_BufferSize = NS;
    DMA_INIT.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_INIT.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_INIT.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_INIT.DMA_Mode = DMA_Mode_Circular;
    DMA_INIT.DMA_Priority = DMA_Priority_High;
    DMA_INIT.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_INIT.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_INIT.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_INIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA2_Stream4, &DMA_INIT);
    DMA_Cmd(DMA2_Stream4, ENABLE);

    gpio_F.GPIO_Mode = GPIO_Mode_AN;
    gpio_F.GPIO_Pin = GPIO_Pin_9;
    gpio_F.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOF, &gpio_F);

    ADC_COMMON.ADC_Mode = ADC_Mode_Independent;
    ADC_COMMON.ADC_Prescaler = ADC_Prescaler_Div2;
    ADC_COMMON.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_COMMON.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_COMMON);

    ADC_INIT.ADC_Resolution = ADC_Resolution_12b;

    ADC_INIT.ADC_ScanConvMode = DISABLE;
    ADC_INIT.ADC_ContinuousConvMode = DISABLE; // ENABLE for max ADC sampling frequency
    ADC_INIT.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_INIT.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
    ADC_INIT.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_INIT.ADC_NbrOfConversion = 1;
    ADC_Init(ADC3, &ADC_INIT);

    ADC_RegularChannelConfig(ADC3, ADC_Channel_7, 1, ADC_SampleTime_3Cycles);
    ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);
    ADC_DMACmd(ADC3, ENABLE);
    ADC_Cmd(ADC3, ENABLE);
    }
    *********************************

    ReplyDelete
    Replies
    1. DMA2 request that maps to ADC3 is (Channel 2, Stream 0) or (Channel 2, Stream 1). This is what you need to use for ADC3.

      DMA2 (Channel 0, Stream 4) in my code maps to ADC1. This is what I used for ADC1.

      Delete
  8. Thanks, problem solved.
    This information is also described in the STM32F407xx datasheet.

    ReplyDelete
  9. Thanks for your information. I think TIM_Prescaler we should consider because in STM32F4 clock scheme, TIM clock come from APB1 pass to this (if APB1 prescaler = 1 X1 else X2), and APB1 prescaler DIV_4 from AHB, I think TIM clock should be 42x2 = 84MHz or not. and TIM3 prescaler should be
    TIM3_TimeBase.TIM_Prescaler = 840-1; // CK_CNT = 84MHz/840 = 100kHz

    ReplyDelete
    Replies
    1. "APB1 clock" is 21MHz and "APB1 timer clock" is 42MHz in this example.
      Yours might be different :)

      Delete
  10. How about for the code of ADC1 channel 0 and channel 1 with DMA channel 0 stream 0 and 4? can you guide more for me ?

    ReplyDelete
  11. Currently, I'm programming ADC1 channel 0 and channel 1 to read the data from accelerometer and I have to use DMA2 stream4 to do like that. Unfortunately, it shows me wrong and same value between two channels. Please help me to fix by my code here

    http://www.mediafire.com/download/jautywxt89uxo38/ADC+and+DMA.rar

    ReplyDelete
  12. Hello , I have a problem with ADC_IRQHandler
    when testing your code but adding a small statement in the beginning to test that the EOC flag is set
    (The interrupt doesn't work , which means that at the moment of calling the "ADC_IRQHANDLER", the " ADC_IT_EOC" is not set .
    So what do you suggest the reason for that ?

    void ADC_IRQHandler(void) // (for testing)
    {
    if (ADC_GetITStatus( ADC1,ADC_IT_EOC) != RESET)
    {
    GPIO_ToggleBits(GPIOD, GPIO_Pin_9);
    ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
    }
    }

    ReplyDelete
    Replies
    1. Does the interrupt handler (ADC_IRQHandler) get called? Try putting a break-point there.
      If it gets called, then everything works as expected.

      You do not need to check if the EOC flag is set because EOC flag CAUSES the interrupt handler to be called; it is sort of redundant.

      Delete
  13. This comment has been removed by the author.

    ReplyDelete
  14. You've wrote...

    >Using TIM3 as a trigger is suited for monitoring temperature variations over time. However, this is an overkill for checking the absolute temperature once >in a while; a software trigger would be a better option for that.

    Could you please explain why checking temperature by TIM3 timer as a trigger is overkill? Why we can't set TIM3 generate interrupt every 3 seconds to measure the temperature?

    ReplyDelete
    Replies
    1. I was mostly thinking about power-aware drivers; if you only need to trigger every 3 seconds, you would probably like MCU to be in some low-power mode most of the time. If you use a timer, you need to keep CPU, SRAM, and whatever other peripherals ON while waiting for the 3-second interrupt.

      If you use a software trigger, you can turn OFF just about everything (even CPU and SRAM) and only keep the VBAT domain ON with some RTC clock that has the ability to wake the MCU up.

      If you don't care about low power, you definitely can use TIM3 to generate interrupt every 3 seconds :)

      Delete
  15. hiii all...
    how to trigger DMA to load next buffer value at required time..

    ReplyDelete
    Replies
    1. This post explains exactly what you are asking for :)

      Delete
  16. Hello Sergey,
    You describe using ADC channel 16 as the temperature sensor read. If the STM32F4 is using PC6 as a GPIO already, is it still possible to use the temperature read function?

    Did you have some calibration table or formula for a wider ambient range, such as -40 to +70?

    Thanks!
    Roger



    ReplyDelete
    Replies
    1. Hi Roger,

      1.
      I may be overlooking something, but how is PC6 being used as a GPIO relates to reading the temperature?

      2.
      The temperature sensor's supported range is -40°C to +125°C with a precision of +/- 1.5°C.
      The temperature is calculated as follows (the parameters are described in the description):
      Temperature (in °C) = {(VSENSE – V25) / Avg_Slope} + 25
      Once you calculate the temperature using the formula above, the actual ambient temperature may be off by some value which we call an offset.
      This offset is constant per chip, but not the same for all chips.
      Calibration refers to a process which adjusts our calculated value by this offset.

      For example, you calculated the temperature to be 25°C using the formula above. You read another temperature sensor (which is calibrated and works properly) and it says that the temperature is 35°C. This tells us that our on-chip temperature sensor's offset is 35-25=10°C.
      You take this 10°C and simply add to the formula above and it becomes:
      Temperature (in °C) = {(VSENSE – V25) / Avg_Slope} + 25 + 10.
      Now you can get accurate readings from the sensor using the adjusted formula.

      Best regards,
      Sergey

      Delete
    2. Hi sergey,

      Thanks for replying and I am sorry for not getting back sooner. I was away on an extended vacation.
      Please let me clarify the question. If I configure that pin as an I/O pin; let's say to read an external logic input, can I also still use it to perform the internal temperature read? My setup uses all of the PC ADC inputs for other signals, PC0,1,2,4,5,6.
      Separately, I configure the internal temperature read as such:

      void controllerTempConfig(void)
      {
      ADC_CommonInitTypeDef ADC_CommonInitStructure;
      ADC_InitTypeDef ADC_InitStruct;

      RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

      ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
      ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;
      ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
      ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
      ADC_CommonInit(&ADC_CommonInitStructure);

      ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
      ADC_InitStruct.ADC_ScanConvMode = DISABLE;
      ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
      ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
      ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
      ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
      ADC_InitStruct.ADC_NbrOfConversion = 1;
      ADC_Init(ADC1, &ADC_InitStruct);

      ADC_RegularChannelConfig(ADC1, ADC_Channel_TempSensor, 1, ADC_SampleTime_144Cycles);
      ADC_TempSensorVrefintCmd(ENABLE);

      ADC_Cmd(ADC1, ENABLE);
      }

      Then to perform a read, I do this:

      ADC_SoftwareStartConv(ADC1); //Start the conversion
      while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
      uint16_t TS_DATA = ADC_GetConversionValue(ADC1);
      float32_t tmp = (float32_t) ((( (110 - 30)*(TS_DATA - *TEMP30_CAL) ) / (*TEMP110_CAL - *TEMP30_CAL) ) + 30);
      uint8_t ctlTemp = (uint8_t) tmp;
      printf("temperature = %i\n", ctlTemp);


      So, if I do NOT configure my PC3 pin as an I/O, the temperature read works 100% with no problem. However, if I configure PC3 as an I/O pin (because I need it for that) , when I try to execute the temperature read (above) it causes the program to crash.

      I'm doing this on the understanding that I can use the pin both ways; as an I/O and as a GPIO. Maybe this is not the case?

      Thanks for your help!


      Delete
    3. Hi Roger,

      It's hard to say what's going on based on the information you've provided.
      I could take a look at your project to see what's wrong.

      Best regards,
      Sergey

      Delete