From 4f3d51cb51b373dcbd809ef16077d8e06c16a02d Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Wed, 12 Feb 2020 14:34:16 -0500 Subject: [PATCH 1/3] stm32_part1 began on first portion of timer write-up --- content/post/stm32-part1.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 content/post/stm32-part1.md diff --git a/content/post/stm32-part1.md b/content/post/stm32-part1.md new file mode 100644 index 0000000..f53f683 --- /dev/null +++ b/content/post/stm32-part1.md @@ -0,0 +1,18 @@ +--- +title: "STM32 with libopencm3 - Part 1: Simple Timer" +date: 2020-02-12 +lastmod: 2020-02-12 +draft: true +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. + + From eb50ed1e5e6d23105941b62e0f05799396db183b Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Wed, 12 Feb 2020 23:07:44 -0500 Subject: [PATCH 2/3] stm32_part1 More progress on first draft, admonition fixes --- content/about.md | 5 +- content/post/emacs_clang_libopencm3.md | 4 +- content/post/gpg_best_practices_and_git.md | 16 ++-- content/post/humble-beginnings.md | 4 +- content/post/password_checker.md | 8 +- content/post/stm32-part0.md | 13 ++- content/post/stm32-part1.md | 101 ++++++++++++++++++++- 7 files changed, 125 insertions(+), 26 deletions(-) diff --git a/content/about.md b/content/about.md index 242fe2c..6b096c7 100644 --- a/content/about.md +++ b/content/about.md @@ -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] -{{% admonition info "Public Key" true %}} +{{< admonition info "Public Key" true >}} ``` -----BEGIN PGP PUBLIC KEY BLOCK----- - mQINBFoTpoMBEADDIjRewOTvJBQF4ZxK/LS7yBL0TuU7VbZzEH3s5YKj63P/Rmvx 8/jMm0iop+uiPNo+0imIGYsdfW77bt95I9+kBm27eVf8mDMldMiS/LBCCmnuQ19u uCq1Fd1O9JQyqxOegianl73NqtvG1UHXmnjdskDJ0N1hP0I7//g61TQkj+Qih8oI @@ -146,7 +145,7 @@ KpNnEkXDMtMCsNkEMzM+3/BkuxLO52zYpDe5tV7Igx0= -----END PGP PUBLIC KEY BLOCK----- ``` -{{% /admonition %}} +{{< /admonition >}} # Resume I do not currently keep an up-to-date version of my resume. In the past, I have diff --git a/content/post/emacs_clang_libopencm3.md b/content/post/emacs_clang_libopencm3.md index e0df48c..b5f15fe 100644 --- a/content/post/emacs_clang_libopencm3.md +++ b/content/post/emacs_clang_libopencm3.md @@ -95,13 +95,13 @@ For an STM32F0 project, the context of the `.clang_complete` file would be: The above assumes that `libopencm3` is also places within the project directory -{{% admonition warning Note %}} +{{< admonition warning Note >}} 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 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 fairly uninvolved workaround. -{{% /admonition %}} +{{< /admonition >}} ## Example {{< img src="/static/img/emacs-clang-libopencm3/completion.png" diff --git a/content/post/gpg_best_practices_and_git.md b/content/post/gpg_best_practices_and_git.md index b3c4e80..f0a3c2e 100644 --- a/content/post/gpg_best_practices_and_git.md +++ b/content/post/gpg_best_practices_and_git.md @@ -84,37 +84,37 @@ this via: + **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: - `/etc/ca-certificates/extracted/cadir/sks-keyservers.net_CA.pem` -{{% /admonition %}} +{{< /admonition >}} Two following parameters should be added to your `~/.gnupg` configuration files: ### GnuPG Versions >2.1 -{{% admonition note "gpg.conf" %}} +{{< admonition note "gpg.conf" >}} ``` 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 ``` -{{% /admonition %}} +{{< /admonition >}} ### GnuPG Versions <2.1 -{{% admonition note "gpg.conf" %}} +{{< admonition note "gpg.conf" >}} ``` keyserver hkps://hkps.pool.sks-keyservers.net keyserver-options ca-cert-file=/path/to/CA/sks-keyservers.netCA.pem ``` -{{% /admonition %}} +{{< /admonition >}} ## *Optional* - Ensure keys refreshed through keyserver To ensure no keys are pulled from insecure sources, or that an attacked would diff --git a/content/post/humble-beginnings.md b/content/post/humble-beginnings.md index 7433f6b..a0755c1 100644 --- a/content/post/humble-beginnings.md +++ b/content/post/humble-beginnings.md @@ -13,11 +13,11 @@ from WordPress) # 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 own fork of the [hugo-even-theme](https://gitlab.com/bdebyl/hugo-theme-even) on my [GitLab](https://gitlab.com/bdebyl) profile. -{{% /admonition %}} +{{< /admonition >}} --- diff --git a/content/post/password_checker.md b/content/post/password_checker.md index 52ba1e0..a659ce5 100644 --- a/content/post/password_checker.md +++ b/content/post/password_checker.md @@ -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** `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 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 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 strength though for my purposes it will be good enough (_I don't care to write 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 install `cracklib` on their system. -{{% /admonition %}} +{{< /admonition >}} This addition was made in the following order: diff --git a/content/post/stm32-part0.md b/content/post/stm32-part0.md index 24b6c07..4905010 100644 --- a/content/post/stm32-part0.md +++ b/content/post/stm32-part0.md @@ -14,17 +14,20 @@ series: turn on the lights! -{{% admonition warning "Windows Users" %}} +{{< admonition warning "Windows Users" >}} This series of write-ups assumes the reader is on a Linux operating system. Windows users _can_ utilize the [**Windows Subsystems for Linux**](https://docs.microsoft.com/en-us/windows/wsl/install-win10) though your mileage may vary! -{{% /admonition %}} + +{{< /admonition >}} # 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 #include @@ -41,8 +44,8 @@ int main(void) { while (1); } - ``` +{{< /admonition >}} # Getting Started with libopencm3 [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 -{{% 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 the `#define` friendly names from above. This is purely for the sake of clarity in hopes of avoiding confusion. -{{% /admonition %}} +{{< /admonition >}} Although the source code is fairly simple, lets dive into it at least _somewhat_. diff --git a/content/post/stm32-part1.md b/content/post/stm32-part1.md index f53f683..c92d299 100644 --- a/content/post/stm32-part1.md +++ b/content/post/stm32-part1.md @@ -1,5 +1,5 @@ --- -title: "STM32 with libopencm3 - Part 1: Simple Timer" +title: "STM32F0 with libopencm3 - Part 1: Simple Timer" date: 2020-02-12 lastmod: 2020-02-12 draft: true @@ -8,7 +8,7 @@ categories: ["Tutorial"] contentCopyright: false hideHeaderAndFooter: false --- -After having reviewed [part 0](/post/stm32-part0) of this series, we can now +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 @@ -16,3 +16,100 @@ only opens the door to misunderstandings. That being said, we will be using timers and their associated GPIO ports with Alternate Function modes. + +# 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 +#include +#include + +#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_CENTER_1, 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_OC3); + timer_enable_oc_output(TIM3, TIM_OC4); + + 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. + +## 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); +``` From a5956f7d0cee53ae6504f299facc8b6b7b52305a Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Mon, 17 Feb 2020 13:11:24 -0500 Subject: [PATCH 3/3] stm32_part1 Finished part 1 timer write-up --- content/post/stm32-part1.md | 195 ++++++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 6 deletions(-) diff --git a/content/post/stm32-part1.md b/content/post/stm32-part1.md index c92d299..4633109 100644 --- a/content/post/stm32-part1.md +++ b/content/post/stm32-part1.md @@ -1,8 +1,7 @@ --- title: "STM32F0 with libopencm3 - Part 1: Simple Timer" date: 2020-02-12 -lastmod: 2020-02-12 -draft: true +lastmod: 2020-02-17 tags: ["libopencm3", "stm32", "tutorial"] categories: ["Tutorial"] contentCopyright: false @@ -15,6 +14,8 @@ 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" >}} + # Straight to the Chase @@ -45,7 +46,7 @@ int main(void) { 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_CENTER_1, TIM_CR1_DIR_UP); + 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); @@ -60,11 +61,9 @@ int main(void) { 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_oc_output(TIM3, TIM_OC3); - timer_enable_oc_output(TIM3, TIM_OC4); - timer_enable_counter(TIM3); while (1) { @@ -76,11 +75,17 @@ int main(void) { ``` {{< /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 @@ -113,3 +118,181 @@ 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: +
[^1]
+ +1. Disable preloading the ARR1 (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); +} +``` +
_Determining the 'length' of an array in C is different than in +other languages.[^2]_
+ +## 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)