The LMS101 Project
Mechanical keyboards are bulky and expensive when implemented in synthesizer designs. With the advent of capacitive touch technologies, MCUs and DACs, these Camosun College Electronics students juxtapose their analog synthesizer voice of yesteryear with a digital touch keyboard of today.
— ADVERTISMENT—
—Advertise Here—
Although modular synthesizers have existed since the 1950s, they have become increasingly popular in the last decade. In modular synthesis, individual modules emit and receive audio and control voltage (CV) signals via jacks (“patch points”) for audio routing and parameter automation. Modules must be connected using patch cables to configure the behavior of the synthesizer. With this flexibility, the possible configurations—and resulting sounds—are limited only by one’s imagination.
Don Buchla pioneered unique modular synthesizer designs in the 1960s that were well ahead of their time, and, therefore, were mostly relegated to experimental and academic settings. Buchla also invented the sequencer and was one of the first to employ touch pads rather than traditional mechanical keys [1]. These early touch pads were implemented through complex voltage divider networks. Technology has progressed since then, as it does, and made touch capacitance a more feasible implementation for such a feature.
We are a team of Electronic and Computer Engineering Technology students at Camosun College and we built a synthesizer called the LMS101 for our final project. The LMS101 (Figure 1) features an analog wavefolding oscillator, a 25-key touch-capacitive keyboard, and many other tools to explore synthesis. The LMS101 was heavily inspired by the 1972 Buchla Music Easel, one of the first analog synthesizers that featured a touch keyboard [1]. We elected to build a touch keyboard using touch capacitance, and we will outline its design in this article.
— ADVERTISMENT—
—Advertise Here—
The complete LMS101 Synthesizer. Bottom: touch-capacitive keyboard controller. Top: analog wavefolding synthesizer and modulation controls.
CAPACITIVE SENSING
Capacitive-touch sensing measures changes in the capacitance of a surface, such as a screen or a copper pad, to detect whether a touch has occurred. The human body in proximity or contact with the measured surface acts as a capacitor that changes the capacitance of the surface. Typical capacitive touch circuits utilize a capacitive sensor (which is itself a capacitor), charge transfer circuit, a sample and hold (S&H), and a sigma delta analog-to-digital converter (ADC) to detect user interaction with the capacitive sensor.
The charge transfer circuit is an RC circuit with a known time constant (τ). Once the circuit has been charged, the charge is transferred to the capacitive sensor. The S&H and sigma delta ADC are used to measure the rate at which both charging events occur. The initial charging of the known RC circuit can then be compared to the charging rate of the capacitive sensor to determine its τ value. A touch event is detected when the change in capacitance crosses a predefined threshold.
Self-Capacitance: Self-capacitive sensors are the simplest to design. They consist of a two-dimensional conductive pad and a dielectric overlay. (This could be solder resist or an acrylic panel.) Their baseline capacitance typically comes from a combination of the sensor’s natural capacitance (CX) and parasitic capacitance (Cp) formed with the nearby ground plane (Figure 2). When a finger approaches this pad, a capacitive coupling occurs with the finger (Cf), and our sensor’s capacitance is now equal to the parallel combination of both capacitances:
CT= CX + Cf +Cp (Figure 2).
Oscilloscope captures comparing charge transfer events in absence (left) and presence (right) of touch. Larger pulse is charging of known RC circuit and smaller pulse the charging of the capacitive key
While implementation of self-capacitive sensors is relatively straightforward, it does come with its own drawbacks and constraints. The electric field induced in the sensor element is 360 degrees, which means placement of moving metal bodies or noisy circuitry near the sensors can cause erroneous detection events. The shape of the field also means that detection occurs on both sides the sensors, which could be a benefit depending on the intended application.
There is a second method of capacitive sensing, mutual capacitance, which utilizes two electrodes (one “driven” and one “sense”). The application of mutual capacitance is more complex than that of self-capacitive sensing, and gives higher resolution and multi-touch detection capabilities. Since our detection requirements are for a piano-like key bed, we chose self-capacitive sensing for its simpler implementations.
HARDWARE DESIGN
Off-the-Shelf Hardware: For those who don’t wish to fully implement a capacitive sense signal chain, many of the major hardware manufacturers produce their own line of capacitive touch controllers.
The chip we chose is the Microchip Technologies QTouch AT42QT2120 [2] self-capacitance sensor (which we’ll refer to as the QTouch chip). This chip is available in several package sizes: SOIC, TSSOP and VQFN with 12 sense channels.
The QTouch chip can operate in a standalone mode as well as in a communications mode, where it can be controlled via I2C (up to a maximum clock rate of 400kHz). As we set out to build a keyboard controller than can act as a MIDI instrument or talk to a synthesizer directly via a control voltage scheme, we focused on the comms mode operation of the device.
Multiplexing I2C: For our keyboard to have a two-octave range (25 keys), two octave transposition buttons and a pitch bend slider (requiring 3 channels), we needed three QTouch chips. The QTouch chips have a pre-set I2C address of 0x1C that cannot be changed. This fixed address meant that we had to multiplex our I2C bus. We choose the Texas Instruments (TI) TCA9548A [3], a 1-to-8 I2C bus switch. It is also available as a breakout board from Adafruit (listed under the same part number), which made prototyping easier. However, when we went to order parts after our initial prototyping, we discovered the TI part sold out everywhere. Fortunately, there are many similar products available, and Diodes Incorporated makes a pin-compatible part—the PI4MSD5V9548ALEX [4].
Setting up the multiplexer is as simple as grounding its three address pins to set its I2C address, wiring each QTouch chip to a multiplexer channel (don’t forget your pull-up resistors), and connecting a microcontroller (MCU) to the I2C bus on other side of the multiplexer.
Keys/Buttons: While researching capacitive sensors, we found design guides from several manufacturers (Atmel, Freescale, TI and others). Most of these design guides [5] are focused on small, button-sized touch sensors. They typically recommend making these buttons no bigger than the average fingertip, often warning that an increase in size can lead to your touch sensor becoming a proximity sensor.
During prototyping, we tested a range of key shapes, sizes and patterns. All the keys we fabricated could be detected by the QTouch chip, so there is clearly room for artistic embellishment, provided that testing is done to validate the designs. A single touch chip can monitor buttons with different shapes and sizes (therefore different baseline capacitances), since the QTouch chip allows for per-key tuning. For simplicity’s sake, we opted for solid keys, despite our successful tests with hatched and patterned keys. We had no issue with designing keyboard keys (80mm tall by 15mm wide)—which were much larger than the manufacturers’ guidelines—without tuning the QTouch chips’ sensitivity.
Sliders/Wheels: QTouch chips can also detect a single three-channel capacitive or resistively interpolated slider/wheel using channels 0-2. The geometry for this sensor arrangement appears to be more prescribed than single keys, and we had to be careful when it came to sizing and shaping the sensor elements.
We followed the design recommendations of the manufacturer closely for the pitch bend slider on our keyboard. To match the proportions of the musical keys, the design recommendations call for a resistive slider rather than capacitive, so we went with that.
Sensor Materials: As we considered materials for touch key tests, we found that many articles and application notes discussing the design of capacitive sensors recommend exotic (at least to the student or hobbyist) materials such as Indium Tin Oxide. However, plain old copper will do, provided you don’t need a transparent sensor such as a touchscreen. When we say “plain old copper,” we really do mean it; our proof of concept consisted of copper tape on a cardboard box with clear packing tape as a dielectric (Figure 3). We took inspiration from KontinuumLAB’s DIY MIDI instruments [6] for this design.
Proof-of-concept capacitive touch keyboard, constructed from copper tape, cardboard, and packing tape, wired to a breadboard with a single QTouch IC and Raspberry Pi Pico for testing
If you wish to construct a sturdier instrument, designing a custom PCB is a good option. We used Altium, which allows for the creation of arbitrary copper shapes with its polygon tool [7]. We found it much easier to dimension and layout our key bed in Autodesk’s Fusion360 as a sketch (Figure 4). By constructing a parametric sketch, we were able to tweak key dimensions until we were satisfied with the result. Bringing our Fusion360 sketch into Altium was as simple as exporting it as a .dxf file. The outline of each key could then be converted into a copper pour by using the convert select primitives to region command [7].
Sketch of key bed design for the LMS101 in Autodesk’s Fusion 360
SPACE CONSTRAINTS
Since the electric field produced by self-capacitance sensors is 360 degrees, it can detect a touch on either side of the PCB. In fact, it is recommended that no circuitry is placed directly behind the sensor elements. This imposes a significant space constraint on the controller design, because the bigger you make your keys, the less space you have for component layout (Figure 5). Confidence and comfort with small, surface-mount packages help to reduce the impact of this constraint.
To shield our keyboard controller from noise produced by the switch mode power supply powering our synthesizer, we applied a hatched (approximately 50% infill) ground plane to the back of our keyboard (Figure 5). This reduces the sensitivity of the keys as it contributes to the parasitic capacitance. However, it made no noticeable difference in our ability to detect key presses.
Altium render of the back of the LMS101 keyboard, showing part layout constrained to the upper quarter of board due to space constraints of capacitive sensors
When designing the touch circuitry layout on the PCB, it is important to remember that the traces connecting the sensor elements to the QTouch chips serve as extensions of the sense element, itself. We had to balance keeping trace lengths as short as possible with also keeping the traces as far as possible from the touch pads, to avoid misdetection.
With everything laid out, we created a flow chart showing how all of this hardware communicates together (Figure 6). You can see how I2C is used for touch event detection and multiplexing, GPIO is used to drive a logic buffer for the Gate output, and SPI is used to communicate with the DAC (we chose the Microchip MCP4822) for the Pitch output.
Flow chart illustrating I2C, GPIO and SPI communication between MCU and peripherals
FIRMWARE DESIGN
With the physical design of the keyboard shaping up, we turned our attention to choosing a MCU and firmware design. The criteria for MCU selection were not particularly demanding. We needed one I2C peripheral to communicate with the QTouch chips, one SPI peripheral to send CV levels to DAC outputs, two ADCs to read potentiometer values for controller parameters, and some GPIO pins for LED control and QTouch chip change interrupts. We chose the Raspberry Pi Pico because of its flexible GPIO and peripherals, and wrote the firmware code in Arduino C++ with the Arduino Mbed OS RP2040 board support package.
QTouch Driver: The QTouch chip communicates over I2C, so retrieval of the necessary data can be accomplished with an I2C library and a relatively simple driver composed of a read and write register function (Listing 1). Both can be written for single-byte transfers, because the majority of the QTouch values are 8-bits. The odd 16-bit value (for example, key detection status) is represented as two registers—a high and low byte—and retrieved via two sequential read calls.
Listing 1
Code snippet for I2C communications with QTouch chip
void write_reg(MbedI2C *QTouch, uint8_t dev_addr, uint8_t reg_addr, uint8_t reg_data)
{
byte i2c_error = 0;
do {
QTouch->beginTransmission(dev_addr); // Take the bus notify the device
QTouch->write(reg_addr); // Queue the register address to write to
QTouch->write(reg_data); // Queue the data to write to that register
i2c_error = QTouch->endTransmission(); // Catch the error code
} while (i2c_error > 0);
}
uint8_t read_reg(MbedI2C *QTouch, uint8_t dev_addr, uint8_t reg_addr)
{
uint8_t rxData = 0;
uint8_t i2c_error = 0;
delayMicroseconds(547); // minimum I2C bus idle time, per testing
do {
QTouch->beginTransmission(dev_addr); // Take the bus and alert device of interest
QTouch->write(reg_addr); // Write the register address of interest to bus
i2c_error = QTouch->endTransmission(); // Catch the error code
} while (i2c_error > 0);
QTouch->requestFrom(dev_addr, 1); // Expect the data packet
while (!QTouch->available()); // Spin until data is available
rxData = QTouch->read(); // Wire.read only reads 1 byte at a time
return rxData;
}
Multiplexer Driver: Writing the code for the I2C multiplexer was even easier than it was for the QTouch driver, because the multiplexer only has a single control register controlling which channels are open. A single function can be written to open and close channels over I2C (Listing 2). This is done by beginning an I2C data write transmission with the address of the multiplexer and sending a single command byte. The bit position of a set bit in this command byte corresponds to which channel you wish to open. For example, writing a 0x04 opens channel 3 while writing 0x08 opens channel 4. Because we are only interested in transmitting on a single channel at one time, we can select the correct channel by bit shifting a 1 left by the desire channel number to get the correct power of 2.
Listing 2
Code snippet for multiplexer channel select function
void mux_select_ch(MbedI2C *QTouch, uint8_t mux_ch_num) {
byte i2c_error;
do {
QTouch->beginTransmission(MUX_ADDR); // Take control of the bus and alert the mux
// Channels are enabled by setting the bit corresponding…
//… to their channel #, ie. 2^n where n = channel #
QTouch->write((1 << mux_ch_num)); // Tell the mux which channel we want
i2c_error = QTouch->endTransmission(); // Release the bus now that our channel is set
} while (i2c_error > 0);
}
This single function can now be called to route the I2C traffic to the each QTouch chip, based on the multiplexer channel to which it is connected.
Keyboard Driver: Each QTouch chip has a change pin that it pulls low on detection of a key touch status change (pressed or released key). Pairing this feature with an interrupt pin for each chip gives us a notification to trigger I2C data retrieval. This status data describes the new state of touch detection on the keyboard and, when compared to the previous state, tracks the overall interaction with the keyboard touch elements.
SORTING KEY TYPES
Based on the data retrieved, we started by separating out detection of the “utility keys” (slider and octave transposition) from the musical keys. To make this separation, we check whether a musical key or a utility key has been touched. Slider interaction can be differentiated from key interaction by reading that chip’s detection status register’s bit 0 (touch change detected, or TDET) and bit 1 (slider change detected, or SDET). This determines if a key (where SDET = 0 and TDET = 1) or the slider has been touched (where SDET = 1 and TDET = 1). This is important, because the slider data is held in a different register (slider position register) from that for the keys (key status register).
If care is taken with the design of the slider, the coding required to make use of it is straightforward. If the SDET and TDET (bit 1 and bit 0) bits are set in the detection status register, then the contents of the slider position register are valid. The slider position register contains a value ranging from 0-255, representing where along the slider a touch event has occurred.
The octave buttons are detected in the same way as the musical keys. On each interrupt, the key status register can be checked for 1s, where each 1’s bit position corresponds to a channel that is in detection. Differentiating octave buttons from the musical keys must be done by simply keeping track of which channels on which QTouch chip they are connected to. We handle this by masking the octave bits in key status, and comparing to the previous state of these bits. Based on the result of the comparison, we set an octave offset value (-1, 0, 1) that is used later during note calculation.
The QTouch chip generates an interrupt on both press and release of a touch element. In either instance, checking the key status register (12 data bits delivered in 16-bit value). This 12-bit value is a binary representation of which keys are in detection (1 = touch, 0 = no touch). By keeping track of the contents of key status since the last interrupt, we can tell whether a touch or a release has occurred. For our purposes, we translated this press/release status into a gate signal (0V-5V) which controls note on/off for our synthesizer.
Rather than handling each chip’s key status register individually, we use them to build and update a single, 32-bit, key status variable. This involves shifting out the bits representing the slider channels, octave buttons and unused channels, and then bitwise ORing the shifted result against a 32-bit status variable, with its appropriate bits masked off to combine existing and updated states, as shown in Figure 7.
Diagram illustrating how the 32-bit key status variable is built from three separate QTouch sensors
KEYBOARD CAPABLITIES
From here, we wanted our keyboard to be capable of both MIDI host and a 1V-per-octave (1V/oct) control voltage output for compatibility with both our own synthesizer voice and other compatible synthesizers. To achieve both, we first had to decide on a range. This range was ultimately constrained by our 12-bit DACs and the 1V/oct control voltage we needed them to output.
Ultimately, we decided on an overall note range of C2-C6, with default octave state corresponding to C3-C5. Pressing either octave up or down buttons would shift the range by one octave up or down with no wrapping. This meant that we needed a function to extract which key (0-24) was in detection from our bit-field. This was done by bitwise ANDing a 1 against each bit position, to find which bit is set (Listing 3).
Scanning either at the least significant bit (LSB) or most significant bit (MSB) first will determine whether your controller exhibits a left or right key priority for which note is played first. If you have the luxury of multiple voices (our synth is a mono-synth), you will want to scan your entire bit field for every key in detection. Our active key (0-24) could then be converted to a MIDI value based on offsets dictated by the current octave offset (Listing 3).
From this MIDI value, we converted to a CV value that could be passed to our 12-bit DAC (Listing 3). It is at this stage that we utilize the pitch bend data (if available) to calculate the note to be outputted.
Ideally, the pitch bend also should be incorporated into the MIDI calculation. However, due to limited time, we opted to limit the scope of the pitch bend slider to only alter the pitch control voltage.
Listing 3
Code snippets for active key mapping, MIDI note calculations from active key and DAC level calculations from MIDI note
int8_t map_status_to_active_key(uint32_t key_status_new)
{
for (uint8_t i = 0; i < 25; i++) {
uint8_t j = 24 - i;
uint8_t match = (key_status_new & (1 << j)) >> j;
if (match == 1) {
return j;
}
}
return -1;
}
uint16_t active_midi_note_from_key_num(int8_t active_key_num, int8_t button_octave_offset)
{
// eg. On power on our DEFAULT_OCTAVE_OFFSET = 3 & button_octave_offset = 0.
// If the left most key is pressed (ie. active_key_num = 0)
// 0 + ((3 + 0)* 12) + 12 = 48 == C3
// We add 12 at the end because in midi C0 = 12
return active_key_num + ((DEFAULT_OCTAVE_OFFSET + button_octave_offset) * 12) + 12;
}
uint16_t dac_level_from_active_note(int8_t midi_note)
{
if (midi_note < 0) return 0;
// Compensate MIDI note number for DAC range calculation
uint8_t octave = ((midi_note / 12) - 1) - 2;
uint8_t note = midi_note % 12;
float level = (4095.0 * (12.0 * octave + note) / 12.0) / 4.096;
return (uint16_t)level;
}
CONCLUSION
Over our 14-week semester, we designed a flexible analog mono-synth with an integrated 25-key, touch-capacitive keyboard, to provide the user with an intuitive and expressive controller that quickly weaves intricate melodies. It emits useful control voltages, such as Pitch and Gate, which control the rest of the synthesizer. We designed a custom PCB keyboard, inspired by the 1972 Buchla Music Easel, but brought it up to speed with contemporary technologies, such as capacitive touch sensing ICs, an I2C multiplexer, a 12-bit DAC and an MCU running custom C++ code.
Given the possibility of firmware updates, we will continue working on the keyboard firmware to implement features we did not get to during our semester. We have mapped all the key event information (pitch and note on/off) to MIDI data, to facilitate future implementation of USB MIDI compatibility. We also intend to implement an Arpeggio feature, whereby the keyboard will cycle through multiple notes when more than one key is held. The hardware is there; we must simply write the code. Similarly, we hope to implement a Pressure control voltage output, since we struggled to get it working with the hardware we chose. This may require changing capacitive sensing ICs, or perhaps experimentation with other dielectrics.
References:
[1] J. Aikin, “The Horizons of Instrument Design: A Conversation with Don Buchla,” Keyboard Magazine, 12 (1982). Updated 11/29/2017 and reproduced in a forum on modwiggler.com 8/10/2020:https://modwiggler.com/forum/viewtopic.php?t=235923
[2] Atmel, “AT42QT2120 Datasheet,” 06 2012. [Online]. Available: https://ww1.microchip.com/downloads/en/DeviceDoc/doc9634.pdf
[3] Texas Instruments, “TCA9548A Datasheet,” 05 2012. [Online]. Available: https://www.ti.com/lit/ds/symlink/tca9548a.pdf?ts=1640158640665
[4] Diodes Incorperated, “PI4MSD5V9548A Datasheet,” 01 2017. [Online]. Available: https://www.diodes.com/assets/Datasheets/PI4MSD5V9548A.pdf
[5] Feargal Cleary, Microchip Technology Inc., “AN2934 – Capacitive Touch Sensor Design Guide,” 2020. [Online] https://ww1.microchip.com/downloads/aemDocuments/documents/TXFG/ApplicationNotes/ApplicationNotes/Capacitive-Touch-Sensor-Design-Guide-DS00002934-B.pdf
[6] KontinuumLAB, “KontinuumLAB,” 2021. [Online]. Available: https://www.kontinuumlab.com/welcome
[7] Altium, “Working with Custom Pad Shapes in Altium Designer,” 20 September 2019. [Online].
https://www.altium.com/documentation/altium-designer/working-with-custom-pad-shapes-ad
RESOURCES
Raspberry Pi
– Pi Pico RP2040 (Microcontroller)
Microchip/Atmel
– AT42QT2120 (QTouch sensor controller w/I2C)
– MCP4822 (Dual 12-bit DAC w/SPI)
– MCP6004 (Quad Op Amp)
Texas Instruments
– TCA9548A (I2C 1-to-8 multiplexer – used in our prototype)
Diodes Incorporated
– 74LVC125A (Non-inverting Buffer)
– PI4MSD5V9548ALEX (I2C 1-to-8 multiplexer – work-alike to TCA9548 used in our final assembly)
Altium | www.altium.com
Arduino | www.arduino.cc
Autodesk | www.autodesk.com
Diodes Incorporated | www.diodes.com
KontinuumLAB | www.kontinuumlab.com
Microchip Technology | www.microchip.com
Raspberry Pi | www.raspberrypi.org
Texas Instruments | www.ti.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • APRIL 2022 #381– Get a PDF of the issue
Sponsor this ArticleLiam Brinston (labrinston@gmail.com). Lead Keyboard Hardware/Firmware Developer. Liam originally studied to be a scientist, graduating from the University of Alberta in 2013 with a BSc specializing in Molecular Genetics. After moving to Vancouver Island in 2015 and joining the Victoria Makerspace, Liam discovered the joy of making things, prompting him to return to school to study electronics engineering at Camosun College.
Michael Brown (seriokomb@gmail.com). Lead Synthesizer Hardware Developer. Michael is a musician who spends his leisure time composing electronic soundscapes. He has been dabbling in DIY electronics for years, mainly building Eurorack synthesizer modules. This curiosity led him to study electronics engineering at Camosun College. He works at an electronics repair shop, which focuses on repairing, designing and manufacturing audio hardware.
Silas Jantzen (silas@jantzen.ca). Lead Synthesizer Firmware Developer. Silas returned to school to study electronics engineering at Camosun College, after spending nearly two decades working in the telecommunications industry IT. He has long been interested in computers and electronic music production as a dabbler and tinkerer, so building a synthesizer instrument from scratch for this project has been a dream come true.