When we started designing the MEGA65 phone, we did a lot of searching to find a way to give it really nice sound on the internal speaker, both so that games and demos would sound great, but also so that it can ring really loud. There is nothing worse than a phone than rings too quietly to be easily heard.
So we were pretty happy when we found a 40mm diameter and <5mm thick 2W speaker that claims peak levels of close to 100dB. We coupled this with a nice amplifier chip that can in theory deliver enough power to make good use of this speaker. Privision was also made for stereo, although the first prototype device will have only one speaker installed, to keep life simple.
The amplifier is an SSM2518 I2C controlled digital amplifier, which means we have easy digital control via the I2C bus, both for settings, and also for setting the volume level. We already have the I2C bus working, and can read and write its registers at $FFD7030-$FFD7042. Also, the audio cross-bar mixer has outputs setup to feed this amplifier. So in principle, we have all the ingredients we need to make it work. Now is the time to actually get it working.
First, let's look at the I2C configuration that we need. There are 19 registers, only a few of which are important to us, and of those, only certain bits are important:
$FFD7030 - bit 0 = Software master power-down, and must be 0 for normal operation.$FFD7030 - bit 5 = "no BCLK". If 1, then MCLK is used instead of BCLK to generate the sample clock. Thus we want this 0, so that we can just have BCLK, and, hopefully, require one less pin.
$FFD7030 - bit 7 = software reset, and must be 0 for normal operation.
$FFD7032 - bits 5 - 6 = Serial Data Format. 01 = left-justified samples, which is what we want.
$FFD7032 - bits 2 - 4 = Serial Audio Interface Format. 000 = I2S, with left or right justification set by bits 5 and 6 of the same register.
$FFD7032 - bits 0 - 1 = Sample rate range. 10 = 32 - 48KHz, 11 = 64-96KHz. I'm not really sure what this does. I'll also have to work out what our actual real sample rate is, as I have a suspicion that we are providing the audio at ~200KHz.
$FFD7033 - bit 7 = Generate (1) or use external (0) BCLK signal. We want 0.
$FFD7033 - bit 6 = LRCLK shape selection: 0=50% duty cycle, 1= single clock pulse. We want 0.
$FFD7033 - bit 4 = MSB first (0) or LSB first (1) in samples. We want MSB first.
$FFD7035 - Left channel volume. $00 = loudest, $FF = muted.
$FFD7036 - Right channel volume. $00 = loudest, $FF = muted.
$FFD7037 - bit 0 = master mute. 0 = unmuted, which is what we want.
$FFD7037 - bit 1 = left channel mute, as above.
$FFD7037 - bit 2 = right channel mute, as above.
Thus we want, keeping the other bits as their default values from the data sheet:
$FFD7030 = $04
$FFD7032 = $23
$FFD7033 = $00
$FFD7035 = $00
$FFD7036 = $00
$FFD7037 = $00
To test, I have Commando loaded, since it plays a tune while waiting for the game to start, and I can hear it on the headphone jack, but not from the internal speaker, even when I set the above register values. Time to probe pins...
The audio should be on pin 10, the SDATA pin of the SSM2518, but when I poke it with the oscilloscope, there seems to be nothing there. Am I generating the audio on the correct pin? We can use a special bitstream I produced to test this, that plays a unique binary pattern on every pin of the FPGA, so that I can quickly verify this sort of thing. It already proved invaluable when getting other subsystems like the LCD panel and touch interface working.
Ah, interesting! When I run that bitstream with the I2C settings as above, I can hear noise on the speaker, which makes sense, since all the input lines to the SSM2518 are being driven with various wave-forms as part of this identification feature I just described. So good news, we know things are physically wired correctly, in that there is some way to get sound out of it, and that the speaker itself is working as well.
Ok, so let's find out which pin SDATA really is, and whether I have it correctly mapped. The waveform on each pin is a series of narrow spikes to indicate the time-base, and then 8 time steps with the signal high, followed by the pin number encoded in binary. Thus the SDATA pin's waveform below means it is pin 1+2+4+64 = pin 71.
Pin #71 = FPGA pin U4, which I can confirm is connected to the i2s_speaker signal in megaphoner1.vhdl. Now to find out where that goes, and why it is not showing any signal. It connects to i2s_speaker_data_out in machine.vhdl. This connects it to i2s_speaker_data_out in iomapper.vhdl, which connects it to the signal of the same name in audio_complex.vhdl, where it is... connected to ground. Right. That would be a problem.
Looking through audio_complex.vhdl, there are actually quite a few problems to sort out:
1. When I wrote it, we were expecting that the headphone output would be using a similar I2S audio amplifier, and signals are being generated for that. But the headphones are in fact being fed with a circuit that is more or less identical to that of the Nexys4 boards, i.e., directly feeding a single pin for left and another for right at very high speed, and using a 3-stage low-pass filter to produce the acutual audio.
2. The audio going to the headphones is actually the audio marked for the speaker.
3. As noted above, the actual speaker output is not connected to anything.
Thus we need to (1) rename the headphone I2S output to speaker output, and (2) the speaker output to headphones, and (3) connect the freshly renamed speaker output to the actual speaker. We can also (4) remove the duplicated output signal for the speakers that we are not using, since what was the headphone i2s output is actually what we need. Okay, those changes weren't too hard. Now to wait the ~30 minutes for synthesis to run, to see if it has worked. Hopefully at least I will see the audio on pin U4, and if I am really lucky, the other audio control signals will all be good, and we should have audio. I'll be able to tell you in half an hour...
Well, that doesn't seem to have changed anything. This is quite frustrating, because I can no longer see any obvious reason why this would be the case. The speaker output in the audio mixer must have audio, because it is what was driving the headphones before the change. Also, the default mixer configuration from the hypervisor on powerup has both headphone and speaker output configured, hence how the headphones were working when they were actually using the speaker channel. Thus I am confident that it is not that the audio input is zero.
But if the channel being used has real audio, why are we seeing the SDATA line stay low the whole time? An instance of i2s_transceiver is used to actually produce the signal that is plumbed through to SDATA. It is being fed the spkr_left and spkr_right channels (both of which have both SIDs mixed in) as inputs, and the only other thing it needs to work is the i2s CLK and SYNC signals. Those two signals are also routed to the SSM2518's corresponding pins, so I can probe those in real-time, and confirm that they have sensible signals on them. More the point, they both have regular edges, which means that the sample shifting logic in i2s_transceiver should be clocking the samples out without problem.
So some assumption in the above must be false, as otherwise we would be seeing something on the pin. The question is what, and more the point, how can I tell which of these two parts is wrong?
Probably the first thing to try, is to feed some known waveform out on the U4 pin, but from within the audio_complex.vhdl file, so that the plumbing through to the pin can be verified. That will at least narrow things down. The MEMS microphone clock is handily available there, so I'll try feeding that through, and see if we then get a nice pulse-train on the pin. Either way we will have narrowed the problem down.
Okay, so the pulse-train is visible, so the plumbing is fine. So now the question is whether the sample data being fed to the i2s transceiver is all zeroes, or whether the i2s transceiver isn't working properly. Another synthesis run, and I am still seeing flat-line ground output on the SDATA pin, so I presume that the i2s transceiver is not working properly for some reason.
Now, the i2s transceiver is not particularly complex: It takes i2s_sync and i2s_clk signals as timing inputs, and then the samples to be transmitted. I was about to describe how the thing works, when I spotted what I think is the problem: It checks for edges on the i2s_sync line to work out when to load the next sample for transmission. However, the edge detection happens only on the detection of an edge of the i2s_clk signal -- but part of the i2s_sync edge detection was happening outside of that, which means that sync edges could get missed, resulting in the transceiver never knowing when to transmit the next sample, which would cause it to shift out zeroes forever -- which is exactly what I am seeing. So, I'll try moving that single line of code to the right place, and see if that works...
Okay, so that fixed that problem -- we now have samples visible on the SDATA pin... But still no sound. Just in case it was the I2C settings had been reset, I checked that, and they look fine. Indeed, running the bitstream that plays unique wave-forms on each pin, I still get noise from the speaker, so everything seems to be generally in order. I just need to double-check that the settings are all right.
One thing that comes to mind, is that the test bitstream has a waveform on the MCLK pin as well as the BCLK pin, where as my bitstream doesn't, instead having only a signal on BCLK. Reading again through the datasheet, it looks like we need to have MCLK regardless, but can have no BCLK, if we configure MCLK as the BCLK source. This likely explains the silence. So we need to (1): Configure the I2C registers for MCLK as the BCLK source; and (2) route the BCLK to the MCLK pin in the VHDL.
Finally, I am getting some sound out after having rerouted to the MCLK line, with the BCLK line idle -- although it sounds like high-frequency white-noise. This is a good sign, and as discussed above, not unexpected after having re-read the documentation.
Now I am hoping that by adjusting the registers of the SSM2518, I might be able to get proper sound out, since it is now presumably only a matter of the sample format and frequency. But I might also need to adjust the MCLK signal, because it seems that the SSM2518 is not really designed to just receive a bit clock, but expects many more clock-ticks per sample, than there are bits in a sample.
First, $FFD7030 needs bit 5 set, to tell the SSM2518 that there is no BCLK, just MCLK.
Next comes the problem with the bits per sample: Bits 1 to 4 of $FFD7030 set the clock:sample ratio, but the lowest ratio available is 64:1, whereas we are using something lower. In fact, I need to go through how I am generating the clock again, so that I can figure out what the current ratio is. In i2s_clock, I generate these signals based on a target sample rate of 44.1KHz, which results in a rather irregular interval. It seems to me that this simply can't work.
The datasheet tells us that MCLK must be between 2.048 MHz and 6.144 MHz, if we are going to use it as the source of the BCLK line. Given that we are expected to have at least 64 BCLK cycles per sample, this gives us a sample rate of between 32 KHz and 96 KHz. 2.822 MHz would be required for 44.1 KHz sample rate, which would be rather difficult to generate from the 40 MHz input clock we have. This would require 14.1723356 cycles per BCLK, which would be rather annoying to calculate.
Frankly, this part of the operation of the SSM2518 I am finding rather confusing and contradictory. For example, the timing diagrams for digital audio formats indicates that any number of BCLK pulses can be used per sample, which is probably what I built the VHDL implementation assuming. To add to my confusion, the white noise I am hearing doesn't change if I change the volume settings of the SSM2518. In fact, I can't seem to find any way to vary the sound level. Debugging is of course hindered by the ~30 minutes it takes to synthesise.
So maybe it is time to make a simple custom bitstream that just controls the SSM2518, and tries to play some simple low-frequency signal, so that I can try to debug things. I just found this delightful site: https://www.doulos.com/knowhow/vhdl_designers_guide/models/sine_wave_generator/, that makes it very easy to generate a sine-wave generator in VHDL. So let's modify the pin probing bitstream to try to play a nice sine-wave tone, and see what progress we can make there, and then when we have it hopefully working without too much trouble, back port the control settings into the main bitstream.
First cut of that is done, and produces a different white noise to the regular bistream, but indeed produces some noise, so that's a start. Unfortunately, it seems to have zero bearing with whatever I feed on the SDATA line. In fact, I can leave the SDATA line tied low, and still get the white noise. Frustrating. I'll have to sleep on this, to see if I can think about what might be going on.
It's now tomorrow. My first thought is that the white noise I am hearing is some kind of artefact of the sample rate. To test this, I am resynthesising my little test bitstream with half the sample rate of before. If this results in a lower tone, then it will be a good clue.
While waiting for that, the other thing that I have discovered is that the audio signal being fed to the speaker is actually a square-wave signal with a time-base of ~200ns = ~5MHz. There doesn't seem to be any filtering on it, however, to shape the noise out of the audible band. Interestingly, if I put an oscilloscope probe on pin 6 of the SSM2518, which should be the MCLK signal, noise is introduced on the speaker. Most curious... What this does tell me, however, is that this thing is going to produce so much EMI noise, that it isn't funny. The leads to the speakers will have to be shielded, at a bare minimum, and likely need ferrite beads on them to stop the EMI noise. Probably we will need some kind of low-pass filter, similar to that on the headphone output as well, so that the acoustic noise can be removed.
Anyway, changing the sample rate doesn't seem to change the sound. But the MCLK frequency didn't change from ~1MHz, which is probably much of the problem. We should be able to increase this quite a bit, which might be enough to push the acoustic noise well up into the ultrasonic range. The SSM2158 can take a MCLK of upto ~38 MHz. This is a bit sad, because if it could take 40 MHz, we could just pass the 40MHz clock out. But we can easily use 25 MHz, being half of the 50 MHz clock we have for ethernet. Let's see if that increases the time-base of the speaker output square wave, and/or pushes the white noise out of the acoustic range.
While waiting for that to synthesise, I did finally find the schematic of the SSM2518 evaluation board at https://ez.analog.com/audio/f/q-a/4096/ssm2518-evb-issue, which tellingly has a pile of filtering coils and capacitors on the speaker outputs on sheet 6. We'll have to take a closer look at that, and potentially incorporate it onto our rev2 pcb.
Anyway, pushing the frequency up to 25MHz has changed the white-noise. It is now much quieter, but still present. Oddly I can't pick up any clock on the MCLK pin now, although the SYNC (left/right select) signal is still running at the correct sample rate. I am not sure if it is my oscilloscope that is the problem here, not being able to pick up the narrow pulses of the 25 MHz clock, although it hasn't been a problem in the past. It could also be that I need to make these high-speed pins use the fast slew option of the FPGA to get good enough signal integrity. It's certainly worth a try.
Ok, so using fast slew and 24mA drive strength has made MCLK visible, and also stopped the funny sound artefacts when I probe it, which confirms that it is probably what the problem was. The noise is still there, but relatively quiet. Probing the speaker output line confirms that the time-base of the audio signal is now much higher, which explains the reduced volume of the white-noise. This pretty much confirms that we need some acoustic and EMI-rejection filtering between the SSM2518 and the speaker.
It might be that the same filter circuit we use for the headphones output will work fine, as previously mentioned. Because the speakers connect via a header, we can try some different things here, without having to re-spin the pcb. We could even make a little daughter-board that fits onto those connectors, and also has a couple of the other bodge fixes that we have implemented lately, so that the prototype device can be a bit more robust, until we make the rev2 device(s) later in the year.
Now, back to trying to get some sensible sound out, I have re-enabled the sine-wave generator, but still just getting the high-frequency noise. At this point, it is possible that the I2C configuration is wrong again, as I have powered everyhthing down again, and only set the bit to clear the mute flag. To change this, I have to load (but thankfully not synthesise) the normal bitstream, so that I can talk to the I2C bus via its memory-mapped registers. Loading that up, I was immediately hit by how much worse and high-pitched the acoustic noise is without the increased MCLK frequency. So I am at least achieving something.
So now the question is whether we need filtering before we can even get any useful sound out, or whether it is only needed to get rid of the noise. My feeling is the latter. What I really want to do, is to some how quantify whether the SSM2518 is taking any notice of my samples, or whether it is just putting random samples out.
On that topic, the FPGA is certainly outputting what looks like valid samples, with the correct 1 cycle delay after the SYNC signal toggles, as this shot shows (apologies for the poor quality, trying to get the probes to hold on, and hold the camera at the same time requires more appendages than I possess, and while I have been known to pull my socks on without using my hands, there are limits):
What was interesting, is that in the process of trying to get this shot, I accidentally touched MCLK and the SYNC lines together, and then there was some different noise -- so the SSM2518 is clearly listening for something.
Anyway, let's try to revise what settings we need to accept this sample format: It is "standard i2s", i.e., the sample occurs just after the SYNC line toggles, not just before it. We have the most significant bit of the sample first.
$FFD7030 = $20 (use MCLK as BCLK, don't mute, ignore BCLK/sample ratio, since we will specify I2S format later)
$FFD7031 = $00 (no EMI reduction/sound quality trade-off, enable automatic sample rate detection)
$FFD7032 = $02 (I2S audio format, 32-48KHz sample rate)
$FFD7033 = $00 or $80 (either using real (0) or internally generated BCLK(1) signal, 50% duty cycle expected on SYNC line, MSB comes first in serial data). Here it is not clear to me if we should be using the "real" BCLK, if we are telling it to use MCLK as BCLK. My gut feeling is that, yes, we should, as otherwise BCLK will be generated using the BCLKs/sample frequency ratio.
$FFD7034 = $10 (left and right channel mappings as default)
$FFD7035 = $00 (left channel maximum volume)
$FFD7036 = $00 (right channel maximum volume)
$FFD7037 = $80 (not muted, no fancy filters)
$FFD7038 = $0C (auto-restart on over-current and related conditions)
$FFD7039 = $80 (set high-performance mode, and don't automatically power own)
Okay, in trying those out, I have discovered that the BCLK/sample ratio is being used. Choosing a larger value results in louder and lower-frequency white-noise.
Also, discovering the example driver for the SSM2518 from microchip, I was led to the values for $FFD7038 and $FFD7039. The latter in particular sets the high-performance mode, which seems to get rid of the acoustic noise, so that's a good thing.
However, there is still no sound to be heard, even though there is clearly sample data being fed to it in the I2S format, with a working SYNC/LRCLK signal. In short, I am now fairly confident that the audio signals I am feeding it are correct, and the I2C settings are also correct -- but still no sound.
So, now I am trying to set the I2S clock generation to exactly match the 64 cycles per sample mode that it explicitly supports, in the hope that this might get it working. Again, I can see a nice clear SYNC/LRCLK signal, and I can see the SDATA lines, with the MSB first, and the sine table values cycling through. But still no sound.
More hunting around on the internet. Found this: https://analogdevices.telligenthosting.net/audio/f/q-a/4147/ssm2518-test/3695. This at least has a table that shows how to get the MCLK line to be used to provide BCLK directly. This confirms that $FFD7033 should be $00, not $80 (i.e., BCLK_GEN=0), so that BCLK is simply a copy of MCLK.
More hunting through the datasheet: It turns out that in this mode, MCLK must be between ~2 and 6 MHz, so I will now modify the I2S clock generator to generate a 5MHz clock, and use 64 cycles per sample, giving a sample rate of ~78 KHz.
Again, silence (not even static noise now, which is nice), unless I bridge the MCLK and SDATA pins, in which case there is nice loud static. Most weird, but I feel that I am getting closer to a solution.
Is it something stupid like incorrect pin assignment? Well, first, lets see if it is the MCLK or the SDATA line that needs the signal from the other, by first connecting the MCLK line to the SDATA line internally, so that MCLK ~5MHz clock appears also on the SDATA line.
First attempt at this is causing a quite loud click, and then the FPGA de-programs, presumably because the power rail sags too low. This is probably a good sign that it is trying to drive the speaker loudly. I'll turn the speaker volume down a bit, to avoid that, which is just done via the I2C registers.
Ok, so by putting the clock on the SDATA pin, I can make an absolute racket, so that even at reduced volume level, it is really loud. It's no wonder that it was making the power rail sag at full volume.
The question is now exactly what format it is expecting the audio, to get it to play something legible. Anyway, as much for my rememberance as anything, here is the current register settings:
:0FFD7030: 20 00 02 00 10 50 FF 80 0C 80
The 50 is the volume for the channel with the speaker on it, and at that level it is already plenty loud enough if I run that bitstream that puts MCLK on SDATA.
So, now we know that the only problem is with the format of the audio, not anything else.
Trying the four settings for SDATA_FMT, I2S standard and left-justified are both silent, although there is an audible pop between them, suggesting that they are interpreting the signals differently. Right-justified formats on the other hand, produce static. 16-bit right-justified is quite a bit louder than 24-bit right-justified.
Now if I enable the "LSB first" bit, the behaviour is different: Now the left-justified (I2S standard and true left-justified) make some sound, with true left-justified louder. The right-justified modes are now silent.
This makes me think that there is something funny with the interpretation of either the LSB/MSB-first and left/right justification interpretation. What would be really handy right now, would be to be able to see someone else's example waveform that they use to feed an SSM2518, as I am sure it must now be some stupid simple error.
Well, I guess the next step is to work out which part of the 32 BCLK counts in each sample that is being used. To test this, I produced a bitstream that moves a single bit through all the possible positions, and there was no noticeable difference in the sound. So now I am trying to vary the number of bits set in each encoded sample, to see if that makes any noticeable difference. Actually, there is some subtle difference in the background noise when the single set bit was at the start, but I can't make anything else of it.
Basically the chip seems to be behaving rather randomly. Which just reminded me: This board did get fried with 6.45V on VCC_FPGA early in its life, and it is possible that this chip might have got damaged in the process. In fact, it is quite possible. Okay. On that note, it is time to give up for the night, and try to replace the chip in the morning, as it will need the SMT reflow facilities at work to do (and someone who is skilled in driving them).
Okay, we replaced the chip, loaded Commando to test, and then started setting the SSM2518 I2C registers, and suddenly, the sounds of success!
Before I lose them, here are the register settings that have working sound:
It was a very pleasant and welcome suprise that everything started working once I had the chip replaced. I'm not sure what I would have tried, had it not worked. I'd put a picture of it working, but that doesn't really work for sound...
Now the main remaining problem is that if I make the volume too loud, the whole FPGA resets, presumably because the amplifier suddenly sucks too much current, and the VCC to the FPGA sags too low. I'll need to think of a way to confirm and fix that, if that is the problem. But for now, I am happy with the progress.
Edit: Here is a short video I made at home of it playing the music in Nebulus: