Merge branch 'stm32_part1_writeup' into 'master'
stm32_part1 began on first portion of timer write-up See merge request bdebyl/bdebyl-site!6
This commit is contained in:
@@ -24,10 +24,9 @@ you are on a mobile device, and my full public key:
|
|||||||
`70A4 AA02 555D BD55 9189 B4E0 F32B E05E ADAA 54FC`[^2]
|
`70A4 AA02 555D BD55 9189 B4E0 F32B E05E ADAA 54FC`[^2]
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
{{% admonition info "Public Key" true %}}
|
{{< admonition info "Public Key" true >}}
|
||||||
```
|
```
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
mQINBFoTpoMBEADDIjRewOTvJBQF4ZxK/LS7yBL0TuU7VbZzEH3s5YKj63P/Rmvx
|
mQINBFoTpoMBEADDIjRewOTvJBQF4ZxK/LS7yBL0TuU7VbZzEH3s5YKj63P/Rmvx
|
||||||
8/jMm0iop+uiPNo+0imIGYsdfW77bt95I9+kBm27eVf8mDMldMiS/LBCCmnuQ19u
|
8/jMm0iop+uiPNo+0imIGYsdfW77bt95I9+kBm27eVf8mDMldMiS/LBCCmnuQ19u
|
||||||
uCq1Fd1O9JQyqxOegianl73NqtvG1UHXmnjdskDJ0N1hP0I7//g61TQkj+Qih8oI
|
uCq1Fd1O9JQyqxOegianl73NqtvG1UHXmnjdskDJ0N1hP0I7//g61TQkj+Qih8oI
|
||||||
@@ -146,7 +145,7 @@ KpNnEkXDMtMCsNkEMzM+3/BkuxLO52zYpDe5tV7Igx0=
|
|||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
```
|
```
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
# Resume
|
# Resume
|
||||||
I do not currently keep an up-to-date version of my resume. In the past, I have
|
I do not currently keep an up-to-date version of my resume. In the past, I have
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ For an STM32F0 project, the context of the `.clang_complete` file would be:
|
|||||||
<sub>The above assumes that `libopencm3` is also places within the project
|
<sub>The above assumes that `libopencm3` is also places within the project
|
||||||
directory</sub>
|
directory</sub>
|
||||||
|
|
||||||
{{% admonition warning Note %}}
|
{{< admonition warning Note >}}
|
||||||
There is a strange issue that is encountered with non-working completion for new
|
There is a strange issue that is encountered with non-working completion for new
|
||||||
header include statements. The workaround for this includes running `M-x irony-server-kill`after new header
|
header include statements. The workaround for this includes running `M-x irony-server-kill`after new header
|
||||||
additions to your current working file. Irony's server is clever enough to
|
additions to your current working file. Irony's server is clever enough to
|
||||||
restart itself after a completion request is triggered via `TAB` so this is a
|
restart itself after a completion request is triggered via `TAB` so this is a
|
||||||
fairly uninvolved workaround.
|
fairly uninvolved workaround.
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
{{< img src="/static/img/emacs-clang-libopencm3/completion.png"
|
{{< img src="/static/img/emacs-clang-libopencm3/completion.png"
|
||||||
|
|||||||
@@ -84,37 +84,37 @@ this via:
|
|||||||
+ **Debian/Ubuntu, RHEL:** `sudo update-ca-certificates`
|
+ **Debian/Ubuntu, RHEL:** `sudo update-ca-certificates`
|
||||||
|
|
||||||
|
|
||||||
{{% admonition tip "CA Path" %}}
|
{{< admonition tip "CA Path" >}}
|
||||||
On my system the full path to the CA certs is:
|
On my system the full path to the CA certs is:
|
||||||
|
|
||||||
- `/etc/ca-certificates/extracted/cadir/sks-keyservers.net_CA.pem`
|
- `/etc/ca-certificates/extracted/cadir/sks-keyservers.net_CA.pem`
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
Two following parameters should be added to your `~/.gnupg` configuration files:
|
Two following parameters should be added to your `~/.gnupg` configuration files:
|
||||||
|
|
||||||
### GnuPG Versions >2.1
|
### GnuPG Versions >2.1
|
||||||
|
|
||||||
|
|
||||||
{{% admonition note "gpg.conf" %}}
|
{{< admonition note "gpg.conf" >}}
|
||||||
```
|
```
|
||||||
keyserver hkps://hkps.pool.sks-keyservers.net
|
keyserver hkps://hkps.pool.sks-keyservers.net
|
||||||
```
|
```
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
|
|
||||||
{{% admonition note "dirmngr.conf" %}}
|
{{< admonition note "dirmngr.conf" >}}
|
||||||
```
|
```
|
||||||
hkp-cacert /etc/ca-certificates/path/to/CA.pem
|
hkp-cacert /etc/ca-certificates/path/to/CA.pem
|
||||||
```
|
```
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
### GnuPG Versions <2.1
|
### GnuPG Versions <2.1
|
||||||
{{% admonition note "gpg.conf" %}}
|
{{< admonition note "gpg.conf" >}}
|
||||||
```
|
```
|
||||||
keyserver hkps://hkps.pool.sks-keyservers.net
|
keyserver hkps://hkps.pool.sks-keyservers.net
|
||||||
keyserver-options ca-cert-file=/path/to/CA/sks-keyservers.netCA.pem
|
keyserver-options ca-cert-file=/path/to/CA/sks-keyservers.netCA.pem
|
||||||
```
|
```
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
## *Optional* - Ensure keys refreshed through keyserver
|
## *Optional* - Ensure keys refreshed through keyserver
|
||||||
To ensure no keys are pulled from insecure sources, or that an attacked would
|
To ensure no keys are pulled from insecure sources, or that an attacked would
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ from WordPress)
|
|||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
# Disclaimer
|
# Disclaimer
|
||||||
{{% admonition warning "Out of Date" %}}
|
{{< admonition warning "Out of Date" >}}
|
||||||
The information in this article is **out-of-date**. I am, and have been, using my
|
The information in this article is **out-of-date**. I am, and have been, using my
|
||||||
own fork of the [hugo-even-theme](https://gitlab.com/bdebyl/hugo-theme-even) on
|
own fork of the [hugo-even-theme](https://gitlab.com/bdebyl/hugo-theme-even) on
|
||||||
my [GitLab](https://gitlab.com/bdebyl) profile.
|
my [GitLab](https://gitlab.com/bdebyl) profile.
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ It may be worth mentioning, to folks less familiar with `awk`, that the
|
|||||||
being piped into `sha1sum`. I discovered incorrect `sha1sum` outputs **without**
|
being piped into `sha1sum`. I discovered incorrect `sha1sum` outputs **without**
|
||||||
`FNR==1` resulting in a useless password check!
|
`FNR==1` resulting in a useless password check!
|
||||||
|
|
||||||
{{% admonition note Note %}}
|
{{< admonition note Note >}}
|
||||||
`IFS=` would not have fixed the above newline issue, as the problem stems
|
`IFS=` would not have fixed the above newline issue, as the problem stems
|
||||||
from the output of `pass "$p"` and **not** the filenames.
|
from the output of `pass "$p"` and **not** the filenames.
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
That takes care of gathering our passwords, but we'll revisit this again in the
|
That takes care of gathering our passwords, but we'll revisit this again in the
|
||||||
next part.
|
next part.
|
||||||
@@ -108,10 +108,10 @@ it's weak (_i.e. "Exists in attack dictionary", "Too short", etc._) was to use
|
|||||||
well-documented or fully-fledged application to fully determine password
|
well-documented or fully-fledged application to fully determine password
|
||||||
strength though for my purposes it will be good enough (_I don't care to write
|
strength though for my purposes it will be good enough (_I don't care to write
|
||||||
my own version of this, yet.._).
|
my own version of this, yet.._).
|
||||||
{{% admonition note Note %}}
|
{{< admonition note Note >}}
|
||||||
I made this part of the script **optional**, as not every user would want to
|
I made this part of the script **optional**, as not every user would want to
|
||||||
install `cracklib` on their system.
|
install `cracklib` on their system.
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
This addition was made in the following order:
|
This addition was made in the following order:
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,20 @@ series: turn on the lights!
|
|||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
{{% admonition warning "Windows Users" %}}
|
{{< admonition warning "Windows Users" >}}
|
||||||
This series of write-ups assumes the reader is on a Linux operating
|
This series of write-ups assumes the reader is on a Linux operating
|
||||||
system. Windows users _can_ utilize the [**Windows Subsystems for
|
system. Windows users _can_ utilize the [**Windows Subsystems for
|
||||||
Linux**](https://docs.microsoft.com/en-us/windows/wsl/install-win10) though your
|
Linux**](https://docs.microsoft.com/en-us/windows/wsl/install-win10) though your
|
||||||
mileage may vary!
|
mileage may vary!
|
||||||
{{% /admonition %}}
|
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
# Straight to the Chase
|
# Straight to the Chase
|
||||||
|
|
||||||
For those that want to cut to the chase and save time, here is the full source
|
For those that want to cut to the chase and save time, here is the full source
|
||||||
code with friendly names to get you started:
|
code with friendly names to get you started:
|
||||||
|
|
||||||
|
{{< admonition note "Source Code" true >}}
|
||||||
```C
|
```C
|
||||||
#include <libopencm3/stm32/gpio.h>
|
#include <libopencm3/stm32/gpio.h>
|
||||||
#include <libopencm3/stm32/rcc.h>
|
#include <libopencm3/stm32/rcc.h>
|
||||||
@@ -41,8 +44,8 @@ int main(void) {
|
|||||||
|
|
||||||
while (1);
|
while (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
# Getting Started with libopencm3
|
# Getting Started with libopencm3
|
||||||
[libopencm3](https://github.com/libopencm3/libopencm3) is a very powerful,
|
[libopencm3](https://github.com/libopencm3/libopencm3) is a very powerful,
|
||||||
@@ -114,11 +117,11 @@ Makefile's variables of things you may want to change:
|
|||||||
|
|
||||||
# Explanation
|
# Explanation
|
||||||
|
|
||||||
{{% admonition info "Naming Convention" %}}
|
{{< admonition info "Naming Convention" >}}
|
||||||
As a note to the reader: below I will not refer to the GPIO port or pins using
|
As a note to the reader: below I will not refer to the GPIO port or pins using
|
||||||
the `#define` friendly names from above. This is purely for the sake
|
the `#define` friendly names from above. This is purely for the sake
|
||||||
of clarity in hopes of avoiding confusion.
|
of clarity in hopes of avoiding confusion.
|
||||||
{{% /admonition %}}
|
{{< /admonition >}}
|
||||||
|
|
||||||
Although the source code is fairly simple, lets dive into it at least
|
Although the source code is fairly simple, lets dive into it at least
|
||||||
_somewhat_.
|
_somewhat_.
|
||||||
|
|||||||
298
content/post/stm32-part1.md
Normal file
298
content/post/stm32-part1.md
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
---
|
||||||
|
title: "STM32F0 with libopencm3 - Part 1: Simple Timer"
|
||||||
|
date: 2020-02-12
|
||||||
|
lastmod: 2020-02-17
|
||||||
|
tags: ["libopencm3", "stm32", "tutorial"]
|
||||||
|
categories: ["Tutorial"]
|
||||||
|
contentCopyright: false
|
||||||
|
hideHeaderAndFooter: false
|
||||||
|
---
|
||||||
|
After having reviewed [**Part 0**](/post/stm32-part0) of this series, we can now
|
||||||
|
explore controlling GPIO with the hardware timers! Other tutorials have used the
|
||||||
|
Systick timer as a good introduction to adding a delay for blinking an
|
||||||
|
LED. However, it is my belief that this leads to confusion for beginners and
|
||||||
|
only opens the door to misunderstandings. That being said, we will be using
|
||||||
|
timers and their associated GPIO ports with Alternate Function modes.
|
||||||
|
|
||||||
|
{{< img src="/static/img/stm32-examples/part1/blinky.gif" >}}
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
# Straight to the Chase
|
||||||
|
|
||||||
|
For those that want to cut to the chase and save time, here is the full source
|
||||||
|
code with friendly names to get you started:
|
||||||
|
|
||||||
|
{{< admonition note "Source Code" true >}}
|
||||||
|
```C
|
||||||
|
#include <libopencm3/stm32/gpio.h>
|
||||||
|
#include <libopencm3/stm32/rcc.h>
|
||||||
|
#include <libopencm3/stm32/timer.h>
|
||||||
|
|
||||||
|
#define LED_PORT GPIOC
|
||||||
|
#define LED_PIN_BLU GPIO8
|
||||||
|
#define LED_PIN_GRN GPIO9
|
||||||
|
#define TIM_PSC_DIV 48000
|
||||||
|
#define SECONDS 1
|
||||||
|
|
||||||
|
volatile unsigned int i;
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
rcc_clock_setup_in_hsi_out_48mhz();
|
||||||
|
rcc_periph_clock_enable(RCC_GPIOC);
|
||||||
|
rcc_periph_clock_enable(RCC_TIM3);
|
||||||
|
|
||||||
|
gpio_mode_setup(LED_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, LED_PIN_BLU | LED_PIN_GRN);
|
||||||
|
gpio_set_output_options(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, LED_PIN_BLU | LED_PIN_GRN);
|
||||||
|
gpio_set_af(LED_PORT, GPIO_AF0, LED_PIN_BLU | LED_PIN_GRN);
|
||||||
|
|
||||||
|
timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
|
||||||
|
|
||||||
|
// The math for seconds isn't quite right here
|
||||||
|
timer_set_prescaler(TIM3, (rcc_apb1_frequency/TIM_PSC_DIV)/2*SECONDS);
|
||||||
|
timer_disable_preload(TIM3);
|
||||||
|
timer_continuous_mode(TIM3);
|
||||||
|
timer_set_period(TIM3, TIM_PSC_DIV);
|
||||||
|
|
||||||
|
timer_set_oc_mode(TIM3, TIM_OC3, TIM_OCM_PWM1);
|
||||||
|
timer_set_oc_mode(TIM3, TIM_OC4, TIM_OCM_PWM2);
|
||||||
|
|
||||||
|
int tim_oc_ids[2] = { TIM_OC3, TIM_OC4 };
|
||||||
|
|
||||||
|
for (i = 0; i < (sizeof(tim_oc_ids)/sizeof(tim_oc_ids[0])); ++i) {
|
||||||
|
timer_set_oc_value(TIM3, tim_oc_ids[i], (TIM_PSC_DIV/2));
|
||||||
|
timer_enable_oc_output(TIM3, tim_oc_ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_enable_counter(TIM3);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
|
|
||||||
|
# Set up the GPIO
|
||||||
|
Assuming the reader is either familiar with GPIO setup for the STM32F0, or has
|
||||||
|
reviewed [**Part 0**](/post/stm32-part0) of this series we will set up the GPIO
|
||||||
|
pins tied to the LEDs (_port C, pins 8 and 9_) in the Alternate Function mode.
|
||||||
|
|
||||||
|
Knowing that we'll be using `GPIOC`, we should enable this peripheral:
|
||||||
|
```C
|
||||||
|
rcc_periph_clock_enable(RCC_GPIOC);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternate Functions
|
||||||
|
The STM32 microcontroller's GPIO has a hardware feature allowing you to tie
|
||||||
|
certain port's pins to a different register as part of the output or input
|
||||||
|
control:
|
||||||
|
{{< img src="/static/img/stm32-examples/part1/stm32-af-diagram.png"
|
||||||
|
sub="GPIO Alternate Function Diagram" >}}
|
||||||
|
|
||||||
|
For accomplishing this, a few things need to happen:
|
||||||
|
|
||||||
|
1. The desired GPIO pins need to be set to `GPIO_MODE_AF` in `gpio_mode_setup()`
|
||||||
|
1. The alternate function mode number `GPIO_AFx` has to be set for the pins using `gpio_set_af()`
|
||||||
|
|
||||||
|
{{< admonition warning "Note for Different STM32Fx Microcontrollers" >}}
|
||||||
|
Review the datasheet for the specific **STM32Fx** microcontroller being
|
||||||
|
programmed, as the Alternate Function mappings may be *significantly* different!
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
|
|
||||||
|
## GPIO Alternate Function Setup
|
||||||
|
For the STM32F0 we are using in this series, the Alternate Function selection
|
||||||
|
number desired is `GPIO_AF0` for use with `TIM3_CH3` (_timer 3, channel 3_) and
|
||||||
|
`TIM3_CH4` (_timer 3, channel 4_):
|
||||||
|
{{< img src="/static/img/stm32-examples/part1/stm32-af-gpiomap.png"
|
||||||
|
sub="STM32F051 Alternate Function Mapping" >}}
|
||||||
|
|
||||||
|
|
||||||
|
Ultimately, the code with `libopencm3` becomes the following for our use case:
|
||||||
|
```C
|
||||||
|
gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO8 | GPIO9);
|
||||||
|
gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, GPIO8 | GPIO9);
|
||||||
|
gpio_set_af(GPIOC, GPIO_AF0, GPIO8 | GPIO9);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Set up the General Purpose Timer
|
||||||
|
From the previous section we chose the two on-board LEDs on the STM32F0
|
||||||
|
Discovery board tied to `PC8` and `PC9`. From the Alternate Function GPIO
|
||||||
|
mapping, we know these will be Timer 3 (_channels 3, and 4_).
|
||||||
|
|
||||||
|
Knowing that we'll be using `TIM3`, we should enable this peripheral:
|
||||||
|
```C
|
||||||
|
rcc_periph_clock_enable(RCC_TIM3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timer Mode
|
||||||
|
The first step in setting up the timer, similar to GPIO, is setting the timer
|
||||||
|
mode. The encompass the divider amount (_dividing the peripheral clock_),
|
||||||
|
alignment for capture/compare, and up or down counting:
|
||||||
|
|
||||||
|
| Divider Mode | Description |
|
||||||
|
|-------------------------|------------------------------------------------|
|
||||||
|
| `TIM_CR1_CKD_INT` | No division (_use peripheral clock frequency_) |
|
||||||
|
| `TIM_CR1_CKD_INT_MUL_2` | Twice the the timer clock frequency |
|
||||||
|
| `TIM_CR1_CKD_INT_MUL_4` | Four times the timer clock frequency |
|
||||||
|
|
||||||
|
| Alignment Mode | Description |
|
||||||
|
|------------------------|----------------------------------------------------------------------------------------------------|
|
||||||
|
| `TIM_CR1_CMS_EDGE` | Edge alignment, counter counts up or down depending on direction |
|
||||||
|
| `TIM_CR1_CMS_CENTER_1` | Center mode 1: counter counts up and down alternatively (_interrupts on counting down_) |
|
||||||
|
| `TIM_CR1_CMS_CENTER_2` | Center mode 2: counter counts up and down alternatively (_interrupts on counting up_) |
|
||||||
|
| `TIM_CR1_CMS_CENTER_3` | Center mode 3: counter counts up and down alternatively (_interrupts on both counting up or down_) |
|
||||||
|
|
||||||
|
| Direction | Description |
|
||||||
|
|--------------------|---------------|
|
||||||
|
| `TIM_CR1_DIR_UP` | Up-counting |
|
||||||
|
| `TIM_CR1_DIR_DOWN` | Down-counting |
|
||||||
|
|
||||||
|
For our purpose, it's easier to have no division (_multiplication_), edge
|
||||||
|
alignment, using up counting direction (_can be down-counting, too_):
|
||||||
|
|
||||||
|
```C
|
||||||
|
timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timer Prescaler
|
||||||
|
In addition to the timer clock, set by the peripheral clock (internal), each
|
||||||
|
timer has a perscaler value. This determines the counter clock frequency and is
|
||||||
|
equal to `Frequency/(Prescaler + 1)`. This is the value the timer will count to prior
|
||||||
|
resetting (default behavior). We can get the exact value of this frequency,
|
||||||
|
provided we didn't change the clock divisions via `rcc_apb1_frequency` (_unsigned
|
||||||
|
integer value_).
|
||||||
|
|
||||||
|
For the sake of simplicity in dividing the clock into easy decimal values, we
|
||||||
|
will utilize setting up the High Speed Internal clock to 48MHz and dividing by
|
||||||
|
48,000:
|
||||||
|
```C
|
||||||
|
rcc_clock_setup_in_hsi_out_48mhz(); // Place at the beginning of your int 'main(void)'
|
||||||
|
...
|
||||||
|
|
||||||
|
// SECONDS: integer value of period (seconds) of LED blink
|
||||||
|
timer_set_prescaler(TIM3, (rcc_apb1_frequency/48000)/2*SECONDS));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timer Period
|
||||||
|
Having set the prescaler to determine the maximum count of the timer, there is
|
||||||
|
an additional period we need to set. For our purposes, this will simply be the
|
||||||
|
same value of the prescaler:
|
||||||
|
```C
|
||||||
|
timer_set_period(TIM3, 48000);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timer Additional Configuration
|
||||||
|
There are two minor settings we want to configure for the timer:
|
||||||
|
<div style="display: none;">[^1]</div>
|
||||||
|
|
||||||
|
1. Disable preloading the ARR<sup id="fnref:1" class="footnote-ref"><a href="#fn:1">1</a></sup> (auto-reload register) when the timer is reset
|
||||||
|
1. Run the timer in continuous mode (never stop counting, clear the status
|
||||||
|
register automatically)
|
||||||
|
|
||||||
|
```C
|
||||||
|
timer_disable_preload(TIM3);
|
||||||
|
timer_continuous_mode(TIM3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timer Channel Output Compare Mode
|
||||||
|
Since we are utilizing Timer 3's channel 3 (`GPIOC8`), and channel 4 (`GPIOC9`)
|
||||||
|
we need to determine the output compare mode we want to use for each channel. By
|
||||||
|
default the mode for each channel is frozen (unaffected by the comparison of the
|
||||||
|
timer count and output compare value).
|
||||||
|
|
||||||
|
| Output Compare Mode | Description |
|
||||||
|
|----------------------|------------------------------------------------------------------------------------|
|
||||||
|
| `TIM_OCM_FROZEN` | (default) Frozen -- output unaffected by timer count vs. output compare value |
|
||||||
|
| `TIM_OCM_ACTIVE` | Output active (high) when count equals output compare value |
|
||||||
|
| `TIM_OCM_TOGGLE` | Similar to active, toggles the output state when count equals output compare value |
|
||||||
|
| `TIM_OCM_FORCE_LOW` | Forces the output to low regardless of counter value |
|
||||||
|
| `TIM_OCM_FORCE_HIGH` | Forces the output to high regardless of counter value |
|
||||||
|
| `TIM_OCM_PWM1` | Output is active (high) when counter is **less than** output compare value |
|
||||||
|
| `TIM_OCM_PWM2` | Output is active (high) when counter is **greater than** output compare value |
|
||||||
|
|
||||||
|
Essentially, what we will be doing is using PWM (pulse-width modulation) at a
|
||||||
|
very slow speed to create an alternating "blinky" effect on the LEDs. Using the
|
||||||
|
alternating PWM output-compare modes will yield this effect:
|
||||||
|
|
||||||
|
```C
|
||||||
|
timer_set_oc_mode(TIM3, TIM_OC3, TIM_OCM_PWM1);
|
||||||
|
timer_set_oc_mode(TIM3, TIM_OC4, TIM_OCM_PWM2);
|
||||||
|
```
|
||||||
|
|
||||||
|
In layman's terms: _only one LED will be on at a time, alternating._
|
||||||
|
|
||||||
|
## Timer Channel Output Compare Value
|
||||||
|
Lastly, we need to set the values that the output compare looks to for it's
|
||||||
|
comparison. For this example, we want a 50%-on/50%-off time for ease of timing
|
||||||
|
the duration of LEDs on-time determined by the frequency and period of the
|
||||||
|
timer:
|
||||||
|
|
||||||
|
```C
|
||||||
|
// (48,000 / 2) = 24,000
|
||||||
|
timer_set_oc_value(TIM3, TIM_OC3, 24000);
|
||||||
|
timer_set_oc_value(TIM3, TIM_OC4, 24000);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercise for the Reader
|
||||||
|
A fun exercise in C to reduce repetition would be by creating an array of timer
|
||||||
|
output compare address values and looping through them to set them to the same
|
||||||
|
value.
|
||||||
|
|
||||||
|
Garbage collection _may_ be discussed in a future post in this series, however
|
||||||
|
this is not intended to be a "How-To C" series and should instead focus on the
|
||||||
|
microcontroller. That being said, there is still some fun to have.
|
||||||
|
|
||||||
|
The following snippet will be provided as a note and exercise for the reader in
|
||||||
|
exploring memory allocation and garbage collection:
|
||||||
|
```C
|
||||||
|
int tim_oc_ids[2] = { TIM_OC3, TIM_OC4 };
|
||||||
|
|
||||||
|
for (i = 0; i < (sizeof(tim_oc_ids)/sizeof(tim_oc_ids[0])); ++i) {
|
||||||
|
timer_set_oc_value(TIM3, tim_oc_ids[i], 24000);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<center><sub>_Determining the 'length' of an array in C is different than in
|
||||||
|
other languages.[^2]_</sub></center>
|
||||||
|
|
||||||
|
## Enable the Timer
|
||||||
|
Lastly, to kick everything off we need to enable both the timer and the relevant
|
||||||
|
output-compare outputs.
|
||||||
|
|
||||||
|
```C
|
||||||
|
// Note: these cannot be OR'd together
|
||||||
|
timer_enable_oc_output(TIM3, TIM_OC3);
|
||||||
|
timer_enable_oc_output(TIM3, TIM_OC4);
|
||||||
|
|
||||||
|
timer_enable_counter(TIM3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Another Exercise for the Reader
|
||||||
|
The same for loop for `timer_set_oc_value()` can be appended to
|
||||||
|
for `timer_enable_oc_output()` as discussed previously:
|
||||||
|
|
||||||
|
```C
|
||||||
|
int tim_oc_ids[2] = { TIM_OC3, TIM_OC4 };
|
||||||
|
|
||||||
|
for (i = 0; i < (sizeof(tim_oc_ids)/sizeof(tim_oc_ids[0])); ++i) {
|
||||||
|
timer_set_oc_value(TIM3, tim_oc_ids[i], 24000);
|
||||||
|
timer_enable_oc_output(TIM3, tim_oc_ids[i]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Fin
|
||||||
|
Lastly, as always, we should not forget to place the microcontroller in an
|
||||||
|
infinite loop:
|
||||||
|
```C
|
||||||
|
while (1);
|
||||||
|
```
|
||||||
|
|
||||||
|
The reasons for why this is done was discussed in [**Part
|
||||||
|
0: Turn it on!**](/post/stm32-part0/#turn-it-on)
|
||||||
|
|
||||||
|
[^1]: The Auto-Reload Register is the value automatically loaded into the timer when it finishes counting
|
||||||
|
[^2]: [Determining the size of an array in C](https://stackoverflow.com/a/37539)
|
||||||
Reference in New Issue
Block a user