Posts 1 & 2 done
All checks were successful
Build and push latest / publish (push) Successful in 42s
All checks were successful
Build and push latest / publish (push) Successful in 42s
This commit is contained in:
parent
ffc5552e26
commit
bee1dfae25
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
content/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
content/**/*.webm filter=lfs diff=lfs merge=lfs -text
|
||||
content/**/*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
@ -1,108 +0,0 @@
|
||||
---
|
||||
title: "STM32 For Beginners: setup and scaffolding."
|
||||
date: 2024-09-26T00:36:00+03:00
|
||||
draft: false
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
series: ["STM32 For Beginners"]
|
||||
tags: ["Embedded", "STM32"]
|
||||
---
|
||||
|
||||
{{< video src="/test.webm" typ="video/webm" preload="auto" caption="two" >}}
|
||||
|
||||
{{< figure src="/test2.png" caption="More memes." >}}
|
||||
|
||||
# Code
|
||||
|
||||
STM32Cube will now generate code for you. This is all the code that's necessary for initialization of the microcontroller hardware (clocks, interrupts, etc.) and peripherals (GPIOs, UART, etc.) This allows us to skip a bunch of menial code writing ourselves.
|
||||
|
||||
The code itself is structured in a bit of a weird way, if you're faced with something like this for the first time. Namely, it has a bunch of commented sections which say "USER CODE BEGIN/END". There's a few rules regarding this code structure:
|
||||
|
||||
1. Generated files get written over and adjusted if you regenerate the code again.
|
||||
2. Write code **between** a set of BEGIN/END comments, and your code will be fine.
|
||||
3. Do not delete or move these comments.
|
||||
4. Any files you create yourself are unaffected by the generator.
|
||||
|
||||
## Structure
|
||||
|
||||
For the purpose of this guide, we'll be interested in the `main.c`file, located in the `Core\Src\` folder.
|
||||
|
||||
In there, locate the `int main(void)` function and make note of the following sections before and after the line:
|
||||
```cpp
|
||||
/* Private user code ---------------------------------------------------------*/
|
||||
/* USER CODE BEGIN 0 */
|
||||
|
||||
/* USER CODE END 0 */
|
||||
```
|
||||
and
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
# 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
|
||||
|
||||
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,
|
||||
* `pData`, the pointer to the data we wish to write over UART,
|
||||
* `Size`, indicates how many bytes from `pData` are to be transmitted,
|
||||
* `Timeout`, the time in milliseconds to wait for the function to complete, before returning an error.
|
||||
|
||||
In order to invoke this, we need to do a few steps first.
|
||||
|
||||
First, we must define the string to write. Declaring a char array and initializing it with a predefined string. We can do this by writing `const char* my_str = "Hello world";`
|
||||
|
||||
Next up, we need the length of the the string. We could count it ourselves, and save it to a variable; or we could use the `strlen` function from the C standard library. For illustration purposes, we'll do the latter: `int length = strlen(my_str);`
|
||||
|
||||
Finally, we need to dispatch the entire thing into the transmit function. `pData` will be our string variable (it's already a pointer), `Size` will be the length of the string, and for the `huart` argument, we'll use `&huart2`. This can be checked from the STM32CubeMX configurator, look at which of the UART interface is being used for the VCP pins.
|
||||
|
||||
In completeness, it looks like this:
|
||||
```cpp
|
||||
/* Private user code ---------------------------------------------------------*/
|
||||
/* USER CODE BEGIN 0 */
|
||||
#include <string.h> // This include is necessary for the strlen function.
|
||||
/* USER CODE END 0 */
|
||||
```
|
||||
and
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
const char* my_str = "Hello world";
|
||||
int length = strlen(my_str);
|
||||
HAL_UART_Transmit(&huart2, my_Str, length, 100);
|
||||
|
||||
HAL_Delay(1000); // This is necessary to stop the UART from continously writing and spamming.
|
||||
// HAL_Delay(n) will make the microcontroller wait for n milliseconds.
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
This code
|
||||
127
content/posts/002-stm32begin-001-setup.md
Normal file
127
content/posts/002-stm32begin-001-setup.md
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "STM32 For Beginners: setup"
|
||||
date: 2024-09-26T00:36:00+03:00
|
||||
draft: false
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
series: ["STM32 For Beginners"]
|
||||
tags: ["Embedded", "STM32"]
|
||||
---
|
||||
|
||||
Welcome to the series. This is a multipart series created partially to guide students of the Tallinn University of Technology Robotics
|
||||
Club through the programming of a simple STM32. Our end goal with this series is to write the software for a simple folkrace robot,
|
||||
PID controller and all. But, we'll be starting from the beginning. And the beginning is: tools, configuration, code structure.
|
||||
|
||||
# Assumptions
|
||||
|
||||
This course assumes that you have the following physical items:
|
||||
* a Nucleo F303k8,
|
||||
* USB micro cable for connecting the Nucleo to your PC,
|
||||
* a few female-to-female jumper wires,
|
||||
* a through-hole LED with an appropriate through-hole resistor.
|
||||
|
||||
Software wise, two pieces of software are needed at a minimum:
|
||||
* STM32CubeIDE,
|
||||
* PuTTY.
|
||||
* minicom as a PuTTY alternative, if you're on MacOS or Linux.
|
||||
|
||||
# Project Creation
|
||||
|
||||
Our base will be an STM32CubeIDE project. CubeIDE is an Eclipse based editor which comes with an integrated compiler, debugger, and microcontroller configuration software (STM32CubeMX). The benefits, specially for beginners, is that STM32CubeMX lets us configure the microcontroller graphically, and will generate a standard hardware abstraction library (HAL) implementation to load the configuration at microcontroller's startup.
|
||||
|
||||
When you boot STM32CubeIDE for the first time, or following an update, you'll be given the "Information Center" panel. You can close it from the top left area, it won't be needed.
|
||||
|
||||
Click "File" from the top left, the "New" selection, and "New STM32 Project". This will open the "**Target Selection**" menu.
|
||||
|
||||
Important: we are working with the Nucleo development **board**. Ergo, **select the second tab** at the top of the "Target Selection" menu called "Board Selector".
|
||||
|
||||
To quickly locate our board, type "NUCLEO-F303K8" into the "Commercial Part Number" field, and select the only option in the "Boards List" menu. Once highlighted, click "Next" at the bottom.
|
||||
|
||||
Give the project a name and leave everything else as is.
|
||||
|
||||
Press "Finish". You will be asked if you want to initialize all peripherals with their default mode, click "Yes".
|
||||
|
||||
The project is now generated and you will be brought into the STM32CubeMX configuration view.
|
||||
|
||||
## MCU Configuration
|
||||
|
||||
Our goal is to configure the MCU as follows:
|
||||
* We will have one digital input, called IN_BTN.
|
||||
* We will have one digital output, called OUT_LED.
|
||||
* We will use USART2 as a virtual COM port (VCP) to print data back to us.
|
||||
|
||||
To do this, first, we must remove the erroneous MCO function of the PF0 pin. Left click the green PF0 pin, and select "Reset state".
|
||||
|
||||
{{< video src="/posts/stm32begin-001-001.webm" type="video/webm" >}}
|
||||
|
||||
Then we have to configure the digital inputs and outputs. As will be explained later, digital inputs and outputs are handled via the GPIO (General Purpose Input-Output) functionality of a pin. The Nucleo board has on onboard LED tied to pin **PB3**. So we'll start from there.
|
||||
|
||||
Click PB3, click "GPIO_Output". This assigns the pin as a digital output. Further, we should name the pin so that we could reference it in code later. Right click PB3, select "Enter user label", and type in "OUT_LED".
|
||||
|
||||
{{< video src="/posts/stm32begin-001-002.webm" type="video/webm" >}}
|
||||
|
||||
For the input, we have to choose a random pin. For the purposes of our setup, we'll choose **PB0** as our input. Click it,
|
||||
set it as a "GPIO_Input", and name it as "IN_BTN".
|
||||
|
||||
{{< video src="/posts/stm32begin-001-003.webm" type="video/webm" >}}
|
||||
|
||||
For purposes that we'll explain later, we need to set this pin as a **pull-up** as well. We do this from the left side panel of
|
||||
the STM32CubeMX window. From there, select "GPIO" under the "System Core" section, select "PB0" and configure it as a "Pull up"
|
||||
from the "GPIO Pull-up/Pull-down" drop down.
|
||||
|
||||
{{< video src="/posts/stm32begin-001-005.webm" type="video/webm" >}}
|
||||
|
||||
With this, our GPIOs are configured. We now need to check the VCP and USART2. By default, USART2 is enabled on pins PA15 and
|
||||
PA2. They should be named as "VCP_RX" and "VCP_TX" respectively. What we need to do is check and modify the baud rate for this
|
||||
subsystem. Again, from the left side panel, look for the "Connectivity" section, select "USART2", and look for the "Baud Rate"
|
||||
menu in the "Parameter Settings" area. Modify it to be `115200`.
|
||||
|
||||
{{< video src="/posts/stm32begin-001-009.mp4" type="video/mp4" >}}
|
||||
|
||||
With this, our setup is done! Save the file with "Ctrl-S" or from the "File" menu. It will ask if you want to generate the
|
||||
project, press "Yes". Alternatively, click the shaft and cog button on the top left.
|
||||
|
||||
{{< figure src="/posts/stm32begin-001-010.png" >}}
|
||||
|
||||
After this, you will be taken to the `main.c` file. If the program asks for a perspective shift, press "Yes".
|
||||
|
||||
# Code
|
||||
|
||||
STM32Cube will now generate code for you. This is all the code that's necessary for initialization of the microcontroller hardware (clocks, interrupts, etc.) and peripherals (GPIOs, UART, etc.) This allows us to skip a bunch of menial code writing ourselves.
|
||||
|
||||
The code itself is structured in a bit of a weird way, if you're faced with something like this for the first time. Namely, it has a bunch of commented sections which say "USER CODE BEGIN/END". There's a few rules regarding this code structure:
|
||||
|
||||
1. Generated files get written over and adjusted if you regenerate the code again.
|
||||
2. Write code **between** a set of BEGIN/END comments, and your code will be fine.
|
||||
3. Do not delete or move these comments.
|
||||
4. Any files you create yourself are unaffected by the generator.
|
||||
|
||||
## Structure
|
||||
|
||||
For the purpose of this guide, we'll be interested in the `main.c`file, located in the `Core\Src\` folder.
|
||||
|
||||
In there, locate the `int main(void)` function and make note of the following sections before and after the line:
|
||||
```cpp
|
||||
/* Private user code ---------------------------------------------------------*/
|
||||
/* USER CODE BEGIN 0 */
|
||||
|
||||
/* USER CODE END 0 */
|
||||
```
|
||||
and
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
From here, head to the next post to figure out how to write data to our PC and view it.
|
||||
214
content/posts/003-stm32begin-002-printing.md
Normal file
214
content/posts/003-stm32begin-002-printing.md
Normal file
@ -0,0 +1,214 @@
|
||||
---
|
||||
title: "STM32 For Beginners: printing"
|
||||
date: 2024-09-26T00:36:00+03:00
|
||||
draft: false
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
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
|
||||
|
||||
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,
|
||||
* `pData`, the pointer to the data we wish to write over UART,
|
||||
* `Size`, indicates how many bytes from `pData` are to be transmitted,
|
||||
* `Timeout`, the time in milliseconds to wait for the function to complete, before returning an error.
|
||||
|
||||
In order to invoke this, we need to do a few steps first.
|
||||
|
||||
First, we must define the string to write. Declaring a char array and initializing it with a predefined string. We can do this by writing `const char* my_str = "Hello world";`
|
||||
|
||||
Next up, we need the length of the the string. We could count it ourselves, and save it to a variable; or we could use the `strlen` function from the C standard library. For illustration purposes, we'll do the latter: `int length = strlen(my_str);`
|
||||
|
||||
Finally, we need to dispatch the entire thing into the transmit function. `pData` will be our string variable (it's already a pointer), `Size` will be the length of the string, and for the `huart` argument, we'll use `&huart2`. This can be checked from the STM32CubeMX configurator, look at which of the UART interface is being used for the VCP pins.
|
||||
|
||||
In completeness, it looks like this:
|
||||
```cpp
|
||||
/* Private user code ---------------------------------------------------------*/
|
||||
/* USER CODE BEGIN 0 */
|
||||
#include <string.h> // This include is necessary for the strlen function.
|
||||
/* USER CODE END 0 */
|
||||
```
|
||||
and
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
const char* my_str = "Hello world";
|
||||
int length = strlen(my_str);
|
||||
HAL_UART_Transmit(&huart2, my_str, length, 100);
|
||||
|
||||
HAL_Delay(1000); // This is necessary to stop the UART from continously writing and spamming.
|
||||
// HAL_Delay(n) will make the microcontroller wait for n milliseconds.
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
This code can now be built with the hammer icon at the top left, and then loaded onto the microcontroller using
|
||||
the green play button:
|
||||
|
||||
{{< video src="/posts/stm32begin-001-006.mp4" type="video/mp4" preload="auto" caption="Don't mind the lack of an STM32 being connected...">}}
|
||||
|
||||
## 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 [https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html](PuTTY).
|
||||
If you're more commandline inclined and on Linux or MacOS, you may also be interested in [https://wiki.emacinc.com/wiki/Getting_Started_With_Minicom](minicom).
|
||||
|
||||
### 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.
|
||||
You'll encounter the following screen:
|
||||
|
||||
{{< figure src="/posts/stm32begin-001-007.png" >}}
|
||||
|
||||
We need to change the "Connection type" to "Serial", configure the baud to match that of our USART2 (115200), and
|
||||
provide the proper serial line. On Windows, it'll be something similar to "COM00" where 00 is a set of numbers.
|
||||
On Linux, you'll be looking for a `ttyACM0` style entry in your `/dev/` folder. As a final result, before pressing
|
||||
"Open" at the bottom, your setup should look something like this:
|
||||
|
||||
{{< figure src="/posts/stm32begin-001-008.png" >}}
|
||||
|
||||
### 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.
|
||||
|
||||
Once installed, we need to provide two arguments to the command: `-D` for the device path, which is going to be `ttyACM0` (replace 0 with a potentially different number) in the `/dev/` folder, and `-b` for the baud (115200).
|
||||
The final command should look something like this:
|
||||
|
||||
```sh
|
||||
minicom -D /dev/ttyACM0 -b 115200
|
||||
```
|
||||
|
||||
If you encounter file permission errors, make sure you have appropriate permissions. Use `stat` to figure out
|
||||
what group (Gid) the relevant ttyACM or ttyUSB belongs to, add yourself to that group with `sudo usermod -aG dialout my_username_here`. Close your current terminal and open your new one, and-or replug the device to
|
||||
refresh relevant permissions.
|
||||
|
||||
In the end, you shuold see our "Hello world" print in perpetuity...
|
||||
|
||||
// insert image here about printing.
|
||||
|
||||
Notice how the lines aren't broken up, and it's all in sequence. In order to break the lines up, we need to add
|
||||
`\n\r` to the end of our `Hello world`. These are a newline and carriage return character respectively, and will
|
||||
force a new line on most systems. With that, the updated code looks like the following:
|
||||
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
const char* my_str = "Hello world\n\r";
|
||||
int length = strlen(my_str);
|
||||
HAL_UART_Transmit(&huart2, my_str, length, 100);
|
||||
|
||||
HAL_Delay(1000);
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
Notice how we only needed to modify the string here, and the length in the `length` variable was adjusted
|
||||
accordingly.
|
||||
|
||||
## 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
|
||||
library that we're using, this function is `_write`.
|
||||
|
||||
We have to put this code _outside_ of the `main()` function, so we'll use the `USER CODE BEGIN 0` section.
|
||||
|
||||
The function itself accepts the following arguments:
|
||||
* `file` the file handle, we can ignore this argument;
|
||||
* `ptr` is the pointer to the string that's being printed, we have to transmit this via UART;
|
||||
* `len` is the length of the string in `ptr`.
|
||||
|
||||
The function must return the number of characters written, so, assuming success, we return `len` in every case.
|
||||
|
||||
```cpp
|
||||
/* Private user code ---------------------------------------------------------*/
|
||||
/* USER CODE BEGIN 0 */
|
||||
#include <stdio.h> // necessary for printf
|
||||
// string.h is no longer needed, as we're not using any functions from it.
|
||||
int _write(int file, char* ptr, int len)
|
||||
{
|
||||
HAL_UART_Transmit(&huart2, ptr, len, 1000);
|
||||
return len;
|
||||
}
|
||||
/* USER CODE END 0 */
|
||||
```
|
||||
|
||||
This allows us to replace the code in our `main()` function with a simple invokation of `printf`:
|
||||
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
printf("Hello world\n\r");
|
||||
|
||||
HAL_Delay(1000);
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
`printf` will take your string, and will invoke `_write` with it. This allows us to kick the heavy lifting of
|
||||
string composition to the C library, instead of doing it ourselves. This simple example doesn't illustrate it quite
|
||||
well, but we'll see it in effect soon enough.
|
||||
|
||||
Normal `printf` usage rules apply from here-on-out. So if we wish to print a variable, for example, the code would
|
||||
look like the following:
|
||||
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
int data = 0;
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
printf("Hello world. Some data: %d\n\r", data);
|
||||
data++;
|
||||
|
||||
HAL_Delay(1000);
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
BIN
content/posts/stm32begin-001-001.webm
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-001.webm
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-002.webm
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-002.webm
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-003.webm
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-003.webm
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-005.webm
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-005.webm
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-006.mp4
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-006.mp4
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-007.png
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-007.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-008.png
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-008.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-009.mp4
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-009.mp4
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/posts/stm32begin-001-010.png
(Stored with Git LFS)
Normal file
BIN
content/posts/stm32begin-001-010.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user