Kjetil's Information Center: A Blog About My Projects

Terminominal Update and lazyboNES Hack

I have made a small update to Terminominal to fix some bugs. Version 0.2 can be downloaded here and the Git repo has been updated.

The first bug was handling of repeating horizontal tabs which are supposed to move the cursor each time, but that did not happen. The second bug was incorrectly parsing unknown set/reset mode parameters, which I noticed because bash or readline sent some unsupported codes even though the terminal was set to VT100. The third bug was printing of incoming NUL bytes which are better left ignored.

Just for fun I also made a hack to support the lazyboNES emulator hacks to send key release events and produce sound. This has not been included in the source code directly since it is too specific, so here is a patch of how it can be done:

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d084b6..2e74444 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,8 @@ target_link_libraries(terminominal PRIVATE
         pico_multicore
         hardware_pio
         hardware_dma
+        hardware_gpio
+        hardware_pwm
         )
 
 pico_add_extra_outputs(terminominal)
diff --git a/ps2kbd.c b/ps2kbd.c
index 341c739..7f604d5 100644
--- a/ps2kbd.c
+++ b/ps2kbd.c
@@ -38,6 +38,8 @@ static uint ps2kbd_prog_offset;
 static bool key_pressed[UINT8_MAX + 1];
 static bool key_ext_pressed[UINT8_MAX + 1];
 
+static bool release_mode = false;
+
 
 
 #ifdef KEYBOARD_NORWEGIAN
@@ -224,19 +226,25 @@ static void ps2kbd_handle_scancode(uint8_t scancode)
           break;
 
         case 0x01: /* F9 */
+          release_mode = true;
+          /*
           eia_send(0x1B);
           eia_send('[');
           eia_send('2');
           eia_send('0');
           eia_send('~');
+          */
           break;
 
         case 0x09: /* F10 */
+          release_mode = false;
+          /*
           eia_send(0x1B);
           eia_send('[');
           eia_send('2');
           eia_send('1');
           eia_send('~');
+          */
           break;
 
         case 0x78: /* F11 */
@@ -360,6 +368,40 @@ static void ps2kbd_handle_scancode(uint8_t scancode)
 
   case PS2KBD_STATE_BREAK:
     key_pressed[scancode] = false;
+    if (release_mode) {
+      switch (key_to_byte[scancode]) {
+      case 'w':
+        eia_send('W');
+        break;
+      case 'a':
+        eia_send('A');
+        break;
+      case 's':
+        eia_send('S');
+        break;
+      case 'd':
+        eia_send('D');
+        break;
+      case 'h':
+        eia_send('H');
+        break;
+      case 'j':
+        eia_send('J');
+        break;
+      case 'k':
+        eia_send('K');
+        break;
+      case 'l':
+        eia_send('L');
+        break;
+      case '0':
+        eia_send('=');
+        break;
+      case ',':
+        eia_send(';');
+        break;
+      }
+    }
     ps2kbd_state = PS2KBD_STATE_IDLE;
     break;
 
diff --git a/terminal.c b/terminal.c
index 1a93434..c0d8f85 100644
--- a/terminal.c
+++ b/terminal.c
@@ -6,6 +6,11 @@
 #include "eia.h"
 #include "error.h"
 
+#ifndef __linux__
+#include "pico/stdlib.h"
+#include "hardware/pwm.h"
+#endif /* !__linux__ */
+
 #define PARAM_MAX 8
 #define PARAM_LEN 12
 
@@ -60,6 +65,42 @@ static bool mode_line_feed         = false;
 
 
 
+#ifndef __linux__
+void sound_play(int freq)
+{
+  uint slice_num;
+  uint chan;
+
+  slice_num = pwm_gpio_to_slice_num(11);
+  chan = pwm_gpio_to_channel(11);
+
+  if (freq > 0) {
+    uint32_t clock = 125000000;
+    uint32_t divider16 = clock / freq / 4096 + (clock % (freq * 4096) != 0);
+    if (divider16 / 16 == 0) {
+      divider16 = 16;
+    }
+    uint32_t wrap = clock * 16 / divider16 / freq - 1;
+    pwm_set_clkdiv_int_frac(slice_num, divider16 / 16, divider16 & 0xF);
+    pwm_set_wrap(slice_num, wrap);
+    pwm_set_chan_level(slice_num, chan, wrap * 50 / 100);
+    pwm_set_enabled(slice_num, true);
+
+  } else {
+    pwm_set_enabled(slice_num, false);
+  }
+}
+
+
+
+void sound_init(void)
+{
+  gpio_set_function(11, GPIO_FUNC_PWM);
+}
+#endif /* !__linux__ */
+
+
+
 static inline int col_max(void)
 {
   return (mode_column_132) ? 131 : 79; /* 0-Indexed */
@@ -341,6 +382,10 @@ static inline void cursor_deactivate(void)
 
 void terminal_init(void)
 {
+#ifndef __linux__
+  sound_init();
+#endif /* !__linux__ */
+
   cursor_row = 0;
   cursor_col = 0;
   cursor_print_attribute = 0;
@@ -604,6 +649,14 @@ void terminal_handle_escape_csi(uint8_t byte)
     escape = ESCAPE_NONE;
     break;
 
+#ifndef __linux__
+  case 'x': /* Sound */
+    param_int = (param_used) ? atoi(param[0]) : 0;
+    sound_play(param_int);
+    escape = ESCAPE_NONE;
+    break;
+#endif /* !__linux__ */
+
   case ';':
     param_index++;
     if (param_index >= PARAM_MAX) {
          


Here is a video of this in action.

Topic: Open Source, by Kjetil @ 11/07-2025, Article Link