iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔊

Stereo Audio Output from M5Atom via I2S (UDA1334A)

に公開

TL;DR

  • While M5Atom can output sound via I2S, the NS4168 included in official kits is a mono amplifier, so it cannot support stereo audio sources.
  • I confirmed that stereo output is possible from M5Atom by using the UDA1334A stereo-compatible DAC module.
  • The code used for verification is available on GitHub.

https://github.com/kn1cht/m5atom-stereoi2s-uda1334a

M5Atom and Audio Output

M5Atom is a development module in the M5 series characterized by its minimal size and a relatively high number of GPIO pins for its size.
As you can see by searching for "atom" on Switch Science, many expansion modules are available that can be used simply by plugging them in.

For M5Atom, ATOM Echo and ATOM SPK are available as modules for handling sound.

The Standard NS4168 on M5Atom is Mono Only

These modules are equipped with an amplifier chip called NS4168. Sound signals are sent from the M5Atom through the I2S (Inter-IC Sound) interface, allowing it to play music or speak synthesized voices.

I2S is a standard that can provide left and right channel stereo output using three signal lines.
However, the NS4168 can only output in mono. As is clear from the circuit diagram in the datasheet, it only has output pins for just one speaker in the first place.
Even if a stereo signal is input, it can only play either the left or the right channel.

NS4168 circuit diagram
From the NS4168 datasheet

I Want Stereo Output on M5Atom Too!

The intended use of ATOM Echo and ATOM SPK seems to be DIY smart speakers or audio players. Mono might be sufficient for making it speak or playing a bit of music. However, since I2S can actually support stereo, I really want to listen to music in stereo.

Another reason, which only applies to a limited number of people, is that stereo output is also convenient when using vibrotactile actuators (transducers). This autumn, the "hapStak" haptic device development module was released, allowing vibration control from the M5Atom via I2S.

hapStak
hapStak

This module also uses the NS4168, so only one transducer can be used per M5Atom. If stereo output were possible from M5Atom, the possibilities would expand, such as sending different vibrations to two transducers with a single Atom, or moving a transducer while outputting sound from a speaker.

Verification Environment

Hardware

Parts used

Software

Trying out the UDA1334A Module

When you search for "I2S stereo", the first thing that comes up is Adafruit's UDA1334A I2S Stereo DAC Module. It features a 3.5mm stereo mini jack and audio output pins, and it apparently supports formats other than I2S (though we'll use it normally with I2S this time).

https://learn.adafruit.com/adafruit-i2s-stereo-decoder-uda1334a/pinouts

Regarding the pinout, the Adafruit documentation is very clear, so please take a look. Although there are many pins, it only requires 5 wires—2 for power and 3 for I2S—to work.

UDA1334A module pinout
From left: VIN (Power) / GND (Ground) / WSEL (Left/Right ch selection) / DIN (Audio signal) / BCLK (Bit clock)

I2S pin names have several notations, so it's good to remember their roles. For example, the WSEL pin is used to select which channel's signal to send (left or right), but it's also called Word Select or Left Right Clock in English.

M5Atom side notation Module side notation
LRCK WSEL
Data DIN
BCLK BCLK


Connected state (Using GPIO 22, 19, 33)

Common Pitfalls

The combination of M5Atom and UDA1334A basically "works if you connect it and upload the sample." However, if you hit certain points where it's easy to make mistakes, you'll end up struggling with why no sound is coming out.

https://twitter.com/kn1cht/status/1469585957154603008

(I hit the pitfall points about twice after this. Be careful not to be overconfident!)

I2S pin numbers differ between ATOM Echo and ATOM SPK

Both ATOM Echo and ATOM SPK are modules using the NS4168. However, they are not compatible because they use different GPIO pins.

https://twitter.com/mongonta555/status/1370765861426851844?conversation=none

- BCLK LRCK Data
ECHO G19 G33 G22
SPK G22 G21 G25

Although we aren't using the expansion modules themselves this time, if you plug the cables into pin numbers different from what the sample code expects, it won't work correctly—resulting in silence or strange noise. Be sure to check the pin number specifications in the sample code you are using, and either rewrite the code or change the cable positions.

Slight cable loose connections can cause noise

This might be an issue with the quality of the jumper wires I used, but even a slight change in the angle of the cables plugged into the breadboard caused phenomena such as the sound disappearing and being replaced by a "hissing" noise, or total silence.

Even if no sound comes out in the sample, grabbing the base of the cable with your fingers might result in normal sound. Please try this if you encounter similar issues. Also, if you plan to use this setup regularly rather than just for testing, it would be better to take measures to fix the cables more securely.

Running the ATOM SPK Sample

First, let's try uploading the ATOM SPK sample PlayRawPCM (the GPIO pins used will change to 22, 21, and 25 as shown in the photo above).
Readers can jump straight to the ESP8266Audio sample described later, but the SPK sample is "plug and play" with almost no code changes, making it useful for checking operations immediately after purchase or when troubleshooting.

https://github.com/m5stack/M5Atom/tree/master/examples/ATOM_BASE/ATOM_SPK/PlayRawPCM

If you are using PlatformIO, you should find the files by navigating to .pio/libdeps/{environment_name}/M5Atom/examples/ATOM_BASE/ATOM_SPK/PlayRawPCM. For this test, I'll copy the four sample files into the src/ folder, open PlayRawPCM.ino, and then build and upload it.

Uploading PlayRawPCM
chocobo_loop_r.c (straight to the point)

If connected correctly, pressing the button on the M5Atom once should play the sample music on an endless loop. As proof that sound was produced, here is a photo showing an audio cable connected to the line-in of a PC.

Operation of PlayRawPCM

Making it Stereo

Running the ESP8266Audio Sample

First, I considered whether I could extend the ATOM SPK sample to support stereo.
However, as you can see in AtomSPK.cpp, this sample directly calls the ESP-IDF I2S functions. While it might be possible to modify this for stereo support, I felt it would be easier to develop using a library.

So, let's use ESP8266Audio, an audio library for ESP series SoCs. ESP8266Audio has many sample codes, and I wasn't sure which one would be the simplest. For now, I decided to try PlayMP3FromSPIFFS.

https://github.com/earlephilhower/ESP8266Audio/blob/master/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino

Code Modifications

Unlike the previous ATOM SPK sample, the code requires a few modifications.

  1. Arduino.h -> M5Atom.h
@@ -1,4 +1,4 @@
-#include <Arduino.h>
+#include <M5Atom.h>
 #ifdef ESP32
   #include <WiFi.h>
   #include "SPIFFS.h"
  1. AudioOutputI2SNoDAC -> AudioOutputI2S
    • AudioOutputI2SNoDAC seems to be a class for playing audio in environments without a DAC. Since we have a DAC this time, using NoDAC will result in strange noise being played, and it won't work correctly.
    • There are three places to change, including the diff shown below, so don't forget to change them all.
@@ -8,7 +8,7 @@
 #include "AudioFileSourceSPIFFS.h"
 #include "AudioFileSourceID3.h"
 #include "AudioGeneratorMP3.h"
-#include "AudioOutputI2SNoDAC.h"
+#include "AudioOutputI2S.h"

 // To run, set your ESP8266 build to 160MHz, and include a SPIFFS of 512KB or greater.
 // Use the "Tools->ESP8266/ESP32 Sketch Data Upload" menu to write the MP3 to SPIFFS
@@ -18,7 +18,7 @@
 
 AudioGeneratorMP3 *mp3;
 AudioFileSourceSPIFFS *file;
-AudioOutputI2SNoDAC *out;
+AudioOutputI2S *out;
 AudioFileSourceID3 *id3;
 
 
  1. out->SetPinout(19, 33, 22);
    • Specify the pin numbers used by the M5Atom. The arguments are BCLK, LRCK, and Data in that order.
    • Also, make sure to remove NoDAC from the line new AudioOutputI2SNoDAC(); just above this. These classes allow implicit type conversion, so even if you make a mistake, it might still compile (I've made this mistake twice).
@@ -56,7 +56,8 @@ void setup()
   file = new AudioFileSourceSPIFFS("/pno-cs.mp3");
   id3 = new AudioFileSourceID3(file);
   id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
-  out = new AudioOutputI2SNoDAC();
+  out = new AudioOutputI2S();
+  out->SetPinout(19, 33, 22);
   mp3 = new AudioGeneratorMP3();
   mp3->begin(id3, out);
 }

Writing files to SPIFFS

Unlike the ATOM SPK example, PlayMP3FromSPIFFS references the audio file (pno-cs.mp3) from a file system called SPIFFS. In PlatformIO, you can create a data/ folder in your project, place the files you want to upload there, and run "Upload Filesystem Image" from the menu.

https://labo.mycabin.net/electronics/arduino-esp32/1268/

SPIFFS upload button
SPIFFS upload button

https://twitter.com/kn1cht/status/1469631204026884097?conversation=none

It worked. I was surprised by how good the sound quality is.

Verifying stereo with PlayMP3FromSPIFFS

The sample audio source has the same signal on both left and right, so let's check if it's actually in stereo. I edited pno-cs.mp3 so that the left and right volume balance changes drastically every two seconds and then uploaded it.

pno-cs-pan.mp3
pno-cs-pan.mp3

If you change the filename, don't forget to update the filename in the sample code as well.

@@ -53,7 +53,7 @@ void setup()
   Serial.printf("Sample MP3 playback begins...\\n");

   audioLogger = &Serial;
-  file = new AudioFileSourceSPIFFS("/pno-cs.mp3");
+  file = new AudioFileSourceSPIFFS("/pno-cs-pan.mp3");
   id3 = new AudioFileSourceID3(file);
   id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
   out = new AudioOutputI2S();

Stereo output operation
Result of recording the output from the module on a PC

The waveform looks very similar to the mp3 editing screen above, but this is the recorded sound coming from the M5Atom and the UDA1334A module. It's playing correctly in stereo!

https://twitter.com/kn1cht/status/1469645828763820034?conversation=none

Dynamically Changing Left/Right Volume Balance Using the IMU

Just achieving stereo output turned out to be a minor extension of the sample code. As an experiment more unique to the M5Atom, let's try using the built-in inertial measurement unit (IMU) MPU6886 of the Atom Matrix to change the left and right volume balance based on the tilt of the device.

As far as I searched the ESP8266Audio repository, I couldn't find a function to independently control the left and right volume. Instead, it seems that using the AudioOutputMixer class allows you to play multiple files simultaneously and change their respective volumes.

https://github.com/earlephilhower/ESP8266Audio/blob/master/examples/MixerSample/MixerSample.ino

So, I prepared one audio source that plays only on the left and another that plays only on the right, and modified the MixerSample code to change the volume according to the tilt obtained from the IMU.
The code is long, so please check it out on GitHub.

https://github.com/kn1cht/m5atom-stereoi2s-uda1334a/blob/mixer-imu/src/MixerSampleWithM5AtomIMU.ino

https://twitter.com/kn1cht/status/1469682415530811394?conversation=none

Perhaps because I don't fully understand how to use the Mixer, there is some click noise every second. Nevertheless, you can see that the volume balance changes interactively based on the left-right tilt.

[Tips] Do not put a large delay() in loop() when using ESP8266Audio

As written in the README, sound will not output correctly if you use delay() within the loop() function in ESP8266Audio. If there is a process you want to execute less frequently, handle it by using conditional branches to trigger it at specific intervals.
The following example executes every 100 milliseconds.

  if(millis() % 100 == 0) {
    M5.IMU.getAttitude(&pitch, &roll);
    Serial.println((pitch + 45.0) / 90.0);
    stub[0]->SetGain((pitch + 45.0) / 90.0);
    stub[1]->SetGain((- pitch + 45.0) / 90.0);
  }

[Tips] Looping Playback

In the ESP8266Audio samples, playback stops when it reaches the end of the audio file. Calling begin() again to loop the playback does not result in restarting from the beginning (when I tried it, it caused a CPU panic and reset at the timing of the loop).

https://github.com/earlephilhower/ESP8266Audio/issues/8

If you want to loop safely, it seems best to recreate the file and playback objects from scratch each time. I separated the initialization part into a function and called it at every loop.

void initializeAndStartMP3(const char* filename, int id) {
  Serial.printf("Starting %s\n", filename);
  file[id] = new AudioFileSourceSPIFFS(filename);
  id3[id] = new AudioFileSourceID3(file[id]);
  id3[id]->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
  stub[id] = mixer->NewInput();
  stub[id]->SetGain(0.5);
  mp3[id] = new AudioGeneratorMP3();
  mp3[id]->begin(id3[id], stub[id]);
}

This process takes a bit of time, and the sound playback stops during that interval. Consequently, I haven't achieved seamless loop playback without interruptions.

Conclusion

I tried outputting stereo audio from the M5Atom using the UDA1334A stereo DAC module. While there were some minor pitfalls, I was able to proceed with connecting the pins and writing the code without too much difficulty.

The main use for stereo output will likely be music-related. How about trying to build your own portable music player or a receiver to make wired earphones wireless via Bluetooth?

GitHubで編集を提案

Discussion