Difference between revisions of "STM32 LED Blink"

From Stm32World Wiki
Jump to navigation Jump to search
imported>Lth
imported>Lth
Line 23: Line 23:
 
== Main Loop With Delay ==
 
== Main Loop With Delay ==
  
This approach - while wildly misguided - is often seen in examples, particularly Arduino based ones.  In this approach, the [[led]] is simply toggled in the main loop of the program, with an appropriate delay.  Using Stm32CubeIde and it's HAL libraries, the main loop will look something like:
+
This approach - while quite misguided - is often seen in examples, particularly Arduino based ones.  In this approach, the [[led]] is simply toggled in the main loop of the program, with an appropriate delay.  Using Stm32CubeIde and it's HAL libraries, the main loop will look something like:
  
 
<pre>
 
<pre>
Line 34: Line 34:
 
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
 
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
  
// Wait for 100 ms
+
// Wait for 500 ms
HAL_Delay(100);
+
HAL_Delay(500);
  
 
// Rinse and repeat :)
 
// Rinse and repeat :)
Line 46: Line 46:
 
</pre>
 
</pre>
  
The approach is simple and easily understood.  It will toggle the [[led]], not caring what the previous state was, and then wait for 100 ms.  The result will be approximately 5 blinks per second:
+
The approach is simple and easily understood.  It will toggle the [[led]], not caring what the previous state was, and then wait for 500 ms.  The result will be approximately 1 blinks per second:
  
 
{{#ev:youtube|UY-kx0wHR08}}
 
{{#ev:youtube|UY-kx0wHR08}}
Line 52: Line 52:
 
The keyword in the above description is "approximately".  I made the claim earlier that this approach was generally misguided and that is part of the problem.  In reality, this approach has at least two problems.
 
The keyword in the above description is "approximately".  I made the claim earlier that this approach was generally misguided and that is part of the problem.  In reality, this approach has at least two problems.
  
First of all, the call of both HAL_GPIO_TogglePin and HAL_Delay are not single instructions, so the actual time spend in the loop will be "a tiny bit" longer than 100 ms resulting in a frequency which is slighly below the intended 5 Hz.  Sure, it will only be "off" by a few micro seconds, but it will be off!
+
First of all, the call of both HAL_GPIO_TogglePin and HAL_Delay are not single instructions, so the actual time spend in the loop will be "a tiny bit" longer than 500 ms resulting in a frequency which is slightly below the intended 1 Hz.  Sure, it will only be "off" by a few micro seconds, but it will be off!
  
 
The second problem is the "HAL_Delay".  While that is running, the processor is tied up in doing "nothing".  While doing nothing but flashing a [[led]] that is of course OK, but typically one would want the processor to do "other things" and if that is the case the actual LED frequency will be even more unpredictable.
 
The second problem is the "HAL_Delay".  While that is running, the processor is tied up in doing "nothing".  While doing nothing but flashing a [[led]] that is of course OK, but typically one would want the processor to do "other things" and if that is the case the actual LED frequency will be even more unpredictable.
Line 71: Line 71:
 
// Check the current tick
 
// Check the current tick
 
uint32_t now = HAL_GetTick();
 
uint32_t now = HAL_GetTick();
if (now % 100 == 0 && now != then) { // Only if the current tick is 500 ms after the last
+
if (now % 500 == 0 && now != then) { // Only if the current tick is 500 ms after the last
  
 
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // Toggle LED
 
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // Toggle LED
Line 79: Line 79:
  
 
// Other stuff can be done here without affecting the blink frequency as long as
 
// Other stuff can be done here without affecting the blink frequency as long as
// whatever is being done take less than 100 ms.
+
// whatever is being done take less than 500 ms.
 
 
 
     /* USER CODE END WHILE */
 
     /* USER CODE END WHILE */

Revision as of 05:29, 3 November 2020

When learning a new programming language, programmers often - if not always - begin with a humble "hello world" application, which will print "Hello World!" on the display. As common as that, when it comes to embedded programming (where a display might not be available) a typical "first application" is one which will blink a led. And for this reason, most development boards comes with one or more leds which can be controlled with a GPIO pin.

In this article, I will be using my own Green Pill development board, which for all intents and purposes is comparable to the common Blue Pill. The board is based on an STM32F103 processor, includes a 8 MHz external crystal and has got a led attached to the PC13 GPIO pin.

Common Settings

For these examples, I will be using ST's Stm32CubeIde, which includes Stm32CubeMx. Stm32CubeMx is used to "configure" the processor.

When starting a new project in Stm32CubeIde, I generally go through some common settings. First step I configure the Serial Wire debug (including the trace):

Stm32CubeMX Sys Settings.png

Second step is to configure the CPU to enable the external crystal:

Stm32CubeMx crystal setting.png

Final step is to configure the various clocks:

Stm32CubeMx clock.png

The important values here is the value of the external crystal (in this case 8 MHz), the value of HCLK, which is the frequency the processor will run at. Also important to notice is the value of the APB1 Timer Clocks. This is the frequency at which the timers will operate (in this case 72 MHz - remember this value for later examples).

Main Loop With Delay

This approach - while quite misguided - is often seen in examples, particularly Arduino based ones. In this approach, the led is simply toggled in the main loop of the program, with an appropriate delay. Using Stm32CubeIde and it's HAL libraries, the main loop will look something like:

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

	// Toggle the LED
	HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);

	// Wait for 500 ms
	HAL_Delay(500);

	// Rinse and repeat :)

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

The approach is simple and easily understood. It will toggle the led, not caring what the previous state was, and then wait for 500 ms. The result will be approximately 1 blinks per second:

The keyword in the above description is "approximately". I made the claim earlier that this approach was generally misguided and that is part of the problem. In reality, this approach has at least two problems.

First of all, the call of both HAL_GPIO_TogglePin and HAL_Delay are not single instructions, so the actual time spend in the loop will be "a tiny bit" longer than 500 ms resulting in a frequency which is slightly below the intended 1 Hz. Sure, it will only be "off" by a few micro seconds, but it will be off!

The second problem is the "HAL_Delay". While that is running, the processor is tied up in doing "nothing". While doing nothing but flashing a led that is of course OK, but typically one would want the processor to do "other things" and if that is the case the actual LED frequency will be even more unpredictable.

Main Loop Without Delay

A somewhat better approach would be to check the time in the main loop - something like this:

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  uint32_t then = 0;

  while (1)
  {

	// Check the current tick
	uint32_t now = HAL_GetTick();
	if (now % 500 == 0 && now != then) { // Only if the current tick is 500 ms after the last

		HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // Toggle LED

		then = now; // Reset then = now
	}

	// Other stuff can be done here without affecting the blink frequency as long as
	// whatever is being done take less than 500 ms.
	
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Compared to the previous example, this one is still running in the main loop, but without a forced delay. Instead of the delay, this version will check the "HAL_GetTick()".

Using a timer

While the previous example is much better than the first, there's still room for improvement and that improvement will come from using one of the STM32F103 timers.

A third approach to blinking a led is to use one of the built-in timers of the CPU.

First step is to use Stm32CubeMx to configure the timer. Begin by enabling a clock source:

Blink Tim4 - source.png

Enabling the Internal Clock means the timer will be run by the ADB1 clock, which was configured earlier to run at 72 MHz.

We next need to divide this down to a usable frequency. We define two User Constants 'T4_PRE' and 'T4_CNT':

Tim 4 User Variables.png

Switch to the Parameter Settings tab and use the above constants in place of Prescaler and Counter:

Tim 4 Parameters.png

The final step is to enable the Global Interrupt for this timer:

Tim4 Global Interrupt.png

We can now generate code and finish the last few things.

First - while the User Constants are defined in the header file, I like to put them directly in the source like this:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define T4_PRE 7199
#define T4_CNT 4999

/* USER CODE END PD */

We now need to define the Interrupt callback:

/* USER CODE BEGIN 0 */

// Override the weak call back function
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
      if (htim->Instance == TIM4) {
              HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
      }
}

/* USER CODE END 0 */

This function will be called every time the counter reach the end - which in our case will be once every 500 ms.

And the final step is to start the timer:

  /* USER CODE BEGIN 2 */

  // Fire up the timer
  HAL_TIM_Base_Start_IT(&htim4);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	// Not doing anything here

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

That is it - notice that the while loop is completely empty.

PWM (Pulse Width Modulation)

The final approach to blinking a LED would be to use PWM (Pulse-width modulation). Unfortunately, the PC13 GPIO port used in the previous examples, is not able to do hardware PWM, so in order to demonstrate this approach an external LED will have to be hooked up to a pin offering this.