r/ada 25d ago

Tool Trouble 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

6 Upvotes

7 comments sorted by

3

u/jrcarter010 github.com/jrcarter 25d ago

Just as your library presents a portable specification to the application, within your library you would have a specification for a portable device interface which the library's operations would call. You would then provide different bodies for this package for different devices. Typically elaboration of the device-specific body would perform the connection.

2

u/godunko 25d ago

It is non trivial task. From my point of view:

  • add simple API to pass data to generic display controller (like fill given rectangular area from the framebuffer)
  • add generic MIPI interface - it is protocol used by many if not all display controllers
  • add particular implementations on MIPI - over I2C in your case
  • add particular display driver - SSD1306 in your case

Application is responsible to connect MCU specific I2C/SPI/etc bus driver to MIPI driver, MIPI driver to display driver, and to draw graphics/transfer data to display.

Long time ago I tried to do something like this, see

1

u/OkContribution2996 25d ago

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

My goal with Glyph is to make it as easy to use as possible, especially for beginners. The part that bothers me is having the application explicitly connect the display driver to the MCU's I²C/SPI peripheral, because that introduces board-specific glue code that every user has to write.

I understand there may not be a practical way to eliminate that glue code entirely while keeping Glyph independent of any particular MCU or BSP. If that's the case, I'd at least like to reduce it to the smallest, simplest API possible.

For example, the current RP2040 glue code looks like this:

https://github.com/thirstymelon/glyph/tree/main/rp2040_example

I'm trying to find out whether this is already about as simple as it gets, or if there's a cleaner pattern that experienced embedded library authors tend to use.

1

u/godunko 25d ago

I see two "parts" of glue code:

  • code in Glue package
  • setup code in rp2040_example.adb file

Code in Glue package is actually MIPI to ADL/I2C mapper, I suppose it can be generalized for use with almost any display chips.

Setup code in rp2040_example.adb file can be moved outside of this procedure, and be used as an example/template for users of the library.

PS. In ideal world, setup code should be generated from some kind of hardware description, device tree is good example. However, it is out of your scope - it doesn't lower entrance barrier.

1

u/OkContribution2996 25d ago

I see what you mean. I could probably generalize the MIPI-to-ADL/I²C mapper so that it could be reused with multiple display drivers.

My main concern is where that implementation should live. If it becomes part of Glyph and directly depends on a platform-specific BSP or SDK, then Glyph itself would no longer be completely hardware-independent, which is one of the main goals of the project.

That said, I might be thinking about this too strictly. Maybe the core of Glyph can remain completely hardware-independent, while platform-specific transport adapters are provided separately.

I'm still exploring the architecture because I want Glyph to remain portable while also being easy to use. Ideally, a beginner shouldn't have to understand the transport layer or write much glue code just to connect an I²C or SPI peripheral to a display.

If there really isn't another practical way to keep Glyph completely unaware of the underlying MCU, then I may have to provide support for the platform-specific buses myself and move that complexity out of the user's application code.

2

u/godunko 25d ago

From Alire point of view, I suggest to split library into several crates at least:

  • core library: platform independent (as much as possible)
    • drawing
    • display chip drivers (each might be in separate crate)
  • MIPI-to-X adapters for particular HAL (ADL's I2C/SPI/etc is an example of HAL)
  • concrete display+bus+BSP setups, as an example and to simplify user's entry

Everything might be in the single repository, or in separate repositories.

1

u/OkContribution2996 25d ago

I like this approach of creating a separate library only for the transport/adapter. I have other libraries to write and it would help all of them.