Luddites hate it!
For my group’s Embedded System Digital Audio Loop Station project, the base technology requirements are pretty specific: at a bare minimum, a microcontroller and corresponding audio adapter are necessary, and we went with the project’s recommendations for these: the Teensy 4.1 development board and the Rev D2 audio shield. I won’t go into detail about their specs here, since I already touched on them in a previous post.
Instead of starting completely from scratch, we decided to use a few Arduino and Teensy libraries to facilitate interaction with I/O and raw memory manipulation. The Arduino libraries and utilities are mainly used to compile and upload projects for devices without an underlying “system”, while the Teensy libraries manage the data flow through the board and audio adapter. For our project in particular, the Teensyduino Audio Library has provided a strong foundation, but it has a quite steep learning curve, especially for those new to embedded development (i.e. those like myself). More on this later.
Before deciding which technologies I liked the least/most, I had to get the hardware to the point where I could play with it. Prior to starting this project, I’d soldered approximately 8 pins in my life; we’re currently using a prototyping breadboard for many of the connections, but header pins still needed to be soldered to the board and adapter, so they could be attached to the breadboard and each other.
The gallery above shows the order in which I connected the various components, and images 3 & 4 show the unfortunate consequences of this sequence: the memory chips in the image are much smaller than they appear, and attaching them with pin headers already in place proved to be very frustrating.
The soldering connections are messy and, initially, I didn’t clean up the excess flux. After running a memory test on the flash memory attached to the adapter, I found that it couldn’t be recognized, despite multiple attempts to tidy up the connections. I even tried using a desoldering pump to start over, to no avail. I practiced more care when attaching the chips to the Teensy board, and although they still look terrible, they were at least recognized (though honestly we aren’t using any of the extra memory yet). I’d say this was the steepest learning curve so far, but at the same time very exciting.
Patching the Peripherals
Learning how to read a pin diagram and patch a breadboard presented its own new set of challenges, but I eventually arrived at a functional prototype:
Now I could finally start taking the thing for a ride, and I started by test driving some of the prepared examples in the Audio Library. Getting the applications onto the device was relatively straightforward, especially when using the Arduino IDE. In short time, I was able to get sound into and out of the device, see things on the display, and print logs to the Serial Monitor.
Speaking of IDEs…
This was all well and good, but before I really jumped into development, I wanted my environment to be nice and comfy. As I’ve stated in another previous post, I’m a big fan of command-line programs and terminal-based editors. Also, my muscle memory makes using anything but Vim-like key bindings feel like using a rotary phone. Yes, I know many IDEs offer such key bindings, but there’s much more to Vim that I enjoy.
Using a command-line interface allows common tasks to be automated via scripts and configuration files. The Arduino CLI, while relatively new (it’s only at version 0.3), already offers a solid feature set, and I’ve aliased a few commands to make compiling and uploading a cinch. The end result is the same, so no problems arise when my project partner uses the IDE and I use the CLI.
The Arduino Framework
Since the Teensyduino libraries are meant to interface with Arduino libraries, the projects that employ them need to use a project structure that differs from standard C++ projects. Each “sketch” (main project file with a
.ino extension) must have a
setup and a
loop function: internally, these are wrapped in a
main function, but this is done because programs flashed onto embedded systems don’t exit like other programs we’re probably more familiar with.
External functions and classes can be defined and included from other
.cpp/.h files in the project directory, but I’ve found that this must be done with care: certain tasks should only be executed in
setup, and other actions should only be performed in
loop. I found this out the hard way when, after implementing a basic audio loop, I manically started extracting responsibilities into various classes, instead of enforcing a short develop/test cycle. These were intended to model signal flow in a traditional audio recording/production environment, and I was convinced I had a well-established model, but by the next time I tried to verify the program’s compilation via our GitHub workflow and run the program on the hardware, there were enough errors to make diagnosing their source(s) quite daunting.
In retrospect, I should have been practicing Test-Driven Development, or at least unit testing of some sort. This, however, presents a challenge in itself, since testing frameworks for Arduino projects are slim, and most lack certain desirable features. The ArduinoUnit framework looked promising, and I was about to roll back a few commits and start using it for tests, but then tragedy struck: the audio adapter’s onboard microphone stopped processing input, and replacing it didn’t fix the problem (maybe I didn’t remove enough of the initial solder?). I attempted to connect a peripheral line-in audio jack, but that didn’t register either, so I either botched that, too, or there is a fundamental connection issue with the audio adapter. Since we’re past the point when we should be focusing on hardware connections, I instantly purchased a whole new adapter. While I appreciate the soldering experience, I made sure to buy one with pin headers attached, so I could dive right back into development when it arrives.
In the meantime, I found a different framework that allows for unit testing without a physical device, Arduino-CI. It doesn’t use a virtual machine emulator; rather, it tests the program as if it were a C++ program running on the computer. This is mainly intended to test libraries, but it can test compilation of “sketches” and, with some tweaks, it can also perform unit tests on the sketches (the functionality under inspection simply needs to be extracted from the
.ino file into a
.cpp file. Here comes another learning curve, I hope it’s not too steep!