LCDs & C++ & Linux = Beginner-Friendly?

Intro

There are many reasons to choose a programming language and environment for your projects and products. C++ has gotten a reputation for being old, outdated, difficult to learn and full of dangers. Python is often proposed as a simpler alternative, especially for people new to programming. These statements all have merit, but I would like to propose an alternate way of thinking about it.

Background

I started programming in 1979. At that time there were very few choices of programming language and very little access to documentation. The home computer vendors chose to provide a BASIC language interpreter on their machines and include a book with some introductory lessons and examples.  When you turned on the computer, it couldn't do anything except interpret your hand-typed BASIC programs. There were no built-in 'apps' or anything functional. This was enough to get me hooked on computers - the only limit to what you could do was your imagination. The programming world made sense to me until I saw a video game running on my same machine. I soon learned that BASIC was quite slow compared to writing code in the native language of the machine. So... I started learning 6809 assembly language. Then along came Pascal and C and soon after C++. The C language was pivotal for me because it bridged the gap between machine language and high level language. It allowed me to write efficient code that was also portable - a very powerful combination. C is still the most widely available way to program nearly every general purpose computer.

Friendly C++ Code

For many years I was content to write most of my projects in C. I had seen C++'s complicated syntax and brain-twisting options and preferred the more straightforward functional programming model of C. It wasn't until I started using the Arduino ecosystem that I saw that C++ could provide a friendlier model for programming. Arduino library classes are defined with a consistent pattern that allows for simple use - they define a high level API while containing efficient function definitions within. I've patterned my use of C++ along those lines to create what I think are easy to use code libraries. For me, the complexity of C++ rears its ugly head when there is excessive use of macros, templates and inheritance in ways which make it difficult to see how the source code gets turned into what actually executes. My "flat" hierarchy allows easy access to beginners and easy maintenance for people wanting to change the underlying code. With this use of C++, it looks a lot like Python.

Python and C++ Tradeoffs

There are benefits and liabilities in each camp. There's also personal preference. I actually find Python more frustrating to work with compared to C++. The requirement of having a virtual environment with specific library and Python versions is probably the worst aspect for me (beyond the slow performance and poor management of bits and bytes). My aim with my C/C++ code is to have a minimum of external dependencies and the simplest possible build script. The downside to my choice is that building even small projects, on SBCs like the Raspberry Pi, can be quite slow. When developing code, each change subjects you to a slow edit/compile/run cycle. Python's edit/run cycle is certainly quicker. I mitigate this problem by developing C++ code on a very fast machine 😀.

The Challenges of 'Physical Computing' on Linux

Linux is the run-everywhere, do-everything operating system. By convention, it treats nearly everything like a file, so physical devices also get that treatment. GPIO, I2C, SPI and other interfaces have been managed by devicetree drivers which present their interfaces in the /dev folder. Some of these devices (e.g. GPIO pins) in the past were supported by custom drivers by vendors like Raspberry Pi. It's taken a while, but Linux now has a standard interface for GPIO (gpiod) that has been adopted by most Linux SBC vendors (including for the RPI). This makes it easier to create portable code that will run on more than just RPIs. This doesn't solve all problems because the configuration of these interfaces still varies greatly between systems. There are also two different (incompatible) versions of GPIOD in the wild, but it's relatively easy to #ifdef your code to work with both of them. Using the gpiod library (or file interface) for GPIO doesn't allow for fast updates. The max speed you can change or read GPIO pins is on the order of several thousand times per second. However, if you directly access the GPIO MMIO (memory mapped I/O) registers of the CPU, you can change/read GPIO in parallel (up to 32 at a time) in the millions of times per second. In fact, the RPI4 is capable of doing that at up to 75MHz (in native code). Python is not able to get close to these speeds.

Small Displays on Linux

Connecting little LCDs to SBCs like the Raspberry Pi makes a lot of sense. Popular, small (under 4 inch) LCDs available from multiple vendors are affordable and easy to control with a standard SPI interface. The challenge is the long list of unique initialization sequences of the different types of LCDs and deciding how the software will produce the content (aka pixels) for them. There are many "re-invented wheels" which do this job. For example, if you search the internet for "ST7789 driver" you will find literally thousands of different projects, all doing similar things and written in multiple languages. Besides Sitronix's popular line of STxxxx LCD controllers, there are thousands of other drivers/projects to control the other LCD controllers (e.g. ILITek ILI9341, Solomon Systech, etc). The thing is, these little displays are all nearly identical in their use of the MIPI display command set and data formats. I decided to make a more universal solution (my bb_spi_lcd library) which can talk to ALL of these displays with a common API and portability across Arduino and Linux, esp-idf and others. That portability comes in the form of C/C++ code that can compile on almost any target system. My aim was to reduce frustration and simplify everyone's projects. I wrote it in C (with an optional C++ wrapper class) to make the code very efficient on slower, more constrained systems and it helps with portability too since a 8MHz MCU with 2k of RAM can't even hope to run Python.

Framebuffer Driver?

This is the other decision point when working with small LCDs on Linux - use custom code to generate the pixels or create/mirror a Linux framebuffer. If you don't have a framebuffer driver for your display, you can do something like fbcp (framebuffer copy) to copy output (like from SDL2 games) to your small LCD. The latest version of Raspbian complicated this a bit by removing DispManx (the RPI-specific display manager API). The replacement is KMS/DRM which is in wide use on many Linux distributions. Dispmanx had advantages for RPI Zero - it could do fast scaling and pixel conversion using the GPU. The original RPI Zero doesn't have NEON SIMD and is too slow to do those kind of operations at 60fps on the CPU. Many users are running old versions of Raspbian just to keep using Dispmanx. Another way to accomplish this is to use a Devicetree driver for your specific LCD. This can create a desktop image running on your little LCD, All types of framebuffer drivers have a common problem - it's difficult to navigate/use a GUI on such a low resolution display.

What can an LCD do without a FB Driver?

This question probably comes to mind if you're accustomed to using LCD displays on Linux with a framebuffer driver. Without a root/system driver controlling the display, you'll need to use drawing functions to draw individual pixels, lines, rectangles, circles and text. This can be much faster than is possible with a framebuffer driver because you can update only the region of the display that has changing pixels instead of updating the whole display. With my display libraries, I have a standard set of 2D drawing functions for everything needed, including compressed bitmaps. If you're trying to get someone else's project to display content (e.g. some Python Pillow code), you'll have trouble, but if you're in charge of drawing your data in C/C++, it's relatively easy to use.

Arduino -> Linux

Most of my recent FOSS efforts have been toward microcontrollers. This same code can compile and run on Linux because as a general rule, I separate the portable C code from the OS-specific parts. To port my libraries to a new system you only need to provide some wrapper functions for I/O and possibly memory allocation. Where this really comes in handy is my imaging and display libraries. I've optimized them for working with 16-bit RGB565 pixels (the most practical format for small LCDs and constrained devices); they also support other standard pixel formats. For small LCDs on Linux, my imaging codecs are actually much easier to use than the standard ones which ship with Linux distributions (e.g. libgif, libpng, libjpeg). The reason is that I created simpler paths (aka less code) to generate RGB565 output that's ready to display on your small LCD.

Example Project

I've created many example projects, but this particular one is a good example to go with this blog post. It's a simple command line interface (CLI) tool which uses my LCD and GIF libraries to play animations on small LCDs. I've preconfigured it to support 4 popular LCD 'HATs', but it can easily be adapted to work on almost any display:

https://github.com/bitbank2/rpi_gif_hat

Here's a video of it running on the Waveshare 1.3" 240x240 LCD HAT:



Please have a look at the source code and I think you'll agree that it really showcases a simple way to use C++ to get performant, simple programs on Linux.

If you look through my other repos, you'll find similar examples written for Linux/RPI which are beginner friendly. Please feel free to leave comments here if you have questions or constructive criticism.


Comments

Popular posts from this blog

How much current do OLED displays use?

How to speed up your project with DMA

Fast SSD1306 OLED drawing with I2C bit banging