To the Home Page

EclairM0, the pocket notepad

Small, lightweight, battery-powered gadget, fully open source, that can be used as a notepad, USB macropad, TV remote, hardware authenticator, signal generator…

Published on April 24, 2025 · Reading time: 14 minutes

EclairM0 front view

Code on GitHub Build guide

Table of Contents

Overview

I’m excited to present the compact USB macropad, which I came up with back in my college days and finally made into a real thing. EclairM0 is small (5×4 cm), very thin (only 8 mm), lightweight (15 g), and can last 10+ hours on a single charge.

I’ve designed this device to be as small as possible, but also comfortable, intuitive, and practical. It has 14 low-force tactile buttons with a large touch target, which is more than enough for entering any text. The small monochrome OLED screen has yellow pixels that are easier on the eyes when the device is used in low-light environments. The reversible USB‑C port is conveniently located on the top.

Components on the bottom of the PCB
Components on the bottom of the PCB

The 32‑bit ARM microcontroller, ATSAMD21E18 by Microchip, offers great energy efficiency and is supported by modern programming languages such as MicroPython, while still being available in a hacker-friendly TQFP32-0.8mm package with visible pads. It has native ADC/DAC, SPI, PWM and USB capabilities, and there’s enough RAM and ROM.

All electronic components are placed on a single, 4-layer, 0.8 mm thin PCB, created in KiCad and manufactured by AISLER. I’ve also designed in OpenSCAD and printed a snap-fit enclosure, and cut out the front text using a CNC router.

Check out this overview video to see the device in action:

The Go-od and Bad

You can skip this section if you are not interested in the firmware implementation details.

If you have no prior experience in programming microcontrollers, then MicroPython and its fork CircuitPython are a great starting point. However, on SAMD21 with only 256 KB of storage, it is impossible to fit the complete standard library, let alone store larger scripts. An external flash memory would require a separate SPI bus and extra space on a PCB.

Luckily, some programming languages happen to have the expressiveness of Python and the performance and size of C/C++. Go is for sure such a solution, so I finally had a chance to try it for the first time.

Despite Go being Google’s product, and me trying to stay away from big corpo offerings as much as possible, I’ve quite enjoyed using it. It has flexible memory management with garbage collection, obligatory explicit namespacing, and built-in concurrency. But what I really like is that it kinda forces me to have a unified code style. I cannot have unused imports or variables, and I have to follow the formatting and naming conventions — otherwise my code will not compile at all.

Example TinyGo configuration in GoLand
Example TinyGo configuration in GoLand

TinyGo is the community-maintained version of the language that runs on microcontrollers and in WebAssembly environments. It’s compatible with many third-party libraries. The optional plugin can be downloaded for both Visual Studio Code and GoLand, providing device-specific autocomplete and refactoring options.

Handling user input

How can one enter any meaningful text with only 14 keys? Well, the same way you were able to SMS on old phones, that is by pressing the same key multiple times to get different characters and symbols. Actually, some phones released in the 00s had multi-tap QWERTY-like keyboards, for example the Nokia E55 or Blackberry Pearl.

Nokia E55’s keypad
Nokia E55’s keypad

But the question remains: how can you enter text with only 14 keys when there is no Space, Shift, Enter present? Nokia E55 had 20-ish buttons and a dedicated Backspace key.

Look at what the mechanical keyboard community has done: they removed many keys from their ≤40% designs, including modifiers and special keys. To delete a single character, you press O and P keys at the same time because these two are closest to the expected location of Backspace on a full-size keyboard.

You can learn more about how to use small keyboards in Greg Leo’s YouTube video.

Original QAZ keyboard in a convenient enclosure (source: cbkbd.com)
Original QAZ keyboard in a convenient enclosure (source: cbkbd.com)

EclairM0 uses the combined approach. Press the QW key multiple times to cycle between “q”, “w”, and “1”. To delete a character, press ZX and OP buttons simultaneously. These concepts are also called tap dance and combos, respectively.

The notes app is meant to be used for short reminders, so it doesn’t have the support for uppercase characters (no Shift key), line breaks (no Enter key), and many more. But I have added extra combos for controlling the brightness (ZX + BN), cutting the entire line of text (ZX + CV), and sending data to the connected USB host (ZX + L).

Oh, and there’s no Copilot key. You’re welcome.

Keymap for the notes app
Keymap for the notes app

Other than that, it is not much different from any other custom mechanical keyboard. It is a diodeless 5×3 key matrix. When a key is pressed, the circuit between row and column is closed, which is recognized by a microcontroller as a digital signal low and further processed in software (including debouncing to ignore false inputs).

TinyGo does not have the built-in generic keypad library, but once I implemented the button scanning routine, it was super easy to configure the microcontroller as a USB HID keyboard. Everything is done behind the scenes when I import the keyboard package, no need for writing custom report descriptors or manually mapping ASCII codes to keyboard codes:

import "machine/usb/hid/keyboard"

text := "hello world"
count, err := keyboard.Keyboard.Write([]byte(text))

Displaying information

The display for EclairM0 had to be small, well documented (datasheet, sample code), use as few extra components as possible due to physical constraints, and have good outdoor visibility.

The only option I could come up with was a 0.91" 128×32 OLED display with an SSD1306 controller, commonly found in cheap modules from China. TFT and IPS screens have backlight that is useless during the day and disturbing at night, E‑ink screens are slow, fragile and require complex circuitry, and the smallest available Sharp Memory LCDs are too big.

A jellybean module (top) and a professionally installed display (bottom)
A jellybean module (top) and a professionally installed display (bottom)

The UI font is inspired by Fira Sans, adjusted for readability trying to fit as much text as possible, and with many unused glyphs omitted (such as uppercase letters). Using a variable-width typeface with true line wrapping is a difficult task. I have to calculate length for each line of text, make sure very long words and repeating whitespaces are handled correctly, keep a valid position for a cursor, and send new data to the display, all within 10 ms on average.

Fira Sans adapted for the monochrome display
Fira Sans adapted for the monochrome display

SSD1306 and many other similar display controllers use a simple memory layout. The screen is divided into 8-pixel-tall rows (pages), where each column of pixels in a page is described by a single byte. So instead of drawing the text pixel-by-pixel, which is slow because of multiple bit operations performed, I am copying glyph data directly to the buffer.

A single 8×5 character copied to the buffer
A single 8×5 character copied to the buffer

Finally, the initialization sequence code for the display had to be patched. It was impossible to control the brightness with the default settings, but changing both pre-charge period (a time it takes for pixels to fully turn on) and deselect voltage (voltage applied to the common electrode when pixels are turned off) registers to 0 did the trick.

Storing user data

You can skip this section if you are not interested in the firmware implementation details.

In TinyGo, you can either use the FAT file system that is compatible with every version of Windows, or try littlefs designed for reliability on microcontrollers. I went yet another route, and I am writing data directly to the flash memory to make things super fast. There is only one “note” that I write to or delete from; having more could be difficult to implement and maybe even misleading.

According to the SAMD21 microcontroller datasheet, the NVM memory is divided into rows, 256 bytes each. Rows can be erased independently (all bits set to “1”), but I cannot erase half a row or just a few bytes. It is possible, however, to write 64 bytes of data to any location (flip “1” bits to “0”). Therefore, I have decided to reserve 4 bytes of the last page for a metadata section. It’s not worth it to span data across multiple rows, the remaining 252 characters is actually quite a lot of text for such a small screen.

NVM memory split into rows and pages
NVM memory split into rows and pages

But why is the metadata section at the end of the row, not at the beginning as one would expect? Consider this: note data is read from memory only once on boot, but written multiple times. Since I have to write exactly 64 bytes at once, I would have to either keep the metadata in the main buffer and hide it from the user, or create a temporary copy of the buffer to avoid writing out of bounds. It is much faster to just perform up to 4 writes, and then on the fifth write place the metadata at the end of the last page.

In TinyGo, the starting point of user-available storage is calculated from the compiled code’s size and injected into an internal flashDataStart variable during linking. This means a more recent firmware version may overwrite user data if the program is larger than the previous one. The variable can also be set in a custom linker script, so I’ve set the predictable value of 0x38000 (224 kB), which means only the last 32 kB are available for writing.

NVM memory usage
NVM memory usage

Erasing data and then writing new information to the same memory location is risky. Any NVM row has a guaranteed minimum of only 25,000 write+erase cycles, per the “Flash Endurance and Data Retention” table in the datasheet. And if you run into a firmware bug or power loss, it is impossible to gracefully recover from such a situation. In my notes app, an old row is discarded after new data has been successfully written to the adjacent one. 32 rows are used for wear leveling, and at any moment only one is considered valid.

Power management

Finding a reliable power source for a battery-powered design was very challenging. It looks like nobody was brave enough to ship me the cells I needed, so I was pretty much limited to what local electronic component distributors could offer. The small 85 mAh battery I’ve used in EclairM0 is 25×15×3 mm, just like advertised — but only when measured without the protection circuit module. With the PCM installed, the total thickness is about 5 mm, which is unacceptable.

I could not find anything else nearby. The workaround was to reverse engineer the circuit and relocate protection chip and double MOSFET onto the main PCB. It requires a lot of attention and may be dangerous, so the latest revision has an extra pad where an unmodified battery can be soldered directly.

Battery and its PCM chips (Q1, U3) soldered to the main PCB
Battery and its PCM chips (Q1, U3) soldered to the main PCB

The battery cannot be charged directly from USB. The maximum safe charge-discharge current is 0.5 C (42 mA) according to its technical specification. A charge management controller, like MCP73831 used in here, first provides a limited current based on the programming resistor’s value, and then it keeps the voltage at about 4.2 V:

Finally, there needs to be a way to check the current battery state. SAMD21 has multiple analog input pins, capable of measuring voltages up to 3.3 V, and one of these can be connected to the output of a simple 1/2 divider. It’s not as precise as a dedicated fuel gauge chip, but it’s good enough for checking whether the battery is OK or not.

Clocks configuration

One of my goals for the device was to have great battery life. It isn’t really possible at the default CPU speed of 48 MHz.

SAMD21 has a complex clock system that may be difficult to understand at first. There are multiple clock sources, such as an internal 8 MHz oscillator or an external 32.768 kHz crystal. The signal they generate is assigned to one of the nine clock generators, responsible for producing prescaled (lower) frequencies. Or the other way around, signals can be multiplied up to 48 MHz using a digital frequency-locked loop mechanism. These clocks are associated with various peripherals (CPU, ADC, SPI, USB) using clock multiplexers and disabled on demand using the power manager system. And this is only the tip of an iceberg!

Clock distribution diagram (source: SAMD21 series datasheet)
Clock distribution diagram (source: SAMD21 series datasheet)

The default clock configuration in TinyGo is good enough, but it can be improved. By default:

First things first, setting the prescaler (“divider”) of GCLK0 to 6 means this clock generator’s output will be 8 MHz. It does not make sense because I could just use OSC8M, but I would rather not divert too much from the default configuration. The negative side effect is that the device will no longer be recognized by PC, so I have configured a new generator, GCLK5, set its source to DFLL48M without prescaling, and assigned it to the USB peripheral.

To enable watchdog, a microcontroller’s feature that will automatically perform a reboot on failure, I had to set up another generator, GCLK8, from the internal ultra-low-power oscillator (ULPOSC32K), prescaled by 32 to get the 1 kHz signal.

All these changes result in a power consumption reduced by 75% (down to 10 mW), with no noticeable performance drop. At this point, I don’t even need to figure out how the deep sleep works on this microcontroller.

Idle current (mA) at 3.7 V (sorry for using a cheapo multimeter)
Idle current (mA) at 3.7 V (sorry for using a cheapo multimeter)

Credits

Designing this device was a great opportunity to encounter many electronic engineering challenges for the first time: analyzing larger datasheets and schematics and finding suitable part replacements, soldering tiny components, measuring objects and arranging these in three dimensions, using a CNC router and a 3D printer with all the possible quirks and problems, “safely” working with Li‑Po batteries, and creating larger programs in an environment with limited resources.

Thank you to the members of multiple mechanical keyboard communities on Reddit and Discord. I haven’t sent a single message, but for sure I have learned a lot and avoided some serious mistakes.

EclairM0 is primarily based on Adafruit’s Trinket M0, which is the minimal SAMD21 board, having even fewer components than recommended by Microchip. Some early prototypes were inspired by first revisions of Radomir Dopieralski (deshipu)’s PewPew LCD. The barebone OLED circuit is based on a blog post by Michael Teeuw.

I have not worked with any SAMD21 microcontroller before, and at first, it was impossible to understand anything in the 1000+ page long datasheet… until I ran into Thea Flowers’ articles on this microcontroller, with a very detailed explanation of what is really going on, and with sample source code.

Clocks and watchdog configuration code was based on the MicroPython’s SAMD21 port.

Finally, thank YOU for getting through this very long article. I hope you enjoyed reading it.

— Matt

FAQ

Why Eclair?

The first version of the device was made of two bare PCBs (pale brown, without soldermask), and all the electronics in between acted as a “filling.” So I called it Biscuit because it reminded me of one.

I have run into some significant reliability issues with the initial design and changed it a lot, but decided to keep a similar name. Eclair is smaller and more lightweight, after all.

The M0 in the name follows the convention made by Adafruit, where many of their devices powered by SAMD21 microcontroller (which is based on ARM Cortex-M0+ architecture) have this in their names. It’s also better for SEO :)

Where I can buy it?

This device will not be made available for purchase in the foreseeable future, neither as a complete device nor as a kit.

If you’d like to make one for yourself or your friends, please follow the build guide and check out the source files.

A free product means no expectations and obligations. I don’t want to deal with supply chain shortages, quality problems, challenges with shipping worldwide, and support tickets from angry consumers. I don’t want to resolve issues with RF interference or potentially harmful substances used in manufacturing (such as lead in solder). I don’t want to spend the fortune on USB certification ($10,000 just for a unique VID/PID pair!) And I really don’t want to run into any legal trouble.

Why no Wi‑Fi or Bluetooth?

I’m aware that having no wireless communication limits the potential use for the device. There were only two reasonable options that wouldn’t fit the project’s requirements anyway. I would also have to design an antenna circuit and confirm that other components on a PCB won’t degrade the signal. (Using an existing breakout board would be considered cheating.)

ESP32 by Espressif (featuring Wi‑Fi, BLE) has way too many variants to pick from. The wireless stack is not open source – some software does not support the chip or use it as a coprocessor. It would probably drain the battery like crazy.

nRF52 by Nordic (featuring BLE, NFC) seems like a better choice, but it is not available in any beginner-friendly footprints. nRF52840 chips with native USB are difficult to source, and devkits are really expensive. No Wi‑Fi, unfortunately.

Check out other blog posts: