‹ cd ~/workcinderclock.md

CINDERCLOCK

design study

Isometric line drawing of CinderClock, showing the 100 mm dome button on top, the wide display aperture, the raked control face and the speaker grille

This is a demonstration of our work process, written as the design rationale for our first product. Every decision below is here with the reasoning that produced it. If you are deciding whether to hand us a hardware project, this document is the evidence.

Also as PDFs: the 27 page product dossier, which condenses this page and adds the pricing, manufacturing and sourcing plans, and a 6 page executive brief for a faster read.

What is this?

CinderClock, our debut product, is a multi-purpose standalone desktop clock. It is built to keep your phone away from the delicate matters of sleep and focus.

Why?

After many failures of the iPhone alarm, we asked why we use our phones to wake us up in the first place. The failures had a cause. iOS carries a setting called Attention Aware Features, which quiets alerts when the phone decides you are looking at the screen. Apple separately confirmed a fault in April 2024 that stopped some alarms from sounding at all, and reports continued through 2025.

Back in the day we used phones for the convenience. As our devices got more and more loaded with unnecessary things, the convenience was replaced by burdens:

In search of a dedicated bedside alarm clock, we found that nothing on the market was worth its money. The cheap end is crowded with trash. The expensive end sells clocks that want an app, an account, or a subscription: the Philips SmartSleep lists at $199.99 and the Loftie at $169.99, with Hatch charging $49.99 a year for its content library and Loftie $59.99 for its own. The full spectrum, from landfill ambassadors to gimmick stands, did not have a single member worth the cost with the features we wanted.

So for our first product launch we set out to make our own alarm clock.

Our goal was a standalone device that kept time, played audio, and was programmable. Programmable here means it stores user-created alarm programs and follows them automatically. Most alarm clocks, your phone app included, have very simple programming options. You can set one alarm for a specific time, have it repeat every day, and maybe follow weekdays and weekends. We feel that this is too limiting.

What if you want it to ring only on certain days of the week? Ring 2 days on and 2 days off regardless of the week? Follow your local sunrise and sunset times so you wake up with the sun? Ring for 3 weeks in a row with one week off, if you work shifts?

These are not gimmicks either. Sun following was the feature we wanted for ourselves most. Having an app do that on the phone defeats the first part of the brief. The rest are useful to the many people who never agreed to a 9 to 5.

With this in mind we got to work.

Where it started. Tap any image to enlarge.

Intentions, Constraints, Systems

Whenever we do anything, we always start with core principles. At Endothermal Systems the core principles are Intentions, Constraints, and Systems. Good intentions set you on the right path, good constraints focus your attention, and good systems get you to the destination.

The Intentions with this project were:

The Constraints were:

A System is a network of methods you can run again and again. The core method we use is of the ideal outcome. Visualize an ideal outcome of whatever you hope to achieve, adhering to the rules of physics. If it satisfies you in that ideal form, then workout the details of how to get there. If, however, the solution you propose cannot satisfy you even in its ideal form, then scrap that idea altogether.

The method of ideal outcome requires you to consider every possible solution you can think of. In theory that is. List out all possible solutions to this problem, evaluate each one according to a defined scale, visualize the ideal outcome of each one, reevaluate, keep the ones that pass, scrap the rest.

If you cannot do this due to limited information, go one level below, break the problem into smaller problems. Try to define ideal solutions to those and find practical candidates for the ideal solutions. Do this until you reach the smallest atomic problem and go back up to the top level problems.

There are more nuanced ways to our System which will become apparent as you read on.

The finished unit. 140 by 160 by 170 mm, 3D printed in two parts, with the 100 mm arcade button on top and the control face raked back for a bedside viewing angle.
The finished unit. 140 by 160 by 170 mm, 3D printed in two parts, with the 100 mm arcade button on top and the control face raked back for a bedside viewing angle.

Part one: Requirements

Once the top level constraints are set it is time to define the lower level requirements.

Standalone. The device must be standalone, with no reliance on external entities. Which means:

Mechanical interface.

Modular design.

Part two: Components

At the basic level an alarm clock has three components: a display, a computer, and a speaker.

The display

A display can be analog or digital. An analog watch face is of little use on a programmable device, since there is no useful way to show program options on a pair of hands. So the device must have a digital interface, and the question becomes which kind.

The screen is the part you interact with most, so it was chosen first, and the other constraints all touch it. It has to be bright enough to read across a room and dim enough to not wake you up. It has to be low power, because the device runs on a battery. It has to be low fidelity to save power and avoid too much light hitting your eyes in the morning. And it has to be able to carry a menu to display all program options.

Seven technologies could do some of that.

OptionCan show menusPowerCostThe failing
7-segment, 4 digitNoLowLowCommercial parts stop near 14 mm digits and show nothing but digits
Chained 7-segment modulesBarelyMediumMediumMany modules wired by hand, and the face degrades as the count grows
Character LCD (1602 / 2402)LimitedBacklight always onLowLow contrast despite the backlight
VFDYesHighMediumThermally inefficient, closer to an incandescent lamp, so it drains a battery
7-segment plus small OLEDYesLowMediumTwo screens split the face and the gains cancel
Monochrome OLEDYesLowLowPixel burn-in under uneven use
MicroLEDYesLowProhibitiveFabrication complexity prices it for AR headsets and luxury televisions

If you could combine independent pixel control, LCD wear resistance, VFD brightness, and low power at low cost, you would have made the best screen ever made. In the real world every option fails somewhere, so the job is to choose the failure you can manage.

Burn-in is the only failure in that table that software can address, which selects the monochrome OLED. The management ships as four screen saver methods on a settings-selected idle interval, which move and invert what is displayed so pixel wear evens out.

Finding the part took longer than choosing the technology. We wanted something like the panel on a Bambu Lab P1S printer, large enough to read and detailed enough for menus, and it is not a thing you can search for directly, because you do not know the terms it is sold under in many cases. A 3.12 inch 256 by 64 SSD1322 panel eventually surfaced through a chain of related listings. The 64 pixels of height carry either a large time font or a full menu. The 256 of width carry the time, the date, and the alarm panel at once.

The computer

Computer here does not refer to a PC or a specific chip. It is the general term for a processor that performs computations. The computing part of the clock is two jobs: timekeeping and programmability.

Timekeeping on a digital device wants a crystal oscillator based chip called a real time clock (RTC). The most commonly available one is the DS3231 module. It is reliable, temperature compensated, keeps time offline from its own coin cell, and it works over I2C (a simple communication protocol).

It is designed around a rechargeable coin cell and trickle charges whatever you fit. Put an ordinary non-rechargeable cell in it and the cell will be damaged. The fix is to remove the small resistor connecting the power rail to the battery, which disables the charging circuit, after which a standard cell is fine to use. It is a two minute modification.

For programmability on a relatively simple device you can go with a microprocessor or a microcontroller. Both are computers. Microprocessors are faster but need peripherals around them for memory and input and output. Microcontrollers are complete computing units but less powerful. For low power embedded systems, microcontrollers are the better fit.

One of the most available and best documented microcontrollers right now is the ESP32. Its ease of use and low cost make it a good board for this application, especially given the modularity constraint. It is not the first choice for commercial products, where companies drive cost down to pennies, but the versatility of an ESP32 at a few dollars more than a penny chip is justified here, even more so when we use most of its capabilities.

We chose the ESP32-S3, the more capable model of the S variants. In line with modularity we use a dev board version of the microcontroller, seated on pin headers on our custom PCB rather than soldered to it, and the same treatment goes to every other module on the board.

The speaker

Choosing the right speaker matters for an alarm clock. The sound must be clear and loud, and the part must be easy to source.

The ESP32-S3 has no digital to analog converter and no audio amplifier of any kind. It has an I2S interface, which is a digital audio bus, so an external amplifier is the only path. The common part for the job is the MAX98357A, an I2S class D amplifier that is cheap, widely available, and well documented, and which comes on open hardware breakout boards from several vendors even though the chip itself is a commercial part.

It drives 4 ohm and 8 ohm speakers, and we chose 4 ohms for volume. From a 5 volt supply it delivers 3.2 watts into 4 ohms against 1.4 watts into 8 ohms. The lower impedance draws more current, and that extra current is where the loudness comes from. On a device that runs from a battery this is a deliberate trade: the amplifier only draws that current while an alarm is actually ringing, which is a rounding error, in theory, against the standby current that sets the runtime.

Among the 4 ohm speakers on the market we found a particular 30 by 30 mm unit to be the best. It has clear sound, a small form factor, and an acceptable retail price of about 3 dollars. It has no official name. Many AliExpress vendors carry it in their stores simply as a "30x30 mm speaker".

With these components a proof of concept was put together on a breadboard.

The 256 by 64 OLED running on its own, checking contrast before anything else was wired to it
The 256 by 64 OLED on its own, before anything else was wired to it
The first working stack on a breadboard: processor, display, clock chip and joystick
The first working stack: processor, display, clock chip and joystick

The rest of the board

Screen, computer, and speaker make the skeleton. A consumer product needs more.

An SD card module, the Adafruit 4682, holds a card with push to eject access. This lets us store a large number of audio files and lets users upload their own. A bare SMD socket would have worked, but it would have needed additional small capacitors and an SMD assembly step, which we wanted to avoid. A board like this should be fully through hole for maximum hobby repairability.

Environmental sensing was an easy addition, since the only thing on the I2C bus so far was the clock. A BME280 gives temperature, humidity, and pressure, and the standby face had room on the right for all three. This is what we mean by using the whole chip. Modules like that raise the value of the device at marginal monetary cost and near zero development cost.

The CinderClock environment screen showing 21.5 degrees Celsius, 47 percent relative humidity, and a right column with pressure, trend, and the day's extremes
The sensor screen. Pressure trend and the day's high and low come free with the part. Every screen below is rendered from the firmware's own drawing code, not photographed.

A light sensor lets the device adapt its brightness to the room. A BH1750 module handles that, over I2C.

The board also breaks out its live buses on labelled expansion headers: I2C twice, SPI, UART, spare GPIO, and the I2S audio bus. The audio header is the interesting one, because it means the amplifier can be swapped for a different one with no board revision at all. A modularity claim is worth what its connectors are worth.

A note on sourcing. We prefer the most common, best documented part available, because sourcing availability and reliable documentation are what make a product repairable and long lived. Twice in this project the common part did not exist. The display had to be hunted through unrelated listings, and later the charger did too. When no common part satisfies the constraint, the constraint wins and we go hunting.

The bare main PCB, revision 2, front face, showing the through-hole footprints and module headers
The bare main board, front
The bare main PCB, revision 2, back face, showing silkscreen labels for each module position
The back, with a silkscreen label at every module position

Part three: Interface

Setting a time takes two motions. You move between fields, and you change the value under the cursor. That single observation removed direct digit entry before any comparison started.

ControlTwo motionsFast scrollingPinsThe failing
Four buttonsYesBy auto-repeatFourFixed scroll rate
Clickable dialYesYesThreeOne axis, so field movement and value changes share a motion
D-padYesBy auto-repeatFour or fiveSame behaviour as four buttons at the same or higher pin count
Analog joystickYesBy holding a directionThreeAnalog axes cannot wake the processor from sleep

To interact with the device we chose only mechanical inputs: one analog joystick, four tactile buttons, and one large arcade button. The joystick gives motion in four directions with variable intensity, closer to the centre or further from it, plus a tactile switch in its stem, for five inputs from one component on three pins.

The joystick moves between fields horizontally and changes field values vertically. Intensity is tied to speed, so holding it at the extreme of its travel increases a value faster than holding it part way in the same direction. The hand sets the rate, the thing the four button option could not offer.

The four buttons give dedicated controls for specific, reusable actions:

A, Action. Dismiss alarms, start and stop timers, and the action button in games.

B, Back. The universal back button, which reverts changes on any screen.

C, Confirm. Confirms a selection and acts as the primary save button, and doubles as a secondary multipurpose button for showing and hiding extra items, such as centiseconds on the stopwatch.

D, Menu. Primary multi-purpose button. Opens the main menu from standby and returns to it from sub-screens. While an alarm is ringing it is the snooze button. On the Display Test screen it toggles inversion, so a panel can be calibrated against both polarities.

Two dedicated buttons and two multipurpose buttons offer great versatility without imposing limits.

The 100 mm arcade button is tied to the Action button and performs the same functions. The two serve different situations. The small Action button is for operations inside the clock interface. The arcade button is for dismissing an alarm early in the morning without aiming at anything, and for flapping the bird in Fly Bird.

The result is a complete control interface that anyone who has stood at an arcade cabinet already knows how to use.

A hand striking the 100 mm illuminated dome button on the top face of the clock
Dismissing an alarm takes no aim
The control riser PCB fitted with hot-swap mechanical key sockets and keycaps
The control riser fitted with hot swap key sockets

The four keys sit on a separate riser board rather than the main PCB, so the control layout can be changed without touching the main board. Two risers exist: tactile switches, and hot swap mechanical key sockets.

Part four: Audio

Audio took the longest of anything in the project. It is where the difference between assembling parts and engineering a system shows.

The first attempt was the obvious one. Add the standard ESP32-audioI2S library, which drives the MAX98357A over I2S, and move on. It added 500 kb of library to decode MP3 files we did not need, and it carried its own real time operating system inside it, which fought the one we were already running.

Which raises the question of why a clock needs an operating system at all.

A microcontroller runs one loop. Ours draws the screen, reads the joystick, checks the alarm list, and polls the sensors. A full redraw of a 256 by 64 OLED over SPI takes tens of milliseconds. Audio does not tolerate that. The amplifier consumes 44100 samples every second at a fixed rate, and if nothing refills its buffer during a redraw it runs dry and you hear it. So audio cannot share a loop with the interface. It has to be a separate job the chip switches to on its own schedule, which is what FreeRTOS is for, and the ESP32-S3 has two cores, so it does not even have to take turns. Audio gets one core, the interface gets the other. Once you own the scheduler, a library that also wants to own it is a problem.

Switching to the older I2S driver clashed with the new ADC drivers, so the ADC went legacy too, which produced its own downstream errors and semaphore conflicts that crashed the system on boot.

An external MP3 player module, the DFPlayer, was the next attempt. It works, and it has its own decoder chip. But it has a long mounting time, so every sound means connecting to it and waiting while it searches the card. That is unusable for interface clicks. You also cannot browse the card from the device, so you have to already know the exact path of the file you want. Scrapped.

The third approach was to go back to basics. A WAV file is a 44 byte header followed by samples. The header states the sample rate, the bit depth, and the channel count. There is nothing to decode. Once the header is read you are copying bytes from a card to a peripheral, and the peripheral sets the pace.

So the audio is one FreeRTOS task pinned to its own core, doing nothing else. It waits on a queue one item deep, written so a new command overwrites a waiting one instead of stacking behind it. Three commands reach it: play a file, play a tone, stop. On a play command it opens the file, reads the header, and loops: take the SD mutex, read 8 kb, release the mutex, apply volume as an integer multiply, duplicate each mono sample into left and right, hand the block to the I2S driver.

That last call blocks until the DMA has room for it, and that is the entire timing mechanism. The hardware throttles the task at exactly the audio rate, so the task never has to guess how fast to run. The mutex is held only around the card read and never across the write, because the write can block for 93 ms and the file portal needs the card too.

The I2S channel opens once at boot and closes only for light sleep. The driver is configured to push silence into empty buffers and not leave them stale. Closing the channel between sounds makes the amplifier drop its clock, and it takes 50 to 200 ms to lock again, which is longer than a button click, so the click would never be heard at all.

Mono files play through a stereo channel on purpose, because the S3's I2S driver has a bug in mono slot mode where it emits alternating zero samples.

Three things about that path were learned by getting them wrong first, and they are in the corrections section below.

Part five: Programs

A programmable clock has one characteristic failure, which is that you cannot tell what you actually set. Everything in this section is shaped by avoiding it.

The recurrence model rests on one setting called Cycle, which takes the value Weeks or Days and changes the meaning of every row underneath it. In Cycle: Weeks an alarm fires on the weekdays you tick, every Nth week, which expresses "weekdays at 7:00" and "every other Tuesday" with the same two controls. In Cycle: Days it fires every N days counted from a start date and ignores the day of the week entirely, which expresses "every 3 days". When Cycle is set to Days, the day of week row disappears, because it no longer means anything.

Two decisions guard the failure mode directly. The alarm list writes patterns back in plain language, as Daily, Weekdays, Every 3 days, 3d on/2d off, so the list is readable without opening anything. And each editor shows a live next-ring line computed by the same code the scheduler uses to decide whether to fire, so the preview and the behaviour cannot disagree.

What follows is everything the device does.

Alarms. Five types: simple, repeating, on and off duty cycle, sunrise, and sunset. A repeating alarm takes any pattern the calendar allows: every day, every 2 days, every 14 days, weekdays, weekends, chosen days. An on/off alarm adds a duty cycle, so "3 days on, 2 days off" is a setting and not a workaround. Each alarm carries its own sound file and its own volume, under a master volume that caps all of them. A pre-alarm can give a gentler cue some minutes ahead with its own sound. An alarm rings for 5 minutes and then logs itself as missed. History keeps the last ten events with their outcome, which answers the question that follows oversleeping.

Alarms are never silent. If the card is missing or the file is unreadable, a built-in siren plays, and muting the interface sounds does not mute alarms. There is no mute path at all.

The CinderClock repeating alarm editor showing 06:45 AM, a Weeks cycle, an interval of one week, the day mask MTWTF, a start date, and a computed next ring line
Cycle: Weeks. The day row reads MTWTF, so this one is weekdays. The bottom line is the scheduler's own answer to when it next rings.
The same editor switched to a Days cycle, showing 2 days on and 2 days off with a start date of 12 October and the next ring on Friday 16 October
Cycle: Days. Two days on, two days off, counted from a start date. The day-of-week row is gone, because in this mode it means nothing. No phone alarm expresses this.

Getting it to stop. Dismissal is the 100 mm button, sized to be hit without aiming. Snooze is deliberately harder and offers three modes: a single press, three presses at least half a second apart, or a sequence of randomly drawn joystick directions that each have to be fully released before the next one counts. The random sequence asks for 10 to 30 moves and redraws the direction each time, so it cannot be done in your sleep. Snooze is capped at three per alarm. An alarm can also be gated behind a game, and a third loss dismisses it, because being trapped by a game at 6 a.m. is a worse outcome than getting up late.

A repeating alarm ringing on CinderClock at 6:45 AM, with bell icons either side and hints for the snooze and dismiss buttons
Ringing. The set time is the large figure, the current time sits bottom right, and the two ways out are on the bottom row.
The CinderClock pre-alarm screen at 6:30 AM with a single dismiss hint
Pre-alarm. A gentler cue some minutes ahead, with its own sound and its own volume.
The same ringing alarm in joystick snooze mode, the bottom left prompt reading RIGHT x3
Joystick snooze. The prompt names a direction and a count. Both are drawn at random and the count only falls when the stick is fully released between moves.
Wild Runner running as an alarm dismissal, with two hearts, a finish flag showing 22 seconds remaining, and a distance counter
A game in the way. From-alarm mode puts a finish flag on the HUD. The audio does not stop while you play.

Sun follow. Sunrise and sunset are computed on the device from latitude and longitude entered in degrees, minutes and seconds, with the UTC offset derived from longitude and editable. A world map with a crosshair sets a position by eye, so coordinates do not have to be known. Times recompute daily, so a sunrise alarm drifts with the season. Nothing in this touches a network.

The CinderClock Sun Follow screen, latitude 43 degrees 14 minutes north and longitude 76 degrees 55 minutes east beside a world map with a crosshair on the position
Sun Follow. Coordinates on the left, a world map with a crosshair on the right. The map is there so a position can be set without knowing the numbers.

Calendar. A month grid with short ticks beside each day number, one tick per alarm, stacked up to four, with monthly totals. The ticks are drawn from the scheduler code rather than recalculated, so the calendar doubles as a check on a recurrence setting. An alarm set to every 3 days should march across the month in threes, and if it does not, the setting is wrong.

The CinderClock calendar showing October 2026 as a Monday-first month grid, today boxed, alarm ticks beside most day numbers, and monthly totals in the right pane
October, checked. Today is boxed. The ticks come from the scheduler, so a pattern that does not march across the month the way you expected is a setting that is wrong.

Timers and stopwatch. Saved, editable timers with presets, running in the background while you go do something else in the menus. A Pomodoro cycle takes work and rest periods from 1 to 999 minutes and announces each phase change with a countdown and different jingles for work and rest. A stopwatch with four laps and centiseconds, which can be hidden, because a fast changing display is a distraction on a desk. One joystick move from standby opens a quick menu for alarm, timer, and stopwatch.

A CinderClock countdown timer at four minutes and thirty-seven seconds, with hourglass icons at both edges
A timer running. It keeps running while you go elsewhere in the menus.
The CinderClock stopwatch at 02:34.82 with three recorded laps listed in the right pane
Stopwatch. Four laps in the right pane. Centiseconds are a toggle, because a digit changing 100 times a second is a distraction on a desk.
The CinderClock Pomodoro screen in its work phase, 17 minutes 12 seconds remaining, tomato icons at both edges
Pomodoro, work phase. The icon changes with the phase and each change gets its own jingle.
The CinderClock quick access screen showing four boxes labelled Alarm, Timer, Stopwatch and Env, with Timer selected
One move from standby. Four destinations, no tree to remember. This is the whole navigation model.

Ambient. A sound loop with a slow full screen animation, for sleep or focus: Fire, Rain, Blobs, Life, and Waves, at about 10 frames per second, since anything faster reads as busy. Reactive mode drives the animation from the audio, so a sudden sound seeds a spark in the fire or a glider in Life. Pendulum mode ticks on alternate seconds, locked to the second the clock is displaying, with the sound built into the firmware so it needs no card. Sessions run 15, 30, 45, 60 or 90 minutes, or endlessly, with optional fade-out and auto-dim.

The CinderClock Fire ambient animation filling the panel with a dithered flame field, the time knocked out in a small box at the top
Fire. The clock is knocked out of the animation so it stays legible over any effect.
The CinderClock Life ambient animation, a Conway's Game of Life field of square cells across the panel
Life. In reactive mode a transient in the audio drops a glider into the field.

Chime. An hourly strike with five modes: off, every hour, a daytime window, sunrise to sunset from your coordinates, or specific chosen hours. It ships set to 08:00 to 22:00. It strikes by count or once, and it always yields. During an alarm, a snooze, a game, or a firmware update it will not start, and if it is already striking it stops.

The CinderClock chime hours screen, a grid of 24 numbered checkboxes with the waking hours filled and one hour cleared
Custom hours. Twenty-four checkboxes, one per hour. Same grammar as the alarm day picker, on purpose: the device teaches one interaction and then reuses it.

Games. Eight, at three difficulties each: Ping Pong, Maze, Fly Bird, Space Dudes, Frank, Bricked Out, Wild Runner, and Meltdown. High scores are kept per game and per difficulty.

Ping Pong running on CinderClock, two paddles and a ball, with three hearts for lives and a rally score of 5 of 12 in the right pane
Ping Pong. Lives on the left, rally target on the right.
Maze running on CinderClock, a generated grid filling the left pane with the player square inside it and a countdown of 42 seconds in the right pane
Maze. The grid is generated per round, and it is the one game on a timer.
Fly Bird running on CinderClock, three pipe pairs scrolling and the bird between them, with a score of 7
Fly Bird. The 100 mm dome button flaps.
Space Dudes running on CinderClock, three rows of icon enemies advancing from the right toward the player on the left
Space Dudes. Enemies advance from the right and the player shoots into them.
Frank the snake running on CinderClock, a thirteen-segment body with two turns in it and food ahead in the field
Frank. A snake, named after nobody in particular.
Bricked Out running on CinderClock, a brick wall on the left with a notch chewed into it, a ball in flight and a vertical paddle on the right
Bricked Out. The wall is on the left and the paddle is vertical. Destroyed bricks sometimes drop a token.
Wild Runner on CinderClock, the player mid-jump over a cactus with a spider ahead and a distance counter of 214 metres
Wild Runner. Jump the cactus, duck the spider.
Meltdown running on CinderClock, a reactor core drawn as an ASCII heat field with a temperature gauge on the right and the prompt HOLD
Meltdown. Hold the core inside the striped band. The core is the same ASCII heat engine the thermal screen saver uses.

The clock itself. Two standby faces, 12 or 24 hour, seconds and date optional. Six brightness levels, plus automatic dimming from the light sensor and a night level below the manual minimum. Two font themes. Four screen savers, Checker, Distribute, Jump, and Thermal, which are the burn-in management to even out pixel wear.

The CinderClock left-split standby face, the time at left with date above and callouts below, and an alarm list in the right pane
Face one, left split. The right pane carries the next two alarms, so the answer to when it rings is on the screen already.
The CinderClock centered standby face, the time filling the panel with the date above and the environment reading on the bottom row
Face two, centered. The same information, arranged around the largest possible digits.
The CinderClock alarm editor drawn in Theme A, the BoldPixels font, showing 06:45 AM
Theme A, BoldPixels. The shipping default.
The same CinderClock alarm editor drawn in Theme B, the Gumball font, showing the same 06:45 AM
Theme B, Gumball. Same screen, same alarm, same everything else. A theme here is a typeface and nothing more.

Files. Alarm sounds are WAV files on a removable card: PCM, 16-bit, mono, 44100 Hz, with an ffmpeg command in the manual for conversion. To get them onto the card without removing it, the clock raises its own WiFi hotspot with a file portal on it. The WiFi is off at boot, you turn it on by hand, and it closes itself after 5 minutes idle.

The CinderClock card browser listing WAV files in the folder SD slash 01, with birdsong dot wav selected
Browsing the card. A breadcrumb, a scroll position, and your own files. Picking a sound does not require knowing its path.

Updates. The case has no external USB port. We fitted one early on and it spoiled the face, so it went. Firmware arrives two ways instead. The default is a file dropped into that same portal, which needs no internet, no server, and no account. The other is the clock joining your home WiFi to fetch a release from us over TLS. That path is optional, and the clock never checks for updates on its own.

Both paths flash the same way. The image carries a checksum verified before a single byte of flash is written. It installs to a second partition, and the bootloader reverts to the old one unless the new one reaches its main loop and survives 60 seconds. Alarms, settings, history, and saved timers are not touched. The self test deliberately checks nothing else: not the SD card, and not the clock chip. Losing power at any point leaves you on the firmware you already had.

Documentation. A 28 section manual on the clock's own screen, and the full manual served from the firmware over that hotspot, with no card and no internet needed. The device explains itself without this document.

Part six: Enclosure

Connecting the arcade button to the board and sizing it up settled the scale question immediately. In our heads the device was about two cans of coke. In fact it is about the size of a toaster. That is fine. It is still portable, and the extra bulk helps the tactility.

The finished enclosure measures 140 by 160 by 170 mm, printed in PLA, with PETG as the fallback. The front face is not flat: the bottom speaker section is vertical, and the interface panel is raked back 70 degrees for the best viewing angle from a bedside table. Upright, you cannot see the screen properly.

That angle then decided how the box is made.

The interface plate carries the holes for the screen and the controls, so it cannot sit at an angle to the build plate, or layer lines run diagonally across the whole visible face. It has to be printed parallel to the plate. Printed as one piece the box also needs a great deal of support built on top of internal faces, removing them is hard and ruins the face. The first single piece print took 26 hours at 0.2 mm layer height and came out with a set of serious issues that added up to an unusable prototype. Reprinting with different settings fixed some of them but not enough.

So the box is cut along the same 70 degree plane that set the geometry, into front and back halves. The two pieces join with a sliding dovetail, a stopper piece at the top makes sliding go only one way, and a pair of M2 stopper screws at the bottom prevent unsliding. Small test pairs of just the joint were printed to dial the fit, and it took three tries.

Three printed test pairs of the sliding dovetail joint at different tolerances
Three test pairs at different tolerances
A hand holding the chosen dovetail joint, the two enclosure halves engaged
The fit that was kept

The back panel now prints with almost no support. The front panel uses grid support for easy removal, with top interface layers raised to 6 and a two layer top Z distance, 0.32 mm at 0.16 mm layer height. These settings use more filament, but the support comes off in one piece, which matters more.

The same geometry through the slicer, before and after the cut.

And the same prints on the machine.

Even with manufacturing methods in mind during the design the reality collides with your expectations. Fast iteration is key.

Front elevation of the CinderClock enclosure showing the display aperture, the four keys, the joystick and the speaker grille
Front
Left elevation of the CinderClock enclosure showing the 70 degree rake of the control face
Left
Right elevation of the CinderClock enclosure showing the card slot
Right
Top view of the CinderClock enclosure showing the 100 mm dome button
Top
Bottom view of the CinderClock enclosure
Bottom

Part seven: Power

Two choices mattered here: the cell and the charger.

We wanted 18650 cells, because a standard 18650 is easy to buy anywhere and the lipo market is a thousand incompatible variants. And we did not want to weld them into a pack because that goes against the repairability. Modules exist for exactly this reason. The cells drop into holders and the leads connect to the circuit, so any cell can be removed and replaced at any time with no welding. The holders add a little resistance, which is negligible at these currents.

The charger was the harder half. The usual charging modules are notorious in the hardware community, and a battery board has to account for over-current, over-voltage, over-temperature, and short circuit protection, plus charge and discharge limits. Nothing on AliExpress carried all of it. The part came from Taobao instead: one board with the full protection set, plus separate leads for battery input and current output, which many boards do not break out at all.

This is the second of the two sourcing cases mentioned earlier. Both times the constraint was safety or capability, and both times the common part was the wrong answer.

What the build corrected

Three decisions were wrong, and the build reported each one. We publish them because a design record without them is a brochure, and because the rule each produced is the part that transfers to the next project.

A fix caused the fault. Audio stuttered early on, so a software ring buffer went in between the card reads and the DMA to smooth the stream, generously sized. It got worse in a specific way: the stutter became rhythmic, roughly three times a second, which is the ring buffer oscillating between its own high and low watermarks at the rate the audio drains it. Deleting the buffer fixed it. The DMA ring already underneath holds 32 descriptors of 512 frames, 64 kb, which is 371 ms of audio, against a card read of about 40 ms. The headroom being added already existed, in hardware, nearly ten times over. The rule: measure the headroom you have before adding a layer meant to increase it.

A part was specified for a reason that did not hold. The processor was chosen as an ESP32-S3 N16R8, with 8 mb of PSRAM, on the expectation that smooth audio would need large buffers there. It does not. A DMA copy path through the PSRAM cache creates coherency problems that are not worth the space, so the working path uses 16 kb of internal SRAM and a 64 kb DMA ring, and the firmware runs on the smaller part as well. The reason we picked the part turned out to be wrong. The part survived on other grounds, at $5 with headroom for everything else the device does. The rule: record the reason a part was chosen, so you find out when it stops being true.

A related result sits next to it: doubling the DMA ring to 128 kb works, and then fails after about three minutes, when the channel is torn down for light sleep and cannot be reopened because the heap has fragmented and there is no longer 128 kb of contiguous DMA-capable memory. 64 kb reallocates every time. The answer to a fragmentation problem was to ask for less.

The echo that accompanied the early stutter was a separate fault with a one line fix. On underrun the driver replays whatever was last in the buffer, which is why a stall sounded like a short loop rather than a gap. Telling it to zero the empty descriptors turns an underrun into silence.

Reasoning twice is not verification. The OLED header was placed mirrored on the first board. The orientation had been reasoned through repeatedly, in the software and against the physical module, flipping the board mentally and by hand, and it still ended up wrong. The rule: check it again on a different day.

One decision was reversed on taste. A full icon set was designed and inserted into every menu, then removed because it did not fit the rest of the interface. Judgement about how a thing looks is part of the engineering, and part of what a client is buying.

The standby clock face before the digit spacing was corrected, digits crowding the suffix
Before the spacing fix, the digits crowd the suffix
The same standby face after the spacing fix, with the seconds set clear of the time
After

The build, in detail

Tap any image to enlarge.

What we do not claim

What is open

The firmware source and the documentation are published under Apache 2.0. The PCB design files and the enclosure models publish at launch, once the board has taken its final revision. The bill of materials is public. Two bundled pixel fonts stay under Creative Commons Attribution-ShareAlike with their attributions intact, and their intermediate BDF files ship as well, so the conversion into the firmware is reproducible.

Attribution has to travel with the work, and a sealed appliance has one place to put it. So it is on a screen the owner can reach.

The CinderClock About screen showing the Endothermal Systems wordmark and the tagline Engineering solutions with a HUMAN touch, beside a live thermal blob animation
About, top of the page. The blob on the right is live, drawn by the same heat engine as the Meltdown core.
The same About screen scrolled to the bottom, listing the CC BY-SA font credits for BoldPixels by YukiPixels and Gumball by Aaron Amar
About, bottom of the page. The font credits, on the device, where the licence requires them to be.

Conclusion

Our goal of making a multi-purpose standalone desktop clock was achieved.

The key feature of this device is not in any of its functions. The key is how far it can be expanded beyond what it is today, because of the forward looking approach taken at every decision above.

Everything we can open about this project is open. We invite the community to develop it further, to meet the needs of as many people as possible.

This is how we work at Endothermal Systems.

Common questions

Does CinderClock need an app or an account? No. Every feature runs on the device, and there is no companion app, no account, and no service behind it. It can reach the internet for exactly one thing, an update you ask it to fetch, and it works fully without ever doing so.

It has WiFi. Is it really offline? Yes, in the sense that matters: the WiFi is off at boot, it is started deliberately, it serves only to open a file transfer portal and to install firmware. The decision to turn it on is fully yours.

What exactly is open source? The firmware source and the documentation, under Apache 2.0, published now. The PCB design files and the enclosure models publish at launch. The bill of materials is public as well, so the parts list can be checked against the price. Two bundled fonts remain under CC BY-SA with attribution.

How long does the battery last? We do not know yet, so we do not say. The device is designed around a 2 week target and the processor sleeps between seconds to reach it, but the runtime has not been measured on the fitted cells and we will not publish a number until we have taken it.

Why does it cost more than a marketplace alarm clock? It is a different class of object. A marketplace clock is a screen with a small computer behind it and a fixed feature set. CinderClock is a programmable device with a published parts list, replaceable modules, and open design files, built to last and easily repaired.

Can the hardware be modified? Yes. Every active component is a breakout module on pin headers, the main PCB is through hole, and the control riser is a separate board, so the control layout can be changed without touching the main board. The live buses are broken out on labelled headers, including the audio bus, so even the amplifier can be swapped without a board revision.

This is the process, running on our own product. It runs the same way on yours: constraints written down before parts are chosen, options compared in the open, and the failures recorded as rules. Fully transparent.
START A PROJECT →

P.S. It is called CinderClock because a large white boxy shape looks like a cinder block.
Cinder block. CinderClock. Get it?

Related reading: idea to product · hardware prototype cost · pricing and guarantees.

endothermal systems llc · home · work · field notes · pricing · © 2026