Infineon MA12070 Class D

The hissing from my ma12070p board magically stopped. I don't know what fixed it. Based on testing this at 12V via battery, and observing that the hissing seemed proportional to supply voltage, I found a 15V supply to use (instead of the 24V I was initially using). I figured if the hissing was quieter, and I put the lid on the chassis, I could live with the hiss.

So I modified it to use the 15V supply, and let it run continuously for a day or two. Thus far, I'd only been using it with cheap speakers. But since it had been working predictably (except for the PCB hissing), I decided to change to my nicer speakers. I continued to let it run continuously.

And yesterday I noticed there was no more hissing! I don't know exactly when it stopped, actually, as at 15V with the case lid on, it was fairly easy to ignore. I did a power cycle, and the hissing did not return. Just now I reverted back to the original 24V PSU, and there is no hissing!

Now my problem is that it appears there is some noticeable latency/lag between issuing commands (start/stop playback, volume change) and hearing the result of those commands on the speaker. I don't know how to measure this, but it appears to be on the order of half to three-fourths of a second. Enough that, for example, AV sync on video playback is so bad as to be unwatchable.

Also, here's a little Python code for doing some basic interaction with the ma12070p via I2C. It's not complete or "production ready" by any means, but hopefully serves as a useful example.

Code:
#!/usr/bin/python3

from smbus import SMBus


def status(bus):
    for offset in [ 0x1d, 0x40, 0x35, 0x36, 0x7e, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x6d, 0x75, 0x7c ]:
        b = bus.read_byte_data(address, offset)
        print('offset {0}: value={1} / {2}'.format(hex(offset), hex(b), bin(b)))


# I2C address is determined by pins 28/AD0 and 29/AD1
# 0x20 is the default for the ma12070p reference board
address = 0x20
bus = SMBus(1) # using RPI GPIO physical pins 3 and 5

print('initial status:')
status(bus)

# enable limiter
# bit 0 set to 1 => default i2s_sck_pol [default]
# bit 6 set to 1 => use the limiter
bus.write_byte_data(address, 0x36, 0x41)

# enable processor AND use right-justified I2S
# bit 3 set to 1 => use the audio processor
# bits 0:2 all 0 => i2s standard / right-justified 20 bits
bus.write_byte_data(address, 0x35, 0x8)

# set volume
# default is 0x18 =>  0 dB
#            0x19 => -1 dB
#            0x20 => -2 dB
# ...
#            0x3a => -34 dB
#            0x40 => -40 dB
#            0xfa => -50 dB
# ...
#            0xa8 => -144 dB
bus.write_byte_data(address, 0x40, 0x3a)

# set power mode profile 3 (PMP3)
bus.write_byte_data(address, 0x1d, 0x3)

print('status after writes:')
status(bus)

bus.close()
 
The hissing from my ma12070p board magically stopped. I don't know what fixed it. Based on testing this at 12V via battery, and observing that the hissing seemed proportional to supply voltage, I found a 15V supply to use (instead of the 24V I was initially using). I figured if the hissing was quieter, and I put the lid on the chassis, I could live with the hiss.

So I modified it to use the 15V supply, and let it run continuously for a day or two. Thus far, I'd only been using it with cheap speakers. But since it had been working predictably (except for the PCB hissing), I decided to change to my nicer speakers. I continued to let it run continuously.

And yesterday I noticed there was no more hissing! I don't know exactly when it stopped, actually, as at 15V with the case lid on, it was fairly easy to ignore. I did a power cycle, and the hissing did not return. Just now I reverted back to the original 24V PSU, and there is no hissing!

Now my problem is that it appears there is some noticeable latency/lag between issuing commands (start/stop playback, volume change) and hearing the result of those commands on the speaker. I don't know how to measure this, but it appears to be on the order of half to three-fourths of a second. Enough that, for example, AV sync on video playback is so bad as to be unwatchable.

Also, here's a little Python code for doing some basic interaction with the ma12070p via I2C. It's not complete or "production ready" by any means, but hopefully serves as a useful example.

Code:
#!/usr/bin/python3

from smbus import SMBus


def status(bus):
    for offset in [ 0x1d, 0x40, 0x35, 0x36, 0x7e, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x6d, 0x75, 0x7c ]:
        b = bus.read_byte_data(address, offset)
        print('offset {0}: value={1} / {2}'.format(hex(offset), hex(b), bin(b)))


# I2C address is determined by pins 28/AD0 and 29/AD1
# 0x20 is the default for the ma12070p reference board
address = 0x20
bus = SMBus(1) # using RPI GPIO physical pins 3 and 5

print('initial status:')
status(bus)

# enable limiter
# bit 0 set to 1 => default i2s_sck_pol [default]
# bit 6 set to 1 => use the limiter
bus.write_byte_data(address, 0x36, 0x41)

# enable processor AND use right-justified I2S
# bit 3 set to 1 => use the audio processor
# bits 0:2 all 0 => i2s standard / right-justified 20 bits
bus.write_byte_data(address, 0x35, 0x8)

# set volume
# default is 0x18 =>  0 dB
#            0x19 => -1 dB
#            0x20 => -2 dB
# ...
#            0x3a => -34 dB
#            0x40 => -40 dB
#            0xfa => -50 dB
# ...
#            0xa8 => -144 dB
bus.write_byte_data(address, 0x40, 0x3a)

# set power mode profile 3 (PMP3)
bus.write_byte_data(address, 0x1d, 0x3)

print('status after writes:')
status(bus)

bus.close()

Glad the hissing is gone, although it's odd how it disappeared 😕

I found ESP32 Merus Amp driver code

snapclient/MerusAudio.c at master * jorgenkraghjakobsen/snapclient * GitHub

Between your code and this code maybe I can come up with register values.

Lots of playing to do.
 
And is the casing a metal one??

Yes, it's metal, see pictures in post #296.


What's the length of your wires connected from amp to speakers?

I was using two different sets of speaker wires, one is 112 inches, the other 54 inches. I tried all four combinations of the two sets of speakers and two speaker cables. The two different cables don't make any difference.

However, the speakers do make a difference. The original cheaper speakers I was using - Paul Carmody Overnight Sensations - cause the hiss. My nicer speakers - Jeff Bagby Solstice MTM - do not cause the hiss!

Just now I tried a third pair of speakers, Paul Carmody Speedsters - no hiss!

So at least now I know the source of the hissing, though I don't know why the different speakers cause hiss or no hiss.
 
Now my problem is that it appears there is some noticeable latency/lag between issuing commands (start/stop playback, volume change) and hearing the result of those commands on the speaker. I don't know how to measure this, but it appears to be on the order of half to three-fourths of a second. Enough that, for example, AV sync on video playback is so bad as to be unwatchable.

I fixed this by removing the Allo Kali. It appears the Kali adds a non-trivial amount of latency to the I2S stream, and in turn, creates some obvious lag. I've been using a Kali for years, and never noticed this; but in the past, it was always in the context of being purely for music playback. IOW, this is the first time I've used the Kali for audio that needs to be synced to something else (i.e. video).

This is pretty easy to demonstrate, actually. Use a simple, low-level tool like "aplay" (part of the ALSA suite) to play some sound file. With the Kali, there is a noticeable delay from when you press Enter to actually hearing the sound. But without the Kali, as soon as you hit Enter, you hear sound. Clearly this is not a precise measurement, but, at least with the ma12070p, the effect is pronounced enough to be obvious.
 
# enable processor AND use right-justified I2S
# bit 3 set to 1 => use the audio processor
# bits 0:2 all 0 => i2s standard / right-justified 20 bits
bus.write_byte_data(address, 0x35, 0x8)

I believe you got these values from page 78 of the manual...

format i2s_format 2:0
0 0 0 0 0 0 0 0 i2s standard
0 0 0 0 0 0 0 1 Left justified
0 0 0 0 0 1 0 0 Right justified 16bits
0 0 0 0 0 1 1 0 Right justified 18bits
0 0 0 0 0 0 0 0 Right justified 20bits
0 0 0 0 0 1 1 1 Right justified 24bits

I always found the values odd. I just looked at page 17 of the manual and it has this...

0x35(2-0) i2s_format PCM word format:
000: i2s
001: left justified (default)
100: right justified 16bits
101: right justified 18bits
110: right justified 20bits
111: right justified 24bits

Obviously a typo for Right justified 18bits and Right justified 20bits

I still can't get this amp to work, and was hoping for some help.

Here's some code from an ESP32 MA12070P driver program that I assume works...
Code:
   gpio_config(&io_conf);

   gpio_set_level(MA_NMUTE_IO, 0);
   gpio_set_level(MA_NENABLE_IO, 1);

   i2c_master_init();

   gpio_set_level(MA_NENABLE_IO, 0);

   uint8_t res = ma_read_byte(MA120X0_ADDR,1,MA_hw_version__a);
   printf("Hardware version: 0x%02x\n",res);

   ma_write_byte(MA120X0_ADDR,1,MA_i2s_format__a,8);          // Set i2s left justified, set audio_proc_enable
   ma_write_byte(MA120X0_ADDR,1,MA_vol_db_master__a,0x50);    // Set vol_db_master

   res = ma_read_byte(MA120X0_ADDR,1,MA_error__a);
   printf("Errors : 0x%02x\n",res);

   res = 01; // get_MA_audio_in_mode_mon();
   printf("Audio in mode : 0x%02x\n",res);

   printf("Clear errors\n");
   ma_write_byte(MA120X0_ADDR,1,45,0x34);
   ma_write_byte(MA120X0_ADDR,1,45,0x30);
   printf("MA120x0P init done\n");

   gpio_set_level(MA_NMUTE_IO, 1);
   printf("Unmute\n");

Here's code from the .h file associated with the code...

Code:
//------------------------------------------------------------------------------audio_proc_enable---
// Enable Audio proc, bypass if not enabled 
#define MA_audio_proc_enable__a 53
#define MA_audio_proc_enable__len 1
#define MA_audio_proc_enable__mask 0x08
#define MA_audio_proc_enable__shift 0x03
#define MA_audio_proc_enable__reset 0x00
#define set_obj_MA_audio_proc_enable(o,y) ({ uint8_t __ret = o.read(53); o.write(53,(__ret&0xf7)|((y<<3)&0x08)); }) 
#define set_MA_audio_proc_enable(y) ({ uint8_t __ret = ma_read_byte(53); ma_write_byte(53,(__ret&0xf7)|((y<<3)&0x08)); }) 
#define get_obj_MA_audio_proc_enable(o) (o.read(53) & 0x08)>>3 
#define get_MA_audio_proc_enable() ( ma_read_byte(53) & 0x08)>>3 
//------------------------------------------------------------------------------audio_proc_release---
// 00:slow, 01:normal, 10:fast 
#define MA_audio_proc_release__a 53
#define MA_audio_proc_release__len 2
#define MA_audio_proc_release__mask 0x30
#define MA_audio_proc_release__shift 0x04
#define MA_audio_proc_release__reset 0x00
#define set_obj_MA_audio_proc_release(o,y) ({ uint8_t __ret = o.read(53); o.write(53,(__ret&0xcf)|((y<<4)&0x30)); }) 
#define set_MA_audio_proc_release(y) ({ uint8_t __ret = ma_read_byte(53); ma_write_byte(53,(__ret&0xcf)|((y<<4)&0x30)); }) 
#define get_obj_MA_audio_proc_release(o) (o.read(53) & 0x30)>>4 
#define get_MA_audio_proc_release() ( ma_read_byte(53) & 0x30)>>4 
//------------------------------------------------------------------------------audio_proc_attack---
// 00:slow, 01:normal, 10:fast  
#define MA_audio_proc_attack__a 53
#define MA_audio_proc_attack__len 2
#define MA_audio_proc_attack__mask 0xc0
#define MA_audio_proc_attack__shift 0x06
#define MA_audio_proc_attack__reset 0x00
#define set_obj_MA_audio_proc_attack(o,y) ({ uint8_t __ret = o.read(53); o.write(53,(__ret&0x3f)|((y<<6)&0xc0)); }) 
#define set_MA_audio_proc_attack(y) ({ uint8_t __ret = ma_read_byte(53); ma_write_byte(53,(__ret&0x3f)|((y<<6)&0xc0)); }) 
#define get_obj_MA_audio_proc_attack(o) (o.read(53) & 0xc0)>>6 
#define get_MA_audio_proc_attack() ( ma_read_byte(53) & 0xc0)>>6 
//------------------------------------------------------------------------------i2s_format---
// i2s basic data format, 000 = std. i2s, 001 = left justified (default) 
#define MA_i2s_format__a 53
#define MA_i2s_format__len 3
#define MA_i2s_format__mask 0x07
#define MA_i2s_format__shift 0x00
#define MA_i2s_format__reset 0x01
#define set_obj_MA_i2s_format(o,y) ({ uint8_t __ret = o.read(53); o.write(53,(__ret&0xf8)|((y<<0)&0x07)); }) 
#define set_MA_i2s_format(y) ({ uint8_t __ret = ma_read_byte(53); ma_write_byte(53,(__ret&0xf8)|((y<<0)&0x07)); }) 
#define get_obj_MA_i2s_format(o) (o.read(53) & 0x07)>>0 
#define get_MA_i2s_format() ( ma_read_byte(53) & 0x07)>>0 
//------------------------------------------------------------------------------audio_proc_limiterEnable---
// 1: enable limiter, 0: disable limiter 
#define MA_audio_proc_limiterEnable__a 54
#define MA_audio_proc_limiterEnable__len 1
#define MA_audio_proc_limiterEnable__mask 0x40
#define MA_audio_proc_limiterEnable__shift 0x06
#define MA_audio_proc_limiterEnable__reset 0x00
#define set_obj_MA_audio_proc_limiterEnable(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xbf)|((y<<6)&0x40)); }) 
#define set_MA_audio_proc_limiterEnable(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xbf)|((y<<6)&0x40)); }) 
#define get_obj_MA_audio_proc_limiterEnable(o) (o.read(54) & 0x40)>>6 
#define get_MA_audio_proc_limiterEnable() ( ma_read_byte(54) & 0x40)>>6 
//------------------------------------------------------------------------------audio_proc_mute---
// 1: mute, 0: unmute 
#define MA_audio_proc_mute__a 54
#define MA_audio_proc_mute__len 1
#define MA_audio_proc_mute__mask 0x80
#define MA_audio_proc_mute__shift 0x07
#define MA_audio_proc_mute__reset 0x00
#define set_obj_MA_audio_proc_mute(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0x7f)|((y<<7)&0x80)); }) 
#define set_MA_audio_proc_mute(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0x7f)|((y<<7)&0x80)); }) 
#define get_obj_MA_audio_proc_mute(o) (o.read(54) & 0x80)>>7 
#define get_MA_audio_proc_mute() ( ma_read_byte(54) & 0x80)>>7 
//------------------------------------------------------------------------------i2s_sck_pol---
// i2s sck polarity cfg. 0 = rising edge data change 
#define MA_i2s_sck_pol__a 54
#define MA_i2s_sck_pol__len 1
#define MA_i2s_sck_pol__mask 0x01
#define MA_i2s_sck_pol__shift 0x00
#define MA_i2s_sck_pol__reset 0x01
#define set_obj_MA_i2s_sck_pol(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xfe)|((y<<0)&0x01)); }) 
#define set_MA_i2s_sck_pol(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xfe)|((y<<0)&0x01)); }) 
#define get_obj_MA_i2s_sck_pol(o) (o.read(54) & 0x01)>>0 
#define get_MA_i2s_sck_pol() ( ma_read_byte(54) & 0x01)>>0 
//------------------------------------------------------------------------------i2s_framesize---
// i2s word length. 00 = 32bit, 01 = 24bit 
#define MA_i2s_framesize__a 54
#define MA_i2s_framesize__len 2
#define MA_i2s_framesize__mask 0x18
#define MA_i2s_framesize__shift 0x03
#define MA_i2s_framesize__reset 0x00
#define set_obj_MA_i2s_framesize(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xe7)|((y<<3)&0x18)); }) 
#define set_MA_i2s_framesize(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xe7)|((y<<3)&0x18)); }) 
#define get_obj_MA_i2s_framesize(o) (o.read(54) & 0x18)>>3 
#define get_MA_i2s_framesize() ( ma_read_byte(54) & 0x18)>>3 
//------------------------------------------------------------------------------i2s_ws_pol---
// i2s ws polarity. 0 = low first 
#define MA_i2s_ws_pol__a 54
#define MA_i2s_ws_pol__len 1
#define MA_i2s_ws_pol__mask 0x02
#define MA_i2s_ws_pol__shift 0x01
#define MA_i2s_ws_pol__reset 0x00
#define set_obj_MA_i2s_ws_pol(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xfd)|((y<<1)&0x02)); }) 
#define set_MA_i2s_ws_pol(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xfd)|((y<<1)&0x02)); }) 
#define get_obj_MA_i2s_ws_pol(o) (o.read(54) & 0x02)>>1 
#define get_MA_i2s_ws_pol() ( ma_read_byte(54) & 0x02)>>1 
//------------------------------------------------------------------------------i2s_order---
// i2s word bit order. 0 = MSB first 
#define MA_i2s_order__a 54
#define MA_i2s_order__len 1
#define MA_i2s_order__mask 0x04
#define MA_i2s_order__shift 0x02
#define MA_i2s_order__reset 0x00
#define set_obj_MA_i2s_order(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xfb)|((y<<2)&0x04)); }) 
#define set_MA_i2s_order(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xfb)|((y<<2)&0x04)); }) 
#define get_obj_MA_i2s_order(o) (o.read(54) & 0x04)>>2 
#define get_MA_i2s_order() ( ma_read_byte(54) & 0x04)>>2 
//------------------------------------------------------------------------------i2s_rightfirst---
// i2s L/R word order; 0 = left first 
#define MA_i2s_rightfirst__a 54
#define MA_i2s_rightfirst__len 1
#define MA_i2s_rightfirst__mask 0x20
#define MA_i2s_rightfirst__shift 0x05
#define MA_i2s_rightfirst__reset 0x00
#define set_obj_MA_i2s_rightfirst(o,y) ({ uint8_t __ret = o.read(54); o.write(54,(__ret&0xdf)|((y<<5)&0x20)); }) 
#define set_MA_i2s_rightfirst(y) ({ uint8_t __ret = ma_read_byte(54); ma_write_byte(54,(__ret&0xdf)|((y<<5)&0x20)); }) 
#define get_obj_MA_i2s_rightfirst(o) (o.read(54) & 0x20)>>5 
#define get_MA_i2s_rightfirst() ( ma_read_byte(54) & 0x20)>>5

Just so I understand, using MA_i2s_format__a as an example...

Does the #define initially set the bits to x'01' or 001?
Does the code later set it to decimal 8 or 1000? How can you fit 1000 into
3 bits?

I hope that the driver works, and that by understanding it I can get my amp
to play music instead of static.

Thanks in advance!

Mike
 
No, define just sets values to variables. MA_i2s_format__a holds the register address, in this case 53. However, if you read the comment in the code, it sets 2 things - i2s format and audio_proc_enable which also happens to be in register 53 but one bit before. So 8 is 1000 in binary which means the code sets audio_proc_enable to 1 and i2s_format to 000. Not sure why they didn't use the functions defined in the .h rather than direct I2C register manipulation.
 
No, define just sets values to variables. MA_i2s_format__a holds the register address, in this case 53. However, if you read the comment in the code, it sets 2 things - i2s format and audio_proc_enable which also happens to be in register 53 but one bit before. So 8 is 1000 in binary which means the code sets audio_proc_enable to 1 and i2s_format to 000. Not sure why they didn't use the functions defined in the .h rather than direct I2C register manipulation.

Thank you!
 
I believe you got these values from page 78 of the manual...

Yes, indeed, pages 76-82 of the datasheet document the i2c register map. I never caught the typo before!


Here's some code from an ESP32 MA12070P driver program that I assume works...

My gut feel is that you might want to cut out all the junk, and just strive for a very minimal program that at least gets sound playback working, and then start to make it fancier. All those macros in the H file are probably helpful in a bigger/more complex program, but to get rolling, I would just hardcode addresses and values directly.

I think you can cut out the ENABLE/MUTE stuff, since I believe you are using the reference board (like me), which has those hard-coded. I doubt they are causing any problem, but again, in the interest of making things as simple as possible...

Here's some thoughts on the code you posted:

Code:
   gpio_config(&io_conf);
   i2c_master_init();

   uint8_t res = ma_read_byte(MA120X0_ADDR,1,MA_hw_version__a);
   printf("Hardware version: 0x%02x\n",res);

Just curious, does this actually print something out? And is that something what you expect?

Code:
   ma_write_byte(MA120X0_ADDR,1,MA_i2s_format__a,8);          // Set i2s left justified, set audio_proc_enable
   ma_write_byte(MA120X0_ADDR,1,MA_vol_db_master__a,0x50);    // Set vol_db_master


So, FYI, you might already know this, but "MA120X0_ADDR" should be 0x20 for the reference board. More generally, this is set by pins 28/AD0 and 29/AD1; it's just the I2C address.

That second line, volume setting, is a little more straightforward, so I'll cover that first: I don't see MA_vol_db_master__a in your H file, but it should have a value of 0x40 (64 in base10). Also, the value of 0x50 for the volume should be -56 dB. In my testing, going below 0x40 (-40dB) was so quiet as to barely be audible. Volume of course will be dependent on your speakers and power supply, but I'd set that slightly higher. I found 0x3a (-34dB) to be comfortable "conversation volume" with 24v supply and my speakers, which I think are around 85 dB sensitivity.

Note the line where you set the I2S format is the same as what I did in my sample code. Note you used "8" which implies base-10, and I used "0x8" which implies base-6. "8" happens to be the same in hex and decimal, so in this particular case, it's OK. But in general, if you use mix up hex/decimal, it can cause problems.

I overlooked something here initially myself. That is, setting the I2S format uses offset 0x35 (53 in base-10) -- but only the lower three bits (0:2). That 0x35 offset is shared with the volume limiter+processor config, which uses the upper 5 bits (7:3). So the reason I used 0x8 was so that bit 3 would be a one, which means "use the audio processor". My understanding is that volume control is a function of the audio processor. All other bits should be zero; in particular, the lower three bits (0:2) should be zero, which according to page 78 of the datasheet, means "i2s standard" or "right justified 20bits" (or just "i2s" according to page 17).

I suspect that's why the H file you posted has all those MA_i2s_format macros, to abstract the low-level bit math.

Anyway, what I would do is try all possible I2S format values, making sure to always keep that bit 3 set to one. In other words:

0b1000 (bin) = 0x8 (hex) = 8 (dec) -> i2s standard / Right justified 20bits (?)
0b1001 (bin) = 0x9 (hex) = 9 (dec) -> Left justified
0b1100 (bin) = 0xc (hex) = 12 (dec) -> Right justified 16bits
0b1101 (bin) = 0xd (hex) = 13 (dec) -> right justified 18bits (?)
0b1110 (bin) = 0xe (hex) = 14 (dec) -> Right justified 18bits / 20bits (?)
0b1111 (bin) = 0xf (hex) = 15 (dec) -> Right justified 24bits

Hopefully that's somewhat helpful!
 
Another thing I meant to ask in the previous message: have you previously used the ESP32 as an I2S transport? If so, with which DAC did you use it? That may give us some hint as to the specifics of the ESP32's I2S format. IOW, there may be some config on the ESP32 side as well.
 
Yes, indeed, pages 76-82 of the datasheet document the i2c register map. I never caught the typo before!




My gut feel is that you might want to cut out all the junk, and just strive for a very minimal program that at least gets sound playback working, and then start to make it fancier. All those macros in the H file are probably helpful in a bigger/more complex program, but to get rolling, I would just hardcode addresses and values directly.

I think you can cut out the ENABLE/MUTE stuff, since I believe you are using the reference board (like me), which has those hard-coded. I doubt they are causing any problem, but again, in the interest of making things as simple as possible...

Here's some thoughts on the code you posted:

Code:
   gpio_config(&io_conf);
   i2c_master_init();

   uint8_t res = ma_read_byte(MA120X0_ADDR,1,MA_hw_version__a);
   printf("Hardware version: 0x%02x\n",res);

Just curious, does this actually print something out? And is that something what you expect?

Code:
   ma_write_byte(MA120X0_ADDR,1,MA_i2s_format__a,8);          // Set i2s left justified, set audio_proc_enable
   ma_write_byte(MA120X0_ADDR,1,MA_vol_db_master__a,0x50);    // Set vol_db_master


So, FYI, you might already know this, but "MA120X0_ADDR" should be 0x20 for the reference board. More generally, this is set by pins 28/AD0 and 29/AD1; it's just the I2C address.

That second line, volume setting, is a little more straightforward, so I'll cover that first: I don't see MA_vol_db_master__a in your H file, but it should have a value of 0x40 (64 in base10). Also, the value of 0x50 for the volume should be -56 dB. In my testing, going below 0x40 (-40dB) was so quiet as to barely be audible. Volume of course will be dependent on your speakers and power supply, but I'd set that slightly higher. I found 0x3a (-34dB) to be comfortable "conversation volume" with 24v supply and my speakers, which I think are around 85 dB sensitivity.

Note the line where you set the I2S format is the same as what I did in my sample code. Note you used "8" which implies base-10, and I used "0x8" which implies base-6. "8" happens to be the same in hex and decimal, so in this particular case, it's OK. But in general, if you use mix up hex/decimal, it can cause problems.

I overlooked something here initially myself. That is, setting the I2S format uses offset 0x35 (53 in base-10) -- but only the lower three bits (0:2). That 0x35 offset is shared with the volume limiter+processor config, which uses the upper 5 bits (7:3). So the reason I used 0x8 was so that bit 3 would be a one, which means "use the audio processor". My understanding is that volume control is a function of the audio processor. All other bits should be zero; in particular, the lower three bits (0:2) should be zero, which according to page 78 of the datasheet, means "i2s standard" or "right justified 20bits" (or just "i2s" according to page 17).

I suspect that's why the H file you posted has all those MA_i2s_format macros, to abstract the low-level bit math.

Anyway, what I would do is try all possible I2S format values, making sure to always keep that bit 3 set to one. In other words:

0b1000 (bin) = 0x8 (hex) = 8 (dec) -> i2s standard / Right justified 20bits (?)
0b1001 (bin) = 0x9 (hex) = 9 (dec) -> Left justified
0b1100 (bin) = 0xc (hex) = 12 (dec) -> Right justified 16bits
0b1101 (bin) = 0xd (hex) = 13 (dec) -> right justified 18bits (?)
0b1110 (bin) = 0xe (hex) = 14 (dec) -> Right justified 18bits / 20bits (?)
0b1111 (bin) = 0xf (hex) = 15 (dec) -> Right justified 24bits

Hopefully that's somewhat helpful!

I'm not actually using the driver, just trying to determine if I can manually
set the registers to simulate a driver. This may not be possible.

Another thing I meant to ask in the previous message: have you previously used the ESP32 as an I2S transport? If so, with which DAC did you use it? That may give us some hint as to the specifics of the ESP32's I2S format. IOW, there may be some config on the ESP32 side as well.

I had the same thought. The PCM5102A dac chip works with the SCK pin grounded. Haven't been able to reverse engineer the i2s settings, but any help would be much appreciated.
 
I had the same thought. The PCM5102A dac chip works with the SCK pin grounded. Haven't been able to reverse engineer the i2s settings, but any help would be much appreciated.

I took a quick glance at the pcm5102a datasheet. Looks like it supports multiple types of I2S streams, just like the ma12070p. So, I guess we still don't know exactly what kind of I2S stream your ESP32 outputs. But if you had a working pcm5102a DAC with it, then I'd operate under the assumption that it's outputting some reasonable I2s stream that the ma12070p should be able to use.

Hrm, actually - did you have any kind of driver for the pcm5102a?

Speaking of the different I2S pins - how are you wiring your ESP32 to the ma12070p? You said the SCK pin on the pcm5102a is grounded; that would correspond to the MCLK pin on the ma12070p. You definitely can not ground the MCLK pin on the ma12070p reference board. In my testing, I generally had success running a jumper from the BCLK pin to the MCLK pin. My Allo Kali had an MCLK output, but I was unable to get any sound when I tried to jumper that the ma12070p reference PCB. I also tested with an Amanero (USB to I2S device); it also has an MCLK pin. I can't remember exactly how that test worked out, but I think in one scenario, I had to keep the MCLK-BCLK jumper, and in another scenario, I got it to work using the Amanero's MCLK.

A super quick web search suggests the ESP32 does not natively provide an I2S MCLK, but looks like you might be able to hack something that can be used as an MCLK. I would start by simply jumpering BCLK to MCLK on the ma12070p board (like I did), and just make a mental note about this to circle back to if necessary.

And a related question, just for comparison's sake: what kind of pcm5102a DAC were you previously using? And how was it wired to the ESP32?


I'm not actually using the driver, just trying to determine if I can manually set the registers to simulate a driver. This may not be possible.

I guess the obvious question is: why not use the driver directly to see if it works?

If that driver is loaded, it may take exclusive control of the i2c bus. At least that's how it is on the RPI - if I load the merus_amp driver, then I am unable to manually send i2c commands to the ma12070p. So only when I loaded some other generic i2s driver on the RPI was I able to use my simple i2c code to talk to the ma12070p. In short, I would expect that you "should" be able to simulate the driver via manual i2c, if the mechanisms are anything like the RPI.
 
I took a quick glance at the pcm5102a datasheet. Looks like it supports multiple types of I2S streams, just like the ma12070p. So, I guess we still don't know exactly what kind of I2S stream your ESP32 outputs. But if you had a working pcm5102a DAC with it, then I'd operate under the assumption that it's outputting some reasonable I2s stream that the ma12070p should be able to use.

Hrm, actually - did you have any kind of driver for the pcm5102a?

Squeezelite-ESP32 comes with just a few drivers, a generic i2s, TAS57XX, TAS5713, and AC101. The generic i2s works with the PCM5102a.

Speaking of the different I2S pins - how are you wiring your ESP32 to the ma12070p? You said the SCK pin on the pcm5102a is grounded; that would correspond to the MCLK pin on the ma12070p. You definitely can not ground the MCLK pin on the ma12070p reference board. In my testing, I generally had success running a jumper from the BCLK pin to the MCLK pin. My Allo Kali had an MCLK output, but I was unable to get any sound when I tried to jumper that the ma12070p reference PCB. I also tested with an Amanero (USB to I2S device); it also has an MCLK pin. I can't remember exactly how that test worked out, but I think in one scenario, I had to keep the MCLK-BCLK jumper, and in another scenario, I got it to work using the Amanero's MCLK.

A super quick web search suggests the ESP32 does not natively provide an I2S MCLK, but looks like you might be able to hack something that can be used as an MCLK. I would start by simply jumpering BCLK to MCLK on the ma12070p board (like I did), and just make a mental note about this to circle back to if necessary.

I made a splitter wire from ESP32 BCLK to Merus BCLK/MCLK.

And a related question, just for comparison's sake: what kind of pcm5102a DAC were you previously using? And how was it wired to the ESP32?

https://www.amazon.com/gp/product/B...c&pd_rd_w=oOBZc&pd_rd_wg=WDJkq&ref_=pd_gw_unk

It's just a 3-wire connection (BCLK, LRCLK, and DATA). SCLK is hard wired to ground to force the chip to use it's internal PLL.

I guess the obvious question is: why not use the driver directly to see if it works?

If that driver is loaded, it may take exclusive control of the i2c bus. At least that's how it is on the RPI - if I load the merus_amp driver, then I am unable to manually send i2c commands to the ma12070p. So only when I loaded some other generic i2s driver on the RPI was I able to use my simple i2c code to talk to the ma12070p. In short, I would expect that you "should" be able to simulate the driver via manual i2c, if the mechanisms are anything like the RPI.

I may end up asking if the Squeezebox team can incorporate this driver into their code. I wish I were smart enough to "compile" this driver code and "manually" load it into the Merus amp.

I was hoping that a driver was just i2c commands to change GPIO's and registers. Since I don't think any GPIO's need changing (such as the startup procedure for /ENA and /MUTE), I can change registers very easily using the Squeezelite-ESP32 web interface.

The reason I asked for help with the driver code is the sinking feeling that perhaps there were more documentation errors.

One thing I just did was to connect the amp to my RPi again. I dumped all the Merus registers before playing music, and again while music was playing. Maybe I can discover something else?

What's frustrating is that the static sound from the speaker doesn't start until I hit play. When I hit pause the static continues for around 10 seconds, then goes silent. Seems like it wants to play something but can't.
 
I was hoping that a driver was just i2c commands to change GPIO's and registers. Since I don't think any GPIO's need changing (such as the startup procedure for /ENA and /MUTE), I can change registers very easily using the Squeezelite-ESP32 web interface.

Are you currently able to send I2C commands via the Squeezelite-ESP32 web interface? Does this web interface also allow you to read I2C address+offset values?


One thing I just did was to connect the amp to my RPi again. I dumped all the Merus registers before playing music, and again while music was playing. Maybe I can discover something else?

That gave me an idea - just to get up and running, you could connect the I2S from the ESP32, but connect the I2C to the Raspberry Pi. That gives you an easy way to read and write the I2C (e.g., use my Python code as a starting point), and you can quickly iterate over different settings to see what works. If that works, then the only problem left is to be able to do the same I2C stuff directly from the ESP32.

I suppose this has the possibility for a ground loop, not sure how severe a potential problem that is. (Much of my knowledge comes from having learned things the hard way! 😉)
 
Are you currently able to send I2C commands via the Squeezelite-ESP32 web interface? Does this web interface also allow you to read I2C address+offset values?




That gave me an idea - just to get up and running, you could connect the I2S from the ESP32, but connect the I2C to the Raspberry Pi. That gives you an easy way to read and write the I2C (e.g., use my Python code as a starting point), and you can quickly iterate over different settings to see what works. If that works, then the only problem left is to be able to do the same I2C stuff directly from the ESP32.

I suppose this has the possibility for a ground loop, not sure how severe a potential problem that is. (Much of my knowledge comes from having learned things the hard way! 😉)

You and me both. Just so we are on the same page...

When you say I2C address+offset values, do you mean the I2C address of the Merus DAC, which is 0x20 in my case, and by offset values do you mean the registers themselves? If so, then yes. If I can figure out the register values, I can create JSON code to kick off when the ESP32 powers up. This way the register changes can occur automatically.
 
Here is what I currently have in a field called dac_controlset

{ "init": [ {"reg":53,"val":00}, {"reg":54,"val":17} ] }

When I reboot the ESP32, register 53, or 0x35, is loaded with 0x00, and register 54, or 0x36, is loaded with 0x11. So far no bit combination I've tried seems to work, but I assume this can do what your Python code is doing.
 
You and me both. Just so we are on the same page...

When you say I2C address+offset values, do you mean the I2C address of the Merus DAC, which is 0x20 in my case, and by offset values do you mean the registers themselves? If so, then yes. If I can figure out the register values, I can create JSON code to kick off when the ESP32 powers up. This way the register changes can occur automatically.

Yup, we are on the same page. On the reference board that we both have, the I2C address is hardwired to 0x20.

Note, at the risk of adding confusion, the datasheet also uses the word "address" to refer to what I'm calling "offset", and you are calling "register".
 
A super quick web search suggests the ESP32 does not natively provide an I2S MCLK, but looks like you might be able to hack something that can be used as an MCLK. I would start by simply jumpering BCLK to MCLK on the ma12070p board (like I did), and just make a mental note about this to circle back to if necessary.

Not sure if I found something, but...

I get a message on the console when the ESP32 boots...

I (3891) I2S: APLL: Req RATE: 44100, real rate: 44099.988, BITS: 16, CLKM: 1, BCK_M: 8, MCLK: 11289597.000, SCLK: 1411199.625000, diva: 1, divb: 0

I think MCLK is just a calculated field, freq (44100) * 256. But SCLK ends up being freq * 32. According to the datasheet, page 23, freq * 32 is not a valid CLK for a 44100 sample rate. Freq * 32 is valid for higher sample rates. I'm going to see if I can get the sample rate changed to 96000. Do you know the sample frequency you are sending to your amp?

Mike
 
Someone else posted, then deleted it... but I'll say something similar: I think you need to somehow get your ESP32 to emit a 32 bit stream, looks like you're outputting a 16 bit stream.

Pretty sure I'm sending 44.1khz, 32bit to the ma12070p (see the S32_LE part):

Code:
# cat /proc/asound/card0/pcm0p/sub0/hw_params 
access: MMAP_INTERLEAVED
format: S32_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 220
buffer_size: 440

That's what the Linux merus-amp driver sets up automatically. (Though I'm not sure why they set it to 44.1khz, the hardware supports higher sampling rates. This implies playing music with a higher samplerate will be downsampled.)

But - when I was first testing, and using the a generic I2S driver, which by default seems to assume the hardware will play multiple bit formats, it was only silence when trying to play 16-bit files. I have several files with different sample rates and bit sizes. The ma12070p would only play 32 bit files. And now that I'm using the merus driver, it's only allowing 32 bit (trying to play a 16-bit file will result in an error).

That's another avenue you can explore, get (or generate) some test files with different sample rates and bit formats. But I think you first have to coerce the ESP32 to output 32-bit PCM.

(On Linux, I actually don't have to worry about the format of the source audio, as the software will convert it to what the hardware wants on the fly.)