pschatzmann / arduino-audio-tools

Arduino Audio Tools (a powerful Audio library not only for Arduino)
GNU General Public License v3.0
1.53k stars 236 forks source link

ES8388 produces noise only at certain volume levels #1719

Closed jwktje closed 3 weeks ago

jwktje commented 1 month ago

Problem Description

I'm using a board with an ES8388. Mostly works well. Relevant code is below. Randomly I noticed that some volumes introduced some hiss. So I experimented and notices initially volumes above 70 started to slowly add noise. No worries, just stay below 70 I thought.

But randomly lower values do the same. Here are some of my findings testing with intervals of 5.

I'm confused as to why in the lower values suddenly noise is introduced on a spectrum. Is there any firmware reason or configuration error that might cause this?

Really want to resolve this. I'll do whatever is needed on my end to figure this out.

Device Description

ES32 with ES8388 module

Sketch

DriverPins my_pins;                                  // board pins
AudioBoard audio_board(AudioDriverES8388, my_pins);  // audio board
I2SCodecStream i2s_out_stream(audio_board);          // i2s coded
TwoWire myWire = TwoWire(0);                         // universal I2C interface

WAVDecoder decoder;
File file;                                // final output stream
StreamCopy copier(i2s_out_stream, file);  // copies data

void audio_init(bool test) {
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);
  LOGLEVEL_AUDIODRIVER = AudioDriverWarning;
  Serial.println("Setup starting...");

  // Set SD pins?
  SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS);
  if (!SD.begin(SD_CS)) {
    Serial.println("Card Mount Failed");
    return;
  }
  my_pins.addI2C(PinFunction::CODEC, SCLPIN, SDAPIN, ES8388ADDR, I2CSPEED, myWire);
  my_pins.addI2S(PinFunction::CODEC, MCLKPIN, BCLKPIN, WSPIN, DOPIN, DIPIN);
  my_pins.begin();

  audio_board.begin();
  audio_board.setVolume(25); // 0 to 100
  auto i2s_config = i2s_out_stream.defaultConfig();
  i2s_out_stream.begin(i2s_config);  // this should apply I2C and I2S configuration

  // set volume
  audio_set_volume(29);
}


### Other Steps to Reproduce

_No response_

### What is your development environment

_No response_

### I have checked existing issues, discussions and online documentation

- [X] I confirm I have checked existing issues, discussions and online documentation
pschatzmann commented 1 month ago

I suggest that you use the I2SCodecStream setVolume() API which uses values between 0 and 1.0.

However this does not have any impact on your issue.

The volume in the I2SCodecStream/AudioBoard is managed by the audio driver and the es8388 API. I am not aware of any issues and I never noticed any, so I tend to think that it might be a hardware problem (e.g. a noisy power supply)? How can I reproduce this ? with what sound ? Can you reproduce this with the sine output test case ?

You could also try to manage the volume as described in the WIKI.

jwktje commented 1 month ago

@pschatzmann Thanks for the quick reply. I will test soon with the I2SCodecStream.setVolume() and report back if that changes the behaviour.

In the meantime, I have the schematic for the ES8388 module. The only meaningful difference I can find between this and the "TYPICAL APPLICATION CIRCUIT" from the datasheet is that the capacitors on VMID, ADCVREF and VREF are unpolarized rather than polarized.

Screenshot 2024-09-27 at 16 59 30

Apart from that, I couldn't really find any issues. I tested with 3 different ESP32's and 2 different ES8388 and checked all my wiring. So I'm not sure what the issue could be.

The noise is a little better on battery when compared to USB power. But it definitely is there in both scenarios. Regardless of if I'm playing or not playing audio.

pschatzmann commented 1 month ago

Based on what you were saying, I was testing with an AudioKit and this minimal sketch

#include "AudioTools.h"
#include "AudioLibs/AudioBoardStream.h"

AudioInfo info(44100, 2, 16);
AudioBoardStream out(AudioKitEs8388V1);

void setup(void) {  
  // Open Serial 
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  // start Codec & I2S
  Serial.println("starting I2S...");
  auto config = out.defaultConfig(TX_MODE);
  config.copyFrom(info); 
  out.begin(config);
  // set volume with max noise
  out.setVolume(0.88);
}

void loop() {
  out.writeSilence(1024);
}

I could not hear any noise via the earphones! I was powering the device via the UART usb cable of my notebook.

ps. just some more comments to your code:

  my_pins.begin();
  audio_board.begin();

are not necessary since they will be executed by i2s_out_stream.begin

pschatzmann commented 1 month ago

Maybe you can compare it with this

jwktje commented 1 month ago

I have done a bunch more testing, and I think 2 things are happing at the same time.

See my reduced test-case below.

There is some noticeable hiss, especially at 0.25 volume, on my board, that I don't hear on an Olimex ESP-ADF. So that part may very well be hardware related. So I'm gonna thoroughly compare the schematics in hopes of figuring out the cause.

But secondly, when the file finishes playing, I hear a looped glitch. Which seems to me like it could be because I should be handling this differently. When the file is done copying, should copier.copy() just keep running? Or do I need to catch that it ran through the entire file, write silence, and stop copying?

Because I think that the hiss (possibly HW related) and the looped glitch (probably firmware related) together might be what I'm hearing.

#include "AudioTools.h"
#include "AudioLibs/AudioSourceSD.h"
#include "AudioLibs/I2SCodecStream.h"
#include "AudioCodecs/CodecWAV.h"

// I2C
#define SDAPIN 18        // I2C Data
#define SCLPIN 23        // I2C Clock
#define I2CSPEED 100000  // Clock Rate
#define ES8388ADDR 0x10  // Address of ES8388 I2C port

// I2S, your configuration for the ES8388 board
#define MCLKPIN 0  // Master Clock
#define BCLKPIN 5  // Bit Clock
#define WSPIN 25   // Word select
#define DOPIN 26   // This is connected to DI on ES8388 (MISO)
#define DIPIN 35   // This is connected to DO on ES8388 (MOSI)

// SD
#define SD_CS 21
#define SD_SCK 14
#define SD_MISO 12
#define SD_MOSI 13
#define SD_SS 15

DriverPins my_pins;                                  // board pins
AudioBoard audio_board(AudioDriverES8388, my_pins);  // audio board
I2SCodecStream i2s_out_stream(audio_board);          // i2s codec
TwoWire myWire = TwoWire(0);
WAVDecoder decoder;
File file;
StreamCopy copier(i2s_out_stream, file);  // copies data

void setup() {
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);
  LOGLEVEL_AUDIODRIVER = AudioDriverWarning;
  Serial.println("Setup starting...");

  SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS);
  if (!SD.begin(SD_CS)) {
    Serial.println("Card Mount Failed");
    return;
  }

  my_pins.addI2C(PinFunction::CODEC, SCLPIN, SDAPIN, ES8388ADDR, I2CSPEED, myWire);
  my_pins.addI2S(PinFunction::CODEC, MCLKPIN, BCLKPIN, WSPIN, DOPIN, DIPIN);

  auto i2s_config = i2s_out_stream.defaultConfig();
  i2s_out_stream.begin(i2s_config);
  i2s_out_stream.setVolume(0.25); //The most problematic level in my full firmware. 

  Serial.println("Playing audio file...");
  file = SD.open("/test.wav", FILE_READ);
}

void loop() {
  copier.copy();
}
pschatzmann commented 1 month ago

Check if your installed version supports i2s_config.auto_clear = true. If not you might need to upgrade to have the latest corrections: the default value is set to true...

jwktje commented 1 month ago

Okay that did improve most of the issue I have! I added i2s_config.auto_clear = true; Now it (I assume) autodetects when the file is finished, and it mutes the DAC probably.

What I observe now is that the glitchy loop at the end of the playback is gone. But strangely the white noise hissing is gone when the file ends, but not during playback.

So there still is some remaining problem. Which may or may not be hardware related. So I will do further testing on different boards, with different WAV files, with different volume levels. Just so I know what the real remaining issue is.

The complete silence between playback after auto_clear was added is throwing me for a loop. Because this tells me that the hiss isn't passively coming from my PCB through analog interference. It really is produced by the ES8388 at certain set volumes.

EDIT: Just so I know we're testing with the same parameters; what is the best WAV file format to use? I'm testing with 44.1 kHz 16 bit stereo PCM WAV.

josef2600 commented 1 month ago

hi jwktje, first i wanted to thank you for your info's, it made me to respond too! i have tested the ES8388 too. funny thing is, i get a lot more noise. i am using Bluetooth, when it gets connected, it just get so jittery, and it is not even playing. but as i understand, the ES8388 is a very bad DAC. it has a very high distortion and the quality is very bad at high volumes like over 0.6 . i am moving on to other DAC's . by the way, i am using hi-fi system, so i hear everything on the line. but if you are using class D amps, you wont get so many jitter.

jwktje commented 1 month ago

I'm still testing a bunch, ruling out variables. Just so we don't waste Phil's time with issues that are not his problem. I have excluded some things. The issue is not due to:

  1. My earphones (I've tested multiple and they all give the white noise at the same digital volume levels)
  2. My specific singular PCB (I've tested 2 different boards that are the same, and they have the same issue)
  3. The ES8388 (I've tested an Olimex ESP-ADF board and it doesn't have this issue)

By which I can mostly conclude there must be a faulty hardware design. But I have not found any difference yet between my design and the ESP-ADF board by Olimex. Luckily they also publish their schematics, so I can compare. And I haven't found differences in the schematic. Maybe the exact routing of the traces on the board could introduce some noise like I'm getting? I'm not sure.

I will continue testing to further narrow it down

josef2600 commented 1 month ago

i am not sure if it helps or not, but i have seen few things in them. first, the quality of the chips. second, can you check the Vcc line that goes to esp8388? usually there is a resistor in series for it. it is supposedly there to remove noise, but it is 100 ohm and i don't like it! but i would like to know which board has it and how it is. next, do you use the inside amp or not? because if you do enable and use it, then it is normal to get hissss! get the output sound lower by resistors. specially if you use external amp. and if possible, move the conversation to discussions, since it is not related to the software anymore. at least i don't think so. but, if you really want quality, you have to move away from esp8388. one easy and fine and cheap option is UPD6379A . but then you have to get the original Japanese. its inexpensive with a very good sound. and it uses Resistor string conversion method. and it is 16 bit. it has a S/N ratio 100 dB . that is believable! it is from NEC. but if you are going to use external DAC, you have to know few things! it is going to be hard. the code has to be specified for that chip, the board has to be separated from IF parts (esp32), and the hi-fi DAC's dont have ADC. meaning no microphone. unless you are going for high end, then they start from at least 80$ DAC's. personally, i love Resistor string conversion. and do know that in that method the resolution is max 16 bit. no more. because it doesn't need any more. and for PCB, it is best to separate the boards, the ground and Vcc from Esp32 (or what ever you use) is going to be extremely noisy. but if you like to be simple, just use a class D low power amp like PAM8403. in the input put a resistor divider like a 10 to 1 so the hiss and noise will be gone. hope it helps.

jwktje commented 1 month ago

Short update for anyone that might be following this issue. I have completely redesigned my board and ordered it yesterday. The reason being that I found some design choices in my old design that might cause interference and noise.

So just to fully exclude if my issue is caused by this, I did a thorough revision. Once I'm able to test with this board I will report back.

josef2600 commented 1 month ago

Short update for anyone that might be following this issue. I have completely redesigned my board and ordered it yesterday. The reason being that I found some design choices in my old design that might cause interference and noise.

So just to fully exclude if my issue is caused by this, I did a thorough revision. Once I'm able to test with this board I will report back.

good. i am interested!

jwktje commented 3 weeks ago

Update. Completely revised the board and received it today. Flashed my reduced test case. It somewhat improved but still between 0.2 and 0.3 volume it starts being very glitchy and noisy. My board almost exactly matches the Olimex ESP-ADF when it comes to the DAC side. So I'm very confused. The behaviour did change a bit with my current HW rev.

Its very noisy and glitchy between 0.21 and 0.29. At 0.20 it's fine and at 0.3 it's fine. It also happens from 0.51 to 0.59. Then it's fine again at 0.61, not 0.6. There must be some reason for this, surely.

I'm not fully sure it's hardware related still. Is there some ES8388 fuse/config that I need to set at boot that Olimex has already done in the factory or something?

Below is my current test sketch.

#include "AudioTools.h"
#include "AudioLibs/AudioSourceSD.h"
#include "AudioLibs/I2SCodecStream.h"
#include "AudioCodecs/CodecWAV.h"

// I2C
#define SDAPIN 18        // I2C Data
#define SCLPIN 23        // I2C Clock
#define I2CSPEED 100000  // Clock Rate
#define ES8388ADDR 0x10  // Address of ES8388 I2C port

// I2S, your configuration for the ES8388 board
#define MCLKPIN 0  // Master Clock
#define BCLKPIN 5  // Bit Clock
#define WSPIN 25   // Word select
#define DOPIN 26   // This is connected to DI on ES8388 (MISO)
#define DIPIN 35   // This is connected to DO on ES8388 (MOSI)

// SD
#define SD_CS 21
#define SD_SCK 14
#define SD_MISO 12
#define SD_MOSI 13
#define SD_SS 15

DriverPins my_pins;                                  // board pins
AudioBoard audio_board(AudioDriverES8388, my_pins);  // audio board
I2SCodecStream i2s_out_stream(audio_board);          // i2s codec
TwoWire myWire = TwoWire(0);
WAVDecoder decoder;
File file;
StreamCopy copier(i2s_out_stream, file);  // copies data

float volume = 0.0;
bool testingForwards = true;

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);
  LOGLEVEL_AUDIODRIVER = AudioDriverWarning;
  Serial.println("Setup starting...");

  SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS);
  if (!SD.begin(SD_CS)) {
    Serial.println("Card Mount Failed");
    return;
  }

  my_pins.addI2C(PinFunction::CODEC, SCLPIN, SDAPIN, ES8388ADDR, I2CSPEED, myWire);
  my_pins.addI2S(PinFunction::CODEC, MCLKPIN, BCLKPIN, WSPIN, DOPIN, DIPIN);

  auto i2s_config = i2s_out_stream.defaultConfig();
  i2s_config.auto_clear = false;
  i2s_out_stream.begin(i2s_config);

  Serial.println("Playing audio file...");
  file = SD.open("/test.wav", FILE_READ);
}

void changeVolume() {
    static const unsigned long REFRESH_INTERVAL = 500; // ms
    static unsigned long lastRefreshTime = 0;

    if(millis() - lastRefreshTime >= REFRESH_INTERVAL) {
    lastRefreshTime += REFRESH_INTERVAL;
        if(testingForwards) {
          volume += 0.01;
        } else {
          volume -= 0.01;
        }
        if(volume >= 1 && testingForwards) {
          Serial.println("Going back down");
          testingForwards = false;
        }
        if(volume <= 0 && !testingForwards) {
          Serial.println("Going back up");
          testingForwards = true;
        }
        i2s_out_stream.setVolume(volume);
        Serial.println("Volume: " + String(volume));
   }
}

void loop() {
  copier.copy();
  changeVolume();
}
pschatzmann commented 3 weeks ago

Can you reproduce this issue if you replace the file with a sine ? Using a file makes it not reproducible for me.

In any case you could instead rely on the VolumeStream to control the volume via software and not via hardware.... You can also try different AI_THINKER_ES8388_VOLUME_HACK settings in the config. E.g AI_THINKER_ES8388_VOLUME_HACK 2 is using a shelfing filter.

jwktje commented 3 weeks ago

@pschatzmann Thanks a bunch! That greatly improved things. I now have the hardware volume set at the max of 1.0 and I'm controlling the volume via pot input via VolumeStream.

The annoying glitches are gone! Makes sense because I can keep the digital volume of the DAC fixed on a non-problematic value.

One final thing I noticed that you might be able to shed some light on. With the ES8388 internal volume set to 1.0 I hear some background hiss/noise when the VolumeStream volume is in the lower 1/3rd.

As a test I set the HW volume to 0.9 and it greatly improved. Is this just an inherent limitation of the hardware? If I push the internal headphone amp it will introduce some noise?

pschatzmann commented 3 weeks ago

I would not know: I am using it with the AudioKit whch has an amplifier: 0.9 and 1.0 are just too loud and I am usually using it around 0.4.

Did you check if the option with the filter makes a difference ?

jwktje commented 3 weeks ago

Not sure which options in the filter you mean. But after more testing I’m happy with the results I’m getting. So I consider this resolved. Thanks for the support. Really appreciate it

pschatzmann commented 3 weeks ago

There were quite some people, which submitted different alternatives how to resolve their volume problems. If you check the AudioDriverConfig you can select different options:

jwktje commented 3 weeks ago

Ah you meant the AI_THINKER_ES8388_VOLUME_HACK? I should test that on different settings, thanks for the tip!