Add 004
All checks were successful
Build and push latest / publish (push) Successful in 54s

This commit is contained in:
erki 2024-10-02 20:08:21 +03:00
parent e8528d3739
commit b8f9d2ebcd
2 changed files with 206 additions and 7 deletions

View File

@ -8,15 +8,13 @@ series: ["STM32 For Beginners"]
tags: ["Embedded", "STM32"]
---
# Printouts and "Hello World"
Our first goal will be writing doing a simple printout. A classical "hello world". Well, given our restrictions, this is going to take a bit of doing.
Typically, in C, we'd accomplish this by using `printf()`. However, with a microcontroller, printf goes nowhere by default. So our first goal is to redirect that printf command into something we can pick up with our PCs. We're going to use UART for this.
We've already enabled UART in our cube with the VCP.
## Writing into UART
# Writing into UART
For writing into UART, we'd use the `HAL_UART_Transmit` function. The function requires the following arguments:
* `huart`, which is a pointer to the UART hardware instance we're going to use,
@ -67,14 +65,14 @@ the green play button:
{{< video src="/media/stm32begin-001-006.mp4" type="video/mp4" preload="auto" caption="Don't mind the lack of an STM32 being connected...">}}
## Viewing the Output
# Viewing the Output
Now with that, we've got our microcontroller sending data to our PC via UART. The next question is: how do we
pick that data up and read it? For that, we need a piece of software capable of reading a virtual COM port (VCP).
On Windows, Linux, and MacOS, probably one of the most common software for this purpose is [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html).
If you're more commandline inclined and on Linux or MacOS, you may also be interested in [minicom](https://wiki.emacinc.com/wiki/Getting_Started_With_Minicom).
### PuTTY
## PuTTY
If you're on Windows, follow the link in the previous section to download PuTTY, and then install it. For Linux
users, I would suggest flatpak: `flatpak install uk.org.greenend.chiark.sgtatham.putty`. Once installed, run it.
@ -89,7 +87,7 @@ On Linux, you'll be looking for a `ttyACM0` style entry in your `/dev/` folder.
{{< figure src="/media/stm32begin-001-008.png" >}}
### minicom
## minicom
For minicom, consult your local distro package repository for the relevant package. `dnf search minicom` or `apt
search minicom` should provide the desired results.
@ -137,7 +135,7 @@ force a new line on most systems. With that, the updated code looks like the fol
Notice how we only needed to modify the string here, and the length in the `length` variable was adjusted
accordingly.
## Writing with printf
# Writing with printf
So, we have a way to transmit strings using UART. What we want to do is pipe the output of `printf` into UART.
For this, we need to provide our own definition for an existing C library implementation function. For the C

View File

@ -0,0 +1,201 @@
---
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.