Developing the firmware for Novation's SL MkIII keyboard marked several firsts for me: It was my first Novation product*; It was also the first time I'd worked on firmware, having only worked on desktop and mobile products.
What is application firmware?
I worked on what we call the application firmware.
At Novation, we split the device firmware into two layers.
The bottom layer is the low-level firmware. This is the layer that interacts with the hardware. Low-level engineers understand the intricacies of threads, pins, interrupts, and real-time operating systems. They also consider a blinking LED to be a form of debugging. Maniacs.
The application firmware sits above this abstraction layer and contains the 'business' logic for the device.
Between the two layers is an abstraction layer. Through this abstraction layer, the low-level firmware can notify the application that a button has been pressed, MIDI has arrived or some time has passed. In turn, the application firmware can light LEDs, send MIDI or write to flash.
One of the benefits of this structure is the option for simulation. For this project, I created a JUCE application that simulated the functionality of the low-level firmware. Using this simulator, we could develop and debug the application before we'd received the hardware, and without relying on (let's face it, clunky) embedded IDEs.
Death by requirements
To begin with, the project struggled under the weight of requirements. The goal was to make a professional MIDI keyboard with a built-in Circuit-style sequencer. We were trying to design the requirements in a very formal, waterfall-y way.
The requirements covered everything, from the amount of tension required to pull out a jack cable to the clock resolution of the sequencer. These requirements were difficult to write, laborious to read and almost impossible to get buy-in from the team.
Our solution was to embrace Scrum for the firmware development. We'd toyed around with Scrum before but it had always been Scrum in name only.
This time we wanted to do it properly.
One of the benefits of Scrum is that it welcomes uncertainty, so it seemed like a good fit for this project.
Moving to Scrum came with challenges of its own. For example, how does it fit around rigid manufacturing deadlines? What if a user story requires a change to the hardware? How do you know how much memory and processing power you need?
I'd like to say that we found answers to these questions, but we only found compromises. These are probably topics for another post!
Finding our rhythm
Eventually, we found our rhythm. Scrum facilitated designing and developing in parallel. Using the simulator meant that the application was decoupled from the hardware.
With everything in place, we could slowly make our way through the mountain of features. And there are a lot of features!
There are user-customisable templates; There is a pattern-based sequencer; There is MIDI clock and transport support; There's an arpeggiator; There are scales; There are overlapping keyboard zones; There are CV and gate outputs; There is an InControl mode to control DAWs.
Save our RAM
For me, the biggest change when switching from desktop and mobile to embedded development was the reduced memory. Modern desktop machines have tens of gigabytes of RAM, whereas the processor for SL MkIII has hundreds of kilobytes.
To avoid any out-of-memory crashes, all memory is allocated at startup.
On several occasions, the keyboard failed to initialise because we'd allocated too much memory in the application. Every time this happened, we had to pivot into 'RAM-saving mode'.
We were able to save a large amount of RAM by reducing the flexibility of some of the view components. We'd designed the user interface around a tree of views, as is common in UI applications. Each view had lots of properties to customise its display and appearance. However, this required more and more RAM as the number of views increased. The solution was to remove these properties and make more use of compile-time constants.
Even the allocation itself was costing us a lot of RAM.
After inspecting the disassembly of
malloc(), we noticed that it was using 64-bit alignment on a 32-bit system.
We wrote a custom
new() operator with the correct alignment and eeked out a few more kilobytes of memory.
Despite being high pressure, and at times stressful, it was a great team and a fun project. Each feature brought a new toy to play with, creating a continuum between debugging and jamming.
To work on an embedded product was also highly rewarding. With very few dependencies, it's reassuring to know that once it's working, it's probably going to keep working.
Exposure to processors and the scheduling of real-time operating systems is not only useful for embedded development, it's something that I will take with me into every future project.
*Focusrite and Novation products are designed in the same office