From 09aa04b3c7419921a8514c8ad21ba02fd3a7ba70 Mon Sep 17 00:00:00 2001
From: William Grant <williamgrantuk@gmail.com>
Date: Sat, 13 May 2023 10:13:48 +0100
Subject: [PATCH] morse code generator

---
 CMakeLists.txt       |   1 +
 morse/CMakeLists.txt |  14 ++
 morse/morse.c        | 412 +++++++++++++++++++++++++++++++++++++++++++
 rios/CMakeLists.txt  |   2 +-
 rios/main.c          |  36 ++--
 rios/rios.c          |  54 ++++--
 rios/rios.h          |   2 -
 7 files changed, 485 insertions(+), 36 deletions(-)
 create mode 100644 morse/CMakeLists.txt
 create mode 100644 morse/morse.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bf669d..5145408 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,4 +8,5 @@ set(CMAKE_C_STANDARD 11)
 set(CMAKE_CXX_STANDARD 17)
 
 add_subdirectory(rios)
+add_subdirectory(morse)
 
diff --git a/morse/CMakeLists.txt b/morse/CMakeLists.txt
new file mode 100644
index 0000000..894e753
--- /dev/null
+++ b/morse/CMakeLists.txt
@@ -0,0 +1,14 @@
+if (TARGET tinyusb_device)
+
+add_executable(morse morse.c)
+target_link_libraries(morse pico_stdlib hardware_pwm)
+
+pico_enable_stdio_usb(morse 1)
+pico_enable_stdio_uart(morse 0)
+
+pico_add_extra_outputs(morse)
+
+elseif(PICO_ON_DEVICE)
+    message(WARNING "cannot build morse because TinyUSB submodule is not initialised in the SDK")
+endif()
+
diff --git a/morse/morse.c b/morse/morse.c
new file mode 100644
index 0000000..9c47f6c
--- /dev/null
+++ b/morse/morse.c
@@ -0,0 +1,412 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "hardware/pwm.h"
+
+// buffer sizes
+static const size_t MAX_CHARS = 100u;
+static const size_t MORSE_SIZE = MAX_CHARS * 6u;
+
+// the GPIO pin the buzzer is connected to
+static const unsigned int BUZZER = 18u;
+
+// the 'unit' time in miliseconds
+static const uint64_t UNIT_TIME = 50u;
+
+typedef enum {
+    SHORT_SPACE, // the space between letters, 7 time units long
+    LONG_SPACE,  // the space between words, 3 time units long
+    DOT,  // a dot, 1 time unit long
+    DASH  // a dash, 3 time units long
+} morse_t;
+
+// enable I/O and configure the pwm
+static inline void setup() {
+    stdio_init_all();
+    
+    gpio_set_function(BUZZER, GPIO_FUNC_PWM);
+    unsigned int slice = pwm_gpio_to_slice_num(BUZZER);
+    pwm_set_wrap(slice, 3);
+    pwm_set_chan_level(slice, PWM_CHAN_A, 0);  // start with the buzzer disabled
+    pwm_set_enabled(slice, true);
+
+    sleep_ms(1000);  // I've found it helps if you wait a bit for everything to get going
+}
+
+static inline void enable_buzzer() {
+    pwm_set_chan_level(pwm_gpio_to_slice_num(BUZZER), PWM_CHAN_A, 3);  // 75% duty cycle
+}
+
+static inline void disable_buzzer() {
+    pwm_set_chan_level(pwm_gpio_to_slice_num(BUZZER), PWM_CHAN_A, 0);  // 0% duty cycle (disabled)
+}
+
+// enable the buzzer, wait, then disable it again
+static inline void toggle_buzzer(uint64_t time) {
+    enable_buzzer();
+    sleep_ms(time);
+    disable_buzzer();
+}
+
+
+// convert the given string to morse code
+static size_t string_to_morse(morse_t* morse, char* string, size_t size) {
+    size_t n = 0;
+    for (size_t i = 0; i < size; ++i) {
+        switch (string[i]) {
+            case 'a':
+            case 'A':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'b':
+            case 'B':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 'c':
+            case 'C':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 'd':
+            case 'D':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 'e':
+            case 'E':
+                morse[n++] = DOT;
+                break;
+
+            case 'f':
+            case 'F':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 'g':
+            case 'G':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 'h':
+            case 'H':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 'i':
+            case 'I':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 'j':
+            case 'J':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case 'k':
+            case 'K':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'l':
+            case 'L':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 'm':
+            case 'M':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case 'n':
+            case 'N':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 'o':
+            case 'O':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case 'p':
+            case 'P':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 'q':
+            case 'Q':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'r':
+            case 'R':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case 's':
+            case 'S':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case 't':
+            case 'T':
+                morse[n++] = DASH;
+                break;
+
+            case 'u':
+            case 'U': 
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'v':
+            case 'V':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'w':
+            case 'W':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case 'x':
+            case 'X':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case 'y':
+            case 'Y':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case 'z':
+            case 'Z':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case '1':
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case '2':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case '3':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            case '4':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DASH;
+                break;
+
+            case '5':
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case '6':
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case '7':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case '8':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                morse[n++] = DOT;
+                break;
+
+            case '9':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DOT;
+                break;
+
+            case '0':
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                morse[n++] = DASH;
+                break;
+
+            default:
+                break;  // skip unknown charaters
+        }
+
+        if (string[i] == ' ') {
+            // avoid double and leading spaces
+            if (n > 0u && morse[n - 1u] == SHORT_SPACE) {
+                morse[n - 1u] = LONG_SPACE;
+            }
+        } else {
+            morse[n++] = SHORT_SPACE;
+        }
+    }
+
+    return n;
+}
+
+// print the morse code to the terminal and play it with the buzzer
+static void play_morse(morse_t* morse, size_t size) {
+    for (size_t i = 0; i < size; ++i) {
+        switch (morse[i]) {
+            case SHORT_SPACE:
+                putchar(' ');
+                sleep_ms(UNIT_TIME * 2u);
+                break;
+
+            case LONG_SPACE:
+                printf(" // ");
+                sleep_ms(UNIT_TIME * 6u);
+                break;
+
+            case DOT: 
+                putchar('.');
+                toggle_buzzer(UNIT_TIME);
+                break;
+
+            case DASH:
+                putchar('-');
+                toggle_buzzer(UNIT_TIME * 3u);
+                break;
+        }
+
+        sleep_ms(UNIT_TIME);
+    }
+
+    putchar('\n');
+}
+
+// get a line of input from the user
+static size_t get_line(char* buf, size_t size) {
+    size_t i;
+
+    printf(">> ");
+    for (i = 0; i < size; ++i) {
+        char c = getchar();
+        if (c == EOF || c == '\r' || c == '\n') {
+            putchar('\n');
+            buf[i] = '\0';
+            break;
+        } else {
+            putchar(c);
+            buf[i] = c;
+        }
+    }
+    
+    return i;
+}
+
+int main(void) {
+    setup();
+
+    char* char_buf = malloc(sizeof(char) * MAX_CHARS);
+    morse_t* morse_buf = malloc(sizeof(morse_t) * MORSE_SIZE);
+    if (char_buf == NULL || morse_buf == NULL) {
+        perror("could not allocate buffers");
+        exit(1);
+    }
+
+    while (true) {
+        // read a line from input
+        size_t char_size = get_line(char_buf, MAX_CHARS);
+
+        // output line as morse code
+        size_t morse_size = string_to_morse(morse_buf, char_buf, char_size);
+        play_morse(morse_buf, morse_size);
+    }
+}
+
diff --git a/rios/CMakeLists.txt b/rios/CMakeLists.txt
index 1b2f2a2..afdcbe8 100644
--- a/rios/CMakeLists.txt
+++ b/rios/CMakeLists.txt
@@ -1,7 +1,7 @@
 if (TARGET tinyusb_device)
 
 add_library(rios STATIC rios.c rios.h)
-target_link_libraries(rios hardware_irq)
+target_link_libraries(rios hardware_irq pico_stdlib)
 
 add_executable(rios_main main.c)
 target_link_libraries(rios_main rios pico_stdlib)
diff --git a/rios/main.c b/rios/main.c
index 1aaaf1d..d044fac 100644
--- a/rios/main.c
+++ b/rios/main.c
@@ -1,39 +1,47 @@
 #include <stdio.h>
+#include <stdbool.h>
 #include "pico/stdlib.h"
 #include "rios.h"
 
 static uint32_t task1(uint32_t state) {
-    printf("[T1%d<", state);
+    printf("[T1 %d<", state);
     sleep_ms(20);
-    printf(">T1%d]", state);
+    printf(">T1 %d]", state);
+    
     return ++state;
 }
 
 static uint32_t task2(uint32_t state) {
-    printf("[T2%d<", state);
+    printf("[T2 %d<", state);
     sleep_ms(600);
-    printf(">T2%d]", state);
+    printf(">T2 %d]", state);
+
     return ++state;
 }
 
 static uint32_t task3(uint32_t state) {
-    printf("[T3%d<", state);
+    printf("[T3 %d<", state);
     sleep_ms(2000);
-    printf(">T3%d]", state);
+    printf(">T3 %d]", state);
+
     return ++state;
 }
 
-static inline void init(void) {
+int main(void) {
     stdio_init_all();
-    rios_init();
-}
+    sleep_ms(1000);
+    printf("stdio initialised\n");
 
-int main(void) {
-    init();
+    rios_add_task(250u, 0u, task1);
+    rios_add_task(750u, 0u, task2);
+    rios_add_task(1500u, 0u, task3);
+    printf("tasks added\n");
 
-    rios_add_task(29u, 0u, task1);
-    rios_add_task(77u, 0u, task2);
-    rios_add_task(162u, 0u, task3);
     rios_start();
+    printf("rios started\n");
+    
+    while (true) {}
+
+    return 0;
 }
 
diff --git a/rios/rios.c b/rios/rios.c
index 0d39456..30cd346 100644
--- a/rios/rios.c
+++ b/rios/rios.c
@@ -2,6 +2,9 @@
 
 #include <stdint.h>
 #include <stdbool.h>
+#include <stdio.h>
+#include "pico/stdlib.h"
+#include "hardware/sync.h"
 #include "hardware/irq.h"
 
 typedef struct {
@@ -13,7 +16,7 @@ typedef struct {
 } task_data_t;
 
 static const uint32_t tick_ms = 400u;        // Real time between ticks in ms
-static const uint32_t tasks_period_GCD = 25u;  // Timer tick rate 
+static uint32_t tasks_period_GCD;  // Timer tick rate 
 
 static task_data_t tasks[RIOS_MAX_TASKS];
 static uint32_t tasks_index = 0u;
@@ -22,44 +25,45 @@ static uint32_t running_tasks[RIOS_MAX_TASKS+1] = {UINT32_MAX};
 static uint32_t current_task = 0u;
 static const uint32_t idle_task = UINT32_MAX;
 
-static const uint irq_num = 0u;
-
-static inline void enable_irq(void) {
-    irq_set_enabled(irq_num, true);
+static uint32_t gcd(uint32_t x, uint32_t y) {
+    while (y != 0) {
+        uint32_t t = y;
+        y = x % y;
+        x = t;
+    }
+    return x;
 }
 
-static inline void disable_irq(void) {
-    irq_set_enabled(irq_num, false);
-}
+static int64_t scheduler_isr(alarm_id_t id, void* data) {
+    printf("running scheduler\n");
 
-static void scheduler_isr(void) {
     for (uint32_t i = 0u; i < tasks_index; ++i) {
+
         if ((tasks[i].elapsed_time >= tasks[i].period)
             && (running_tasks[current_task] > i)
             && (!tasks[i].running)) {
-            
-            disable_irq();
+
+            printf("running task %d, %d, %p\n", tasks[i].period, tasks[i].state, tasks[i].tick_func);
+            uint32_t flags = save_and_disable_interrupts();
             tasks[i].elapsed_time = 0u;
             tasks[i].running = true;
             ++current_task;
             running_tasks[current_task] = i;
-            enable_irq();
+            restore_interrupts(flags);
 
             tasks[i].state = tasks[i].tick_func(tasks[i].state);  // execute tick
 
-            disable_irq();
+            flags = save_and_disable_interrupts();
             tasks[i].running = false;
             running_tasks[current_task] = idle_task;
             --current_task;
-            enable_irq();
+            restore_interrupts(flags);
         }
         tasks[i].elapsed_time += tasks_period_GCD;
     }
-}
 
-void rios_init(void) {
-    irq_set_exclusive_handler(irq_num, scheduler_isr);
-    enable_irq();
+    printf("scheduler finished\n");
+    return tasks_period_GCD;  // re-arm the alarm
 }
 
 void rios_add_task(uint32_t period, uint32_t initial_state, rios_task_t task) {
@@ -69,11 +73,23 @@ void rios_add_task(uint32_t period, uint32_t initial_state, rios_task_t task) {
                               .elapsed_time = period,
                               .tick_func = task };
     tasks[tasks_index] = task_data;
+    printf("task %d, %d, %p added\n", period, initial_state, task);
+
+    // update the timer tick rate
+    if (tasks_index == 0u) {
+        tasks_period_GCD = period;
+    } else {
+        tasks_period_GCD = gcd(tasks_period_GCD, period);
+    }
+    printf("tasks_period_GCD: %d\n", tasks_period_GCD);
 
     ++tasks_index;
 }
 
+
 void rios_start(void) {
-    enable_irq();
+    alarm_pool_t* alarm_pool = alarm_pool_create(2, 16);
+    irq_set_priority(2, 0xc0);
+    alarm_pool_add_alarm_in_ms(alarm_pool, tasks_period_GCD, scheduler_isr, NULL, false);
 }
 
diff --git a/rios/rios.h b/rios/rios.h
index 696435d..7324189 100644
--- a/rios/rios.h
+++ b/rios/rios.h
@@ -6,8 +6,6 @@
 
 typedef uint32_t (*rios_task_t)(uint32_t state);
 
-void rios_init(void);
-
 void rios_add_task(uint32_t period, uint32_t initial_state, rios_task_t task);
 
 void rios_start(void);
-- 
GitLab