Skip to content

Small-screen message fonts

Small-screen Companion builds automatically select a compact font for received message text and the channel/sender line, including Ch 0 Public. SSD1306 and SH1106 OLEDs and the U8g2 T-Echo Card display use this on panels smaller than 160 pixels on both axes. ST7735 TFTs now use their native 160x80 dimensions and normal font. The shared renderer selects by panel dimensions, including rotation:

Panel size Font Capital height Line spacing
At least 128 x 64, or 64 x 128 rotated; both axes below 160 Squeezed Regular 6 6 pixels 8 pixels
Smaller panels, including 72 x 40, 128 x 32 and 64 x 48 Picopixel 5 pixels 7 pixels
Either axis at least 160 Normal display font Depends on panel Depends on panel

Both fonts allow one pixel for descenders and one blank pixel row between lines. Character widths vary: most letters advance by 4 pixels, with narrow letters taking less space and M and W taking 6 pixels. Squeezed Regular 6 is a public-domain font by Oliver Kraus. Its bitmap data is stored as constants without heap allocation.

On a 128 x 64 OLED with the single-button message reader, the compact channel/sender line starts at y=8 and message text at y=16. The header uses the same small font. Five complete 6px-font rows fit above the navigation hint at y=56. The software comparison fits three representative 160-character messages in those five rows. Capacity depends on the characters; messages with many wide letters can still overflow. Text wraps at character boundaries, with ... on the last line if necessary. Unsupported characters appear as ?. Long channel/sender names are ellipsized to stay on their own line. An enabled channel footer reserves its own space. Readers without the button hint retain the y=14 origin and y=22 message text.

On V4 and other single-button builds using this reader, the home screen says hold button: inbox. Hold the user button for about 1.2 seconds to open it. The bottom continuously shows 4 <<- 2 <- tap -> 1 ->> 3 hold: Exit without alternating or blinking. Double tap goes to the previous message, one tap advances, three taps select the next channel, and four taps select the previous channel. Channel selection cycles through All, configured channels, and direct messages, starting at the newest message in each filter. The header shows All, Ch N, or DM, including when a channel is empty. Hold to return home; advancing past the last message also returns home. The complete hint fits in the V4's existing 8-pixel footer at y=56, leaving five message rows. The footer keeps the same 6px font, using 108 pixels with 10-pixel margins on the V4. Narrower screens first use the compact hold:X hint without the extra spaces or arrow dashes, then split the same controls across fixed lines. The hint appears even when the inbox is empty. These are button taps. Touchscreen and joystick builds retain instructions appropriate to their controls. During the first eight seconds after startup, holding the button on an ordinary home page enters CLI rescue instead; wait for that startup window to finish before opening the inbox. Exiting a message preview and the WiFi setup page's hold action remain available immediately.

The hidden John reader, opened by a long press on the radio page, uses the same button hints. Three taps jump to the first verse of the next chapter; four taps jump to the first verse of the previous chapter. Chapter navigation stops at the beginning and end of the book. It reserves the hint before pagination and resumes saved bookmarks at the page containing the same text, even after the available page size changes.

Larger display classes keep their existing font. Menus, Bluetooth PINs and WiFi setup QR codes keep their normal layout. TFT drivers use native panel coordinates; the SenseCAP Indicator retains its existing scaling. The normal OLED font uses 7-pixel letters, 8-pixel line spacing and 6-pixel character advances.

The preview buffer holds a complete 160-byte MeshCore message plus its terminator. Previously the main message UI allocated 78 bytes, leaving room for only 77 bytes of text. It still retains 32 previews; larger records add about 2.8 KB of RAM. The firmware RAM guard includes that increase. This history is separate from the offline queue and its mOTA policy.

The tiny 72 x 40 T-Echo Card interface now previews the latest received message below its status bar, with three small-font message rows. A button press dismisses it. It stores one full message, and shows ... when the screen fills. Incoming text does not replace an active Bluetooth pairing PIN.

V4 hardware trial

Enable platformio.nimble.ini in the ignored platformio.local.ini as shown in the NimBLE trial guide, then run:

OUTPUT_DIR=.releases/v4-smallfont bash build.sh build-firmware \
  heltec_v4_2_v4_3_companion_radio_full_femon_nimble \
  --firmware-version v1.17.1.5-halo-keymind-cascade-squeezed6-trial \
  --radio-preset usa-cascadia --profile cascade --standard --require-ota

This build keeps NimBLE, 350 contacts, 40 channels, the V4's 512-frame PSRAM queue, USB mOTA sending and WiFi OTA support. The smaller font is also the default in ordinary small-screen Companion builds from this source. The older _nimble_pixel5 environment name remains available, but also uses the automatic 5px/6px selection.

Use the application .bin for WiFi OTA. A clean USB install uses the merged image at address 0. When manually writing the application at 0x10000, an existing OTA selector may still boot app1. Check the running ver afterward. On the matching V4 16 MB layout only, clearing the 8 KB otadata partition at 0xe000 selects the newly written app0 without clearing NVS/settings. Confirm the partition table and verify the app write before changing that selector.

A custom build can set -D UI_SMALL_MESSAGE_FONT=0 to restore the old font and spacing. Remove any explicit UI_MSG_PREVIEW_SIZE flag too if the old preview capacity is desired. These are compile-time options, not CLI commands. No settings erase is necessary when changing between matching V4 layouts.

Verification

python3 -B test/test_ssd1306_picopixel.py
python3 -B test/test_firmware_ram.py
pio test -e native -f test_display_driver -f test_companion_message_history

The native tests exercise both capital heights, automatic font selection, 160-character messages, complete rows at display edges, long sender lines, navigation hints, reserved footers, overflow markers and tiny/rotated screen geometry. The additional rendering comparison checks all 95 printable ASCII glyphs in each font, under address/undefined-behavior sanitizers. Picopixel is compared pixel for pixel with Adafruit GFX; Squeezed Regular 6 is compared with the upstream BDF fixture independently of the converted C++ tables. This needs a cached PlatformIO Adafruit GFX library; set MESHCORE_GFX_LIBRARY to its directory if needed. It reports a skip when the library is absent. The native tests do not require that dependency. Run only one PlatformIO command at a time.

Automatic 5px/6px results, 2026-09-08

Source revision 469b47d1 passed five representative firmware builds. All five retained the same static RAM usage and startup heap margin as their earlier 5px builds. The four Full profiles also passed required OTA packaging.

Hardware/profile Display Selected capital height RAM beyond the required startup budget, bytes
V4.2/V4.3 Full NimBLE SSD1306 6 pixels 88,072
T096 Full, FEM on ST7735 6 pixels 24,778
Station G3 ESP32 Full SH1106 6 pixels 82,080
RAK3401 Full SSD1306 6 pixels 46,108
T-Echo Card BLE Companion U8g2, 72 x 40 5 pixels 30,992

The native display/history suites passed 36 tests. The Python font, RAM, pairing, display-profile, queue and QR checks passed 38 tests, including pixel comparisons for all 95 ASCII glyphs in each font. The actual SSD1306 software rendering matches the approved 6px comparison image pixel for pixel.

Both NimBLE-V4-VM and NimBLE-V4-Trial were flashed with v1.17.1.5-halo-keymind-cascade-squeezed6-trial-469b47d1. Their partition tables and application hashes were verified, their settings retained, and running versions confirmed. Each passed 201 USB protocol requests without error flags. The Mercerwood V4 reconnected over bonded Bluetooth at MTU 179 and retained the factory-address policy.

The XIAO sent a private 160-byte LoRa message to the Mercerwood V4. All bytes arrived, and the V4 stayed responsive through 20 seconds of display refreshes with no uptime reset or error flags. Both temporary channel settings were restored. After that interval the V4 had 147,196 bytes of free internal heap, a minimum of 146,328 bytes, and a largest free block of 139,252 bytes.

These are short functional checks. Physical readability remains a user judgment. A message with many wide characters can still need truncation.

V4 application size: 1,778,936 bytes (1,152 bytes larger than the earlier 5px trial). SHA-256: 8e6797b90741bf013b3757668e5a23378ac8c0b0ed9c75830939f13305c56a68.

Earlier 5px hardware and build results, 2026-09-08

Source revision eeea15ef passed seven representative firmware builds:

Hardware/profile Display RAM beyond the required startup budget, bytes
V4.2/V4.3 Full NimBLE Picopixel SSD1306 88,072
T096 Full, FEM on ST7735 24,778
Station G3 ESP32 Full SH1106 82,080
RAK3401 Full SSD1306 46,108
Wireless Tracker Full NimBLE capacity trial ST7735 28,022
Wio Tracker L1 Full SH1106 45,744
T-Echo Card BLE Companion U8g2 30,992

These are linked-capacity checks before runtime allocation, not live free heap measurements. The six Full profiles also passed their required OTA packaging checks. The T-Echo Card row is a BLE Companion build.

Both physical V4.3 nodes, NimBLE-V4-VM and NimBLE-V4-Trial, were flashed, their image hashes verified, and their running versions confirmed. Each passed 201 USB protocol requests with zero reported error flags. The Mercerwood V4 also reconnected over authenticated, bonded Bluetooth with MTU 179 and the existing factory-address policy.

A private XIAO-to-V4 LoRa test delivered all 160 message bytes. The V4 continued responding during 20 seconds of message redraws, with zero error flags. Both temporary channel configurations were restored. Free internal heap after that interval was 147,200 bytes, minimum 146,340 bytes, and the largest free block was 139,252 bytes. These are short functional checks; physical readability remains a user judgment.

A matching pre/post-boot Mercerwood measurement showed the longer preview using 2,816 additional PSRAM bytes, with unchanged internal free heap. Boards without PSRAM use their ordinary RAM for the larger preview records.

The native display/history suites passed 31 tests. The Python font, RAM, pairing, display-profile, queue and QR checks passed 38 tests. The Adafruit comparison covers all 95 printable ASCII glyphs.

V4 application SHA-256: d914d80124b499d7b719f8427f3794ab1b6d15fd0112e3ef89ba2fac26b5a974.