CardOS: Writing an OS for the Cardputer

Translations: br
May 25, 2024

I recently got the M5Stack's Cardputer. I was motivated to get it because I knew other people personally that had it too, so we would be able to share our progress, but I was worried it might end up being just another board that sits in my closet forever untouched. Still I decided to give it a shot.

Once I got it in my hands, it really won me. It has all that's required to be a computer: full keyboard for input, display for output, battery for portability, microSD card slot for persistent storage, plus WiFi and Bluetooth for connectivity. The fact that it's a very limited computer, and that it's so tiny, only makes it more charming to me.

I am really interested in the idea of implementing a full system from scratch in order to understand all the different layers and interactions. I've also always wanted to make my own operating system (OS). So before I knew it, I had set on a goal: I was going to make an OS from scratch for the Cardputer.

Now, when I say from scratch, I don't mean necessarily starting from an empty file, just that eventually all the code should have been written by me. One thing I remember from Andreas Kling's (creator of SerenityOS) videos is that he didn't start writing his OS from the bootloader, but from the user interface, and that allowed him to see his changes take effect right away and work in small steps towards the full OS. I also remember Tarn Adams (co-creator of Dwarf Fortress) giving the same tip about how to keep motivation when developing a game. And both of those are people I dearly admire, so I took their advice.

So with that in mind, I started my project from one of the Cardputer's demo code which already had a working display and keyboard, and I wrote a basic shell on top of that.

Only after that did I start implementing my own keyboard and display support from scratch and removing the code from the demo.

Getting the display to work

Implementing display support was much trickier than I expected and got me stuck for a while. By then I had already implemented my own keyboard support, tested it and it worked. But I couldn't even get the display to light up, even after carefully reading its datasheet many times and implementing the whole initialization procedure.

Eventually I took a step back and tested more basic functionality and realized I did not have output GPIOs figured out. This was really surprising as it was needed for the keyboard, which I had already tested. But what I hadn't realized was that even though my code was able to toggle output pins to scan the keyboard, it was implicitly relying on the demo's initialization code to have working pin output, which is why when I replaced all that code with my own display implementation neither the display nor the keyboard worked.

But as often times is the case for unexplainable behaviors, that wasn't the only issue. As it turns out, the ESP32 manual also showed the wrong address for one of the registers needed to configure the GPIOs. Luckily it also showed the right address in a different place, so I eventually noticed it.

So after I implemented support for enabling the output GPIOs, and corrected the register address, I was able to fix the keyboard support to be completely self-contained, and was finally, after a lot of sweat, able to implement display support and remove the dependency from the external library.

This has been the biggest achievement on the project so far, and I'm really proud of it!

Current status

This is where the project is right now:

{image}/cardos_demo.mp4

In other words, keyboard and display support are there without relying on third party libraries. The display communication is quite slow, so there's a lot of room for future improvement 🙂. There's a shell with just a couple commands, including help to show the commands, clear to clear the screen, read to read an arbitrary memory address and led to turn the built-in RGB LED to the desired color. It's possible to cycle through the shell's history to repeat a previous command. And once the terminal fills the screen it automatically clears it.

I've also spent some time fixing bugs. At this point it felt like the OS was finally in a usable state, so I've tagged it v0.1.

Next steps

The next thing I'm working on is to move away from the Arduino toolchain. The main reason is that there's some code that gets implicitly added to the binary when using it, and I want to keep removing dependencies from my code.

Besides that, Arduino basically enforces the usage of a single source file. The cardOS source is currently a single file that is 1900 lines long (with almost 600 being the struct for the font characters) and I'm starting to get lost in it, so it's time to split the code to multiple files.

Finally, Arduino uses C++, and since I'm coding in C, moving away would allow me to use a C compiler instead, which will improve the error messages.

After that I'm going to keep making cardOS more of an actual OS. I want it to feel like a real hackable computer, so what I'm aiming at is to be able to open a text editor, edit a shell initialization file, save, reboot and see the effects of that change. That already gives me enough work: I need to make a text editor, a shell script language, and implement SDcard reader and filesystem support.

Further in the future I'm really curious about implementing support for the WiFi and maybe be able to send and receive IRC messages, if that's feasible. I'm sure it'll be hard, but should be fun too 😃.

And that's what this is all about: fun! It's been two months since I started the project, and I can confidently say this is the project I've had the most fun ever. So as long as it stays fun I'll keep going with it. There's no rush to get anything done, I'm just enjoying the ride 🙂.