Sunday, June 2, 2013

Working with STM32F4 Timers

Working with Timers

Now that we have worked with the GPIO peripheral on the STM32F4 Discovery board, I will move on to setting up a simple timer to blink an LED.  Since we are using the blue LED with the push-button, I will choose the green LED to blink with the timer.

To start open up your existing project in CooCox CoIDE.  In my case I am going to create a new project called Blog_Example_2 and copy the contents from Blog_Example_1.  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 a timer so select the TIM 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 the timer in our main.c file.  In this example, I plan on using the the Timer 2 (TIM2) to blink the green LED every 1 second.  Before we start writing the code we need to understand the system clock configuration. By default the configuration uses the internal RC oscillator which provides a system clock time of 16MHz.  This can be configured as high as 168MHz.  To easily configure the clock configuration you can use an excel file that was created by ST.  The excel file will automatically generate the source file for you to use and has a nice graphical view for the different clock sources.  Below is a link to the page where the file can be downloaded.  The part number is stsw-stm32091.

http://www.st.com/stonline/stappl/productcatalog/app?page=partNumberSearchPage&levelid=LN11&parentid=1533&resourcetype=SW

Once you have this loaded open up the excel file.  I am going to show the configuration using the external 8MHz crystal on the Discovery board to generate a 168MHz system clock frequency.

Set the  following to configure

  1. In the bottom left corner set the following
    • Configuration Mode = Expert
    • VDD = 3.3
    • Main Regulator Output Voltage = Scale1 Mode
    • Flash wait state = 5
    • Prefetch Buffer = OFF
    • Instruction Cache = ON
    • Data Cache = ON
    • Leave Enable I2S Clock Unchecked
  2. In the graphical portion from left left to right
    • Set HSE OSC = 8 MHz
    • Check HSE as the PLL Source
    • Set PLL_M = 8
    • Set PLL_N = 336
    • PLL_Q = 7
    • PLL_P = 2
    • AHBx Prescaler = 1
    • APB1 Prescaler = 4
    • APB2 Prescaler = 2


 


Once you have all the settings properly configured, press the "Generate" button at the bottom of the screen.  This will generate the system_stm32f4xx.c file in the same folder the excel file is located in.  Replace the file in your project with this file.  Also, if you look at the note just under the text box you entered 8 for the HSE OSC it states "Modify the "HSE_VALUE" in stm32f4xx.h.  If you open this file in your project, you can see it defaults to 25MHz (25000000).  Change this value to 8MHz (8000000).

Section of code is stm32f4xx.h file:

#if !defined  (HSE_VALUE)
      //This value was originally set to 25000000 (25MHz)
  #define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */



Then there is one more step we need to do.  In the startup_stm32f4xx.c file there is a function defined as below:

//extern void SystemInit(void);

This by default is commented out and never called.  To use your clock configuration, uncomment this line of code and add the line of code to call SystemInit(); under the Default_Reset_Handler function in the same start_stm32f4xx.c file.  This should be inserted just before the call to the main(); function.  The code should look like the following.  I have highlighted the code that needs to be added. 


void Default_Reset_Handler(void)
{
  /* Initialize data and bss */
  unsigned long *pulSrc, *pulDest;

  /* Copy the data segment initializers from flash to SRAM */
  pulSrc = &_sidata;

  for(pulDest = &_sdata; pulDest < &_edata; )
  {
    *(pulDest++) = *(pulSrc++);
  }
 
  /* Zero fill the bss segment.  This is done with inline assembly since this
     will clear the value of pulDest if it is not kept in a register. */
  __asm("  ldr     r0, =_sbss\n"
        "  ldr     r1, =_ebss\n"
        "  mov     r2, #0\n"
        "  .thumb_func\n"
        "zero_loop:\n"
        "    cmp     r0, r1\n"
        "    it      lt\n"
        "    strlt   r2, [r0], #4\n"
        "    blt     zero_loop");
#ifdef __FPU_USED
  /* Enable FPU.*/
  __asm("  LDR.W R0, =0xE000ED88\n"
        "  LDR R1, [R0]\n"
        "  ORR R1, R1, #(0xF << 20)\n"
        "  STR R1, [R0]");
#endif   
 
  // Call SystemInit function for external clock configuration
  SystemInit();

 
  /* Call the application's entry point.*/
  main();
}


With all of these changes, the system clock will be configured for 168MHz.  If you look at the excel file where we configured the clock source, you see on the right hand side the text

TIM2,3,4,5,6,7,12,13,14

This is set to 84MHz.  This is important to know when we start configuring the Timer in our main.c file.  Now that we have configured our startup code let's start updating our main.c file.  The first step, just like configuring the GPIO is to include the header file.  So now the top of your main file should look like the following:

/* 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"


After the includes, we need to create our label for the TIM2 structure, similar to the way we labled the GPIO structure.   The following code should be just before the main loop.  I have highlighted the new code that has been added.

GPIO_InitTypeDef  GPIO_InitStruct;
TIM_TimeBaseInitTypeDef    TIM_InitStruct; 

Next we need to configure the TIM2 peripheral.  Just like the GPIO there is an RCC associated with Timers.  If you look back at the excel spreadsheet, visually this will show you that RCC_APB1 is associated with TIM2.  Next we need to configure the timer to trigger every 1 second.  To do this we will configure a Count Up mode.  As you can see in the code below we have set the Prescaler to 42000-1 which will configure the clock frequency to 84 MHz/42 kHz = 2kHz.  Then we will configure the Count Up timer to trigger on a period of 2000 cycles.  Taking our Timer frequency of 2kHz / 2000 cycles = 1 second.  Then we load the values and enable the TIM2 Timer.  Initialization code is shown below.

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


We already have the GPIO for the green LED configured from our previous example.  Now all we need to do is write some code to monitor the TIM2 Timer so once the Period expires we toggle the green LED.  In this case we could use an interrupt, but I will just monitor the status of the TIM2 flag.  If TIM_FLAG_Update of TIM2 Timer = 1 we know the period of 2000 has been reached.  At this time we can clear the flag and toggle the green LED (if you recall this was PortD Pin12 on the Discover board).   

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


And that is it.  Timer is configured.

At this point, we have configured the STM32F4 Discovery board to control inputs, outputs and a simple timer.  The key component is to understand the clock sources and their configuration.  When I first started writing this code, I did not realize the the setup was using the internal RC oscillator.  This caused quite a bit of confusion with the timing of the flashing LED.

Next I plan on monitoring the internal temperature sensor on the STM32F4 microprocessor.  If you have any feedback feel free to e-mail or post your comments.






Complete main.c code for this example is shown below.  In the future I plan on sharing the entire code on an external site.


/* 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"

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;


int main(void)
{


        unsigned int _btn_count = 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 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);
        }

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

12 comments:

  1. Hi Michael,
    Thank you for your code.I am beginner in ARM.Your article is very helpful for beginners.I like your method of writing.I expecting your another article with ARM and help
    Sabu
    India

    ReplyDelete
  2. Hi Michael,

    great post.
    A question about the prescaler setting.
    What is the meaning of the "- 1" in the TIM_InitStruct.TIM_Prescaler?
    Can't we set the prescaler only at the 42000 value?
    Same question for the TIM_InitStruct.TIM_Period.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. it is because binary counting always start from 0 i.e. 1 to 10 means 0 to 9. 42000 will lead to timing error for 1 count approx 1/84000000 seconds.It is always good habit to treat numbers as binary for embedded programmer.

      Delete
  3. hi i like your explanation it is too good. You must be a good teacher.

    ReplyDelete
  4. I went trough all of your lessons, and God bless you for this kind of explanation.
    It's really hard to find a good tutorial for F4 series with such a detail description.

    Looking forward for your new posts, especially about accelerometer.

    Good luck!

    ReplyDelete
  5. Hi there,
    your post was really helpful I just have one question.. I am trying to use USART3 + TIM4. By seting the baud rate @ 115200 with this kind of configuration the real baud rate is 38400 means 3 times less. I don't understand why this behavior.
    The value that changes is PLL_M (before was 25 and with the new configuration is 8).
    Thanks for your help.

    ReplyDelete
    Replies
    1. Hi jpablo, I hope that you already solved the problem, but if it's still useful for you or others, recently I faced the same problem with usart.
      The problem was that the HSE value was set to 25 and the Discovery has a crystal of 8 MHz. The solution was to set the right value for HSE:
      In stm32f4xx.h
      change to:
      #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */

      Actually this issue(need to configure properly HSE_VALUE) is already mentioned in this excelent post. I just wanted to point out that the problem with the USART may be caused by a misconfiguration of the HSE_VALUE.

      Great job Michael Roussin, thanks for sharing.

      Delete
  6. Very useful, but a minor omission in step 2 above when configuring the XL tool. You have missed out "Check PLL button in System Clock Mux". You have done so in the screenshot, but not listed this step in the text. Runs veeeeeery slowly (8Mhz instead of 168 MHz) if this is not checked.

    ReplyDelete
  7. Excuse me, I have a problem. I set
    timerInitStructure.TIM_Prescaler = 42000-1;
    timerInitStructure.TIM_Period = 2000-1;
    and led will turn on/off every 5s not 500ms.

    ReplyDelete
  8. Hi, I really like your posts, it helps me a lot as a beginner. And I would like to ask if you are going to write something about USART communication? I'm getting tired with that more than 2 weeks :( HELP PLS

    ReplyDelete
  9. thank you , its really so helpful
    but can u please show us an exemple with current sensor
    Help pls

    ReplyDelete