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 run for up to 8 hours off a rechargeable coin battery.

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 in low-light environments. The reversible USB‑C port is conveniently located on the top and is used for both charging and data transfer.

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 for microcontrollers, then MicroPython and its fork CircuitPython are a great starting point. However, on SAMD21 with only 256 KB of internal memory, it is impossible to store the complete standard library, let alone larger scripts. An external flash chip 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 can be paired 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, or 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 primarily used for short reminders, but since it doubles down as a macropad, it allows for any ASCII characters and select keyboard keys. Some features are activated with the ZX key held down, as shown below.

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. I can then manually send a sequence of inputs or enter whole paragraphs of text:

import kb "machine/usb/hid/keyboard"
import "time"

func main() {
  text := "hello world"
  count, err := kb.Keyboard.Write([]byte(text))

  err = kb.Keyboard.Down(kb.KeyEnter)
  time.Sleep(10 * time.Millisecond)
  err = kb.Keyboard.Up(kb.KeyEnter)

  for {}
}

Displaying text and menus

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 based on NewStroke, which is used by default in KiCad PCB files (and engraved onto the EclairM0’s enclosure). I’ve simply taken a screenshot of the KiCad window with antialiasing disabled, opened it in GIMP, and retouched every single glyph, adjusting the baseline, width, kerning, and overall shape where necessary.

NewStroke adapted for the monochrome display
NewStroke adapted for the monochrome display

Using a variable-width font with true line wrapping is a difficult task. I have to calculate length for each line, 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 about 10 ms.

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

Sourcing a reliable power source for a thin device like the EclairM0 has been challenging. The first few hardware revisions used a Li-Po battery that was supposed to be small (25×15×3 mm), but actually it’s 5 mm thick due to the built-in protection circuit module. I’ve relocated both chips onto the main board, but I wasn’t really sure if I wanted to keep the complicated, potentially dangerous solution.

Initial approach – battery and the PCM chips soldered to the main PCB
Initial approach – battery and the PCM chips soldered to the main PCB

Eventually, I’ve switched to a LIR2025 rechargeable coin battery. Sadly, it has lower capacity, and its round shape makes it difficult to place other PCB components around it.

However, it is compatible with the same charging controller as before, MCP73831 by Microchip. The maximum safe charge current is usually 1C (20 mA, or 40 mA for high-capacity “H” batteries). First, the battery is charged with the limited current based on the programming resistor’s value, and then the voltage is kept close to 4.2 V. Example:

Finally, there needs to be a way to check the current battery state. SAMD21 has multiple analog input pins, and one of these can be connected to the output of a simple 1/5 voltage divider, to measure voltages between 0 and 1 V. 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:

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.

This change has resulted 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… yet.

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 batteries of any kind, and creating larger programs in an environment with limited resources.

Thank you to the members of multiple mechanical keyboard communities on Reddit, Discord, and many other places online. Usually I’m only lurking, but for sure I’ve 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 haven’t 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.

The configuration code for clocks and watchdog was partially based on the MicroPython’s SAMD21 port. I’ve upstreamed some small feature and performance patches to the TinyGo project, so everyone can benefit from these improvements.

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, no soldermask), and all the electronics in between acted as a “filling.” 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 can I 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). 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: