test 1.3 and start on 1.4

This commit is contained in:
Laila van Reenen 2025-09-24 18:25:29 +02:00
parent abfb65c7d4
commit 58432896a4
Signed by: LailaTheElf
GPG Key ID: 8A3EF0226518C12D
9 changed files with 310 additions and 41 deletions

BIN
assets/VersdOS.zip Normal file

Binary file not shown.

BIN
assets/assignment_4.2.zip Normal file

Binary file not shown.

View File

@ -87,8 +87,8 @@ $endif$
\begin{tabular}{r l} \begin{tabular}{r l}
Auther: $for(auther)$& $auther.name$ <$auther.email$> \\ Auther: $for(auther)$& $auther.name$ <$auther.email$> \\
$endfor$\\ $endfor$\\
Class code: & ELERTS10 \\\\ Class code: & $class_code$ \\\\
Exported on: &\today Exported on: & \today
\end{tabular} \end{tabular}
} }

View File

@ -27,7 +27,7 @@ install_arch:
test -e build/install/roboto.zip || curl https://dl.dafont.com/dl/?f=roboto -o build/install/roboto.zip test -e build/install/roboto.zip || curl https://dl.dafont.com/dl/?f=roboto -o build/install/roboto.zip
test -d build/install/roboto && rm -r build/install/roboto || echo test -d build/install/roboto && rm -r build/install/roboto || echo
mkdir build/install/roboto mkdir build/install/roboto
unzip build/install/roboto.zip -d build/install/roboto unzip build/install/roboto.zip -d build/install/roboto
mkdir -p /usr/share/fonts/roboto mkdir -p /usr/share/fonts/roboto
cp build/install/roboto/*.ttf /usr/share/fonts/roboto/ cp build/install/roboto/*.ttf /usr/share/fonts/roboto/
@ -51,7 +51,7 @@ install_ubuntu:
test -e build/install/roboto.zip || curl https://dl.dafont.com/dl/?f=roboto -o build/install/roboto.zip test -e build/install/roboto.zip || curl https://dl.dafont.com/dl/?f=roboto -o build/install/roboto.zip
test -d build/install/roboto && rm -r build/install/roboto || echo test -d build/install/roboto && rm -r build/install/roboto || echo
mkdir build/install/roboto mkdir build/install/roboto
unzip build/install/roboto.zip -d build/install/roboto unzip build/install/roboto.zip -d build/install/roboto
mkdir -p /usr/share/fonts/roboto mkdir -p /usr/share/fonts/roboto
cp build/install/roboto/*.ttf /usr/share/fonts/roboto/ cp build/install/roboto/*.ttf /usr/share/fonts/roboto/
@ -66,9 +66,9 @@ latex/assambly_report.latex: converters/mdToLatex.sh converters/template.latex r
mkdir -p build/assambly_report mkdir -p build/assambly_report
bash converters/mdToLatex.sh report-1/assambly_report.md latex/assambly_report.latex bash converters/mdToLatex.sh report-1/assambly_report.md latex/assambly_report.latex
latex/c_report.latex: converters/mdToLatex.sh converters/template.latex report-1/*.md latex/c_report.latex: converters/mdToLatex.sh converters/template.latex report-2/*.md
mkdir -p build/c_report mkdir -p build/c_report
bash converters/mdToLatex.sh report-1/c_report.md latex/c_report.latex bash converters/mdToLatex.sh report-2/c_report.md latex/c_report.latex
# ======================================= # =======================================
# === pdf generation ==================== # === pdf generation ====================

View File

@ -1,5 +1,6 @@
--- ---
sub_title: "Real Time Systems 10" sub_title: "Real Time Systems 10"
class_code: "ELERTS10"
auther: auther:
- name: "Finley van Reenen (0964590)" - name: "Finley van Reenen (0964590)"
email: "mail@lailatheelf.nl" email: "mail@lailatheelf.nl"

View File

@ -27,7 +27,7 @@ Bit 12 and 14 sould start different as bit 13 and 15. Then invert all four bits
I found the defines in the sourcefile where the _modder_ defines are defined. I found the defines in the sourcefile where the _modder_ defines are defined.
```c-like= ```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
@ -78,7 +78,7 @@ skiped
I added line 6 throw 15, the rest is uncheached. I added line 6 throw 15, the rest is uncheached.
```c-like= ```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
@ -125,7 +125,7 @@ I mesured the toggle time to be around 1.5 seconsds. This is what is expected.
First the power modules needs to be configured to allow for the hige clockspeed. First the power modules needs to be configured to allow for the hige clockspeed.
```c-like ```c
// enable power control // enable power control
RCC->APB1ENR |= RCC_APB1ENR_PWREN; RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// set voltage to support 100 MHz // set voltage to support 100 MHz
@ -136,7 +136,7 @@ Now the flash latancy is set. The powersupply on the board is $3V$. This means t
![](https://live.kladjes.nl/uploads/64ef314f-35ef-4435-9d24-16a72257f785.png) ![](https://live.kladjes.nl/uploads/64ef314f-35ef-4435-9d24-16a72257f785.png)
```c-like ```c
// set flash latency to support 100 MHz // set flash latency to support 100 MHz
FLASH->ACR |= FLASH_ACR_LATENCY_3WS; FLASH->ACR |= FLASH_ACR_LATENCY_3WS;
// Wait until the wait states are used // Wait until the wait states are used

View File

@ -1,5 +1,6 @@
--- ---
sub_title: "Real Time Systems 10" sub_title: "Real Time Systems 10"
class_code: "ELERTS10"
auther: auther:
- name: "Finley van Reenen (0964590)" - name: "Finley van Reenen (0964590)"
email: "mail@lailatheelf.nl" email: "mail@lailatheelf.nl"
@ -11,3 +12,5 @@ auther:
[toc] [toc]
![](/report-2/week_1.3.md) ![](/report-2/week_1.3.md)
![](/report-2/week_1.4.md)

View File

@ -10,9 +10,18 @@ auther:
## assignment 3.1 ## assignment 3.1
base project [opdr_2_1](/assets/opdr_2_1.zip). > Base your code of `opdr_2_1.zip`.
> Configure the `SysTick` timer to set the `COUNTFLAG` in the `STK_CTRL` register every $0.5s$. Replace the for-loop with the following C code: `while (( STK_CTRL & (1 << 16)) == 0);` You have to properly define the symbol STK_CTRL yourself to make this work. Build and debug the project. If all is well, the user LEDs will blink with a frequency of $1 Hz$.
```c With bit 2 of `STK_CTRL` the clock source can be set. `1` for `AHB` or `0` for `AHB/8`. `AHB` is by default `HSI` witch is $16MHz$. I chose to use `AHB/8`.
$$
T = \frac{AHB/8}{f_{out}} = \frac{16\cdot 10^6/8}{2} = 10^6
$$
My resulting code:
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#define RCC_AHB1ENR_BIT_GPIODEN *(volatile uint32_t*)(0x42000000 + 0x00023830 * 32 + 3 * 4) #define RCC_AHB1ENR_BIT_GPIODEN *(volatile uint32_t*)(0x42000000 + 0x00023830 * 32 + 3 * 4)
@ -32,9 +41,10 @@ int main(void)
// Set green and red LEDs // Set green and red LEDs
GPIOD_ODR = 0x5000; GPIOD_ODR = 0x5000;
// SysTick enable with clk source to AHB/8 // SysTick enable with clk source to AHB/8
STK_CTRL = (1<<2) | 1; // (AHB is by default HSI; 16 MHz/8)
STK_LOAD = 500000; // 0.5 sec / (8 MHz / 8) STK_CTRL = 1;
STK_LOAD = 1000000; // 16 MHz / 8 / 2 Hz
// Do forever: // Do forever:
while (1) while (1)
{ {
@ -46,9 +56,15 @@ int main(void)
} }
``` ```
I measured the resulting frequency with an logic analyser ([[#logic-analiser-view-of-LEDs-for-assignment-31]]; channels are collerd to the led color). It measuerd a period time of $499.568ms$, I call this error could be my cheap logic analyser or en error in the internal oscilator.
![Logic analiser view of LEDs for assignment 3.1](https://live.kladjes.nl/uploads/84591270-95b0-4601-aea2-ca44b51adbf9.png)
## assignment 3.2 ## assignment 3.2
```c > Configure the SysTick timer to set the `COUNTFLAG` in the `STK_CTRL` register every $0.5s$ using the CMSIS API. Replace the for-loop with the following C code: `while ((SysTick->CTRL & (1 << 16)) == 0);` The symbol SysTick is defined in the CMSIS API. Build and debug the project. If all is well, the user LEDs will blink with a frequency of $1 Hz$.
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
@ -66,7 +82,7 @@ int main(void)
// SysTick enable with clk source to AHB/8 // SysTick enable with clk source to AHB/8
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = 500000; // 0.5 sec / (8 MHz / 8) SysTick->LOAD = 1000000; // 16 MHz / 8 / 2 Hz
// Do forever: // Do forever:
while (1) while (1)
@ -82,9 +98,13 @@ int main(void)
} }
``` ```
This time I measured $499.586ms$ with the logic analyser.
## assignment 3.3 ## assignment 3.3
```c > B) Configure the SysTick timer to generate an interrupt (also called an exception) every `0.5s`.
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@ -114,7 +134,7 @@ int main(void)
// SysTick enable with interupt and clk source to AHB/8 // SysTick enable with interupt and clk source to AHB/8
STK_CTRL = (1<<1) | 1; STK_CTRL = (1<<1) | 1;
STK_LOAD = 500000; // 0.5 sec / (8 MHz / 8) STK_LOAD = 1000000; // 0.5 sec / (16 MHz / 8)
// Do forever: // Do forever:
while (1) while (1)
{ {
@ -134,11 +154,11 @@ int main(void)
becouse the compiler doesn't know when `flag` changes. Without `volatile` optimisations can think it does not change at all. becouse the compiler doesn't know when `flag` changes. Without `volatile` optimisations can think it does not change at all.
## assignment 3.4 ## assignment 3.4
```c > Configure the SysTick timer to generate an interrupt every `0.5s`. This can be done by using the function `SysTick_Config` from the CMSIS API.
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
#include <stdbool.h> #include <stdbool.h>
@ -164,7 +184,7 @@ int main(void)
// SysTick enable with interupt and clk source to AHB/8 // SysTick enable with interupt and clk source to AHB/8
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = 500000; // 0.5 sec / (8 MHz / 8) SysTick->LOAD = 1000000; // 0.5 sec / (16 MHz / 8)
// Do forever: // Do forever:
while (1) while (1)
@ -186,7 +206,9 @@ int main(void)
## assignment 3.5 ## assignment 3.5
```c > Now based on project opdr_3_4 create a rotation loop which simulates a simple traffic light: green (5 seconds), orange (1 second), red (4 seconds). The time each light is on must be easily adjustable with a granularity of $0.5s$. The processor must be put to sleep in between interrupts. Make use of an enumeration construct (`enum`) for the colors and a `switch`-`case`-statement for the rotation.
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
#include <stdbool.h> #include <stdbool.h>
@ -214,11 +236,11 @@ int main(void)
| GPIO_MODER_MODER14_0 | GPIO_MODER_MODER14_0
| GPIO_MODER_MODER15_0; | GPIO_MODER_MODER15_0;
// Set green and red LEDs // Set green and red LEDs
GPIOD->ODR = GPIO_ODR_OD12 | GPIO_ODR_OD14; GPIOD->ODR = GPIO_ODR_OD12;
// SysTick enable with interupt and clk source to AHB/8 // SysTick enable with interupt and clk source to AHB/8
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = 500000; // 0.5 sec / (8 MHz / 8) SysTick->LOAD = 1000000; // 0.5 sec / (8 MHz / 8)
// time of each color in half seconds // time of each color in half seconds
const uint32_t time_green = 10; // 5 seconds const uint32_t time_green = 10; // 5 seconds
@ -268,9 +290,23 @@ int main(void)
} }
``` ```
Again I validated the timesing with the logicanalyser.
![](https://live.kladjes.nl/uploads/16fe6424-cff1-4ce4-b506-7f8d87ec4dc4.png)
## assignment 3.6 ## assignment 3.6
```c > - reate a copy of the previous project and rename it to opdr_3_6.
> - Using the description of this assignment, define a struct for a “task” and create a global array of 8 empty tasks.
> - Create a function `addTask(...)` to help create a task from a function pointer and other parameters, and add it to the task list (the array) at an appropriate index.
> - Create 4 functions to toggle each led separately, these are the functions that will correspond to 4 tasks.
> - Use `addTask(...)` 4 times to couple each led function to a new task in the task list with periods of: 200 ticks, 500 ticks, 750 ticks, and 300 ticks for green, orange, red, and blue respectively.
> - In the SysTick ISR, walk through the task list and decrement each of the task counters.
> - Think of, and expand on, the task struct to notify per task whether it is in a WAITING or READY state. Set the state in the ISR depending on the task counter.
> - Create a function runReadyTasks() that will walk through the task list and execute any task in the READY state. Replace your switch-case rotation in the function main with a call to this function.
> - Make use of a logic analyzer to verify the timing of the tasks
```c {.numberLines}
#include <stdint.h> #include <stdint.h>
#include <stm32f4xx.h> #include <stm32f4xx.h>
#include <stdbool.h> #include <stdbool.h>
@ -278,9 +314,9 @@ int main(void)
volatile uint32_t ISR_Ticks = 0; volatile uint32_t ISR_Ticks = 0;
struct TASK { struct TASK {
void* fn, void (*fn)(void);
uint32_t counter, uint32_t counter;
uint32_t counter_rst uint32_t counter_rst;
}; };
uint8_t Tasks_len = 0; uint8_t Tasks_len = 0;
@ -291,14 +327,14 @@ void SysTick_Handler()
ISR_Ticks++; ISR_Ticks++;
} }
bool addTask(void* fn, uint32_t counter) bool addTask(void (*fn)(void), uint32_t counter)
{ {
if (Tasks_len >= 8) { if (Tasks_len >= 8) {
return false; return false;
} }
Tasks[Task_len].fn = fn; Tasks[Tasks_len].fn = fn;
Tasks[Task_len].counter = counter; Tasks[Tasks_len].counter = counter;
Tasks[Task_len].counter_rst = counter; Tasks[Tasks_len].counter_rst = counter;
Tasks_len++; Tasks_len++;
return true; return true;
} }
@ -340,11 +376,11 @@ int main(void)
| GPIO_MODER_MODER15_0; | GPIO_MODER_MODER15_0;
// Set all leds off // Set all leds off
GPIOD->ODR = 0; GPIOD->ODR = 0;
// SysTick enable with interupt and clk source to AHB/8 // SysTick enable with interupt and clk source to AHB/8
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = 1000; // 1 ms / (8 MHz / 8) SysTick->LOAD = 2000; // 1 ms / (16 MHz / 8)
addTask(*taskGreen, 200); addTask(*taskGreen, 200);
addTask(*taskOrange, 500); addTask(*taskOrange, 500);
@ -355,14 +391,15 @@ int main(void)
while (1) while (1)
{ {
// Wait a moment // Wait a moment
while (ISR_Ticks != 0) while (ISR_Ticks == 0)
{ {
__asm__(" WFI"); // sleep until SysTick __asm__(" WFI"); // sleep until SysTick
} }
uint32_t ticks = ISR_Ticks; uint32_t ticks = ISR_Ticks;
ISR_Ticks = 0; ISR_Ticks = 0;
for (uint8_t int=0; i<Tasks_len; i++) // decrement all counters
for (uint8_t i=0; i<Tasks_len; i++)
{ {
if (Tasks[i].counter > ticks) if (Tasks[i].counter > ticks)
{ {
@ -374,13 +411,161 @@ int main(void)
} }
} }
for (uint8_t int=0; i<Tasks_len; i++) // rust all tasks where the counter has run out
for (uint8_t i=0; i<Tasks_len; i++)
{ {
if (Tasks[i].counter == 0) if (Tasks[i].counter == 0)
{ {
(&(Tasks[i].fn))(); Tasks[i].fn();
Tasks[i].counter = Tasks[i].counter_rst;
} }
} }
} }
} }
``` ```
![](https://live.kladjes.nl/uploads/c5e6a20b-34ed-4bf0-9bf0-ec175a25e4ae.png)
## Assignment 3.7
> Now add initial delays5 (in systicks) to your tasks. Use an initial delay of 100, 200, 300, and 400 for green, orange, red, and blue respectively. Make use of a logic analyzer to verify the timing.
When A task is created, in the version of [[#Assignment 3.7]], the folloing function is used:
```c
bool addTask(void (*fn)(void), uint32_t counter)
{
if (Tasks_len >= 8) {
return false;
}
Tasks[Tasks_len].fn = fn;
Tasks[Tasks_len].counter = counter;
Tasks[Tasks_len].counter_rst = counter;
Tasks_len++;
return true;
}
```
Here the `counter` and `counter_rst` menbers are set to the same value. `counter` is the counter that is decremented eacht SysClock. `counter_rst` is the value `counter` is reset to if it reachts 0 after the task is run.
Setting `counter` to the initail delay already solves this assignment. The foloing code implements this change.
```c {.numberLines}
#include <stdint.h>
#include <stm32f4xx.h>
#include <stdbool.h>
volatile uint32_t ISR_Ticks = 0;
struct TASK {
void (*fn)(void);
uint32_t counter;
uint32_t counter_rst;
};
uint8_t Tasks_len = 0;
struct TASK Tasks[8];
void SysTick_Handler()
{
ISR_Ticks++;
}
bool addTask(void (*fn)(void), uint32_t counter, uint32_t counter_init)
{
if (Tasks_len >= 8) {
return false;
}
Tasks[Tasks_len].fn = fn;
Tasks[Tasks_len].counter = counter_init;
Tasks[Tasks_len].counter_rst = counter;
Tasks_len++;
return true;
}
void taskGreen()
{
GPIOD->ODR ^= GPIO_ODR_OD12;
}
void taskOrange()
{
GPIOD->ODR ^= GPIO_ODR_OD13;
}
void taskRed()
{
GPIOD->ODR ^= GPIO_ODR_OD14;
}
void taskBlue()
{
GPIOD->ODR ^= GPIO_ODR_OD15;
}
enum STATE {
STATE_GREEN,
STATE_ORANGE,
STATE_RED
};
int main(void)
{
// GPIO Port D Clock Enable
RCC->AHB1ENR = RCC_AHB1ENR_GPIODEN;
// GPIO Port D Pin 15 down to 12 Push/Pull Output
GPIOD->MODER = GPIO_MODER_MODER12_0
| GPIO_MODER_MODER13_0
| GPIO_MODER_MODER14_0
| GPIO_MODER_MODER15_0;
// Set all leds off
GPIOD->ODR = 0;
// SysTick enable with interupt and clk source to AHB/8
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = 2000; // 1 ms / (16 MHz / 8)
addTask(*taskGreen, 200, 100);
addTask(*taskOrange, 500, 200);
addTask(*taskRed, 750, 300);
addTask(*taskBlue, 300, 400);
// Do forever:
while (1)
{
// Wait a moment
while (ISR_Ticks == 0)
{
__asm__(" WFI"); // sleep until SysTick
}
uint32_t ticks = ISR_Ticks;
ISR_Ticks = 0;
// decrement all counters
for (uint8_t i=0; i<Tasks_len; i++)
{
if (Tasks[i].counter > ticks)
{
Tasks[i].counter -= ticks;
}
else
{
Tasks[i].counter = 0;
}
}
// rust all tasks where the counter has run out
for (uint8_t i=0; i<Tasks_len; i++)
{
if (Tasks[i].counter == 0)
{
Tasks[i].fn();
Tasks[i].counter = Tasks[i].counter_rst;
}
}
}
}
```
![](https://live.kladjes.nl/uploads/feb9df6b-ef4d-4bbc-b524-a2ab1d44dc33.png)

80
report-2/week_1.4.md Normal file
View File

@ -0,0 +1,80 @@
# Week 1.4
## Assinment 4.1
> The teacher has built a simple preemptive OS that is still missing some important features. In these assignments youll implement some extra features and gain a bigger understanding in how an OS operates. Next week well start using a fully developed RTOS with all necessary features for a production environment.
> A) Download the project VersdOS.zip.
> B) Import this project, Open and Finish. Build and debug the project. The LEDs should blink.
> C) Browse through the code and especially make sure you understand the scheduling process and the context switch.
> D) Measure the periods at which the LEDs toggle using a logic analyzer and explain why this is not 2× but 8× the `blocking_delay` time.
![](https://live.kladjes.nl/uploads/1cdce708-a2e7-4a3d-b727-d5423b892687.png)
In the Code it the values for blocking delay are 100, 200, 400, 800 for green, orange, red, blue. The `blocking_delay` function wait for this number of SysTicks, witch is set to $1ms$. I measure delays of $0.4s$, $0.8s$, $1.6s$ and $3.2s$. This is indeed 4x slower of what I would expect on first flance. This delay is becouse this is a preemtive scheduler, the other tasks run in between. There are 4 task all with the same priority, so each task takes 4 time longer.
## Assignment 4.2
> A) mplement a non-blocking delay so that a task can request the OS to be kept out of the scheduling loop for a certain number of system ticks. The scheduler should remain preemptive and perform round-robin on all the available(not delayed) tasks. Once the requested number of system ticks have passed, the scheduler should include the task in the selection process. You may use the `taskYield()` function to let the OS know a task is ready to be switched out.
To allow for a non blocking delay the OS sould keep track of the time the to wake it up on the corect time. I did this by adding the new task state `SLEEPING_DELAY` to the enum.
There already is an counter in the task struct. but no function that decrements this counter. So I added the folloing function the the OS.
```c
void decrement_sleeping_delay()
{
for (usize_t i=0; i < MAX_TASKS; i++)
{
if (taskList[i].state == SLEEPING_DELAY)
{
if (taskList[i].counter == 0)
{
taskList[i].state = READY;
}
else
{
taskList[i].counter--;
}
}
}
}
```
This function is added to the `SysTick_Handeler` before the cudeule is run. So that if the counter is 0 the `decrement_sleeping_delay` makes the task ready again, and waits one more SysTick.
The `SysTick_Handeler` now look like the folloing
```c
void SysTick_Handler(void)
{
SysTick_flag = true;
//decrement counter for task in SLEEPING_DELAY
decrement_sleeping_delay();
//select the next task
taskToExecute = schedule();
//request context switch
SCB->ICSR |= (1<<28);
}
```
Before stating to write the delay function for the tasks, in the `schedule` funciton the line that sets the current task to ready sould be removed (line 163 in `VersdOS.c`). If the state is chanced during execution of the task, this line reset is at the next try to reschedule ths state back to `READY` so the state will never actualy be changed.
As last the delay function itself.
```c
void delay(uint32_t ticks)
{
currentTask->state = SLEEPING_DELAY;
currentTask->counter = ticks;
taskYield();
}
```
Now all the line `extern void delay(uint32_t ticks);` can be added to the begin the `main.c` and all the `blocking_delay` calls can be replace by `delay`.
### Result
![](https://live.kladjes.nl/uploads/c65810e2-a5fc-48f3-be6b-2f30acc5747a.png)
This implementation works, only the delays are a bit off. To make it a nice counter again all delays should be decremented by one.