Rabbit Slide Show

On a Keyboard Ruby on Board: PicoRuby and PRK Firmware

2021-11-08

Description

Presentation slide for RubyConf 2021

Text

Page: 1

On a Keyboard
Ruby on Board
PicoRuby and PRK Firmware
hasumikin
RubyConf 2021 / Nov 8-10

Page: 2

Wake up, Ruby...

Page: 3

The Micon has you...

Page: 4

Follow the white rabbit.

Page: 5

Hi!

Page: 6

PicoRuby
and
PRK Firmware

Page: 7

PicoRuby
is a Ruby interpreter
for Microcontorollers

Page: 8

PRK Firmware
is a keyboard firmware
framework in PicoRuby

Page: 9

PRK Firmware

Page: 10

Hardware became familiar
You can design your PCB even on a web
browser
PCB manufacturers are willing to
accept your PCB data via the internet
You can receive your PCB or even PCBA
(assembled PCB) that arrives from
overseas

Page: 11

When you build a keyboard
You can choose
Keycaps for appearance and touch
https://talpkeyboard.net/?category_id=59be183f428f2d49120007b1

Page: 12

When you build a keyboard
You can choose
Keycaps for appearance and touch
Switches for touch
https://talpkeyboard.net/?category_id=59cf8860ed05e668db003f5d

Page: 13

When you build a keyboard
You can choose
Keycaps for appearance and touch
Switches for touch
Layout for usability and appearance

Page: 14

When you build a keyboard
You can choose
Keycaps for appearance and touch
Switches for touch
Layout for usability and appearance
Firmware for programmability

Page: 15

Keyboard firmware
QMK Firmware ... C
KMK Firmware ... Python
PRK Firmware ... Ruby

Page: 16

Keyboard firmware

Page: 17

Stargaze at picoruby/prk_firmware!

Page: 18

e.g.) picoruby/prk_pipigherkin
Gherkin for the Raspberry Pi Pico
PCB available on talpkeyboard.net
Easy to explain but hard for newbies to build

Page: 19

e.g.) prk_pipigherkin/keymap.rb
# Initialize Keyboard
kbd = Keyboard.new
# Initialize GPIO pins
kbd.init_pins(
[ 12, 11, 10, 9, 8 ], # row0, row1,... respectively
[ 7, 6, 5, 4, 3, 2 ] # col0, col1,... respectively
)

Page: 20

e.g.) prk_pipigherkin/keymap.rb
# Default keymap
kbd.add_layer :default, %i(
KC_Q
KC_W
KC_E
KC_A
KC_S
KC_D
Z_LSFT
X_LGUI C_LALT
)
kbd.add_layer :raise, %i(
KC_EXLM
KC_AT
KC_HASH
KC_LABK
KC_LCBR KC_LBRACKET
KC_RABK
KC_RCBR KC_RBRACKET
)
kbd.add_layer :lower, %i(
KC_1
KC_2
KC_3
KC_TAB
KC_NO
KC_QUOTE
KC_ESCAPE KC_LGUI KC_LALT
)
# Mode keys
kbd.define_mode_key :Z_LSFT,
# ...
kbd.define_mode_key :UNDS_RSFT,
kbd.define_mode_key :ENT_RAISE,
kbd.define_mode_key :SPC_LOWER,
KC_R
KC_F
V_LCTL
KC_T
KC_Y
KC_U
KC_G
KC_H
KC_J
SPC_LOWER ENT_RAISE B_RCTL
KC_DLR KC_PERC
KC_LPRN KC_MINUS
KC_RPRN ADJUST
KC_O
KC_L
M_RGUI
KC_P
KC_BSPACE
UNDS_RSFT
KC_CIRC
KC_AMPR
KC_ASTER KC_EQUAL KC_PLUS
KC_LEFT
KC_DOWN
KC_UP
KC_RIGHT KC_BSPACE
ENT_RAISE KC_BSLASH KC_COMMA KC_DOT
KC_SLASH
KC_4
KC_5
KC_6
KC_DQUO KC_MINUS KC_GRAVE
KC_LCTL SPC_LOWER ADJUST
[ :KC_Z,
KC_I
KC_K
N_RALT
KC_7
KC_TILD
KC_RCTL
:KC_LSFT, 150, 150 ]
[ :KC_UNDS, :KC_RSFT, 200, 150 ]
150, 150 ]
[ :KC_ENTER, :raise,
[ :KC_SPACE, :lower,
300, 250 ]
KC_8
KC_PIPE
KC_RALT
KC_9
KC_0
KC_COLON KC_SCOLON
KC_RGUI KC_RSFT

Page: 21

picoruby/prk_firmware/releases

Page: 22

Install into the microcontroller
Connect USB cable while
pressing "boot button"
RPI-RP2 will be mounted
as storage, then

Page: 23

Install into the microcontroller
Boot button on Pro Micro RP2040

Page: 24

Install into the microcontroller

Page: 25

A new drive automatically mounted

Page: 26

Drag & drop keymap.rb

Page: 27

T
H
E
KEYMAP
RELOADED

Page: 28

Unlike QMK Firmware,
You don't need
to compile
anything!

Page: 29

Demonstrations
meishi keypad
meishi means "name card"
Four keys macro pad

Page: 30

Fibonacci number
(DEMO)

Page: 31

Fibonacci in keymap.rb
class Fibonacci
def initialize
@a = 0 ; @b = 1
end
# Omits F0 and F1
def take
result = @a + @b
@a = @b
@b = result
end
end
fibonacci = Fibonacci.new
kbd.define_mode_key :FIBONACCI,
[ Proc.new { kbd.macro fibonacci.take },
:KC_NO, 300, nil ]

Page: 32

Password generator
(DEMO)

Page: 33

Password generator in keymap.rb
class Password
def initialize(c = nil)
@c = c || 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!@#$%^&*()=-+/[]{}<>'
end
def generate
unless @srand
srand(board_millis) # generate rand()'s seed by board_millis
@srand = true
end
password = ""
while true
i = rand % 100
password << @c[i].to_s
break if password.length == 8
end
return password
end
end
passwd = Password.new
kbd.define_mode_key :PASSWD,
[ Proc.new { kbd.macro passwd.generate, [] },
:KC_NO, 300, nil ]
Use it at your own risk

Page: 34

Extend your keyboard
If you wanna input ";" with SHIFT and
":" without SHIFT,
kbd.before_report do
kbd.invert_sft if kbd.keys_include?(:KC_SCOLON)
end
You no longer need to modify .vimrc

Page: 35

Extend your keyboard
class Keyboard
def invert_ctl
#
KC_LCTL: 0b00000001
#
KC_LSFT: 0b00000010
#
KC_LALT: 0b00000100
#
KC_LGUI: 0b00001000
#
KC_RCTL: 0b00010000
#
KC_RSFT: 0b00100000
#
KC_RALT: 0b01000000
#
KC_RGUI: 0b10000000
if (@modifier & 0b00010001) > 0
@modifier &= 0b11101110
else
@modifier |= 0b00000001
end
end
end
kbd.before_report do
kbd.invert_ctl if kbd.keys_include?(:KC_V)
end

Page: 36

Ruby-mode key
(DEMO)

Page: 37

T
H
E
KEYBOARD
REVOLUTIONS

Page: 38

Micon of PRK Firmware
Target MCU is "RP2040"
Raspberry Silicon
Implemented on Raspberry Pi Pico
264KB RAM
Arm 32bit Cortex-M0+ (dual)

Page: 39

Micon of PRK Firmware
RP2040 assembled
on Pro Micro
SPARKFUN
PRO MICRO - RP2040
[model no. DEV-18288]
https://www.sparkfun.com/products/18288

Page: 40

PicoRuby

Page: 41

https://ruby.or.jp/en

Page: 42

Multitasking

Page: 43

Multitasking

Page: 44

T
H
E
RUBY
RESURRECTIONS

Page: 45

Architecture
An mruby app generally consists of VM
code and VM
mruby-compiler has somehow big footprint
It isn't assumed to be embedded

Page: 46

Architecture
Technically, you can embed also Ruby
script and mruby-compiler
It makes sense if the computing resource is big
enough

Page: 47

Architecture
mruby/c is a smaller VM for one-chip
microcontrollers
Naturally, we don't embed mruby-compiler with it

Page: 48

Architecture
PicoRuby = PicoRuby-compiler + mruby/cVM

Page: 49

Architecture
As you know, the syntax of Ruby is
complicated
Easy to imagine that a Ruby compiler
consumes large memory

Page: 50

Architecture
You might think that you can use a
microcontroller that has a big RAM
The answer is "Yes and No"
You should do it if you can
The bigger RAM, the bigger electric
energy consumption
The bigger resource, the more
expensive in general

Page: 51

To make a small Ruby compiler
mruby-compiler depends on mruby
The main reason for big footprint
PicoRuby compiler should be coded from scratch
To make PicoRuby compiler small
Every fine logic should be minimal
Lemon parser generator instead of Bison/Yacc
A part of SQLite project

Page: 52

RAM consumption
$ valgrind
--tool=massif
--stacks=yes
mrbc hello_world.rb
\
\
\
$ valgrind
\
--tool=massif
\
--stacks=yes
\
picorbc hello_world.rb
$ ms_print massif.out.xxx | less

Page: 53

hello_world.rb (20 bytes)
puts "Hello World!"

Page: 54

mrbc hello_world.rb -> 133.5 KB
--------------------------------------------------------------------------------
Command:
mrbc hello_world.rb
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
133.5^
#
|
#
|
#
|
#
|
#
|
#
|
#
|
#
|
#
|
@:::@#:
|
:::@:::::::@:::@:::@:::@#::
|
::::::@:::::@::@:::@::@::@:: ::::@:::@:::@:::@#::
|
@@@@::::::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
|
@@
:: :::: :@:: ::@::@: :@: @::@:: ::::@:::@:::@:::@#::
0 +----------------------------------------------------------------------->ki
0
936.3

Page: 55

picorbc hello_world.rb -> 16.98 KB
--------------------------------------------------------------------------------
Command:
picorbc hello_world.rb
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
16.98^
#
|
@::@# :::@:: @
|
:@::@#::::@::::@ :::
|
:@::@#::::@::::@::::
|
:@::@#::::@::::@::::
|
:@::@#::::@::::@::::
|
:@::@#::::@::::@::::
|
:@::@#::::@::::@::::@
|
:@::@#::::@::::@::::@
|
:@::@#::::@::::@::::@
|
@@::@::@#::::@::::@::::@
|
@ ::@::@#::::@::::@::::@
|
@ ::@::@#::::@::::@::::@
|
::
@ ::@::@#::::@::::@::::@
|
: :
@ ::@::@#::::@::::@::::@
|
: :
@ ::@::@#::::@::::@::::@
|
: ::
@ ::@::@#::::@::::@::::@
|
: ::
@ ::@::@#::::@::::@::::@
|
: ::
@ ::@::@#::::@::::@::::@
|
:: :: :::::::::::::::::::::::::::@:::@: @ ::@::@#::::@::::@::::@:
0 +----------------------------------------------------------------------->ki
0
222.0

Page: 56

keymap.rb of meish2 (2388 bytes)
while !$mutex
relinquish
end
kbd = Keyboard.new
kbd.init_pins(
[ 6, 7 ],
# row0, row1
[ 28, 27 ] # col0, col1
)
kbd.add_layer :default, %i[ RUBY_GUI KC_1
RAISE_ENTER LOWER_SPACE ]
kbd.add_layer :raise,
%i[ FIBONACCI PASSWD RAISE_ENTER ADJUST
]
kbd.add_layer :lower,
%i[ KC_E
KC_F
RAISE_ENTER LOWER_SPACE) ]
kbd.add_layer :adjust, %i[ KC_SCOLON KC_LSFT RAISE_ENTER ADJUST
]
kbd.define_mode_key :RAISE_ENTER, [ :KC_ENTER,
:raise,
200, 150 ]
kbd.define_mode_key :LOWER_SPACE, [ :KC_SPACE,
:lower,
300, 400 ]
kbd.define_mode_key :ADJUST,
[ nil,
:adjust, nil, nil ]
kbd.define_mode_key :RUBY_GUI,
[ Proc.new { kbd.ruby }, :KC_RGUI, 300, nil ]
class Fibonacci
def initialize
@a = 0 ; @b = 1
end
def take
result = @a + @b
@a = @b
@b = result
end
end
fibonacci = Fibonacci.new
kbd.define_mode_key :FIBONACCI, [ Proc.new { kbd.macro fibonacci.take }, :KC_NO, 300, nil ]
class Password
def initialize
@c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!@#$%^&*()=-+/[]{}<>'
end
def generate
unless @srand
# generate seed with board_millis
srand(board_millis)
@srand = true
end
password = ""
while true
i = rand % 100
password << @c[i].to_s
break if password.length == 8
end
return password
end
end
password = Password.new
kbd.define_mode_key :PASSWD, [ Proc.new { kbd.macro password.generate, [] }, :KC_NO, 300, nil ]
kbd.before_report do
kbd.invert_sft if kbd.keys_include?(:KC_SCOLON)
end
kbd.start!

Page: 57

mrbc keymap.rb -> 206.1 KB
--------------------------------------------------------------------------------
Command:
mrbc keymap.rb
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
206.1^
##
|
@#
|
@#
|
@#
|
@#
|
@#
|
:@#
|
:@#
|
:@#
|
::::@#
|
:: :@# :
|
@::::::::::::::::::::: :@# :
|
@::: :: ::: : :: ::::: :@# :
|
@:::::@::: :: ::: : :: ::::: :@# :
|
::::::::@@::::::::@:: : @::: :: ::: : :: ::::: :@# ::
|
@@::::::::: : :: @ :: : :: @:: : @::: :: ::: : :: ::::: :@# ::
|
@ : :: : :: : :: @ :: : :: @:: : @::: :: ::: : :: ::::: :@# ::
|
@ : :: : :: : :: @ :: : :: @:: : @::: :: ::: : :: ::::: :@# ::
|
@ : :: : :: : :: @ :: : :: @:: : @::: :: ::: : :: ::::: :@# ::
|
@ : :: : :: : :: @ :: : :: @:: : @::: :: ::: : :: ::::: :@# ::
0 +----------------------------------------------------------------------->Mi
0
1.476

Page: 58

picorbc keymap.rb -> 61.98 KB
--------------------------------------------------------------------------------
Command:
picorbc keymap.rb
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
61.98^
#
|
@#
|
@ @@#
|
@@:@@#
|
:
@
@@:@@#
|
@::::@:@:::@@:@@#
|
:
@::::@:@:::@@:@@#
|
@@:@:::@::::@::::@:@:::@@:@@#
|
@@:@:::@::::@::::@:@:::@@:@@#
|
::::@:::::@@:@:::@::::@::::@:@:::@@:@@#
|
::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
|
@@@:::
::::::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
|
@@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
|
@:::: ::::@@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
|
@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
| @@: :::::@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
| @ ::: :::@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
| @ ::: :::@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
| @ ::: :::@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
| @ ::: :::@:::::::: @@ :::@::::: ::: @: :: @@:@:::@::::@::::@:@:::@@:@@#
0 +----------------------------------------------------------------------->Mi
0
7.101

Page: 59

Summary
#
script size
mrbc
picorbc
====================================================
hello_world.rb
20 bytes
133.5 KB
16.98 KB
----------------------------------------------------
keymap.rb(meishi2) 2388 bytes
206.1 KB
61.98 KB
----------------------------------------------------
Note: Figures are measured in 64 bit Ubuntu

Page: 60

Implementation
Depending on only libc and less
Considering paddings and Pooled
allocation
Freeing heap memory in a loop instead
of recursion

Page: 61

Depending on only libc and less
glibc for desktop/server application
newlib for embedded application
Small code size
Doesn't have regex.c

Page: 62

Padding in a struct
struct LinkedList {
struct LinkedList *next;
uint8_t
value;
}
// 4 bytes (in 32-bit architecture)
// 1 byte
sizeof(LinkedList);
No
=> 5
=> 8
Yes
✔Data structure alignment (at least in C99)
pointer[4] + uint8_t[1] + padding[3] = sum[8]
You need to pack them well

Page: 63

Paddings waste memomy
LinkedList *top;
(...)
top->next->next->next->next; // 5 items in the list
Consumes 40 bytes to store 5 values
of uint8_t
Total size of pointers will be 1 KB
if there are 250 items in a list
Pooled allocation

Page: 64

Pooled allocation
[n]: bytes
*next[4] ----------->
size[2]
index[2]
data[sizeof(Data)]
data[sizeof(Data)]
...
data[sizeof(Data)]
*next[4] ----------->
size[2]
index[2]
data[sizeof(Data)]
data[sizeof(Data)]
...
data[sizeof(Data)]
*next[4] ------------>
size[2]
index[2]
data[sizeof(Data)]
data[sizeof(Data)]
...
data[sizeof(Data)]
Reduces paddings of data structure
alignment
Reduces the number of pointers
Reduces fragmentation

Page: 65

Before improvement -> 53.20 KB
--------------------------------------------------------------------------------
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
53.20^
#
|
#
|
@# :::
|
@# : :
|
@# : :
|
@@# : :
|
@@# :: :
|
@@#::: :
|
@@#::: :
|
@@::@@#::: :
|
@@: @@#::: :
|
@@:@:::@::::@@: @@#::: ::
|
@::@::@ :@:: @:: :@@: @@#::: ::
|
@:::@@@:@::@: @ :@:: @:: :@@: @@#::: ::
|
@@:@::@:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
::@@@ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
:::::: :::::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
:::::@@:: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
: :: @ :: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
: :: @ :: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
0 +----------------------------------------------------------------------->Mi
0
1.616

Page: 66

After improvement -> 24.21 KB
--------------------------------------------------------------------------------
Massif arguments:
--stacks=yes
--------------------------------------------------------------------------------
KB
24.21^
#
|
@ @:@:@:#
|
@:@@@:@:@:@:#:
@
|
@@:::@:@@@:@:@:@:#::::::@
|
@ :::@:@@@:@:@:@:#::::::@
|
@ :::@:@@@:@:@:@:#::::::@
|
@ :::@:@@@:@:@:@:#::::::@
|
@ :::@:@@@:@:@:@:#::::::@
|
@@:::::::::::::::@::::: ::::@::@::::::@ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
|
::::@ ::::::: :::: : @:: ::@:: :@::@:: :: @ :::@:@@@:@:@:@:#::::::@
0 +----------------------------------------------------------------------->Mi
0
1.082

Page: 67

Freeing in a loop
Freeing long linked list in recursion
causes "stack spike"
Because the call stack becomes deep until the
recursive call reaches the bottom of the list
Freeing in a loop instead of recursion
The call stack doesn't stack up at the cost
of a bit complicated code

Page: 68

Summary
#
script size
mrbc
picorbc
====================================================
hello_world.rb
20 bytes
133.5 KB
16.98 KB
----------------------------------------------------
keymap.rb(meishi2) 2388 bytes
206.1 KB
61.98 KB
----------------------------------------------------
Note: Figures are measured in 64 bit Ubuntu
Finally, I could make
PicoRuby work on the Micon!

Page: 69

The Matrix
Micon
is Everywhere.

Page: 70

On a Keyboard
Ruby on Board

Page: 71

written and presented by
HASUMIKIN

Page: 72

RUBY
supported by
ASSOCIATION and
MONSTARLAB

Page: 73

mentor
MATZ

Page: 74

follow the

Page: 75

an
ESSENTIALLY RUBY
production

Other slides