RubyConf 2021

Picoruby and PRK Firmware

Picoruby and PRK Firmware

by Hitoshi Hasumi

In this presentation titled "Picoruby and PRK Firmware" delivered at RubyConf 2021 by Hitoshi Hasumi, the speaker introduces two innovative projects: PicoRuby, an alternative implementation of the mruby interpreter, and PRK Firmware, a keyboard firmware framework utilizing PicoRuby. The overarching theme is the seamless integration of software and hardware for DIY keyboard enthusiasts and microcontroller applications.

Key Points Discussed:
- Introduction to PRK Firmware:
- PRK Firmware allows users to write keymaps in Ruby, leveraging Ruby's features.
- It serves as an alternative to existing firmware options like QMK (C-based) and KMK (CircuitPython-based).

  • The Revolution in DIY Keyboards:

    • The evolution of the internet has removed barriers in creating printed circuit boards and building keyboards.
    • Essential components include the aesthetic keycap, the mechanical switch, the layout, and the firmware that controls the keyboard behavior.
  • Demonstration of PRK Firmware:

    • Keymaps can be defined in pure Ruby, enabling a more approachable interface for keyboard programming.
    • Automatic recompilation of keymaps makes for easier adjustments without manual compilation.
  • PicoRuby Overview:

    • Designed to run on one-chip microcontrollers, specifically targeting the RP2040 chip from Raspberry Pi.
    • Helps bridge the gap between programming and hardware with a focus on low resource usage to support microcontroller limitations.
  • Technical Benefits of PicoRuby:

    • The PicoRuby compiler is significantly smaller than traditional MRB compilers, minimizing RAM consumption.
    • Demonstrated memory efficiency by compiling common scripts (like "Hello, World!") and keymaps.
  • Support from Ruby Community:

    • The Ruby Association provides grants to enhance Ruby language developments.
    • Mentorship from prominent Ruby figures has been instrumental in the project’s development.
  • Conclusion:

    • The integration of Ruby in hardware programming opens avenues for quickly developing IoT systems.
    • Highlights the importance of co-designing software and hardware in modern development approaches, showing that robust applications can be built efficiently on microcontrollers.
    • The talk underscores that Ruby programming can be as revolutionary in hardware as it has been with frameworks like Ruby on Rails in web development.
00:00:42 Today, I will introduce two products that I am developing: PicoRuby and PRK Firmware. PicoRuby is an alternative implementation of the mruby interpreter designed for one-chip microcontrollers.
00:00:55 PRK Firmware is the world's first keyboard firmware framework utilizing PicoRuby. Firstly, I would like to discuss PRK Firmware.
00:01:05 However, before that, as I’m not sure if you are familiar with DIY keyboards, let me explain a little. In today’s talk, I want to focus on the approach to software and hardware. Ten years ago, there was a significant barrier between software and hardware.
00:01:27 It was difficult for software programmers to create printed circuit boards, as it was an unfamiliar field. However, the internet has profoundly changed these circumstances. Nowadays, you can design an electrical circuit using a web browser and send the data to a PCB manufacturer, which accepts small orders and can assemble almost a final product if you provide a proper bill of materials (BOM).
00:01:46 Internet and cloud services have played significant roles in this production revolution, empowering many people to become keyboard builders. In the DIY keyboard community, while there are many factors to consider, the main points are, in my humble opinion: the keycap, which is the face of a keyboard and significantly affects its appearance.
00:02:11 The switch is another core element that leads us to refer to DIY keyboards as mechanical keyboards. Some of my friends have become obsessed with lubricating switches, and they’ve described the experience as surreal, wondering if they are awake or still dreaming.
00:02:44 Next is the layout, which is essential when building a keyboard. This is the Corne keyboard (crkbd), which is a notable split-type keyboard. Thanks to its design that separates left and right, users can type in a more comfortable posture. The small number of switches is compensated by using layered keymaps that can be defined in the firmware.
00:03:09 Lastly, firmware is crucial, and you need to use one of the available firmware when building a keyboard. I have listed three of them here: QMK, which is the most famous keyboard firmware, written in C. If you choose QMK, you need to write in C to define your keymap.
00:03:26 And then there's KMK, a newer keyboard firmware, which is based on CircuitPython. PRK Firmware is an alternative modern firmware platform that I released this year. It currently has only a few stars, so I would appreciate your support by clicking the star on the PRK Firmware page.
00:03:55 Anyway, let’s take a look at how you can use PRK Firmware. This is the Pipe Working, so-called 30-keyboard because it has 30 keys. You might be wondering if 30 keys are enough. Honestly, I don't think it's sufficient for daily work, but I can manage with fewer keys for small tasks. Besides, doesn’t it look cute?
00:04:25 Above all, this keyboard is perfect for explaining the concept, as it has fewer keys and its keymap is compact enough to visualize. The keymap.rb file for PipeLinking initializes the keyboard class and the GPIO pins according to the circuit design. Ultimately, you define the key maps as the code demonstrates.
00:04:59 There are three layers; for example, you can multiply a key layout in this way. Now, let’s take a closer look at the keycodes. Keys with the 'kc' prefix are predefined, while other keys like 'ent,' 'raise,' and 'spc' are defined by the user. You can specify your mod key using the define mod key method.
00:05:13 The ‘ent’ key works as the enter key if you press and release it quickly. Conversely, if you hold the key down, the entire keymap switches to the raise layer, which offers symbols such as exclamation marks and hash symbols. The important point is that the keymap.rb file is a pure Ruby source code.
00:05:41 I understand that you might still be wondering why it’s exciting to write a key map in Ruby. Don't worry, I will address that later. First, let me finish demonstrating the basic usage of PRK Firmware. Now that you have your keymap.rb in place, the next step is to download the release binary of PRK Firmware from GitHub.
00:06:06 Once you unzip the released file, you will get a .uf2 file. Then, connect the USB cable to the microcontroller while pressing the boot button on it. I will elaborate on the microcontroller later, so let's skip the details for now. You should see the RPI RP2 drive mounted on your computer.
00:06:40 Drag and drop the .uf2 file into the disk, and the drive will remount as PRK Firmware. The final step is to drag and drop the keymap.rb file that you just created. Here comes the first highlight of the show: PRK Firmware will automatically reload your keymap.
00:07:05 You’ll notice something interesting if you’re a keyboard enthusiast: you no longer need to compile your keymap manually. PRK Firmware will compile your keymap on the microcontroller on your behalf. The technical secret behind the compilation in PicoRuby is another topic I will discuss in the second half of this session.
00:07:29 Let me now show you some demonstrations. This is a mesh keypad that I will be using from now. 'Mesh' means 'name card' in Japanese, and it is a very popular kit among keyboard novices in Japan, as it is easy to build. The first demonstration involves the Fibonacci sequence.
00:07:58 Now you are seeing my terminal. When I push a key on the mesh keypad, it will output '1' first, then '2', '3', '5', '8', continuing according to the Fibonacci rule. This serves as an example of how you can extend your keymap using Ruby.
00:08:26 The implementation of the Fibonacci class is quite simple. The first two numbers are created in the initialize method, and subsequent numbers are returned each time you call the 'next' method. To implement it, all you need to do is pass a Proc object to the define mod key method, which will be called when you press the corresponding key.
00:08:50 The PicoRuby runtime environment retains your Fibonacci object and can repeatedly call the 'next' method. The next example is a password generator: another key on the mesh keypad generates a random eight-letter password each time it is pressed.
00:09:05 While skipping over the details of its implementation, it's important to note that whether the random string is strong enough as a password depends on both the implementation and the quality of the pseudo-random number generator in the chip.
00:09:59 Next, I have a more practical example, particularly for Vim users. Generally, you configure your .vimrc file to ensure that the colon key and the same key are seamlessly blended since the colon is one of the most frequently used keys in Vim, and you want to type it without using the shift modifier. This is straightforward to achieve, as PRK Firmware includes an 'invert shift' method out of the box.
00:10:30 You simply write a condition for the functionality you want to implement in a 'before report' filter, which is similar to before filters in Rails. The implementation of the invert shift function itself is also quite simple.
00:10:58 I’m not certain whether inverting the control modifier is useful, but let's examine the invert control method instead of invert shift. This time, it serves as an example of how to invert a modifier key before it reports its keycode. Modifier keys are represented in 8-bit data; the rightmost bit represents the left control key and the fifth bit denotes the right control key.
00:11:26 Once you grasp this concept, the rest is straightforward. It becomes simply a series of bit operations. The key point here is that these implementations can be written in the keymap.rb file, and the PRK Firmware will compile and reload them on the fly.
00:11:52 The last demonstration showcases the Ruby mode key. Do you ever want to use Ruby while taking notes in a text editor? I do. My keyboard starts to blink red when I press the Ruby key. I apologize for not being able to show you the actual blinking keyboard, but what is real? How do you define real?
00:12:23 If we're talking about what you can write, what you can refactor, or what you can run and see, then 'real' simply refers to electrical signals interpreted by PRK Firmware. Consider this: what if you’d like to know the outcome of three raised to the seventh power? My keyboard will provide you with the answer.
00:12:54 The answer is 2187. As evident from the fact that the keyboard can dynamically compile keymap.rb, your keyboard effectively has a Ruby interpreter built into it. To put it another way, your keyboard has an Interactive Ruby (IRB) session running inside.
00:13:26 With this IRB-like capability, you can create Ruby objects, and your keyboard will remember them. Although it may sound like a joke, you can even create an event class if you wish.
00:13:42 Of course, you can instantiate a class and call its methods. I believe it can be described as a kind of revolution. Now, let’s shift to the next main topic: PicoRuby, but before that, I want to mention the microcontroller.
00:14:09 PRK Firmware runs on an RP2040 chip integrated into the Raspberry Pi Pico. The Raspberry Pi Pico is priced at just $4, making it a very affordable option. It boasts excellent functionality and performance.
00:14:25 The RP2040 chip comes with 264 kB of RAM and a 32-bit ARM Cortex M0+ processor. In the image displayed, the entire screen board represents the Raspberry Pi Pico, with the RP2040 chip located at the center.
00:14:48 Furthermore, the Raspberry Pi Foundation distributes the RP2040 chip, allowing other manufacturers to create compatible boards, such as the Pro Micro, which is an Arduino-compatible board and the most popular in the DIY keyboard community. In fact, we can purchase RP2040-based Pro Micros. These are the reasons why I chose the RP2040 as the target chip for PRK Firmware.
00:15:25 Now, we will transition to PicoRuby, the programming language for PRK Firmware. Before diving deeper, I would like to share how the Ruby community supports us in enhancing the ecosystem.
00:15:50 The Ruby Association in Japan is a non-profit organization dedicated to advancing the Ruby programming language. They offer a grant program annually that provides financial assistance for developing projects related to Ruby implementations, libraries, and frameworks.
00:16:22 Fortunately, my project, PicoRuby, was selected for support last year. Additionally, Yukihiro Matsumoto, the creator of Ruby, has provided mentorship throughout the project. I can even text him for help with understanding how Ruby works, why he designed it a certain way, and to gather his thoughts on creating a better implementation.
00:16:47 Our company has also been supportive, allowing me to develop PicoRuby during work hours. Thanks to these significant contributions, I successfully submitted the final report. Anyone, including you, can apply for the Ruby Association grant; however, the application for this year has already closed. I encourage you to consider applying next year.
00:17:14 Now, let’s move forward to PicoRuby. I think it’s a good starting point to explain how the PicoRuby runtime works, as PicoIRB serves as a solid example of that runtime.
00:17:35 It is a multitasking system that manages multiple tasks, switching between them every millisecond. This image shows the transition patterns of the status of tasks. The orange boxes represent tasks, where only one task can be in the 'running' state at any given time.
00:18:11 Once a task is in the 'running' state, it executes its VM code for one millisecond before moving to the back of the ready queue, where multiple tasks await processing. Tasks include scanning pressed keys and controlling LED blinking, among others. This rotation showcases normal task processing.
00:18:30 On the left side of the image, you see other tasks. If a task internally invokes the 'lock' method of a mutex object and fails—most likely because another task currently holds exclusive rights—the task's status will change to 'waiting'.
00:19:01 The waiting task will remain on hold until the mutex is released. Similar situations arise when a task calls the 'sleep' method and wakes up after a specified duration. A task can also suspend itself, pausing until another task resumes it. All tasks I’ve described so far function as infinite loops.
00:19:35 An application for microcontrollers comprises multiple infinite loops. However, if a task doesn’t have an infinite loop and its execution reaches the end of the script, the task's state becomes dormant, meaning it will return memory to the system upon completion.
00:20:16 The task associated with the Ruby mode of IRB that I just demonstrated is one such task. However, I don’t want it to be freed since I plan to use the Ruby object again, which led me to create a special task called the sandbox task. This task is never released and is resurrected repeatedly.
00:20:41 Now, let’s discuss PicoRuby. It’s an alternative mRuby implementation designed for one-chip microcontrollers. The motivation behind developing PicoRuby is to cater to one-chip microcontrollers.
00:21:05 An mRuby application consists of both VM code and the VM itself. Unlike standard Ruby applications, you can separate the entire processing, compile, and run process. Because of this design concept, the RAM consumption of the mRuby compiler is relatively low.
00:21:22 Technically, you can also embed Ruby scripts and the mRuby compiler if adequate memory is available. MRB/C is a smaller virtual machine than mRuby that can execute mRuby intermediate code on a one-chip microcontroller. Therefore, combining MRB-C with the mRuby compiler can lead to smaller code size.
00:22:06 However, I’m developing the PicoRuby compiler, which integrates the PicoRuby compiler with the MRB/C virtual machine.
00:22:18 Having been a member of the MRB/C committee for some years, I've envisioned how to make these systems work together. However, implementing this integration comes with technical challenges, primarily the complexity of Ruby's syntax.
00:22:45 This complexity is part of what allows us to write code as flexibly as we do, which is why we love Ruby. However, creating a Ruby compiler with a small footprint demands considerable time and effort.
00:23:02 You might think that a larger microcontroller like ESP32 could facilitate the integration of mRuby with its compiler, but we cannot always rely on such powerful microcontrollers. Larger RAM means greater energy consumption, hence the motivation to create a smaller compiler continues to hold significance.
00:23:25 In fact, the existing mRuby compiler relies heavily on mRuby itself, which I believe is a primary reason for its substantial memory footprint. Dependency on mRuby permits the use of essential mRuby objects, such as arrays and the compilation process. Conversely, I need to make each file logic minimal in the PicoRuby compiler.
00:23:43 To achieve this, I employ Lemon as the parser generator instead of Bison. Bison is the most commonly used parser generator, utilized in both mRuby and Cerebri. Lemon originated as the parser generator for SQLite.
00:24:00 One day, I asked Matt about creating a Ruby compiler suitable for microcontrollers. He suggested that Lemon could potentially produce smaller code, which is why I’m currently using it. However, to be honest, I'm still uncertain if Lemon genuinely aids in achieving a smaller compiler since I haven't conducted detailed comparisons between the two.
00:24:32 Nevertheless, it is true that the Lemon-based parser is sufficiently compact and operates smoothly up to this point. By emphasizing small code size, we aim for minimal ROM consumption; however, RAM is even more critical. I will now explain how I am maintaining low RAM consumption in the PicoRuby compiler.
00:25:14 Let’s explore the methods being employed to achieve this compression.
00:25:31 This command illustrates how I measure RAM consumption. Valgrind is a memory debugging tool, and we’re using 'Massif,' one of Valgrind's tools.
00:25:46 The '--stacks' option prompts Massif to examine stack memory usage in addition to heap memory. The remaining arguments simply represent the compilation commands for the mRuby compiler or PicoRuby compiler. These commands produce a massive output file.
00:26:07 According to the compiler output, 'Hello, World!' is the most basic Ruby script. The resulting data shows that compiling 'Hello World' through the mRuby compiler consumes 133 kB of RAM.
00:26:30 In comparison, the PicoRuby compiler only consumes 17 kB, which is 73% smaller than the mRuby compiler's footprint.
00:26:52 The next target is the keymap.rb file of the Mesh keypad. I apologize, but the font size is too small to read; please visualize a Ruby script that initializes the keyboard class, sets up GPIO pins, implements the Fibonacci class, and defines mod key methods.
00:27:05 The mRuby compiler compiles the keymap.rb file using 206 kB of RAM, while the PicoRuby compiler can process it with just 62 kB, achieving a 70% reduction.
00:27:26 This is the reason PRK Firmware can compile your keymap on the fly. I need to add that these measurements were taken in a 64-bit Linux environment, so the figures would likely be smaller when measured on a 32-bit microcontroller.
00:27:42 Overall, you will have about 220 kB available for Ruby code out of the 264 kB RAM provided by the RP2040, but more than 100 kB will generally be used for foundational keyboard operations.
00:28:07 Consequently, the mRuby compiler cannot compile your keymap.rb within the remaining memory. Finally, I want to share the three primary strategies I implemented to minimize the PicoRuby size.
00:28:30 As a brief overview, we focus on using only necessary libraries, considering padding, and implementing memory allocation via loops instead of recursion.
00:28:51 These concepts, mainly rooted in C, inform the development of the compiler. As I mentioned earlier, the mRuby compiler depends heavily on mRuby to reuse valuable objects, while the PicoRuby compiler relies solely on the standard C library.
00:29:09 More specifically, it utilizes the C library for microcontrollers. You may find yourself wondering what the difference is. GLIBC is the most used C library, but there are other alternatives like Newlib.
00:29:24 Newlib is an alternative C library specifically designed for embedded systems. It is worth noting that Newlib does not implement regular expressions. Although it includes a header file for regular expressions, the actual implementation is nonexistent.
00:29:39 As such, I developed a small regular expression module for the tokenizer of PicoRuby from scratch.
00:30:00 Next, let's focus on padding and pooling allocations. Imagine a struct for a linked list with two members: a pointer that connects to the next list item and a value storing a single byte of data.
00:30:15 The size of this struct is 8 bytes, not 5, due to data structure alignment rules, resulting in 3 bytes of padding.
00:30:21 If you have a linked list of 5 items, it would consume 40 bytes for just 5 bytes of data. Naturally, this raises the question of how to use memory more efficiently.
00:30:34 Pooling allocation is a memory optimization technique that packs data structures efficiently. A struct designed for pooling allocation includes an array of data as one of its members, along with the size of the array and the index of the current data position.
00:30:46 As a result, the total data count is expressed as the product of the structure size and the number of instances, effectively minimizing padding and memory fragmentation.
00:31:10 Regarding freeing memory within a loop, I noticed a significant spike when I examined memory usage in Massif. This insight led me to develop an approach to reduce this spike.
00:31:32 Consequently, RAM consumption was reduced from 53 kB to 24 kB based on benchmark tests.
00:31:39 This graph illustrates my method of freeing memory within a loop instead of using recursion. When deleting a lengthy linked list at once, the stack will spike if executed recursively.
00:31:58 In contrast, freeing a list within a single loop prevents the accumulation of spikes. By implementing small solutions like this, PicoRuby is now functional on one-chip microcontrollers.
00:32:16 That's the essence of what I wanted to convey regarding PicoRuby and PRK Firmware. I’ll conclude my talk by highlighting the significance of my projects.
00:32:41 As Morpheus said, we are living in a world where the Internet of Things (IoT) is ubiquitous, and everything is interconnected. The importance of learning co-design between hardware and software is increasingly critical.
00:33:03 Just as PCB manufacturers have embraced cloud services, software developers must also work alongside hardware. PicoRuby demonstrates that it’s possible to write Ruby applications for one-chip microcontrollers, and PRK Firmware allows for on-the-fly compilation of your keymap.rb.
00:33:50 This capability enables rapid IoT system development in Ruby. Ruby has only begun its journey in hardware programming, and as we know, it gained fame for its revolutionary concepts with Ruby on Rails, now it’s about marrying microcontroller boards with Ruby.
00:34:14 I titled this talk: 'Ruby on Board.'
00:34:40 Is.
00:35:09 You.