This commit is contained in:
parent
81fb12b821
commit
9a6bb39669
@ -15,3 +15,6 @@ theme = 'beautifulhugo'
|
||||
[[module.mounts]]
|
||||
source = 'static/media'
|
||||
target = 'assets/media'
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
174
content/posts/005-stm32begin-004-pwm.md
Normal file
174
content/posts/005-stm32begin-004-pwm.md
Normal file
@ -0,0 +1,174 @@
|
||||
---
|
||||
title: "STM32 For Beginners [4]: PWM"
|
||||
date: 2024-10-08T00:36:00+03:00
|
||||
draft: false
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
series: ["STM32 For Beginners"]
|
||||
tags: ["Embedded", "STM32"]
|
||||
---
|
||||
|
||||
Digital outputs are all fun and good, but very few things in actual reality are digital (just "on" or "off") in nature.
|
||||
MCUs have a few ways to emulate a truly analog output, with Digital to Analog Converters (DACs) being the most useful one.
|
||||
However, in most cases, we can make due with a digital signal driven in a specific manner. This is where PWM comes in.
|
||||
|
||||
# Generating a PWM, For a LED
|
||||
|
||||
PWM stands for "Pulse Width Modulation". We will be generating a series of pulses, and modulating, changing, the width
|
||||
of those pulses over time. This allows us to do all kinds of fun things in the real world, for example, drive DC motors
|
||||
or dim LEDs.
|
||||
|
||||
The waveform of a typical PWM signal would look something like this:
|
||||
|
||||
{{< figure src="/media/stm32begin-004-001.png" >}}
|
||||
|
||||
The waveform has some key properties:
|
||||
* T</sub>period</sub> is the time of a full ON-OFF cycle, this is usually static per application,
|
||||
* T<sub>ON</sub> is the time during which the output pin outputs a HIGH signal (in the STM32 case, a 3.3 V signal).
|
||||
|
||||
Not pictured is the frequency (F<sub>PWM</sub>) of the PWM signal. This can be derived from the T<sub>period</sub>: F<sub>PWM</sub> = 1 s / T<sub>period</sub>.
|
||||
For, example, if our T<sub>period</sub> = 25 us, then the frequency F</sub>PWM</sub> = 1 s / 25 us = 40 kHz.
|
||||
|
||||
The other property that's not pictured is the duty cycle (D) of the pulse. The duty cycle represents the ratio
|
||||
between the ON and OFF time in a single period: D = T<sub>ON</sub> / T<sub>period</sub>. In the figure above,
|
||||
if we continue with the assumption that T<sub>period</sub> = 25 us, then we can say that T<sub>ON</sub> = 8.3 us,
|
||||
and thus D = 8.3 / 25 = 0.33 = 33%. The duty cycle is the metric that we will be driving by modifying the T<sub>ON</sub>
|
||||
of the signal.
|
||||
|
||||
An important property of the PWM signal is that, once the frequency of the signal is high enough, in most physical
|
||||
systems, the ON-OFF switching will be evened out by the properties of the connected circuitry. This means that the
|
||||
observed output voltage U<sub>OUT</sub> can be expressed as the function: U<sub>OUT</sub> = U<sub>VCC</sub> × D.
|
||||
Note that U<sub>VCC</sub> is the peak voltage of the signal, in our case, 3.3 V. So for our drawing, the averaged
|
||||
output voltage of the PWM signal is: U<sub>OUT</sub> = 3.3 × 0.33 ~= 1.1 V.
|
||||
|
||||
# Timers
|
||||
|
||||
Generating a PWM signal on the STM32 series is done via the timer peripheral. In other cases, these may be called
|
||||
counters, or timer/counters. But in principle, across microcontroller series and manufacturers, timers work the same:
|
||||
they count the number of pulses, and thus, allow you to keep track of time.
|
||||
|
||||
A timer is effectively configured to run at a given frequency, and it will count. It will either count to its maximum
|
||||
limit (unsigned 32-bit integer's maximum value for STM32s), or until it reaches a configured N<sub>period</sub> value.
|
||||
Once that value is reached, usually, the timer is reset and it will either stop or restart, depending on the configuration.
|
||||
The functionality of a generic timer is illustrated in the figure below.
|
||||
|
||||
{{< figure src="/media/stm32begin-004-002.png" >}}
|
||||
|
||||
As can be seen, by modifying the N<sub>period</sub> value, we can modify the real world T<sub>period</sub> in which the timer
|
||||
counts to its reset value.
|
||||
|
||||
The rate at which the timer counts is determined by F<sub>tick</sub>. For STM32s, the timers are powered by APB1 and APB2 clocks.
|
||||
These usually have speeds in the megahertz. Which can be way too big. For this purpose, timers on the STM32 include a frequency divisor:
|
||||
a prescaler. This prescaler value will let us slow down the counting to a reasonable point for us. As such, we can say that F<sub>tick</sub> = F<sub>in</sub> / N<sub>prescale</sub>.
|
||||
|
||||
This also lets us calculate the F<sub>period</sub> = F<sub>tick</sub> / N<sub>period</sub> = F<sub>in</sub> / N<sub>prescale</sub> / N<sub>period</sub>.
|
||||
And if we remember that F<sub>period</sub> = 1 s / T<sub>period</sub>, we can calculate the period time as well. From the
|
||||
above example, we can say that F<sub>period</sub> = F<sub>PWM</sub>.
|
||||
|
||||
This lets us tie the period of the PWM to the period of the timer. But we also need a midway point for inverting the signal.
|
||||
STM32 timers call this value a "compare" value: it's an arbitrary value during which we can do _something_ in. For generating
|
||||
a PWM, the STM32 hardware uses the compare value as the point at which the the pulse inverts its value. This means, we can
|
||||
combine the two previous figures as follows:
|
||||
|
||||
{{< figure src="/media/stm32begin-004-003.png" >}}
|
||||
|
||||
## Configuring a Timer
|
||||
|
||||
We now know that we need to configure a timer to generate PWM. Great.
|
||||
|
||||
From before, we know that we also have to choose two parameters: N<sub>prescale</sub> and N<sub>period</sub>.
|
||||
What you choose as N<sub>period</sub> will dictate the range in which you can adjust the PWM "value" in code.
|
||||
For example, setting a period of 100 will let you input the PWM "power" as a number between 0 and 100.
|
||||
Setting it to some other, more random value, will make the logic harder. So, pick a sane period. In our case,
|
||||
100 will do.
|
||||
|
||||
We thus know that N<sub>period</sub> = 100 and we also know that F<sub>in</sub> = 8 MHz and we wish our output
|
||||
frequency to be F<sub>PWM</sub> = 40 kHz. This lets us calculate N<sub>prescale</sub> as the last unknown in our
|
||||
configuration.
|
||||
|
||||
If F<sub>PWM</sub> = F<sub>in</sub> / N<sub>prescale</sub> / N<sub>period</sub>
|
||||
|
||||
then N<sub>prescale</sub> = F<sub>in</sub> / F<sub>PWM</sub> / N<sub>period</sub>
|
||||
|
||||
thus N<sub>prescale</sub> = 8e6 / 40e3 / 1e2 = 2.
|
||||
|
||||
**Note**: due to the digital nature of our work, and with the number 0 counting as a "1" for mathematical purposes,
|
||||
both N<sub>period</sub> and N<sub>prescale</sub> will need to be input as the calculate value - 1. So we're finally
|
||||
left with:
|
||||
|
||||
N<sub>period</sub> = 99, and N<sub>prescaler</sub> = 1.
|
||||
|
||||
Now we go over into CubeMX. For illustration purposes, we'll be applying the PWM to the LED that's on the dev board.
|
||||
This will have the effect of letting us dim the LED's brightness. Fortunately, the STM32F303k8 dev board has a PWM
|
||||
channel attached to the LED pin PB3. We can check this by clicking the pin and seeing if there's any TIMx_CHy functions
|
||||
attached to it. In the case of PB3, we can see that there's TIM2_CH2, which means it's connected to Timer 2's channel 2.
|
||||
|
||||
{{< video src="/media/stm32begin-004-004.mp4" type="video/mp4" preload="auto" >}}
|
||||
|
||||
Our next step is to enable TIM2 by setting its "Clock Source" to "Internal Clock", and setting "Channel2" to "PWM Generation
|
||||
CH2".
|
||||
|
||||
{{< video src="/media/stm32begin-004-005.mp4" type="video/mp4" preload="auto" >}}
|
||||
|
||||
With that done, we now have to set the counter values. We have to set:
|
||||
* **Prescaler (PSC - 16 bits value)** to our calculated N<sub>prescale</sub> (1),
|
||||
* **Counter Period (AutoReload Register - 32 bits value)** to N<sub>period</sub> (99),
|
||||
* set **auto-reload preload** to "Enabled".
|
||||
|
||||
{{< video src="/media/stm32begin-004-006.mp4" type="video/mp4" preload="auto" >}}
|
||||
|
||||
With this done, generate the code as you would normally.
|
||||
|
||||
## Controlling the PWM
|
||||
|
||||
We've configured the timer, the PWM output, now to start it and play with its duty cycle.
|
||||
|
||||
After code generation, we go to main. The first thing we have to do is actually start the timer
|
||||
and start the PWM generation. This is done as such:
|
||||
|
||||
```cpp
|
||||
/* USER CODE BEGIN 2 */
|
||||
HAL_TIM_Base_Start(&htim2);
|
||||
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
|
||||
/* USER CODE END 2 */
|
||||
```
|
||||
|
||||
The code should be placed in user code section 2, before the while loop but after the MX_x_Init functions.
|
||||
|
||||
* `HAL_TIM_Base_Start(&htimx)` will start the actual timer itself, and
|
||||
* `HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_y)` will start the PWM for that timer on the specified channel.
|
||||
|
||||
If you're using multiple PWM channels from the same timer, you will have to call `HAL_TIM_PWM_Start` for each
|
||||
channel that you're using.
|
||||
|
||||
To set the PWM to a given duty cycle value, we would use the macro function `__HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_y, n)`.
|
||||
Where the `n` is a value between 0 and N<sub>period</sub>. This effectively sets us our duty cycle as well.
|
||||
|
||||
To dim the LED from off to full brightness in sequence, we could do something like this, for example:
|
||||
|
||||
```cpp
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
while (1)
|
||||
{
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0);
|
||||
HAL_Delay(400);
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 20);
|
||||
HAL_Delay(400);
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 40);
|
||||
HAL_Delay(400);
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 60);
|
||||
HAL_Delay(400);
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 80);
|
||||
HAL_Delay(400);
|
||||
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 99); // because our period was 99 and not 100.
|
||||
HAL_Delay(400);
|
||||
/* USER CODE END WHILE */
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
```
|
||||
|
||||
Bootload the code, and off we go. We now have a functional PWM useful for dimming a LED... Or driving motors. Which we'll
|
||||
cover next.
|
||||
32
content/posts/006-stm32begin-005-motors.md
Normal file
32
content/posts/006-stm32begin-005-motors.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "STM32 For Beginners [5]: Motors"
|
||||
date: 2024-10-10T00:36:00+03:00
|
||||
draft: true
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
series: ["STM32 For Beginners"]
|
||||
tags: ["Embedded", "STM32"]
|
||||
---
|
||||
|
||||
Thus far we've learned very basic ways of making our microcontroller interact with the world: GPIO mostly.
|
||||
But to build a folkrace robot we need a little bit more. Motors, specifically motors with variable speed would help.
|
||||
The most simple motor to use in this scenario is a DC motor, like the [Pololu micro metal gearmotors](https://www.pololu.com/category/60/micro-metal-gearmotors).
|
||||
|
||||
# Driving DC Motors, Theory
|
||||
|
||||
To drive a DC motor, we typically use a H-bridge based motor driver. In the case of the Robotics Club course,
|
||||
we are given a [Pololu DRV8835 carrier board](https://www.pololu.com/product/2135) which contains the Texas Instruments DRV8835 dual H-bridge
|
||||
chip. "Dual" in this case means that one board can drive two motors, perfect for a simple differential-drive
|
||||
folkrace robot.
|
||||
|
||||
{{< figure src="https://a.pololu-files.com/picture/0J4056.1200.jpg" caption="Source: https://wwww.pololu.com" >}}
|
||||
|
||||
If we look at the "Using the motor driver" section in the "Description" of the carrier board's page,
|
||||
we notice the following diagram:
|
||||
|
||||
{{< figure src="https://a.pololu-files.com/picture/0J4058.600.png" caption="Source: https://www.pololu.com" >}}
|
||||
|
||||
The right side is dedicated to the motors, and the left side specifies the input signals expected from the microcontroller.
|
||||
We immediately notice that for this control scheme, the "phase-enabled mode", we need to generate two PWM
|
||||
signals, alongside a few GPIO output signals. Let's look a little closer as PWM now, since that's the missing
|
||||
piece for us.
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "STM32 For Beginners [5]: Time of Flight Sensors"
|
||||
date: 2024-10-09T00:36:00+03:00
|
||||
title: "STM32 For Beginners [6]: Time of Flight Sensors"
|
||||
date: 2024-10-11T00:36:00+03:00
|
||||
draft: false
|
||||
summary: "..."
|
||||
author: "Rusted Skull"
|
||||
BIN
static/media/stm32begin-004-001.png
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-001.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
static/media/stm32begin-004-002.png
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-002.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
static/media/stm32begin-004-003.png
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-003.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
static/media/stm32begin-004-004.mp4
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-004.mp4
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
static/media/stm32begin-004-005.mp4
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-005.mp4
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
static/media/stm32begin-004-006.mp4
(Stored with Git LFS)
Normal file
BIN
static/media/stm32begin-004-006.mp4
(Stored with Git LFS)
Normal file
Binary file not shown.
1238
static/stm32begin-004.drawio
Normal file
1238
static/stm32begin-004.drawio
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user