Getting it working.
TL;DR: I extended an API I found on GitHub to support the pigpio libraries.
This was an exercise in updating a C++ API to support a library now shipped with newer versions of the Raspberry Pi. As mentioned earlier, I found and forked a GitHub repo as a place to start.
That API supported a specific Broadcom chipset used on older Raspberry Pi boards, but I have a Pi 3B+ and a much newer 64bit OS, so I needed to go another way.
The pigpio libraries and dev libraries are available as Debian packages, so installing them was easy.
Rabbit Hole 1: Visual Studio Code and the makefile.
I haven’t written C++ code professionally in a while, and then, it was in the context of adding VR interaction logic in a proprietary game engine (Frostbite), and there’s a mile-high stack of build pipeline wrappers around that, so the humble makefile is not something I ever dealt with.
I was utterly ignorant of makefiles, other than running “make” in a shell would compile the source code into an executable I could test. At least now I know how to integrate includes, libraries, and the glorious joy that is pkg-config.
Half of the pain in this was learning how to set up the dev environment correctly. VSCode does some neat stuff, and it’s really easy to get started, but there’s 9 rings of devils in the details. The C/C++ extension is great, until it isn’t. I don’t have C/C++ syntax burned into my brain like I would if I’d been using it consistently from birth, so I tend to lean pretty heavily on Intellisense and code completion. It may be a crutch, but it saves a lot of time and screen space when I’m looking for class functions and objects. I also lean heavily on VSCode’s debug tools.
Without diving too deep into the configurability of VSCode, I’ll just say that for Intellisense and debugging to work, it’s necessary to make sure the c_cpp_properties.json file contains the same set of paths, includes, and libraries as the tasks.json file or makefile. Point is, you can get error squiggles in theneditor but the code will build and run just fine, or the opposite, where Intellisense doesn’t flag any errors, but half the class libraries are missing.
Rabbit Hole 2: pigpio
TL;DR: The pigpio packages provide a general interface to the GPIO pins instead of a chip-specific driver.
The VM205 uses a SPI connection on the Pi’s GPIO pins to receive commands and send scope/probe data. The way you ask the hardware to set up SPI connections and transmit/receive data is a little different from the Broadcom library, but it makes a lot more sense. SPI connections are pretty simple, and once understood, easy to work with based on the structures in the VM205 API.
At first, I hacked the pigpio SPI connection into a function in the VM205 class. It was a sloppy mess that progressed into a less-sloppy struct in the VM205 header, and then finally got its own PigSpi() class.
After that, I just had to instantiate a PigSpi object in the VM205 class to get my connections. I probably did it wrong, because I’m pretty naive about C++ architecture, but it works. The GitHub code as of this writing should run on a Pi3B+ and display a cyan waveform on a black screen.
Rabbit Hole 3: The GUI
This is still in progress. I need a UI that reflects the UX of a hybrid oscilloscope/logic probe, and Velleman decided to write their demo app in Pascal (really?), so that wasn’t going to work for me. I’m already struggling with C/C++ on this project, and even my brain choked on trying to figure out yet another programming language on the fly. But that meant learning a GUI toolkit, which is a commitment all on its own. I chose to implement a C++-based GUI, because I’ll gain more useful knowledge from that than I would from a slog through Pascal, which I literally have not seen on a screen in front of me since 1990.
I also need to take on software projects like a programmer instead of a rat finding its way through a maze. This part got stupid, and has stayed stupid, mostly because my approaches have been stupid. Let’s go through them, shall we?
During all this, I learned a little about GUI conventions, the primary one being that the GUI is a while loop that draws stuff until instructed to stop. Within that loop, events are read from the system, and those cause other code to run or data to change. The functions that run are known as callbacks, and the events are triggered by user inputs and other state changes. This may not be exactly right, but that’s what I seem to be getting from the docs and examples.
Most GUIs work more or less this way, but I didn’t know that, and thus spent a lot of time trying to figure out the best GUI for this project. The qualifiers were:
- As simple as possible (not a thing with GUIs, really)
- Good support and examples
- Lightweight and runs on a Pi.
Qt is a very popular GUI toolkit, massively cross-platform, and with tons of examples. There’s an open-source version and a commercial version, and the documentation is great. Problem is, it’s a hog. I couldn’t get my test examples to build on a 1GB Pi 3B, which started me down the cross compilation rabbit hole because I had the idea that I could cross-compile 64bit ARM executables on my M1 Mac laptop. Maybe it’s possible, I couldn’t make it work. Cross compiling is another rabbit hole I’ll describe later.
Agar is lightweight, runs on Linux, and thus Pi, and was a decent entry point for figuring out how to stand up. It also looked like the simplest. The demo app goes through all the different available widgets and drawing surfaces. Thing is, it’s a C API and I wasn’t sure how that would play with C++. Yes, you can use C API’s with C++ applications, but I wanted to keep everything in C++ as much as I could. It’s biggest con for me is that there isn’t a lot of example code out there and everything seems kind of late-90s open-source. Not a lot of Agar on stackoverflow. Also, there’s no GUI layout editor. I put this one down in favor of something more widely used.
Currently, I’m slogging my way through GTK/gtkmm. The nice thing about this one is that there’s the Glade GUI editor for the layouts and widgets, all of which default to packing algorithms so you don’t have to keep track of layout coordinates for every widget, and it will work on both Ubuntu and PiOS. My dev machine is currently Ubuntu, though that may change. So far, I can create an app and draw in a DrawingArea() widget.
Because I wanted to stick to C++, I’m forced to use gtkmm, which is a C++ wrapper around GTK. Documentation of gtkmm is not great, not awesome. It took me a long time to figure out (thanks stackoverflow) that the only place you’re ever allowed to draw on a pixel buffer is in the on_draw() event in a Cairo context. Near as I can figure out, Cairo is where you draw pictures into a pixel buffer using methods like fill() stroke(), paint(), draw_to(), etc. If you want to draw text in a pixel buffer, Pango is how you do it in GTK/gtkmm.
The Glade UI editor is fairly straightforward: You lay out your widgets, then connect their events to application code. The fun part comes in accessing and manipulating those widgets.
GTK has a class called a Builder(), which reads the .xml file saved out by Glade and creates an object containing all the widgets found in the Glade file. Through that object, you can connect widget events to code. The gtkmm wrapper complicates things a bit, because getting and setting what you want isn’t consistent across all widget types. Also, instead of widget objects, you have to work with pointers to the widget objects in the Builder. Also, all the event signals and functions need to be connected when the Builder is instantiated. I’m pretty sure I’m doing it wrong, but it feels awkward.
It’s kind of complicated and I’m not having much fun keeping things modular, because I’m not having much fun overriding the on_draw() method so I can actually write to the Cairo context in the DrawingArea widget. It’s making me wonder if I should have just stuck with Qt. Maybe I’ll go back to Qt.