Monochrome OLED screens rendered on an STM32 through u8g2.
Each screen is a function of u8g2 drawing calls under src/screens/, called between
u8g2_ClearBuffer() and u8g2_SendBuffer(). Screens can be written by hand or generated;
src/screens/README.md covers the generator settings that produce compatible output.
No vendor driver layer sits under it. The peripherals are driven straight from their
registers, so there is no CubeMX project to regenerate and nothing to keep in sync with a
.ioc file.
The board brings up, finds the display on the I2C bus and draws to it through u8g2. The bus runs slowly to work around a missing pull-up on the module; see I2C.
| Part | Detail |
|---|---|
| MCU board | NUCLEO-G070RB (STM32G070RB, 128 KB flash, 36 KB SRAM) |
| Display | PGO12832A-W, 0.91" OLED, 128x32, SSD1306 controller, I2C |
| Display | Nucleo pin | MCU pin | Alternate function |
|---|---|---|---|
| SDA | CN5 pin 9, D14 |
PB9 | AF6 |
| SCL | CN5 pin 10, D15 |
PB8 | AF6 |
| VCC | CN6 pin 4, +3V3 |
||
| GND | CN6 pin 6, GND |
The display is powered from 3V3 rather than 5V so the I2C lines stay at the same level as the G070 pins.
The module pulls SDA up with a 10k to VCC and leaves SCL alone. The 100n next to it sits across VCC and GND and decouples the supply. So the clock line is held up by the ~40k internal pull-up on PB8 and nothing else, and against the bus capacitance its rise time lands far outside what standard mode allows.
The bus therefore runs at 10 kHz, slow enough for the levels to settle. I2C_TIMINGR is
0xF0423131, derived with the RM0454 formulas for a 16 MHz I2CCLK: PRESC=15 (a 1 us tick),
SCLL=SCLH=0x31 (50 us each), SDADEL=2, SCLDEL=4. A 512-byte frame takes roughly half a second
at this rate.
Soldering 4.7k from SCL to 3V3, and a second one on SDA to stiffen the 10k already there,
removes the constraint, after which I2C_TIMINGR can go back to 0x30420F13 for 100 kHz:
PRESC=3 (a 250 ns tick), SCLL=0x13, SCLH=0x0F, SDADEL=2, SCLDEL=4.
The panel answers at 7-bit address 0x3C. Addresses are passed around as 7-bit values; the
shift into the SADD field happens inside i2c.c.
arm-none-eabi-gcc
make
st-flash # optional, only for `make flash`
Newlib is not required. The firmware builds -nostdlib -ffreestanding, so a bare
arm-none-eabi-gcc without a C library is enough.
make # build/g070-oled.{elf,bin,hex}
make cleanDebug info is kept in every build and the ELF is never stripped, because mcuhex resolves live variables from its symbols.
Either flash over SWD from the browser with mcuhex, pointing it at
build/g070-oled.bin and build/g070-oled.elf, or use the on-board ST-Link:
make flashAt startup it probes the panel at 0x3C, waits for the controller to accept a
no-op, and runs the u8g2 init sequence. A board that finds nothing blinks LD4 at
5 Hz and keeps trying, so reseating a jumper is enough to bring the display up
without a reset.
Once the panel is up, LD4 on PA5 toggles every 500 ms as a heartbeat and each pass redraws the screen.
Two counters are exposed as globals rather than printed, which keeps a UART and two more pins out of the build. Watch them in mcuhex:
| Symbol | Meaning | Healthy value |
|---|---|---|
g_i2c_nacks |
writes the panel did not acknowledge | flat once the screen is drawing |
g_i2c_timeouts |
transfers that never finished | 0 |
The capture is from a debugging build, so it lists counters that are no longer in the tree.
Both climbing means the panel is not answering: check the jumpers first, since a pin that is not quite seated looks exactly like a missing module.
Makefile build rules
docs/ photos used by this file
ld/ linker script
src/ firmware
src/screens/ one function per screen, plus its own notes
src/shim/ the libc pieces u8g2 needs under -nostdlib
lib/u8g2/ vendored u8g2 subset
tools/extract-font.sh pulls one font out of the upstream font file
vendor/cmsis/ ST device headers and ARM core headers
vendor/cmsis holds copies rather than a submodule, so git clone && make is one step. The
sources are cmsis_device_g0 and
CMSIS_5.
lib/u8g2 is a subset of u8g2 for the same reason. A full
clone is 415 MB, most of it the 38 MB u8g2_fonts.c. What is here is the core plus the
ssd1306 128x32 driver, at 1.2 MB. tools/extract-font.sh appends another font to
lib/u8g2/fonts.c when a screen needs one:
tools/extract-font.sh u8g2_font_6x10_tf >> lib/u8g2/fonts.c
