Developing Audio Programs For Embedded Linux, Part 2
Developing Audio Programs For Embedded Linux, Part 2
In my first embedded Linux post, we covered how to write a dead-simple ALSA program and cross
compile it for an ARM embedded device. Next, we need to figure out how to configure I2S audio to
come out on pins from the Beaglebone, hook those pins up to our external DAC and audio
amplifier, deploy our cross compiled ALSA program to our Beaglebone, and run the thing!
In this article we will discuss communication protocols (I2S, I2C, SPI, UART), Linux device trees, and
ALSA configuration, which are all deep topics on their own. In many instances I’m going to link to
more in-depth articles, so hopefully this post can act as a jumping board for you to get more deep
on this stuff. Let’s start!
Materials
Beaglebone Black
I2S DAC and audio amplifier. This one from Sparkfun is cheap and works great!
Speaker (I got a 4” 8ohm speaker)
Jumper wires and breadboard
Host computer (mine was on Debian Stretch but it can work with others)
Before anything, let’s quickly make sure your Beaglebone Black is functional. For simplicity, this
article assumes that you are using the Beaglebone-supplied Linux image on your board, but the
concepts still apply if you made your own image using Buildroot as described in the last article. A
fair amount of the discussion is Beaglebone/AM335x specific, but intended to be presented in a way
that you could investigate a different embedded Linux board you might be working with such as a
Raspberry Pi.
The official docs on how to set up the Beaglebone should get you to a place where you can run the
code in this article. Make sure you can ssh into your board after following those instructions by
invoking this command on your host, to which the Beaglebone is USB connected:
Also, as stated in the Beaglebone docs, always make sure you power down the
board before unplugging it! Otherwise you may end up needing to start the bring
up process all over.
What’s in a circuit?
Before deep diving into anything, here is a high level overview of our circuit and data flow we want
to accomplish by the end of this article:
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 2/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Or put another way, we are going to ask our Beaglebone to direct the audio from our application to
a particular audio device, which we will configure to output the audio as I2S, so that it can talk to our
breakout board that speakers I2S. Before getting into any of that configuration: what is I2S?
Different communication protocols have different tradeoffs – some require a bunch of wires and
clock signals and might be more reliable or faster than others but require a ton of pin real estate,
while others might only require a single wire but have lower throughput. Understanding and
considering these tradeoffs is a key skill, so I would recommend getting familiar with all the major
communication protocols. The big ones are UART, I2C, SPI, USB and the one focused on in this
article, I2S. I2S has persevered for a long time as an audio protocol because it is simple and its
tradeoffs are specially designed to work with high bandwidth (44.1kHz) stereo audio signals.
What is I2S?
A solid and simple overview of I2S can be found here. I chose it for this project because it is an
industry standard for digital audio transmission between ICs and is well suited to the high data rates
in a stereo audio signal. Another common way to get audio out of the Beaglebone is over USB using
a USB DAC, but eventually I wanted to add USB MIDI support to a small program and I’d rather still
have the USB port open. So I2S it was!
That figure was taken from the official Phillips I2S spec.
I would recommend reading the spec – it’s just a few pages because it’s very simple
as far as specs go! USB for example is way hairier… getting comfortable with reading
technical specs is a key skill in embedded software development.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 3/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Continuous Serial Clock (SCK): This is the clock signal that keeps all the bits in sync according
to the desired sample rate.
Word Select (WS, sometimes LRCLK): Since the audio line can contain data for the left or
right channels, this clock describes which channel is currently being transmitted (low means
left channel and high is right)
Serial data (SD): This pin will have the actual audio data for the left and right channels.
I2S docs and this article use the nomenclature “left and right channels”, but in
practice you can implement higher channel counts like 5.1 and 7.1 by adding
additional I2S buses.
One drawback of the I2S audio output on the Beaglebone is that the on-board clock only supports
the 48kHz family of sample rates (so if you want 44.1kHz or multiples of that, you’ll need to set up
your own external clock). See here for more details on why that is. The I2S outputs are pretty easy to
get at, because they are also used for the HDMI output, which has a handy writeup here.
When I was initially looking around for info on how audio works on the Beaglebone, I found that it
helped to directly look at resources about the Beaglebone’s processor, the Sitara AM335x. The
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 4/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Beaglebone has a fair number of resources, but especially if you are working with a less common
platform, you may have a very hard time limiting yourself to the platform-specific docs. I was
tempted for awhile to gloss over the processor itself, but using it in my Google searches for technical
specs and help articles turned out to be really important. Technical specs tend to be very dry and
hard to understand for beginners, but you won’t be able to run away from them forever – so I
recommend embracing them early on to answer your questions that come up!
In this case, all I knew is that I had an I2S breakout board and wanted to get I2S audio output from
the pins on the Beaglebone. So the archaeology began…
Knowing the Beaglebone’s processor, I started by just Googling AM335x audio interface ! This led
me to the AM335x reference manual – searching “audio interface” in this article led me to the
section on TI’s Multi Channel Audio Serial Port (McASP), which is the audio peripheral used on the
Beaglebone’s SoC. This gives a good overview about which pins correspond to which parts of the I2S
signal that we described above. We now know when we need to do further research that AM335x,
I2S, and McASP are important search terms to use in other documents such as the
Beaglebone’s system reference manual.
I soon found via this resource which pins could be set to output I2S. However, those pins can do
more than just output I2S (they can be GPIOs, SPI, or PWM as well).
Here is a quick table for which pins (all on the P9 header) we will need to connect to our I2S
breakout:
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 5/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Pin Multiplexing
For a general purpose dev board like a Beaglebone, there are a ton of different ways one might
want to use the pins and fairly limited physical space. To solve this problem, embedded SoCs will
allow for the same pin to be connected in different ways via configuration in software via a
technique called “pin multiplexing”, or “muxing”. The same exact physical pin can be set to connect
to the audio interface and output a bit clock or instead be configured as a GPIO. On the Beaglebone
Black, there are almost 69 I/O pins on the device but the rest of them can also be used for other
predefined functions.
For context, if size wasn’t an issue, we wouldn’t need pin multiplexing – we could just
have a pin for every signal we wanted to be able to input or output from the board.
So we need a way to mux the pins identified in the previous section so that they are connected to
the McASP interface and output I2S signals. In a bare metal scenario, one would simply write to the
pin directly in their application, set it to output mode, and tell it to be an I2S pin. Things are a little
more complicated in embedded Linux land however; from user space (as opposed to kernel space),
the modern way to multiplex pins on Linux is via the device tree.
Two good audio-specific device tree overlays that you should digest are the BBB AudioCape and
the Bela device tree overlay.
You shouldn’t need to edit device tree overlays yourself in many use cases, at least on platforms like
BBB and Raspberry Pi – these platforms have a ton of community device tree overlays written and
available, and have external parameters exposed ( dtparam s) that are available in board-specific
configuration files read at boot time for configuring the device tree overlays, such as uEnv.txt on
BBB and config.txt on the Raspberry Pi.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 6/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Before dissecting actual DT code, let’s lay out exactly what we need to accomplish:
Reserve the McASP pins so that other device tree overlays don’t try to snag them for another
purpose (you can run into conflicts here)
Multiplex those pins to be set to their relevant McASP function
Enable and configure the McASP device itself
Add a “sound card” node so we can access the McASP device via ALSA
Again, I want to note that for this case, you won’t need to write this stuff yourself; in
most cases it will work out of the box, and when it’s not working, your first action
should be to double-check the user defined parameters in the uEnv.txt or config.txt
(depending on your board). But understanding what’s going on in the dts files will
likely come in handy at some point, especially if you want to design your own
cape/hat/daughterboard. And even then you probably shouldn’t be writing one
from scratch, but rather using and modifying an existing dts file.
See here for a more in-depth look at the nuts and bolts of DTS syntax.
One challenge you may run into with reading snippets of dts files is determining which keywords are
determined by the device tree spec (such as compatible ) and which are just vendor-defined names
of nodes defined higher up in the overlay definition for a specific board like mcasp0 . If you don’t
know a symbol, don’t be afraid to go looking for where it is defined. One place to delve is the docs
for the sound subdir of the TI Linux kernel fork.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 7/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Check out Derek Molloy’s tutorial on BBB DT and GPIOs for some helpful charts he made for the
various modes available on all the Beaglebone’s P8 and P9 pins. There you can confirm that
mcasp0_ahclkx is indeed MODE0 of P9.25, etc. You can also see in that chart that the GND and
power pins only have one mode, so they don’t need to be multiplexed.
You should check out the other McASP pins in the reference manual for setting up external clocks,
the high frequency vs regular bit clock, and other variants on the pinmux you might want to do.
fragment@0 {
target = <&am33xx_pinmux> ;
__overlay__ {
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 8/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
I’m not sure whether setting up the TI audio codec is actually needed if you are using an external
codec (I suspect not?) but I’m including this section to describe why this particular DT overlay for the
audio cape is configuring I2C pins and the tlv320aic3x in the first place.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 9/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
(Copied here directly because that wiki is apparently going down in January!)
The AIC31 audio module contains audio analog inputs and outputs. It is connected to
the main AM335x processor through the TDM/I2S interface (audio interface) and
used to transmit and receive audio data. The AIC31 audio codec is connected via
Multi-Channel Audio Serial Port (McASP) interface, a communication peripheral, to
the main processor. McASP provides a full-duplex direct serial interface between the
host device (AM335x processor) and other audio devices in the system such as the
AIC31 codec. It provides a direct interface to industry standard codecs, analog
interface chips (AICs) and
other serially connected A/D and D/A devices. The AIC31 audio module is controlled
by internal registers that can be accessed by the high speed I2C control interface.
fragment@1 {
target = <&i2c2>;
__overlay__ {
# addre ss -ce l ls = < 1 >;
# size- ce lls = < 0> ;
tlv320aic3x: tlv320aic3x@1b {
compatible = "ti,tlv320aic3x";
reg = <0x1b> ;
status = "okay";
};
};
};
I also want to mention that there is an alternative (newer) syntax for writing DT fragments you may
see, which is equivalent to the above but less compatible with some older parsers. Useful to be
familiar with it so you know it’s just another fragment.
tlv320aic3x: tlv320aic3x@1b {
compatible = "ti,tlv320aic3x";
reg = <0x1b> ;
status = "okay";
};
};
Enable McASP
Now it’s time to enable the McASP audio controller. See the TI docs for more info on what the
vendor-specific fields mean.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 10/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
fragment@2 {
target = <&mcasp0> ;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <& bone_audio_cape_audio_pins>;
status = "okay";
Now we need to configure a soundcard so ALSA is able to use the audio hardware we set up above.
TI has some info about these bindings here.
In this case, we are configuring sound for an ocp (“On-Chip Peripheral”) node.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 11/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
As stated earlier, you probably won’t need to be editing those files directly most of the time. The
way that most users interact with the device tree is by “exporting” (activating) various device tree
overlays at runtime. Derek Molloy has a great tutorial on how to do this, as does Adafruit.
You also may not need to export device tree overlays this way manually – on the Beaglebone and
Raspberry Pi, you choose which device tree overlays to export and which device tree parameters to
set using either the uEnv.txt or config.txt, depending on which platform you are using.
pcm.!default {
type hw
card 0
device 0
}
ctl.!default {
type hw
card 0
device 0
}
When you load up the board, you can run aplay -l and aplay -L to list the available audio
devices to see if whatwe configured from the device tree is available as expected.
Once the software is finally set up, hooking up the I2S audio breakout is the easy part. I used
the MAX98357A from Sparkfun for which they have a handy hookup guide.
First, we need to connect the Beaglebone pins we muxed to the inputs of the breakout board.
One issue I had is that the audio output and input from the BBB seemed to be
switched around based on what was documented in the device tree overlay I was
using. So even though P9_30 is supposed to be the output, I was actually seeing
audio output from P9_28, the supposed audio input! This is a case where having a
logic analyzer is handy, because you can visualize what is coming out of each pin.
Your mileage may vary, so if P9_28 doesn’t work, try P9_30 for audio output.
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 12/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
I also used a resistor to VDD to set the gain of the breakout board per their docs, but this is
optional.
This breakout board is responsible for reading the I2S data stream, converting it to an analog audio
signal, amplifying that signal, and sending that amplified signal to a speaker. So the output of this
board is a +/- connection to a speaker. Analog audio is susceptible to noise, so the shorter these
wires, the better. (The ones I have pictured are pretty long…)
From here, you can do a quick test with the built-in program on Linux, speaker-test -t sine .
Make a sound!
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 13/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
The very last part is to install our ALSA program onto the board and run it!
You can deploy the program from your host to the BBB by navigating to the output directory and
running:
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 14/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
Now go to you Beaglebone’s home directory. You may need to set executable permissions on the
file with chmod x ./booper . After that, run it with ./booper – it should make some noise!
Thanks for reading through all of this! My hope is that this article can serve as a jumping board into
many of the deep topics mentioned. Feel free to reach out with any questions, feedback,
corrections, or anything!
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 15/16
24. 11. 10. 오후 12:30 DEVELOPING AUDIO PROGRAMS FOR EMBEDDED LINUX, PART 2
https://www.jackcampbellsounds.com/2020/12/06/developingembeddedlinuxaudioapplications_part2.html 16/16