diff --git a/config.toml b/config.toml index 90a3f7d..a089b83 100644 --- a/config.toml +++ b/config.toml @@ -15,3 +15,6 @@ theme = 'beautifulhugo' [[module.mounts]] source = 'static/media' target = 'assets/media' + +[markup.goldmark.renderer] + unsafe = true diff --git a/content/posts/005-stm32begin-004-pwm.md b/content/posts/005-stm32begin-004-pwm.md new file mode 100644 index 0000000..1e2f6e0 --- /dev/null +++ b/content/posts/005-stm32begin-004-pwm.md @@ -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: +* Tperiod is the time of a full ON-OFF cycle, this is usually static per application, +* TON 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 (FPWM) of the PWM signal. This can be derived from the Tperiod: FPWM = 1 s / Tperiod. +For, example, if our Tperiod = 25 us, then the frequency FPWM = 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 = TON / Tperiod. In the figure above, +if we continue with the assumption that Tperiod = 25 us, then we can say that TON = 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 TON +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 UOUT can be expressed as the function: UOUT = UVCC × D. +Note that UVCC 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: UOUT = 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 Nperiod 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 Nperiod value, we can modify the real world Tperiod in which the timer +counts to its reset value. + +The rate at which the timer counts is determined by Ftick. 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 Ftick = Fin / Nprescale. + +This also lets us calculate the Fperiod = Ftick / Nperiod = Fin / Nprescale / Nperiod. +And if we remember that Fperiod = 1 s / Tperiod, we can calculate the period time as well. From the +above example, we can say that Fperiod = FPWM. + +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: Nprescale and Nperiod. +What you choose as Nperiod 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 Nperiod = 100 and we also know that Fin = 8 MHz and we wish our output +frequency to be FPWM = 40 kHz. This lets us calculate Nprescale as the last unknown in our +configuration. + +If FPWM = Fin / Nprescale / Nperiod + +then Nprescale = Fin / FPWM / Nperiod + +thus Nprescale = 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 Nperiod and Nprescale will need to be input as the calculate value - 1. So we're finally +left with: + +Nperiod = 99, and Nprescaler = 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 Nprescale (1), +* **Counter Period (AutoReload Register - 32 bits value)** to Nperiod (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 Nperiod. 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. diff --git a/content/posts/006-stm32begin-005-motors.md b/content/posts/006-stm32begin-005-motors.md new file mode 100644 index 0000000..8300cf0 --- /dev/null +++ b/content/posts/006-stm32begin-005-motors.md @@ -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. diff --git a/content/posts/006-stm32begin-005-tofs.md b/content/posts/007-stm32begin-006-tofs.md similarity index 90% rename from content/posts/006-stm32begin-005-tofs.md rename to content/posts/007-stm32begin-006-tofs.md index 64cc4f2..2c8f416 100644 --- a/content/posts/006-stm32begin-005-tofs.md +++ b/content/posts/007-stm32begin-006-tofs.md @@ -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" diff --git a/static/media/stm32begin-004-001.png b/static/media/stm32begin-004-001.png new file mode 100644 index 0000000..7553f99 --- /dev/null +++ b/static/media/stm32begin-004-001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a4f6d5ea9d8ea36da838592cae7c3686283476f66a0a5dfaf6a99be428f03c3 +size 28605 diff --git a/static/media/stm32begin-004-002.png b/static/media/stm32begin-004-002.png new file mode 100644 index 0000000..197b033 --- /dev/null +++ b/static/media/stm32begin-004-002.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11f01ace894dd6c5532a048fd1edbe64ce37ac60fbc881149c3584027f0aa5ec +size 52342 diff --git a/static/media/stm32begin-004-003.png b/static/media/stm32begin-004-003.png new file mode 100644 index 0000000..803a273 --- /dev/null +++ b/static/media/stm32begin-004-003.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bfa68daecc33147a26e45d85a29167d481b17e7e982f24ef5bc2f1ef62a3821 +size 72164 diff --git a/static/media/stm32begin-004-004.mp4 b/static/media/stm32begin-004-004.mp4 new file mode 100644 index 0000000..183c3ce --- /dev/null +++ b/static/media/stm32begin-004-004.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64e5e215e93ab917bf03aff27a8107196309ed870fdf1d42fea9d0d787551bf1 +size 147509 diff --git a/static/media/stm32begin-004-005.mp4 b/static/media/stm32begin-004-005.mp4 new file mode 100644 index 0000000..436adad --- /dev/null +++ b/static/media/stm32begin-004-005.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:473a9cf242dc35ca8093701c361a74f2468200dcdb327650309960ca98d63510 +size 210692 diff --git a/static/media/stm32begin-004-006.mp4 b/static/media/stm32begin-004-006.mp4 new file mode 100644 index 0000000..c7f2861 --- /dev/null +++ b/static/media/stm32begin-004-006.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82854fed1d163c2b779db71810985e27c7f047bc107a0256d8962cef46806d8c +size 99858 diff --git a/static/stm32begin-004.drawio b/static/stm32begin-004.drawio new file mode 100644 index 0000000..871a909 --- /dev/null +++ b/static/stm32begin-004.drawio @@ -0,0 +1,1238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +