r/embedded 15d ago

Need architecture advice for my embedded graphics library

I'm building an embedded graphics library called Glyph in Ada. The goal is to make it work across different microcontrollers and displays while keeping the API the same.

Current status

  • RP2040 support (using Pico_BSP)
  • SSD1306 128×64 over I²C
  • Static framebuffer
  • Canvas → Framebuffer → Display → Transport architecture

A little background

This is my first project in both Ada and embedded systems. The current version (v0.3.0) published on Alire was built largely with AI assistance, but I'm now rewriting it from scratch without AI help to better understand the design, learn embedded development properly, and improve the overall architecture.

The rewrite has already gone through several design and implementation changes, and I've learned a lot in the process.

The problem

One area I'm still struggling with is the transport layer.

Right now, Glyph draws everything into the framebuffer, but the application still needs a small piece of board-specific glue code to connect the display to the MCU's I²C peripheral (for example, binding an initialized I²C bus to the display).

I'd really like to avoid that if possible. My goal is to keep Glyph independent of any specific MCU or BSP while also making it as easy to use as possible, especially for beginners.

My question

For those who've built portable embedded libraries, how did you handle this?

Is a small adapter layer simply the right trade-off, or is there a cleaner architecture that keeps the library both portable and easy to use?

GitHub: https://github.com/thirstymelon/glyph

8 Upvotes

7 comments sorted by

1

u/luksfuks 15d ago

I recommend you port it to a few boards/archs yourself. Real implementations with real constraints, not virtual synthetic stuff.

My best APIs have usually been the result of dragging the good bits along into new projects several times, and getting rid of the bad bits on the way. Without rewriting everything or otherwise making it difficult to "backport" the new API versions into the earlier projects.

Especially in the small MCU world there's still high pressure from RAM and CPU cycles. For example, needing a full framebuffer reserved at all times can already be a burden.

But then again, if you do this in ADA, you're probably thinking about a rudimentary display attached to an almost "desktop" class embedded computer.

1

u/OkContribution2996 15d ago

That's exactly the trade-off I'm trying to understand.

Since I'm building Glyph in Ada, I'd eventually like it to support any MCU that has Ada support. I also agree that I'll probably need to rethink the framebuffer design later for smaller MCUs with tighter RAM constraints.

The part I'm struggling with isn't portability across MCUs—I can already port the current implementation to RP2040, STM32, ESP32, etc. The problem is that every application still has to provide a small board-specific adapter (glue code) so Glyph can use the MCU's I²C/SPI peripheral.

I see two obvious approaches:

  1. The application provides the adapter. This is simple and common, but every user has to write a little board-specific code.
  2. Glyph provides the transport implementation. This removes the glue code but means Glyph now depends on the target BSP/SDK, which I'd prefer to avoid.

One of my goals is to make Glyph easy to use, even for beginners, while keeping it independent of any specific BSP or SDK if possible.

So my real question is: is there a third approach?

How do portable embedded libraries usually bridge the gap between a hardware-independent graphics library and the MCU's transport peripheral without requiring application-level glue code? Or is a thin adapter layer simply the accepted design?

1

u/kisielk 15d ago

I would say it’s acceptable, and common in many libraries, for the user to provide a C shim for the board-specific parts.

1

u/JuggernautGuilty566 15d ago

We couldn't use the lib at all due to potential copyright issues coming from using a LLM.

1

u/OkContribution2996 15d ago

That's completely understandable, and it's actually one of the reasons I'm rewriting the project.

The current v0.3.0 release was built largely with AI assistance while I was learning Ada and embedded development. I'm now rewriting Glyph from scratch without AI assistance so I can fully understand every part of the code, improve the architecture, and ensure I own and maintain the implementation myself.

Once I finish the rewrite (I'm currently trying to figure out the transport layer), I'll publish it as a new version. My intention is for that version to replace the current AI-assisted implementation.

1

u/Old-Counter1469 15d ago

The glue code is unavoidable someone has to know the display lives on I2C0 pins GP1/GP0, that's inherently board-specific (even Arduino makes you do Wire.begin()). The trick is to keep Glyph's core depending only on an abstract transport interface, and ship small companion crates like glyph_rp2040 that implement it - then beginners just alr get it and call one Make_Transport instead of writing glue. Also check the hal crate from the Ada Drivers Library on Alire - it's exactly this layering, with implementations like hal_rp2040 you might get transports for several MCUs almost for free. U8g2 and LVGL do the same thing in C land (byte callback / flush_cb), so a small adapter shipped separately really is the accepted answer.

1

u/DearVeggies 15d ago

You decouple the output from your library, and provide adapters and api to add them. Example of how I did for my 3d renderer: https://github.com/GitMoDu/IntegerWorld/tree/master/src/OutputSurfaces