The parts games are made of
A screen, something to hold, and the four circuits in the gallery that put them together.
Games are the part of Mokxi people keep coming back to, so four parts are worth knowing well: the small OLED, the thumb stick, the rotary encoder and the addressable LED ring. This page is what each one does, what the firmware has to do to drive it, and which example to open to see it working.
The SSD1306 OLED, 128x64
The little blue-and-white screen every hobby kit has. Two wires and power: it
is an I2C slave at address 0x3c, and it acknowledges 0x3d as well if you
set the property that way.
The part models the command set the common libraries actually send (display on and off, the addressing mode, the column and page windows, contrast, segment remap, COM scan direction and the charge pump) and both page and horizontal addressing, so a driver written against a real panel works here unchanged. What you see on the canvas is the panel's own graphics RAM, drawn pixel for pixel.
There is no I2C peripheral on either the Uno or the ESP32-C3 in Mokxi, so the
bus is bit-banged: mokxi_i2c.h pulls the two lines down and lets them go, and
the module's own 4k7 pull-ups bring them back up. Any two pins will do, which
is why the examples use the two everybody expects.
A 128x64 monochrome frame is 1024 bytes. On an ESP32-C3 that is nothing; on an Uno it is half the chip's memory, and sending all of it over a software bus takes a third of a second. That is why the driver sends only the eight-column blocks of each page that changed, and why a game on this panel rubs out the ball where it was and draws it where it is rather than repainting.
See it: OLED dice (ESP32-C3), OLED pong (Uno) and OLED pong on an ESP32-C3.
The thumb stick
Two 10 k potentiometers and a push switch: the KY-023 module and its clones.
Drag the black cap on the canvas and both axes move; let go and it springs back
to the middle, the way the real one does. Click or hold the cap to push the stick
straight down, which shorts SW to ground.
Each axis is a real divider across whatever is on VCC and GND, with the
track's own impedance behind the wiper, so an ADC input with a load on it sees
what it would on the bench.
A thumb stick needs an ADC, and both boards now have one. The Uno's is the
ATmega328P's ten-bit converter on A0 to A5; the ESP32-C3's is ADC1, twelve
bits, on GPIO 0 to GPIO 4. So a stick works on either, an axis reads 0 to 1023
on one and 0 to 4095 on the other, and mokxi_joystick.h in the firmware
library hides the difference.
See it: OLED pong (Uno), OLED pong on an ESP32-C3, ESP32-C3 thumb stick and Snake on two matrices.
The rotary encoder (KY-040)
A shaft that counts clicks either way around, with a switch under it. Three
contacts to ground: CLK and DT are the two halves of a quadrature encoder
and SW is the push. The module carries 10 k pull-ups on the quadrature pair;
SW has none, which is why every sketch reads that one with INPUT_PULLUP.
Three ways to turn it on the canvas:
- drag around the knob: the pointer's bearing about the shaft is the shaft's angle;
- the scroll wheel over the knob: one notch is one detent, away from you clockwise;
- drag up and down: for a touch screen, and for a drag that starts on the shaft itself where a bearing means nothing.
Turning it does not jump the pins from one state to the next. One click is four
quarter steps, played out eight milliseconds apart, so firmware that polls in a
loop sees the sequence the way it would on a bench. And the contacts chatter,
once per quarter step, because that is the whole lesson: a sketch that watches
for a falling edge on CLK counts three clicks for one. Turn the chatter off
in the properties when the encoder is not the point.
mokxi_rotary.h does it the right way, with the sixteen-entry state table: a
transition scores +1, -1, or nothing when the pair jumped two states at once,
which is what a bounce looks like. A click is reported when four quarter steps
have added up and the shaft is back on a detent.
See it: Ring Simon.
The WS2812 ring and strip
Addressable RGB LEDs on one wire, with no clock: a bit is a high pulse whose length is the bit, 0.4 microseconds for a nought and 0.8 for a one, twenty- four bits an LED, green first. The part decodes those pulses off the pin's own edges with the timing tolerance a real strip has, and fifty microseconds of quiet latches the frame.
layout picks the picture. strip is a row, matrix is sixteen wide, and
ring bends the row into a circle, LED 0 at twelve o'clock and the rest
clockwise, which is how the rings people buy are numbered. A ring is a strip
electrically, so nothing about the wiring or the firmware changes; only the
drawing does.
The driver, mokxi_ws2812.h, makes the pulses with a counted loop rather than a
timer, because the ESP32-C3's SYSTIMER ticks at 16 MHz and reading it takes
longer than the pulse being measured. It is an ESP32-C3 header: at 16 MHz an
Uno has twenty cycles for a whole bit and six and a half for the first pulse,
which digitalWrite cannot do without a page of hand-written assembly.
See it: Ring Simon and Rainbow strip.
The five games
Open any of them from the Examples menu, or from the templates on the site.
| Game | Board | Screen | Input |
|---|---|---|---|
| OLED pong | Uno | SSD1306 | thumb stick |
| OLED pong on an ESP32-C3 | ESP32-C3 | SSD1306 | thumb stick |
| Snake on two matrices | Uno | two MAX7219 | thumb stick |
| Ring Simon | ESP32-C3 | WS2812 ring | rotary encoder |
| OLED dice | ESP32-C3 | SSD1306 | arcade button |
The two pongs are the same circuit on the two boards, which is the shortest way to see what a board changes and what it does not.
Each is real firmware compiled for the board it names, running on the same core everything else here runs on. Press Run, then drive the input with the pointer: drag the stick's cap, turn the encoder's knob, hold the button down.
There is a sixth, Stacker, on two MAX7219 matrices with one button, and it plays itself until you press something.