Monday, December 30, 2013

Working with STM32F4 Temperature Sensor

Working with the on-board Temperature Sensor


Now that we have worked with the Timer peripheral on the STM32F4 Discovery board, I will move on to setting up the sampling of the on board temperature sensor.

To start open up your existing project in CooCox CoIDE.  In my case I am going to create a new project called Blog_Example_3 and copy the contents from Blog_Example_2.  As I stated in the last blog, I will just keep adding to the existing code, this way we can see if we encounter any conflicts with the code.  When we created the project before, we came to a tab labeled "Repository".  If this tab is not open you can either click on "Browse in Repository" under the main tab or go to the View -> Repository in the toolbar.  Once you open the "Repository" you will see the items you selected from the first example.  In this example we are going to use the analog to digital converter so select the ADC peripheral and you will see the source file and header file added to your project.


Now that we have the files added, we can start configuring our ADC in the main.c file.  Near the top of the page, under the "Includes" section add the following lines:

// Must be included to setup the A/D
#include "stm32f4xx_adc.h"

After the includes, we need to create our label for the ADC structure, similar to the way we labeled the GPIO structure and TIM structure.   The following code should be just before the main loop.  


/**********************************************************************************
 *
 * The ADC_InitTypeDef is a structure defined in the stm32f4xx_adc.h file.
 * The ADC_CommonInitTypeDef is a structure defined in the stm32f4xx_adc.h file.
 *
 **********************************************************************************/
ADC_InitTypeDef ADC_InitStruct;

ADC_CommonInitTypeDef ADC_CommonInitStruct

Next we are going to de-initialize all ADCs peripherals registers to their default reset values.  Add the following line of code.

ADC_DeInit();

Next we need to configure the ADC peripheral.  Just like the GPIO and TIM there is an RCC associated with with ADC peripheral.  If you look back at the datasheet for the STM32F407VG processor you will find that the ADC1, ADC2 and ADC3 are on the APB2 bus.  Therefore we need to enable the High Speed APB2 peripheral clock.  After the Timer 2 configuration add the following line of code.

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

After this the "Common" ADC settings needs to be configured.  Then the ADC initial settings need to be configured.  These settings include the resolution, scan mode, continuous mode, external trigger configuration, data alignment and number of conversions.  See code below:

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

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

The next statement configures the ADC1 channel, rank and sample time

ADC_RegularChannelConfig(ADC1, ADC_Channel_TempSensor, 1, ADC_SampleTime_144Cycles);

Then we enable the internal temperature sensor

ADC_TempSensorVrefintCmd(ENABLE);

The next statement enables ADC1

ADC_Cmd(ADC1, ENABLE);

Now that we have the ADC configured, we need to read the temperature.  For now we will just use the debugger to read the value of the sensor.  But first we need to read the data then convert it to a temperature.  To find the formula for the conversion of the data, we can look at the reference manual for the STM32F4.  The formula is

Temperature (in °C) = {(VSENSE – V25) / Avg_Slope} + 25

Where:
V25 = VSENSE value for 25° C
Avg_Slope = average slope of the temperature vs. VSENSE curve (given in mV/°C or μV/°C)

Looking at the data sheet for the specific STM32F407VG processor the following values are found

V25 = 0.76V
Avg_Slope = 2.5mV/°C

We will create a float variable called TemperatureValue.  This will be located just after the program main.  I have highlighted the new variable we have created.

int main(void)
{

        unsigned int _btn_count = 0;
        float TemperatureValue = 0;

Now we need to write the logic the read the temperature input and convert it using the formula listed above.

ADC_SoftwareStartConv(ADC1); //Start the conversion
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
     ; //Processing the conversion
TemperatureValue = ADC_GetConversionValue(ADC1); //Return the converted data
TemperatureValue *= 3300;
TemperatureValue /= 0xfff; //Reading in mV
TemperatureValue /= 1000.0; //Reading in Volts
TemperatureValue -= 0.760; // Subtract the reference voltage at 25°C
TemperatureValue /= .0025; // Divide by slope 2.5mV

TemperatureValue += 25.0; // Add the 25°C

To read the value of the sensor make sure the variables window is open to see the actual converted value of the temperature sensor.


Run the debugger and watch the temperature.

The full main.c code is listed below


/* Includes ------------------------------------------------------------------*/
// Must be included if using STM32F4 Discovery board or processor
#include "stm32f4xx.h"

// Must be included if using GPIO (General purpose I/O) peripheral
#include "stm32f4xx_gpio.h"

// Must be included to setup general purpose I/O
// Some peripherals require the setup of RCC (Reset and clock controller)
#include "stm32f4xx_rcc.h"

// Must be included to setup timers
#include "stm32f4xx_tim.h"

// Must be included to setup the A/D
#include "stm32f4xx_adc.h"

void Delay(__IO uint32_t nCount)
{
  while(nCount--)
  {
  }
}

/**********************************************************************************
 *
 * The GPIO_InitTypeDef is a structure defined in the stm32f4xx_gpio.h file.
 * This is a simple way to use ST's library to configure the I/O.
 *
 * This is a common setup for the other peripherals as well.  Each peripheral will have
 * different properties which can be found in the header files and source files.
 *
**********************************************************************************/

GPIO_InitTypeDef  GPIO_InitStruct;


/**********************************************************************************
 *
 * The TIM_TimeBaseInitTypeDef is a structure defined in the stm32f4xx_tim.h file.
 *
**********************************************************************************/

TIM_TimeBaseInitTypeDef TIM_InitStruct;


/**********************************************************************************
 *
 * The ADC_InitTypeDef is a structure defined in the stm32f4xx_adc.h file.
 * The ADC_CommonInitTypeDef is a structure defined in the stm32f4xx_adc.h file.
 *
 **********************************************************************************/

ADC_InitTypeDef ADC_InitStruct;
ADC_CommonInitTypeDef ADC_CommonInitStruct;



int main(void)
{


        unsigned int _btn_count = 0;
        float TemperatureValue = 0;

    /**********************************************************************************
     *
     * This enables the peripheral clock to the GPIOD module.  This is stated in
     * the beginning of the stm32f4xx.gpio.c source file.
     *
    **********************************************************************************/

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

    /**********************************************************************************
     *
     * This block of code defines the properties of the GPIO port.
     * The different options of each item can be found in the stm32f4xx_gpio.h header file
     * Every GPIO configuration should have the following initialized
     *
     * GPIO_InitStruct.GPIO_Pin
     * GPIO_InitStruct.GPIO_Mode
     * GPIO_InitStruct.GPIO_Speed
     * GPIO_InitStruct.GPIO_OType
     * GPIO_InitStruct.GPIO_PuPd
     * GPIO_Init(GPIOx, &GPIO_InitStruct); (x represents port - A, B, C, D, E, F, G, H, I)
     *
    **********************************************************************************/

    //GPIO_InitStruct.GPIO_Pin configures the pins that will be used.
    //In this case we will use the LED's off of the discovery board which are on
    //PortD pins 12, 13, 14 and 15
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15 | GPIO_Pin_14 | GPIO_Pin_13 | GPIO_Pin_12;

    //PIO_InitStruct.GPIO_Mode configures the pin mode the options are as follows
    // GPIO_Mode_IN (Input Mode)
    // GPIO_Mode_OUT (Output Mode)
    // GPIO_Mode_AF (Alternate Function)
    // GPIO_Mode_AN (Analog Mode)
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;

    //GPIO_InitStruct.GPIO_Speed configures the clock speed, options are as follows
    // GPIO_Speed_2MHz
    // GPIO_Speed_25MHz
    // GPIO_Speed_50MHz
    // GPIO_Speed_100MHz
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

    //GPIO_InitStruct.GPIO_OType configures the pin type, options are as follows
    // GPIO_OType_PP (Push/Pull)
    // GPIO_OType_OD (Open Drain)
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

    //Configures pullup / pulldown resistors on pin, options are as follows
    // GPIO_PuPd_NOPULL (Disables internal pullup and pulldown resistors)
    // GPIO_PuPd_UP (Enables internal pullup resistors)
    // GPIO_PuPd_DOWN (Enables internal pulldown resistors)
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;

    //This finally passes all the values to the GPIO_Init function
    //which takes care of setting the corresponding bits.
    GPIO_Init(GPIOD, &GPIO_InitStruct);

    /**********************************************************************************
     *
     * This enables the peripheral clock to the GPIOA module.  This is stated in
     * the beginning of the stm32f4xx.gpio.c source file.
     *
    **********************************************************************************/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    /**********************************************************************************
     *
     * This block of code defines the properties of the GPIOA port.
     * We are defining Pin 0 as a digital input with a pulldown resistor
     * to detect a high level.  Pin 0 is connected to the 3.3V source
     *
   **********************************************************************************/

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOA, &GPIO_InitStruct);


/**********************************************************************************
*
* This enables the peripheral clock to the TIM2 (Timer 2) module.
* The TIM2 module clock is at 84MHz.  The prescaler will be set
* to 42000-1 which will configure the clock for 84MHz/42kHz = 2kHz
* Then we will use the period to define as 2000-1.  This will trigger
* an event every 1 second (or it should).
*
* Since we are using the STM32F4 Discovery board that has a 8MHz crystal
* we need to adjust the following:
*
* 1) Use the clock configuration tool to setup the system_stm32f4xx.c file
*
* 2) In stm32f4xx.h file
*   a) Set the #define HSE_VALUE to the following
*   #define HSE_VALUE    ((uint32_t)8000000)
*
* 3) In startup_stm32f4xx.c file
*   a) Uncomment the line
*   extern void SystemInit(void);
*
*   b) Add the following line under Default_Reset_Handler()
*   just before the line of code that jumps to main();
*   SystemInit();
*
* **********************************************************************************/

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_InitStruct.TIM_Prescaler = 42000 - 1; // This will configure the clock to 2 kHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; // Count-up timer mode
TIM_InitStruct.TIM_Period = 2000 - 1; // 2 kHz down to 1 Hz = 1 second
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // Divide clock by 1
TIM_InitStruct.TIM_RepetitionCounter = 0; // Set to 0, not used
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);


/**********************************************************************************
*
* This enables the A/D converter for sampling the on board
* temperature sensor.
*
* You must first enable the CommonInitStructure
* then enable the specific InitStructure for AD1, AD2 or AD3
*
* Review reference manual RM0090 for register details.
*
* 1) Deinitialize the ADC
* 2) Set the ADC1 peripheral clock
* 2) Set the Common Structure of ADC
* a) Mode = Configures the ADC to operate in independent or multi mode.
* b) Prescaler = Select the frequency of the clock to the ADC. The clock is common for all the ADCs.
* ADCCLK = PCLK2/Prescaler
* c) DMA = Configures the Direct memory access mode for multi ADC mode.
* d) Two Sampling Delay = Configures the Delay between 2 sampling phases.
* # * Time(ADCCLK) where # is between 5 and 20
* 3) Configure The ADC Initialization Settings
* 4) Configure the channels for for ADC1, internal temperature sensor is on channel 16
* 5) Enable the internal temperature sensor
* 6) Enable the A/D conversion for ADC1
*
* **********************************************************************************/

ADC_DeInit();

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

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

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

// ADC1 Configuration, ADC_Channel_TempSensor is actual channel 16
ADC_RegularChannelConfig(ADC1, ADC_Channel_TempSensor, 1,
ADC_SampleTime_144Cycles);

// Enable internal temperature sensor
ADC_TempSensorVrefintCmd(ENABLE);

// Enable ADC conversion
ADC_Cmd(ADC1, ENABLE);

    /**********************************************************************************
     *
     * This block of code blinks all four LED's on initial startup
     *
     * **********************************************************************************/
    GPIO_SetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
    Delay(0xFFFFF);
    GPIO_ResetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);

    while(1)
    {


        //Reads the status of the push-button on the Discovery board
        //If button is pressed blue LED is toggled

        if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
        {
            _btn_count++;
                if (_btn_count > 0xFFFF)
                {
                    GPIO_ToggleBits(GPIOD, GPIO_Pin_15);
                    _btn_count = 0;
                }
        }

        //Monitors the TIM2 flag status.  If the TIM2 period is reached
        //The TIM_FLAG_Update = 1.  If this is true, we clear the flag
        //and toggle the green LED.

   if (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != RESET)
   {
     TIM_ClearFlag(TIM2, TIM_IT_Update);
     GPIO_ToggleBits(GPIOD, GPIO_Pin_12);
   }


// ADC Conversion to read temperature sensor
// Temperature (in °C) = ((Vsense – V25) / Avg_Slope) + 25
// Vense = Voltage Reading From Temperature Sensor
// V25 = Voltage at 25°C, for STM32F407 = 0.76V
// Avg_Slope = 2.5mV/°C
// This data can be found in the STM32F407VF Data Sheet

ADC_SoftwareStartConv(ADC1); //Start the conversion
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
; //Processing the conversion
TemperatureValue = ADC_GetConversionValue(ADC1); //Return the converted data
TemperatureValue *= 3300;
TemperatureValue /= 0xfff; //Reading in mV
TemperatureValue /= 1000.0; //Reading in Volts
TemperatureValue -= 0.760; // Subtract the reference voltage at 25°C
TemperatureValue /= .0025; // Divide by slope 2.5mV
TemperatureValue += 25.0; // Add the 25°C

    } //End of while(1) loop
} //End of main loop


I will work next on writing this data to an SD card.  If you have any questions feel free to comment.  I apologize for the delay in this post.  If has been quite crazy around here.  I look forward to your questions and comments.

Thanks,

Mike

7 comments:

  1. merci bien monsieur Michael Roussin . je veux vous contacter par mail ! pouvez vous m'aidez svp et merci !

    ReplyDelete
  2. Hi mike, i want to know that the converted value we get here, In which form it shows like i get 384 something.
    also i want to know that here we are multiplying that value with 3300. its for what. right now i am not getting..
    i am new to this thats why i asked.

    ReplyDelete
    Replies
    1. Hi Jay, 3300 is a representative of 3.3 V(3300 m V). Since the ADC has a resolution of 12 bit, 3300 mV will be divided into 2^12=4096 steps. Lets say, it gives you 384, meaning that it takes 384 out of 4096, ends up with the actual value of 384/4096x3300 m V.

      Delete
  3. hi, I am tries too much in this but not getting correct temp, here is my equation.

    int ADC_Data(void)
    {
    ADC1ConvertedValue = HAL_ADC_GetValue(&hadc1);
    ADC1ConvertedValue = ((ADC1ConvertedValue * 3300) / 4096);
    return SUCCESS;
    }

    float ADC_Temp(void)
    {
    TCelsius = ((ADC1ConvertedValue - 0.760) / 0.0025) + 25.0 ;
    return TCelsius;
    }

    please help me.

    thanks

    ReplyDelete
  4. hii,
    Pls help me temp sensor program interfacing with stm32f microcontroller but without using any library function but through direct register...

    ReplyDelete
  5. Jeez. Your debugger says that the temperature is 60 degrees. That's super hot and obviously not true.
    Use the sensor calibrated values instead instead of those average values from the datasheet.

    ReplyDelete
  6. Is the calculated temperature is in celsius or in fahrenheit?

    ReplyDelete