• STM32 F4 ADC DMA Temperature Sensor


    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).

    #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);
    }
    #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
    #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).

  • 相关阅读:
    Failed to load module "canberra-gtk-module"
    [Ubuntu18]桌面美化-仿MAC主题
    [Ubuntu]18自定义切换输入法的快捷键
    2016-2017-1 《信息安全系统设计基础》 学生博客及Git@OSC 链接
    2015-2016-2 《Java程序设计》 游戏化
    2015-2016-2 《Java程序设计》项目小组博客
    博客引用书单
    2015-2016-2 《网络攻防实践》 学生博客
    2015-2016-2 《网络攻击与防范》 学生博客
    《网络攻防技术与实践》学习指导
  • 原文地址:https://www.cnblogs.com/shangdawei/p/4793332.html
Copyright © 2020-2023  润新知