202 lines
7.8 KiB
Markdown
202 lines
7.8 KiB
Markdown
---
|
|
title: "STM32 For Beginners [3]: LEDs and inputs"
|
|
date: 2024-10-02T00:36:00+03:00
|
|
draft: false
|
|
summary: "..."
|
|
author: "Rusted Skull"
|
|
series: ["STM32 For Beginners"]
|
|
tags: ["Embedded", "STM32"]
|
|
---
|
|
|
|
In the previous part we got our STM to print stuff out via UART and figured out how view this printout
|
|
on our PC. Using this, we did the typical "Hello world" example of programming. Well, while a printout
|
|
is indeed the common first step for setting up software development enviornments, there's another,
|
|
more simple "Hello world" that's common in the embedded world: flashing a LED. We'll be doing that now.
|
|
|
|
# GPIO - General Purpose Input-Output
|
|
|
|
On an MCU, one of the easiest ways to interact with the world (and other devices) is via General
|
|
Purpose Input-Output pins. A GPIO pin can be set as an input or as an output. For the input, the
|
|
line will translate the voltage that's _being applied to it_ into a true or false (1 or 0) value that
|
|
we can read and respond to with our code. For the output, we can write a true or false value from
|
|
our code onto the line, and in response to that, the MCU will either put a voltage onto that line or
|
|
leave it connected to ground.
|
|
|
|
For the STM32 series of MCUs, the digital logic voltage is 3.3 volts. This, _in principle_, that a
|
|
true value will correspond to a voltage of 3.3 volts, and a false value will correspond to a value of
|
|
0 volts. In reality, there's a bit more to consider. First, some pins on the STM32 controllers
|
|
capable of handling up to 5 volts of input. These pins are marked in the respective controller's
|
|
datasheet as "FT" - "Five volt Tolerant". The other exception is that there's some tolerances built
|
|
into differentiating between true and false: we could say that a true value is a voltage between 2.3
|
|
and 3.3 volts. Again, exact specifications can be found in the datasheet.
|
|
|
|
|
|
# Writing into GPIO Output
|
|
|
|
We previously configured PB3 as our digital output. Now's the time to use it. Specifically, we named
|
|
the pin as "OUT_LED". With this, STM32CubeMX has generated the following defines:
|
|
|
|
* `OUT_LED_GPIO_Port` - this is an alias for PORTB of the MCU,
|
|
* `OUT_LED_Pin` - this an alias for pin number.
|
|
|
|
On most MCUs, pins are divided into different ports. Each port has a specific amount of pins, and to
|
|
address a specific pin, you must know both the pin's port and the pin's number in that port to control
|
|
it. For PB3, the port is PORTB, and the pin itself is numbered as 3 in that port. These defines give
|
|
us a more meaningful alias via which to access these specifiers.
|
|
|
|
For writing into the pin, ST's HAL provides us with the following functions:
|
|
|
|
* `HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState)` - for specifically turning the line high or low,
|
|
* `HAL_GPIO_TogglePin(GPIOx, GPIO_Pin)` - for toggling the pin from its previous state.
|
|
|
|
In both cases, `GPIOx` is the argument for the port, and `GPIO_Pin` is the argument for the pin
|
|
number. `PinState` is either `GPIO_PIN_SET` for writing a 1, or `GPIO_PIN_RESET` for writing a 0 onto
|
|
the line.
|
|
|
|
So, if we wanted to flash the LED, we would write this into our `main()`:
|
|
|
|
```cpp
|
|
/* USER CODE BEGIN 2 */
|
|
|
|
/* USER CODE END 2 */
|
|
|
|
/* Infinite loop */
|
|
/* USER CODE BEGIN WHILE */
|
|
while (1)
|
|
{
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_SET); // LED will turn on.
|
|
HAL_Delay(1000); // Keep the LED on for a second.
|
|
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_RESET); // LED will turn off.
|
|
HAL_Delay(1000); // Keep the LED off for a second.
|
|
/* USER CODE END WHILE */
|
|
|
|
/* USER CODE BEGIN 3 */
|
|
}
|
|
/* USER CODE END 3 */
|
|
```
|
|
|
|
Build and run the program, and you will see LD3 on the Nucleo board (assuming STM32F303k8 Nucleo)
|
|
start flashing.
|
|
|
|
Obviously, with the knowledge that we also have `HAL_GPIO_TogglePin`, this code could be written a bit
|
|
shorter as well:
|
|
|
|
```cpp
|
|
/* USER CODE BEGIN 2 */
|
|
|
|
/* USER CODE END 2 */
|
|
|
|
/* Infinite loop */
|
|
/* USER CODE BEGIN WHILE */
|
|
while (1)
|
|
{
|
|
HAL_GPIO_TogglePin(OUT_LED_GPIO_Port, OUT_LED_Pin); // LED will be toggled from its previous state.
|
|
HAL_Delay(1000); // Maintain state for a second.
|
|
/* USER CODE END WHILE */
|
|
|
|
/* USER CODE BEGIN 3 */
|
|
}
|
|
/* USER CODE END 3 */
|
|
```
|
|
|
|
Congratulations, you've flashed a LED! You've now done your second "Hello world" exercise!
|
|
Now let's make the LED actually respond to some external input.
|
|
|
|
# Reading GPIO Inputs (and buttons!)
|
|
|
|
Let's say that now we want to drive our LED based on the state of another digital input. We designated
|
|
PB0 as a GPIO Input, `IN_BTN`, so let's use it!
|
|
|
|
To read a GPIO, we have a single function: `HAL_GPIO_ReadPin(GPIOx, GPIO_Pin)`. It will return a
|
|
`GPIO_PinState` value, which is the same type we used as an input before. So it's either a value of
|
|
`GPIO_PIN_SET` for when the line is high, or `GPIO_PIN_RESET` for when the piin is set low. We'll
|
|
review this a bit more indepth shortly.
|
|
|
|
First, we must also figure out how to manipulate the pin. If you don't have a push button or something
|
|
else on hand, then all you need is a jumper wire: connect one end of the jumper wire to PB0 (marked
|
|
D3 on the board itself) and the other end either to a GND or 3V3 pin.
|
|
|
|
One thought exercise to consider here is: "What happens when we leave the wire disconnected?" The
|
|
answer is: your pin will float. It may take a high value or a low value, due to inductance. This is
|
|
why we configured the pin with a pull-down in the beginning: the pull-down resistor will assert a low
|
|
state on the pin if it's not connected to a stronger source. This will negate the floating.
|
|
|
|
So, we can bring the line high or low by connecting the wire to 3V3 or GND. Now, onto reading it. To
|
|
read it, we must call the function and save the result into a variable, for example. And then, make
|
|
our logic respond to this value: for example, when the pin is at a state of `GPIO_PIN_SET`, we turn
|
|
the LED on, else, we turn it off.
|
|
|
|
An example code for this is:
|
|
|
|
```cpp
|
|
/* USER CODE BEGIN 2 */
|
|
|
|
/* USER CODE END 2 */
|
|
|
|
/* Infinite loop */
|
|
/* USER CODE BEGIN WHILE */
|
|
while (1)
|
|
{
|
|
GPIO_PinState button_state = HAL_GPIO_ReadPin(IN_BTN_GPIO_Port, IN_BTN_Pin); // declare a new variable and read the pin state into it.
|
|
if (button_state == GPIO_PIN_SET) // Check if the button is being pressed.
|
|
{
|
|
// Write the LED on.
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_SET);
|
|
}
|
|
else
|
|
{
|
|
// Write the LED off.
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_RESET);
|
|
}
|
|
/* USER CODE END WHILE */
|
|
|
|
/* USER CODE BEGIN 3 */
|
|
}
|
|
/* USER CODE END 3 */
|
|
```
|
|
|
|
You will notice that we defined a variable here as well. We made it of type `GPIO_PinState`, as that
|
|
is what `HAL_GPIO_ReadPin` returns. And then we can respond to this using the if statements. Obviously
|
|
you can also write some printf's in or around those statements to see some more of the action.
|
|
|
|
For example, if we wanted to also print out the state of the pin:
|
|
|
|
```cpp
|
|
/* USER CODE BEGIN 2 */
|
|
|
|
/* USER CODE END 2 */
|
|
|
|
/* Infinite loop */
|
|
/* USER CODE BEGIN WHILE */
|
|
while (1)
|
|
{
|
|
GPIO_PinState button_state = HAL_GPIO_ReadPin(IN_BTN_GPIO_Port, IN_BTN_Pin); // declare a new variable and read the pin state into it.
|
|
|
|
printf("The button state is: %d\n\r", button_state);
|
|
|
|
if (button_state == GPIO_PIN_SET) // Check if the button is being pressed.
|
|
{
|
|
// Write the LED on.
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_SET);
|
|
}
|
|
else
|
|
{
|
|
// Write the LED off.
|
|
HAL_GPIO_WritePin(OUT_LED_GPIO_Port, OUT_LED_Pin, GPIO_PIN_RESET);
|
|
}
|
|
|
|
HAL_Delay(250); // Delay to stop printout flooding.
|
|
/* USER CODE END WHILE */
|
|
|
|
/* USER CODE BEGIN 3 */
|
|
}
|
|
/* USER CODE END 3 */
|
|
```
|
|
|
|
This will work by printing the button state out as a 1 or 0. Note the necessity for a delay, this will
|
|
make the logic slower as well, obviously.
|
|
|
|
But, that's it for this lesson. Next time we'll review motors and PWM.
|
|
|