Hearing aid using T4 and audio shield

Status
Not open for further replies.

Arctic_Eddie

Well-known member
I've been working on a hearing aid project and initially used a T3.2 and two Adafruit MCP4725 DACs. The method was to use synchronized ADC stereo inputs, filter a sample of the input with a 3-pole Bessel HP IIR filter, pass it to the DAC boards, amplify the results, and mix it back into the output amp. The filter is using float type which is slow and the I2C bus is worse, and a no-go. The T4 solved some timing problems but the DAC board will not update fast enough even when setting the I2C clock to 3.4MHz. Adafruit has not answered my post on their forum on how to up the clock. However, the improvement would not be great enough to make this method work correctly.

I'm now considering the T4 and the audio shield. I need to increase the amplitude of the 4KHz - 12KHz range by about 10dB and the low range by about 2dB. What is the best approach to accomplish this task?

Thanks
 
I read some of it earlier today and will go over it again. I looked at Paul's tutorial and the design tool and have a diagram of what I think I need. Will order the audio shield today to have some hardware for testing.
 
I found this board, the Adafruit I2S Stereo Decoder - UDA1334A Breakout which would also work except for one reason. The library is incompatible with the T3.2, T4.0, and probably others. Is there a simple way of fixing this problem or am I likely to find only the PJRC audio shield my only alternative?
 
I found this board, the Adafruit I2S Stereo Decoder - UDA1334A Breakout which would also work except for one reason. The library is incompatible with the T3.2, T4.0, and probably others. Is there a simple way of fixing this problem or am I likely to find only the PJRC audio shield my only alternative?

It is a standard I2S board. What you have to do is remove the lines that refer to the sgtl5000 in the audio shield:

Code:
AudioControlSGTL5000	sgtl5000;

  // ...

void setup () {
  // ...

  sgtl5000.enable ();
  sgtl5000.volume (0.5f);

  // ...
}

Here is an example code using a MakerHawk MAX98357 I2S board, which is similar to the Adafruit mono I2S + amplifier board (https://www.adafruit.com/product/3006).

It should work for a stereo board such as the Adafruit board you mention or the HiLetgo PCM5102 I2S (https://smile.amazon.com/gp/product/B07Q9K5MT8/ref=ppx_od_dt_b_asin_title_s00?ie=UTF8&psc=1), adjusting for the stereo board not having the gain/shutdown pins, and that you need to attach an amplifier to the audio outputs.

Note it can be confusing understanding the input/output pins. Typically the Teensy documentation tends to mean the output going from the Teensy to the I2S device (i.e. pin 7 in the Teensy 4.0 or pin 22 in the Teensy 3.x), and input going from the I2S device to the Teensy (pin 8 in the Teensy 4.0, or pin 13 in the Teensy 3.x). The documentation for the I2S boards tends to reverse this, and they talk of input (i.e. DIN) as being the input to the device, and output (DOUT) as going from the device. The clock pins (BCLK, LRCLK and possibly MCLK) are all the same.

<edit>
I have placed this code and other audio examples on the non-official Teensy wiki (https://github.com/TeensyUser/doc/wiki/Audio-Examples).
</extra>

Code:
/*
  Demo of the audio sweep function.
  The user specifies the amplitude,
  start and end frequencies (which can sweep up or down)
  and the length of time of the sweep.

  Modified to eliminate the audio shield, and use Max98357A mono I2S chip.
  https://smile.amazon.com/gp/product/B07PS653CD/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

  Pins:		Teensy 4.0	Teensy 3.x

  LRCLK:	Pin 20/A6	Pin 23/A9
  BCLK:		Pin 21/A7	Pin 9
  DIN:		Pin 7		Pin 22/A8
  Gain:		see below	see below
  Shutdown:	N/C		N/C
  Ground:	Ground		Ground
  VIN:		5v		5v

  Other I2S2 pins not used by the Max98357A device:

  MCLK		Pin 23		Pin 11
  VOUT		Pin 8		Pin 13

  Gain setting:

  15dB	if a 100K resistor is connected between GAIN and GND
  12dB	if GAIN is connected directly to GND
   9dB	if GAIN is not connected to anything (this is the default)
   6dB	if GAIN is conneted directly to Vin
   3dB	if a 100K resistor is connected between GAIN and Vin.

  SD setting (documentation from the Adafruit board)

  This pin is used for shutdown mode but is also used for setting which channel
  is output. It's a little confusing but essentially:

  * If SD is connected to ground directly (voltage is under 0.16V) then the amp
    is shut down

  * If the voltage on SD is between 0.16V and 0.77V then the output is (Left +
    Right)/2, that is the stereo average.

  * If the voltage on SD is between 0.77V and 1.4V then the output is just the
    Right channel

  * If the voltage on SD is higher than 1.4V then the output is the Left
    channel.

    This is compounded by an internal 100K pulldown resistor on SD so you need
    to use a pullup resistor on SD to balance out the 100K internal pulldown.

  Or alternatively, use the HiLetgo PCM5102 I2S IIS Lossless Digital Audio DAC
  Decoder which provides stereo output:
  https://smile.amazon.com/gp/product/B07Q9K5MT8/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1  */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code (edited by meissner afterwards).
AudioSynthToneSweep	tonesweep;		//xy=99,198
AudioMixer4		mixer2;			//xy=280,253
AudioMixer4		mixer1;			//xy=280,175
AudioOutputI2S		i2s;			//xy=452,189

AudioConnection		patchCord1(tonesweep, 0, mixer1, 0);
AudioConnection		patchCord2(tonesweep, 0, mixer2, 0);
AudioConnection		patchCord3(mixer2,    0, i2s,    1);
AudioConnection		patchCord4(mixer1,    0, i2s,    0);
// GUItool: end automatically generated code

const float	t_ampx	= 0.8;
const int	t_lox	= 10;
const int	t_hix	= 22000;
const float	t_timex	= 10;		// Length of time for the sweep in seconds

// Do a sweep in both directions, enabling or disabling the left/right speakers
void do_sweep (int i)
{
  int do_left  = (i & 1) != 0;
  int do_right = (i & 2) != 0;
  float gain   = (do_left && do_right) ? 0.5f : 1.0f;

  Serial.printf ("Sweep up,   left = %c, right = %c\n",
		 (do_left)  ? 'Y' : 'N',
		 (do_right) ? 'Y' : 'N');

  mixer1.gain (0, do_left  ? gain : 0.0f);
  mixer2.gain (0, do_right ? gain : 0.0f);

  if (!tonesweep.play (t_ampx, t_lox, t_hix, t_timex)) {
    Serial.println ("ToneSweep - play failed");
    while (1)
      ;
  }

  // wait for the sweep to end
  while (tonesweep.isPlaying ())
    ;

  // and now reverse the sweep
  Serial.printf ("Sweep down, left = %c, right = %c\n",
		 (do_left)  ? 'Y' : 'N',
		 (do_right) ? 'Y' : 'N');

  if (!tonesweep.play (t_ampx, t_hix, t_lox, t_timex)) {
    Serial.println("ToneSweep - play failed");
    while (1)
      ;
  }

  // wait for the sweep to end
  while (tonesweep.isPlaying ())
    ;

  Serial.println ("Sweep done");
}

void setup(void)
{
  // Wait for at least 3 seconds for the USB serial connection
  Serial.begin (9600);
  while (!Serial && millis () < 3000)
    ;

  AudioMemory (8);
  Serial.println ("setup done");

  for (int i = 1; i <= 3; i++)
    do_sweep (i);

  Serial.println ("Done");
}

void loop (void)
{
}
 
Last edited:
I was not going to use the audio board as I have most of the code already written. I just need an output DAC that is fast. The Adafruit I2C DAC breakout, MCP4725, runs at 400KHz but is way too slow. It has a fast mode, 3.4MHz, but nobody on the Adafruit forum knows how to get the board into this mode. My timing test shows the output to one DAC takes around 350 usec. I need to make it around the entire loop in 25 usec. At this point, I'm beginning to think it is wise to learn the PJRC audio shield as it opens all kinds of future prospects.
 
I was not going to use the audio board as I have most of the code already written. I just need an output DAC that is fast. The Adafruit I2C DAC breakout, MCP4725, runs at 400KHz but is way too slow. It has a fast mode, 3.4MHz, but nobody on the Adafruit forum knows how to get the board into this mode. My timing test shows the output to one DAC takes around 350 usec. I need to make it around the entire loop in 25 usec. At this point, I'm beginning to think it is wise to learn the PJRC audio shield as it opens all kinds of future prospects.
Not knowing much about the details, I suspect the Audio Shield would be too slow for you also. According to the SGTL5000 datasheet, maximum LRCLK can only go 96kHz (https://www.pjrc.com/teensy/SGTL5000.pdf).
 
I found another chip, CS4344, which would work but no breakout board or library yet. I can work with either I2S or SPI.
 
just to understand: is it really sure, that the latency you are talking about is caused by the DAC?
I would suspect latency is caused by the filter delay and the block size of the audio processing and only a small portion really caused by the DAC. And I think the I2C bus frequency will not affect latency at all.
Maybe you want to investigate into techniques that noise cancelling headphones use instead of the usual block processing?
 
I've never heard of anyone who used a I2C DAC for audio...
Do yourself a favor and use a well known shield which is built for audio. It has a microphone- and headphone-connector, too (and it's very loud with the right headphones) So everything will be much easier.
As a plus, you can use the audio library which has the filters you need.
 
Points well taken. I will likely end up with the PJRC shield but want to finish my search for just a DAC breakout.

My 3-pole Bessel IIR filter takes only a few usec to run both channels with the T4. The long delay is entirely within the 4725 board, for whatever reason. My loop time is 6 usec with ADC capture and filter but jumps to 374 usec when the first DAC is added to the loop.

I found another board, the AK-MCP4922 – Dual 12-Bit DAC Breakout. Not sure if 12-bit audio will sound good enough for speech. It uses SPI for communications. Now looking for a US supplier.
 
For pure speech, 8 Bit and (even less! try 5...) are enough.
(Yes, all "audiophiles" and "experts" will hit me now.. they never tried that ;-) )
 
not sure if I understand your problem:
noone is able to hear a latency of 0.374ms ?
 
Hm, I need to look at the datasheet - maybe it takes so long to reach the new dac value? I2C DACs are not made for audio frequencies.
Even the BUS is too slow. 44 (khz) * (16 Bit) = ?
 
Last edited:
The Adafruit library switches the bus to 400KHz(TWBR=12), writes the data, then switches back to the users default, usually 100KHz. I run mine at 400KHz and can bump it to 888KHz(TWBR=1) but the DAC won't work. I need to capture data from two synchronized ADCs at around 20KHz, filter both channels, and output to two DACs. I just ran another timing test using the T4 and the capture and filter takes 6 usec. When I add one DAC, it jumps to 385 usec and the second DAC pushes it to 763 usec. This destroys my ability to capture at the rate I need. The MCP4922 will work but I haven't found a US breakout board.
 
I've discovered that the I2C bus is running at 100KHz no matter what I do to change it. The value of TWBR is 72 and it should be 12. I set it as my 400KHz default and the MCP4725 library does the same thing. I found the Wire library that appears to be used by the compiler. I used the Wire.setClock function with a value of 400000 but printing out TWBR still shows 72. How do you set the bus speed for a T4?
 
I've discovered that the I2C bus is running at 100KHz no matter what I do to change it. The value of TWBR is 72 and it should be 12. I set it as my 400KHz default and the MCP4725 library does the same thing. I found the Wire library that appears to be used by the compiler. I used the Wire.setClock function with a value of 400000 but printing out TWBR still shows 72. How do you set the bus speed for a T4?

TWBR is a legacy emulation for ye ol 8-bit AVR processors. I don't think T4 supports the emulation. use Wire.setClock(400000) and verify with scope on SCL pin.

if your device can do it, the T4 I2C can run at 1 MHz, https://forum.pjrc.com/threads/57319-I2C-maximum-speed
 
Last edited:
When using Wire, I had no control over the speed. When switching to Wire1, my loop time dropped from 384usec to 109usec for 20 passes. However, using Wire1.setClock() does not produce a change in loop time for freq values of 100KHz, 400KHz, 500KHz, 800KHz, and 1MHz. I'm still using pins A4 and A5. It appears to be stuck at 400KHz while Wire is stuck at 100KHz.

PS
It appears that Wire is stuck at 100KHz and Wire1 and Wire2 are stuck at 400KHz.
 
It's now responding to speed changes by moving the Wire1.setClock() to just under the Wire1.begin() in the DAC library CPP file. However, I don't get the right output voltage and by calculation, it's running about double speed. I'll have to put the scope on it tomorrow and see what is happening.
 
Problem Solved
My Owon SDS-7102V scope found it all. It turns out that Wire1 commands do not work or they use different pins than A4 and A5. None on the top of the T4 were active. I changed everything to Wire and set the clock immediately after Wire.begin(). Also, the Adafruit_MCP4725 library has so little in it that I copied the working part directly into my sketch to have more control when experimenting. I tested I2C clocks at 100KHz, 400KHz, and 1MHz and all rates are right on at 50% duty cycle according to the scope. Pullup resistors of 1K are enough to give fast rise times. The DACs are working correctly at all these rates even though the chip data sheet says 400KHz.

Unfortunately, at 1MHz it takes 86 usec to go around the loop so my sample rate will probably be limited to 10Ks/s. This should work with a filter corner freq of 4KHz but I want the rate to go to 40KHz. It appears then that the MCP4724 on I2C will not work. My next choice is an MCP4922 running on SPI but there are no US suppliers of a breakout board. OSHPark makes a board but shipping on a single chip is about three time the chip cost. I'll still investigate that route as I have other high freq apps in mind.

Thank you all for your help in solving this problem. The T4 is a marvelous board and thank you Paul for a great product.
Ed
 
I looked at that and found a nice board from Adafruit that would directly replace the function of a pair of MCP4725 boards. However, the library was written for the Due and other CPUs that show up as incompatible in the Arduino IDE. If I had a board and library that would run fast enough then I would consider I2S. I've never used any I2S hardware so have no experience in that part of Arduino programming.
 
With the audio sibrary, 44.1kHz 16Bit stereo is standard/default. Every codec can do that, the T3.2 can do that. It can do 96kHz stereo if you want. May be a little much for hearing aid...

I'm currently using 256Khz/2 channel, in+out, 16 Bit, over I2S on a Teensy 4 (same price as T3.2) , 2 channel, for a SDR receiver (Thanks to DD4WH) CPU-usage still low....
I2S is all DMA, so no CPU usage for the codec.
 
Don't know - maybe the others can answer this.
This board has no ADC- Don't you need a ADC, too?

Why not use the PJRC Audio shield? it has ADC, DAC, Mic input (mono), line-in (stereo) , Headphone output... + library support
 
Status
Not open for further replies.
Back
Top