Skip to content

U8g2 Displays

github-actions[bot] edited this page Oct 11, 2026 · 2 revisions

U8g2 displays (U8g2, U8x8)

MicroCS can drive monochrome OLED, LCD and e-paper displays from C# through olikraus' u8g2 — SSD1306, SH1106, SSD1309, ST7920, ST7565, UC1701, PCD8544 (Nokia 5110), KS0108, SSD1607 e-paper and ~360 more panels on I²C, SPI or parallel pins. Both u8g2 APIs are wrapped completely:

  • U8g2 — graphics with a frame buffer: text in 2000+ fonts, lines, boxes, circles, bitmaps, rotation, page mode for small RAM, buttons and menus.
  • U8x8 — text only, no frame buffer: 8×8 tiles go straight to the display (a few bytes of RAM).
var oled = new U8g2("ssd1306_i2c_128x64_noname");     // I2C bus 0, address 0x3C
oled.Begin();
oled.ClearBuffer();
oled.SetFont("helvB10_tr");
oled.DrawStr(0, 14, "Hello, MicroCS!");
oled.DrawCircle(64, 40, 16);
oled.SendBuffer();

Step by step on a real board: ESP32-C3 + OLED guide (any ESP32 works the same way).

u8g2 support is optional and off by default: the u8g2 library is not bundled with MicroCS and is not in the release firmware. You turn it on in your own build, pick the displays and the built-in fonts there, and every other font can still be loaded from a file at run time.

Try it on the PC

make fetch-u8g2        # downloads u8g2 2.37.1 into build/third_party (once)
make mcs-u8g2          # -> build/mcs_u8g2 (the CLI with U8g2 / U8x8)
./build/mcs_u8g2 --oled examples/u8g2/hello.cs

--oled attaches a simulated SSD1306 128×64 at I²C 0x3C to the simulated board and draws every frame it receives on the terminal (--oled=sh1106, --oled=128x32, --oled=0x3D, combined with commas). examples/u8g2/ has hello, graphics, page mode, fonts, U8x8 text, menus and a scrolling log; make u8g2-test runs the tests.

In MicroCS Studio: Templates (Alt+T) → U8g2 displays has 20 ready-to-run scripts that together use the whole API — hello, every bus (I²C, SPI, software, parallel, KS0108), all shapes, text and font metrics, fonts from files, bitmaps and icons, rotation / contrast / sleep / clipping, page mode, Print, buttons and text bars, the built-in menus, your own menu, the log terminal, a dashboard, a clock, an animation, buffer screenshots, U8x8 text and status screens, two displays. Snippets: u8g2, u8frame, u8page, u8x8; completion and hover know every U8g2 / U8x8 member. On firmware without u8g2 the templates print a hint instead of failing. CI runs each one against the simulated SSD1306 (node tests/studio/test_templates.js build/mcs_u8g2 --only=u8g2).

Turn it on

Every build looks for the u8g2 sources in the same places and stops with instructions when it cannot find them — nothing is downloaded unless you allow it:

  1. the path you give (MICROCS_U8G2_DIR, also as an environment variable, or U8G2_DIR): a clone of github.com/olikraus/u8g2 (csrc/), the U8g2 Arduino library (src/clib/) or the folder with u8g2.h itself;
  2. u8g2/, third_party/u8g2, lib/u8g2, libs/u8g2, external/u8g2, components/u8g2 in your project, ../u8g2 next to MicroCS, MicroCS/build/third_party/u8g2-* (python3 tools/u8g2.py fetch), an earlier download, ~/Arduino/libraries/U8g2;
  3. with MICROCS_U8G2_DOWNLOAD=ON only: u8g2 2.37.1 (the latest release) is downloaded once into the build folder.

python3 tools/u8g2.py where prints the folder that would be used.

Build Turn on Sources / options
CMake (host, pico-sdk, STM32CubeMX, any toolchain) -DMICROCS_U8G2=ON, or #define MCS_ENABLE_U8G2 1 in the project's mcs_user_config.h (CMake sees it) -DMICROCS_U8G2_DIR=…, -DMICROCS_U8G2_DOWNLOAD=ON, -DMICROCS_U8G2_DISPLAYS="a;b", -DMICROCS_U8G2_FONTS="x;y", -DMICROCS_U8X8_FONTS=…
ESP-IDF menuconfig → MicroCS → U8g2 displays (CONFIG_MICROCS_U8G2=y) CONFIG_MICROCS_U8G2_DIR, CONFIG_MICROCS_U8G2_DOWNLOAD, CONFIG_MICROCS_U8G2_DISPLAYS="a b", …_FONTS, CONFIG_MICROCS_U8X8_FONTS
Zephyr CONFIG_MICROCS_U8G2=y in prj.conf the same CONFIG_MICROCS_U8G2_* options
Arduino IDE install U8g2 (Library Manager), then python3 tools/make_arduino.py --u8g2 --u8g2-displays a,b --u8g2-fonts x,y --u8x8-fonts z (written into the library's src/mcs_user_config.h, where you can also edit them)
PlatformIO lib_deps = olikraus/U8g2 + build_flags = -DMCS_ENABLE_U8G2=1 the lists as -D flags (below) or in include/mcs_user_config.h
make (host) make mcs-u8g2 U8G2_DIR=…, U8G2_DISPLAYS="a b", U8G2_FONTS="x y", U8X8_FONTS=…; make fetch-u8g2 downloads
anything else compile u8g2/csrc/*.c with -ffunction-sections -fdata-sections -DU8G2_USE_DYNAMIC_ALLOC, add csrc to the include path, -DMCS_ENABLE_U8G2=1, link with --gc-sections the MCS_U8G2_* options in mcs_user_config.h

Examples:

# Raspberry Pi Pico, u8g2 next to the project or downloaded once
cmake -S ports/rp2/example -B build/pico -DPICO_SDK_PATH=$HOME/pico-sdk -DMICROCS_FS=tinyfs \
      -DMICROCS_U8G2=ON -DMICROCS_U8G2_DOWNLOAD=ON \
      "-DMICROCS_U8G2_DISPLAYS=ssd1306_i2c_128x64_noname;sh1106_i2c_128x64_noname" \
      "-DMICROCS_U8G2_FONTS=helvB10_tr;6x10_tf;logisoso24_tn"
; platformio.ini
lib_deps = olikraus/U8g2
build_flags = -DMCS_ENABLE_U8G2=1
    '-DMCS_U8G2_DISPLAYS=U8G2_DISPLAY(ssd1306_i2c_128x64_noname)'
    '-DMCS_U8G2_FONTS=U8G2_FONT(u8g2_font_helvB10_tr) U8G2_FONT(u8g2_font_6x10_tf)'

Only the listed displays and fonts end up in the firmware: u8g2 is compiled with -ffunction-sections -fdata-sections and the firmware is linked with --gc-sections (pico-sdk, ESP-IDF, Zephyr, Arduino, PlatformIO, STM32CubeMX and the host build do this already). The u8g2 sources get -w; MicroCS' own code stays warning-clean.

Choose displays and fonts

The names are u8g2's: a display is the setup function u8g2_Setup_<name>_f without prefix and suffix, a font is u8g2_font_<name>. The helper lists them, with sizes:

python3 tools/u8g2.py displays ssd1306          # name, bus, size, frame buffer RAM
python3 tools/u8g2.py fonts helv                # fonts with their size in bytes
python3 tools/u8g2.py fonts --u8x8 chroma
python3 tools/u8g2.py config --displays ssd1306_i2c_128x64_noname,st7920_s_128x64 \
                             --fonts helvB10_tr,6x10_tf --u8x8-fonts chroma48medium8_r

config checks the names and prints the lines for mcs_user_config.h, CMake, make and PlatformIO. In mcs_user_config.h (template: config/mcs_user_config.h):

#define MCS_ENABLE_U8G2   1
#define MCS_U8G2_DISPLAYS U8G2_DISPLAY(ssd1306_i2c_128x64_noname) U8G2_DISPLAY(st7920_s_128x64)
#define MCS_U8G2_FONTS    U8G2_FONT(u8g2_font_helvB10_tr) U8G2_FONT(u8g2_font_6x10_tf)   /* first = default */
#define MCS_U8X8_FONTS    U8X8_FONT(u8x8_font_chroma48medium8_r)
Option Default
MCS_ENABLE_U8G2 0 the u8g2 driver (needs MCS_ENABLE_DRIVERS)
MCS_U8G2_DISPLAYS SSD1306 128×64 + 128×32 and SH1106 128×64 on I²C, SSD1306 128×64 on SPI displays compiled in (each costs 1–3 KB of flash)
MCS_U8G2_FONTS 6x10_tf (default font), 5x7_tr, helvB10_tr, ncenB14_tr, logisoso24_tn (digits), open_iconic_embedded_1x_t (icons) — ≈ 7 KB U8g2 fonts in flash
MCS_U8X8_FONTS chroma48medium8_r, 8x13_1x2_r U8x8 fonts in flash
MCS_U8G2_FONT_DIR "/fonts" where SetFont("name") looks for files
MCS_U8G2_MAX_FONTS 4 font files kept loaded per display (VM heap)
MCS_U8G2_MAX_FONT_SIZE 65536 largest font file accepted
MCS_U8G2_EXTRA 8 slots for mcs_u8g2_add_font() / mcs_u8g2_add_display()
MCS_U8G2_I2C_BUF 34 I²C transfer buffer per display

Fonts from files

All 2043 U8g2 and 131 U8x8 fonts stay usable without building them in. Extract the ones you want and put them on the board's filesystem:

python3 tools/u8g2.py extract profont12_tr unifont_t_symbols -o myfs/fonts
python3 tools/u8g2.py extract --u8x8 pxplusibmcga_f -o myfs/fonts
python3 tools/u8g2.py extract --all -o allfonts           # all of them, ~15 MB
python3 tools/u8g2.py extract --from my_bdfconv_font.c -o myfs/fonts   # your own (bdfconv output)

This writes u8g2_font_<name>.bin / u8x8_font_<name>.bin — the font bytes exactly as u8g2 stores them. Copy them to /fonts (Studio, tools/mcs_remote.py, an SD card, or --fs myfs on the PC). Then SetFont("profont12_tr") uses the built-in font if there is one, else loads /fonts/u8g2_font_profont12_tr.bin into the VM heap (checked, kept for the next call). Other ways: SetFont("/sd/big.bin") (a path) and SetFont(bytes) (a byte[]).

C# API

The classes exist when the firmware has the u8g2 driver: Hal.Has("U8g2"), Drivers.Has("u8g2"). Names accept any spelling u8g2 uses — "ssd1306_i2c_128x64_noname", "ssd1306_128x64_noname" (the bus picks the I²C or SPI variant), "…_1" / "…_2" / "…_f" (page buffer size), "u8g2_Setup_ssd1306_i2c_128x64_noname_f" and Arduino class names like "U8G2_SSD1306_128X64_NONAME_F_HW_I2C" / "U8X8_SH1106_128X64_NONAME_HW_I2C". U8g2.Displays / U8g2.Fonts / U8x8.Fonts list what is compiled in.

Creating a display

Pins
new U8g2(display) / U8g2.I2C(display[, bus[, address[, reset]]]) hardware I²C (bus 0, u8g2's default address 0x3C; give 0x3D as 7-bit)
U8g2.SoftI2C(display, scl, sda[, reset]) bit-banged I²C on any two pins
U8g2.SPI(display, bus, cs, dc[, reset]) hardware SPI (mode and clock from u8g2's display data)
U8g2.SoftSPI(display, clock, data, cs, dc[, reset]) bit-banged 4-wire SPI
U8g2.Soft3WireSPI(display, clock, data, cs[, reset]) 3-wire SPI (9-bit, no DC pin)
U8g2.Parallel8080(display, int[8] data, wr, cs, dc[, reset]) 8080 parallel bus
U8g2.Parallel6800(display, int[8] data, e, cs, dc[, reset]) 6800 parallel bus
U8g2.KS0108(display, int[8] data, e, dc, cs0, cs1, cs2[, reset]) KS0108 128×64 LCDs

U8x8 has the same constructor and factories. Pins are numbers or board names ("GP4", "PA5"); null = not connected. The display is not touched before Begin().

Common to U8g2 and U8x8

Member
Begin([select, next, prev[, up, down, home]]) init + clear + wake the display; the optional pins are menu buttons to GND
InitDisplay(), ClearDisplay(), Clear(), RefreshDisplay() u8g2's init / clear / refresh (e-paper)
SetPowerSave(bool), Sleep(), Wake(), SetContrast(0..255), SetFlipMode(bool)
BusClock, SetBusClock(hz), I2CAddress, SetI2CAddress(addr) used by the next Begin()
Display, Bus, Cols, Rows setup name, bus kind, size in 8×8 tiles
SetFont(name | path | byte[]), Font see fonts
SetCursor(x, y), Home(), CursorX, CursorY, Print(value), Println([value]) Arduino-style printing (U8g2: pixels, baseline; U8x8: tiles); \n starts a new line
UserInterfaceSelectionList(title, start, "a\nb\nc") menu; returns the 1-based choice, 0 = home/abort
UserInterfaceMessage(t1, t2, t3, " Ok \n Cancel ") message box; returns the button (1-based)
UserInterfaceInputValue(title, pre, value, lo, hi, digits, post) number input; returns the value (-1 = aborted)
GetMenuEvent(), SetMenuInput(Func<int>) menus
LogBegin(cols, rows[, autoRedraw = true]), Log(text), LogLine([text]), DrawLog(x, y), LogRedrawOnNewline, LogLineHeightOffset u8log terminal
Dispose() frees the buffers; the display keeps its picture

U8g2 (graphics)

Group Members
frame buffer ClearBuffer(), SendBuffer(), UpdateDisplay(), UpdateDisplayArea(tx, ty, tw, th), SetAutoPageClear(bool), BufferSize, FullBuffer, BufferTileWidth, BufferTileHeight, BufferCurrTileRow
page loop FirstPage() / NextPage(), or Draw(() => …) / Draw(g => …) which runs the loop for you
buffer access GetBuffer() (byte[]), SetBuffer(byte[]), GetPixel(x, y), Dump() (text art), WriteBufferPBM(), WriteBufferXBM()
shapes DrawPixel, DrawHLine, DrawVLine, DrawHVLine(x, y, len, dir), DrawLine, DrawBox, DrawFrame, DrawRBox, DrawRFrame, DrawCircle, DrawDisc, DrawEllipse, DrawFilledEllipse (optional quadrant: U8g2.DrawUpperRight…DrawAll), DrawArc(x, y, r, start, end) (0..255 = full turn), DrawTriangle, DrawPolygon(int[] xy)
bitmaps DrawXBM(x, y, w, h, byte[]), DrawXBMP(…), DrawBitmap(x, y, bytesPerRow, h, byte[]), SetBitmapMode(transparent)
text DrawStr, DrawUTF8, DrawGlyph(x, y, code), DrawStrX2, DrawUTF8X2, DrawGlyphX2, DrawUTF8Line(x, y, w, text, border, isInverted), DrawUTF8Lines(x, y, w, lineHeight, text), DrawButtonUTF8(x, y, flags, width, padH, padV, text), DrawButtonFrame(…)
font metrics GetStrWidth, GetUTF8Width, GetXOffsetUTF8, GetXOffsetGlyph, GetGlyphWidth, IsGlyph, Ascent, Descent, MaxCharHeight, MaxCharWidth
font settings SetFontMode(transparent), SetFontDirection(0..3), SetFontPosBaseline/Bottom/Top/Center(), SetFontRefHeightAll/ExtendedText/Text()
drawing state SetDrawColor(0/1/2), DrawColor, SetClipWindow(x0, y0, x1, y1), SetMaxClipWindow(), IsIntersection(x0, y0, x1, y1), SetDisplayRotation(U8g2.R0..R3 / Mirror / MirrorVertical), Width, Height

Full buffer or page mode. Names ending in _f (the default) keep the whole picture in RAM (128×64: 1 KB): draw, then SendBuffer(). _1 keeps one 8-pixel page (128 bytes) and _2 two: draw inside Draw(...) (or FirstPage/NextPage), which runs your code once per page. Print, DrawLog and the menus work in both modes.

U8x8 (text)

ClearLine(row), FillDisplay(), DrawString(col, row, text), DrawUTF8, DrawGlyph(col, row, code), Draw1x2String / Draw1x2UTF8 / Draw1x2Glyph, Draw2x2String / Draw2x2UTF8 / Draw2x2Glyph, DrawTile(col, row, count, byte[8 * count]), SetInverseFont(bool), GetUTF8Len(text), FontWidth, FontHeight (in tiles), plus the common members. Coordinates are tiles (8×8 pixels).

Menus and buttons

u8g2's three user-interface functions block until the user decides. The keys come from:

  • pins: Begin(select, next, prev[, up, down, home]) — buttons to GND, read with pull-ups by u8g2 itself;
  • your code: SetMenuInput(() => …) returns U8g2.EventNext, EventPrev, EventSelect, EventHome, EventUp, EventDown or EventNone — from a rotary encoder, a touch pad, a remote, a test script. SetMenuInput(null) goes back to the pins.

GetMenuEvent() polls once (for your own menus). While a menu waits, timers, interrupts, the time limit and Ctrl-C keep working (an abort leaves the menu with 0).

Log terminal

oled.SetFont("5x7_tr");
oled.LogBegin(25, 8);                       // 25 columns, 8 lines of text (200 bytes)
oled.Log($"T = {t} C\n");                   // scrolls; redraws the screen on each newline

autoRedraw: false only collects the text; draw it yourself with DrawLog(x, y) (U8g2: pixel position of the first baseline, U8x8: tile).

Constants

R0 R1 R2 R3 Mirror MirrorVertical (rotation) · ColorClear ColorSet ColorXor · DrawUpperRight DrawUpperLeft DrawLowerLeft DrawLowerRight DrawAll · BtnBW0…BtnBW3 BtnShadow0…BtnShadow2 BtnInv BtnHCenter BtnXFrame · EventNone EventSelect EventNext EventPrev EventHome EventUp EventDown (also on U8x8).

Memory and flash

frame buffer (VM heap) _f: width × height / 8 (128×64: 1024 bytes), _2: 2 × width, _1: width bytes; U8x8: none
per display ≈ 200 bytes + the I²C buffer (MCS_U8G2_I2C_BUF)
font files their size, while loaded (MCS_U8G2_MAX_FONTS per display)
flash the driver ≈ 20 KB (Cortex-M, -Os) + the u8g2 code you use + displays (1–3 KB each) + built-in fonts (the default set ≈ 7 KB) — a Raspberry Pi Pico firmware grows by ≈ 95 KB with the default displays and fonts

Errors

Exception When
ArgumentException unknown display (the message lists the compiled-in ones), unknown font / no font file, invalid font data, wrong pin
IOException no display answers at I2C address 0x3C on bus 0 — wiring, address or power; other bus errors
NotSupportedException the board has no I²C / SPI bus with that number
ObjectDisposedException after Dispose()
OutOfMemoryException the frame buffer does not fit the VM heap — use _1 / _2 or U8x8

Build errors are explicit too: MCS_ENABLE_U8G2=1 without the u8g2 headers stops with needs the u8g2 library … python3 tools/u8g2.py fetch, CMake stops with the variables to set, and a misspelled MICROCS_U8G2_DISPLAYS / _FONTS name is reported at configure time.

C API (for firmware)

#include "mcs_driver.h"
/* a font of your own (bdfconv output) or a display the build does not list */
mcs_u8g2_add_font("my_font_tr", my_font_tr);             /* SetFont("my_font_tr") */
mcs_u8x8_add_font("my_tiles", my_tiles);
MCS_U8G2_ADD_DISPLAY(st7920_s_128x64);                   /* needs u8g2.h in that file */

Call them before the VM starts. The driver uses the port's HAL (i2c_*, spi_*, gpio_*, delay); mcs_hal_spi_config() sets the SPI mode and clock per display. Driver sources: modules/drivers/mcs_drv_u8g2.c; simulator: modules/hal/mcs_hal_sim.c (SSD1306 / SH1106 emulation).

u8g2 is © olikraus, BSD 2-clause licensed; its fonts have their own (free) licences, listed in the u8g2 repository.

Clone this wiki locally