Porting Squiggle Minimalist To Kaleidoscope


Background

I have drawn (and built) some (unnecessarily complex) keyboards. All of them use overpowered small computer (ATMega32U4) so I can use existing firmware to make them programmable. It's really nice thing to have a programmable keyboard, you know. It's also nice to understand decently what goes on in your peripheral.

Now, the leading project, or at least in my circle, is QMK which was written in C. I'm not the brightest tool in the shed, but the maintainers of that project make it quite easy, for me, to add new keyboards (which I did for some useless boards of mine). Though it's another story to really understand the inner working of QMK because it is built on top of an operating system. Well, to be absolutely honest, I feel overwhelmed when I have to learn something if I also have to learn an operating system it's built on top of.

So, I decided to look for some other alternatives:

So, considering my situation, I think it's better for me to pick Kaleidoscope.

Keyboard to be Ported

Ackshually, I'm not sure if the correct term is porting but whatever.

The keyboard that I choose is: Squiggle. Specifically the minimalist version which "only" uses a single promicro to power it (doesn't even use ioexpander).

Squiggle Minimalist

Based on what I have done in the past, what I need to provide if I want to make a keyboard supported by a keyboard firmware project are:

So, the matrix approximately looks like this:


 c0  c1  c2  c3  c4         c5  c6  c7  c8  c9

 X - X - X - X - X -------- X - X - X - X - X   r0
 |   |   |   |   |          |   |   |   |   |
 X - X - X - X - X -------- X - X - X - X - X   r1
 |   |   |   |   |          |   |   |   |   |
 X - X - X - X - X -------- X - X - X - X - X   r2
             |   |          |   |
             X - X -------- X - X               r3

While the promicro's pins that it uses:


                +---|==|---+
                |o        o|
                |o        o|
                |o        o|
                |o        o|
                |o        o| r0
                |o   /\   o| r1
             c0 |o  /  \  o| r2
             c1 |o /    \ o| r3
             c2 |o \    / o| c5
             c3 |o  \  /  o| c6
             c4 |o   \/   o| c7
             c9 |o        o| c8
                +----------+

Checking Out Kaleidoscope

I head to readthedocs and then read what I need to install to develop something with it.

All of them can be installed via pacman, package manager of my distro. Yes, I use arch.

In short, I followed this page to setup the toolchain I needed, followed by opening Arduino, and then try to compile some other keyboards' firmwares.

Everything seems nice and dandy, and then I set to explore it.

Exploring Kaleidoscope

See, there are some directories in ~/Arduino/hardware/keyboardio.

The source files of the project lie in the avr dir. The contents of directories other than avr aren't really relevant for this post.

In avr dir, there are some directories and files:

In libraries, there are:

Now, for the main directory, there are some dirs and files:

In src directory, there are:

I won't be touching kaleidoscope_internal if I just want to "port" a keyboard. So, I will see what's inside of kaleidoscope dir.

Because I've found what I'm looking for, I will see what is the content of that dir.

Existing Device Definition

In the past, I used a hand-wired atreus so I will use it as an example from the existing device definitions.

There are two files:

The content of the header file is the following:

#pragma once
#ifdef ARDUINO_AVR_ATREUS
#include <Arduino.h>
#include "kaleidoscope/driver/bootloader/avr/HalfKay.h"
#include "kaleidoscope/driver/bootloader/avr/Caterina.h"
#include "kaleidoscope/device/ATmega32U4Keyboard.h"
namespace kaleidoscope {
namespace device {
namespace technomancy {
struct AtreusProps : kaleidoscope::device::ATmega32U4KeyboardProps {
  struct KeyScannerProps : public kaleidoscope::driver::keyscanner::ATmegaProps {
    static constexpr uint8_t matrix_rows = 4;
    static constexpr uint8_t matrix_columns = 12;
    typedef MatrixAddr<matrix_rows, matrix_columns> KeyAddr;
#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
#ifdef KALEIDOSCOPE_HARDWARE_ATREUS_PINOUT_ASTAR
    static constexpr uint8_t matrix_row_pins[matrix_rows] = {PIN_D0, PIN_D1, PIN_D3, PIN_D2};
    static constexpr uint8_t matrix_col_pins[matrix_columns] = {PIN_D7, PIN_C6, PIN_B5, PIN_B4, PIN_E6, PIN_D4, PIN_B6, PIN_F6, PIN_F7, PIN_D6, PIN_B7};
#endif
#ifdef KALEIDOSCOPE_HARDWARE_ATREUS_PINOUT_ASTAR_DOWN
    /* omitted */
#endif
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
  };
  typedef kaleidoscope::driver::keyscanner::ATmega<KeyScannerProps> KeyScanner;
#ifdef KALEIDOSCOPE_HARDWARE_ATREUS_PINOUT_LEGACY_TEENSY2
  typedef kaleidoscope::driver::bootloader::avr::HalfKay BootLoader;
#else
  typedef kaleidoscope::driver::bootloader::avr::Caterina BootLoader;
#endif
  static constexpr const char *short_name = "atreus";
};
#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
class Atreus: public kaleidoscope::device::ATmega32U4Keyboard<AtreusProps> {};
#else // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
class Atreus;
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD

#define PER_KEY_DATA(dflt,                                                  \
    /* omitted */                                                             \
  )                                                                           \
                                                                              \
    /* omitted */

#define PER_KEY_DATA_STACKED(dflt,                                          \
    /* omitted */                                                             \
  )                                                                          \
    /* omitted */
}
}
EXPORT_DEVICE(kaleidoscope::device::technomancy::Atreus)
}
#endif

Which basically defines:

Now, for the cpp file:


#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
#ifdef ARDUINO_AVR_ATREUS
#include "kaleidoscope/Runtime.h"
#include "kaleidoscope/driver/keyscanner/Base_Impl.h"
using KeyScannerProps = typename kaleidoscope::device::technomancy::AtreusProps::KeyScannerProps;
using KeyScanner = typename kaleidoscope::device::technomancy::AtreusProps::KeyScanner;
namespace kaleidoscope {
namespace device {
namespace technomancy {
const uint8_t KeyScannerProps::matrix_rows;
const uint8_t KeyScannerProps::matrix_columns;
constexpr uint8_t KeyScannerProps::matrix_row_pins[matrix_rows];
constexpr uint8_t KeyScannerProps::matrix_col_pins[matrix_columns];
template<> uint16_t KeyScanner::previousKeyState_[KeyScannerProps::matrix_rows] = {};
template<> uint16_t KeyScanner::keyState_[KeyScannerProps::matrix_rows] = {};
template<> uint16_t KeyScanner::masks_[KeyScannerProps::matrix_rows] = {};
template<> uint8_t KeyScanner::debounce_matrix_[KeyScannerProps::matrix_rows][KeyScannerProps::matrix_columns] = {};
ISR(TIMER1_OVF_vect) {
  Runtime.device().keyScanner().do_scan_ = true;
}

} // namespace technomancy
} // namespace device
} // namespace kaleidoscope

#endif
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD

Which I think pretty clear.

Porting Squiggle

From the previous sections, to support a new device, there are some files that need to be changed or created. In this case:

For boards.txt's addition, I added the following snippet.

squiggle.name=Squiggle # Should be self explanatory.
squiggle.vid.0=0x6969  # Nice.
squiggle.pid.0=0x4200  # Blaze It!

squiggle.upload.maximum_data_size=2560 # Not sure, but everything seems use it.

squiggle.build.extra_flags={build.usb_flags} '-DKALEIDOSCOPE_HARDWARE_H="Kaleidoscope-Hardware-Brick-Squiggle.h"'
# Based on other entries, the format should be Kaleidoscope-Hardware-Vendor-Board.h

squiggle.upload.tool=avrdude              # avrdude is pretty nice.
squiggle.upload.protocol=avr109           # promicro uses this, i think.
squiggle.upload.maximum_size=28672        # usually 32kb. but other boards use this.
squiggle.upload.speed=57600
squiggle.upload.disable_flushing=true
squiggle.upload.wait_for_upload_port=true

squiggle.build.mcu=atmega32u4             # promicro uses it.
squiggle.build.f_cpu=16000000L            # i use 16KHz variant.
squiggle.build.vid=0x6969                 # should be the same as squiggle.vid
squiggle.build.pid=0x4200                 # should be the same as squiggle.pid
squiggle.build.usb_product="Squiggle"
squiggle.build.usb_manufacturer="Brick"
squiggle.build.board=AVR_SQUIGGLE
squiggle.build.core=arduino:arduino

While ~/Arduino/hardware/keyboardio/avr/libraries/Kaleidoscope/src/Kaleidoscope-Hardware-Brick-Squiggle.h, I just need to create a header file that contains the following lines

#pragma once
#include "kaleidoscope/device/brick/Squiggle.h"

As for Squiggle.h and Squiggle.cpp, basically I just copied Atreus.h and Atreus.cpp with some modifications:

#pragma once
#ifdef ARDUINO_AVR_SQUIGGLE
#include <Arduino.h>
#include "kaleidoscope/driver/bootloader/avr/Caterina.h"
#include "kaleidoscope/device/ATmega32U4Keyboard.h"
namespace kaleidoscope {
namespace device {
namespace brick {
struct SquiggleProps : kaleidoscope::device::ATmega32U4KeyboardProps {
  struct KeyScannerProps : public kaleidoscope::driver::keyscanner::ATmegaProps {
    static constexpr uint8_t matrix_rows = 4;
    static constexpr uint8_t matrix_columns = 10;
    typedef MatrixAddr<matrix_rows, matrix_columns> KeyAddr;
#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
static constexpr uint8_t matrix_row_pins[matrix_rows] = {PIN_F4, PIN_F5, PIN_F6, PIN_F7};
static constexpr uint8_t matrix_col_pins[matrix_columns] = {PIN_D4, PIN_C6, PIN_D7, PIN_E6, PIN_B4, PIN_B1, PIN_B3, PIN_B2, PIN_B6, PIN_B5};
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
  };
  typedef kaleidoscope::driver::keyscanner::ATmega<KeyScannerProps> KeyScanner;
  typedef kaleidoscope::driver::bootloader::avr::Caterina BootLoader;
  static constexpr const char *short_name = "squiggle";
};
#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
class Squiggle: public kaleidoscope::device::ATmega32U4Keyboard<SquiggleProps> {};
#else // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
class Squiggle;
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
#define PER_KEY_DATA(dflt,                                                    \
    R0C0, R0C1, R0C2, R0C3, R0C4,             R0C5, R0C6, R0C7, R0C8, R0C9,   \
    R1C0, R1C1, R1C2, R1C3, R1C4,             R1C5, R1C6, R1C7, R1C8, R1C9,   \
    R2C0, R2C1, R2C2, R2C3, R2C4,             R2C5, R2C6, R2C7, R2C8, R2C9,   \
                      R3C3, R3C4,             R3C5, R3C6                      \
  )                                                                           \
                                                                              \
    R0C0, R0C1, R0C2, R0C3, R0C4, R0C5, R0C6, R0C7, R0C8, R0C9,               \
    R1C0, R1C1, R1C2, R1C3, R1C4, R1C5, R1C6, R1C7, R1C8, R1C9,               \
    R2C0, R2C1, R2C2, R2C3, R2C4, R2C5, R2C6, R2C7, R2C8, R2C9,               \
    XXX,  XXX,  XXX,  R3C3, R3C4, R3C5, R3C6, XXX,  XXX,  XXX
#define PER_KEY_DATA_STACKED(dflt,                                            \
    R0C0, R0C1, R0C2, R0C3, R0C4,                                             \
    R1C0, R1C1, R1C2, R1C3, R1C4,                                             \
    R2C0, R2C1, R2C2, R2C3, R2C4,                                             \
                      R3C3, R3C4,                                             \
                                                                              \
    R0C5, R0C6, R0C7, R0C8, R0C9,                                             \
    R1C5, R1C6, R1C7, R1C8, R1C9,                                             \
    R2C5, R2C6, R2C7, R2C8, R2C9,                                             \
    R3C5, R3C6                                                                \
  )                                                                           \
    R0C0, R0C1, R0C2, R0C3, R0C4, R0C5, R0C6, R0C7, R0C8, R0C9,               \
    R1C0, R1C1, R1C2, R1C3, R1C4, R1C5, R1C6, R1C7, R1C8, R1C9,               \
    R2C0, R2C1, R2C2, R2C3, R2C4, R2C5, R2C6, R2C7, R2C8, R2C9,               \
    XXX,  XXX,  XXX,  R3C3, R3C4, R3C5, R3C6, XXX,  XXX,  XXX
}
}
EXPORT_DEVICE(kaleidoscope::device::brick::Squiggle)
}
#endif

Which basically I copied the following information:

And for Squiggle.cpp, I use the following snippet


#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
#ifdef ARDUINO_AVR_SQUIGGLE

#include "kaleidoscope/Runtime.h"
#include "kaleidoscope/driver/keyscanner/Base_Impl.h"
using KeyScannerProps = typename kaleidoscope::device::brick::SquiggleProps::KeyScannerProps;
using KeyScanner = typename kaleidoscope::device::brick::SquiggleProps::KeyScanner;
namespace kaleidoscope {
namespace device {
namespace brick {
const uint8_t KeyScannerProps::matrix_rows;
const uint8_t KeyScannerProps::matrix_columns;
constexpr uint8_t KeyScannerProps::matrix_row_pins[matrix_rows];
constexpr uint8_t KeyScannerProps::matrix_col_pins[matrix_columns];
template<> uint16_t KeyScanner::previousKeyState_[KeyScannerProps::matrix_rows] = {};
template<> uint16_t KeyScanner::keyState_[KeyScannerProps::matrix_rows] = {};
template<> uint16_t KeyScanner::masks_[KeyScannerProps::matrix_rows] = {};
template<> uint8_t KeyScanner::debounce_matrix_[KeyScannerProps::matrix_rows][KeyScannerProps::matrix_columns] = {};
ISR(TIMER1_OVF_vect) {
  Runtime.device().keyScanner().do_scan_ = true;
}
} // namespace brick
} // namespace device
} // namespace kaleidoscope
#endif
#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD

And that's it. Basically that's all I need to do to port it.

Additional Section: Keymap

The following snippet is my keymap from QMK that I've brought over to Kaleidoscope.

#include "Kaleidoscope.h"
#include "Kaleidoscope-Macros.h"
#include "Kaleidoscope-Qukeys.h"

#define MO(n) ShiftToLayer(n)
#define TG(n) LockLayer(n)

enum {
  DVORAK,
  RAISE,
  LOWER
};

enum {
  RESET
};

#define Key_Exclamation LSHIFT(Key_1)
#define Key_At          LSHIFT(Key_2)
#define Key_Hash        LSHIFT(Key_3)
#define Key_Dollar      LSHIFT(Key_4)
#define Key_And         LSHIFT(Key_7)
#define Key_Star        LSHIFT(Key_8)
#define Key_Plus        LSHIFT(Key_Equals)
#define Key_Question    LSHIFT(Key_Slash)
#define Key_Underscore  LSHIFT(Key_Minus)

#define Sft_A SFT_T(A)
#define Sft_S SFT_T(S)
#define Ctl_O CTL_T(O)
#define Ctl_N CTL_T(N)
#define Alt_E ALT_T(E)
#define Alt_T ALT_T(T)
#define Gui_I GUI_T(I)
#define Gui_D GUI_T(D)

#define Rse_Space     LT(1, Space)
#define Lwr_Backspace LT(2, Backspace)

#define Sft_Escape    SFT_T(Escape)
#define Alt_Enter     ALT_T(Enter)

#define Mcr_Reset     M(RESET)

/* *INDENT-OFF* */
KEYMAPS(
  [DVORAK] = KEYMAP_STACKED
  (
       Key_Semicolon ,Key_Comma ,Key_Period ,Key_P         ,Key_Y
      ,Sft_A         ,Ctl_O     ,Alt_E      ,Key_U         ,Gui_I
      ,Key_Quote     ,Key_Q     ,Key_J      ,Key_K         ,Key_X
                                            ,Lwr_Backspace ,Sft_Escape

      ,Key_F     ,Key_G ,Key_C ,Key_R ,Key_L
      ,Gui_D     ,Key_H ,Alt_T ,Ctl_N ,Sft_S
      ,Key_B     ,Key_M ,Key_W ,Key_V ,Key_Z
      ,Alt_Enter ,Rse_Space
  ),

  [RAISE] = KEYMAP_STACKED
  (
       Key_Exclamation ,Key_At           ,Key_UpArrow   ,Key_LeftCurlyBracket ,Key_RightCurlyBracket
      ,Key_Hash        ,Key_LeftArrow    ,Key_DownArrow ,Key_RightArrow       ,Key_Dollar
      ,Key_LeftBracket ,Key_RightBracket ,Key_LeftParen ,Key_RightParen       ,Key_And
                                                        ,___                  ,___

      ,Key_Backslash ,Key_7 ,Key_8 ,Key_9 ,Key_Star
      ,Key_Equals    ,Key_4 ,Key_5 ,Key_6 ,Key_0
      ,Key_Backtick  ,Key_1 ,Key_2 ,Key_3 ,Key_Plus
      ,___           ,___
   ),

  [LOWER] = KEYMAP_STACKED
  (
       Key_Escape    ,Key_Question   ,Key_Underscore ,Key_F1 ,Key_F2
      ,Key_LeftShift ,Key_Tab        ,Key_PageUp     ,Key_F5 ,Key_F6
      ,Mcr_Reset     ,Key_ScrollLock ,Key_PageDown   ,Key_F9 ,Key_F10
                                                     ,___    ,___

      ,Key_F3  ,Key_F4  ,Key_Minus ,Key_Slash   ,Mcr_Reset
      ,Key_F7  ,Key_F8  ,Key_Home  ,Key_LeftAlt ,Key_Enter
      ,Key_F11 ,Key_F12 ,Key_End   ,Key_Insert  ,Key_Slash
      ,___     ,___
   )
)
/* *INDENT-ON* */

KALEIDOSCOPE_INIT_PLUGINS(Macros, Qukeys, MagicCombo);

const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
  switch (macroIndex) {
  case RESET:
    Kaleidoscope.rebootBootloader();
    break;
  default:
    break;
  }

  return MACRO_NONE;
}

void setup() {
  Kaleidoscope.setup();
}

void loop() {
  Kaleidoscope.loop();
}

Something?

I still cannot emulate QMK's combo behaviour to Kaleidoscope.



This material is shared under the CC-BY License.