I started writing this post a few months back, and only got around to finishing it off now, as I tried to hook this test rig up, and realised I didn't have a good summary of the required connections anywhere.
The cellular modems for the MEGA65 phone are in the standard miniPCIe form-factor. Note that this does not mean that they use the miniPCIe signalling -- but rather that they use the connector and physical dimensions of miniPCIe.
By using this standard format, the MEGA65 can use a wide variety of cellular modems -- and can even be upgraded in the field if you want to go from 3G to 4G (or later, to 5G when it becomes available), or just switch cellular modems if you are in a country that uses a different frequency band or communications standard.
This also makes the interfacing much easier, and means that we can revise the PCB as we go along, without having to throw away expensive components like the cellular modem. This is also why the FPGA is on a socketed module (the TE0725 from Trenz.de).
So, to connect the cellular modem we need to supply it with power (the mechanism to cut power for airplane mode will come later), connect the UART serial interface, so that we can talk AT commands to it, and also the PCM or I2S audio interface. The Quectel EC25AU module we are using on the bench uses PCM instead of I2S audio, but the two are very similar in practice. The pins that matter are:
PCM_CLK - A 2MHz square wave clock.
PCM_SYNC - Pulses high for one clock tick every time a new sample is being clocked. This should be run at 8KHz, to match cellular audio standards.
PCM_DOUT - Clocks the data from the FPGA to the modem, starting with the MSB immediately following a PCM_SYNC pulse.
PCM_DIN - The same as PCM_DOUT, but in the opposite direction.
For simplicity on the bench, we are using a
nice little miniPCIe breakout board that is made for these modules, to the point where it even has a SIM card receptacle:
So, we need to know the pins that matter on the EC25AU:
11 - UART_RX = REFCLK- on miniPCIe
13 - UART_TX = REFCLK+ on miniPCIe
45 - PCM_CLK = reserved on miniPCIe
47 - PCM_DOUT = reserved on miniPCIe
49 - PCM_DIN = reserved on miniPCIe
51 - PCM_SYNC = reserved on miniPCIe
(Ground and VCC we will ignore for now)
The miniPCIe breakout board has a 24-pin connector, where we find:
1 - GND
5 - REFCLK- (UART RX on the EC25AU)
8 - REFCLK+ (UART TX on the EC25AU)
18 - Pin 45 = PCM_CLK19 - Pin 51 = PCM_SYNC
17 - Pin 49 = PCM_DIN
20 - Pin 47 = PCM_DOUT
So, it looks like everything we need is there.
But there is a wrinkle: The EC25AU says that all of these signals should be 1.8V, not 3.3V. I've made a level converter board to solve this, which didn't work. So I got some pre-made ones, that worked just fine. The result is a bit of a rat's nest:
The only problem is that the miniPCIe breakout board doesn't actually provide a 1.8V voltage anywhere convenient. This is because the miniPCIe standard used for these modules doesn't provide such a 1.8V reference. So I need to get it from somewhere else. This is also good to know before we get further with the PCB design, to know that we will need to supply our own 1.8V reference.
(Actually, we are supposed to be able to run FPGA IO pins at 1.8V, but only if all pins on a given IO bank are 1.8V. This isn't possible on the TE0725 boards, so we are resorting to the level conversion board. It isn't a big deal, but it is just another thing.)
Fortunately, the Nexys4 FPGA boards we are using have a 1.8V output we can use, but they don't populate the pin header by default, so I need to solder a header on, which is a bit annoying since it is now kind of embedded into the whole rat's nest of wires that form the MEGA65 phone bench prototype. So I'll try just sitting a make jumper lead in the hole-through, and hope it works. It is only used to power a 74LVC245 buffer IC, that takes the 3.3V side and makes it 1.8V, so if it drops out sometimes, it shouldn't be a big problem. That worked, but I did relent in the end, and populate the 1.8V header by soldering in a simple 2x1 0.1" header:
Voltage conversion in the other direction uses a voltage divider made from two resistors to create a ~2V level for the 3.3V side to read as a logical 1, and a diode to allow that to be pulled to ground when the 1.8V side pulls it low. It's not ideal, but it is what I could build using parts we had on-hand today.
From the Nexys4DDR board, we have to map out the pins we are using on the PMOD connectors for the modem UART and PCM audio interfaces. The PCM audio is on JD, and the uart is on JC:
JC1 - RX
JC2 - TX
JD7 - PCM_CLK
JD8 - PCM_SYNC
JD9 - PCM_DOUT
JD10 - PCM_DIN
So, to simplify all that, we need the following connections:
JC1 - miniPCIe header 8JC2 - miniPCIe header 5
JD7 - miniPCIe header 18 via level converter
JD8 - miniPCIe header 19 via level converter
JD9 - miniPCIe header 17 via level converter
JD10 - miniPCIe header 20 via level converter
GND - miniPCIe header 1
All wired up, it looks like this:
(But don't forget the level converters in between!)
So now the process is to validate that the level converter works, so that we don't let the magic smoke out the 4G module.
First step: 1.8V from my dodgy pin-in-hole to the 74LVC245. Check.
Second: Check output voltage on 74LVC245 outputs under both high and low conditions. Check.
Third: Check input voltage from diode lifter under both high and low conditions. Swings from about 2.3V to 0.6V from high to low, while staying 1.8V on the low-voltage side. That should be okay. In fact, it is a wonderful accident that it sits so closely to 1.8V, because the other end of the diode is sitting at about 2.25V, as the roughly 2/3 point between 0V and 3.3V using a resistor voltage divider. It seems that the diode I am using has a voltage drop of almost exactly 2.25V - 1.8V = 0.45V. This is a bit lower than the data sheet suggests (about 0.7V), although it is in the realm of germanium diodes (0.3V). However, this coincidence struck me as as suspicious, so I removed the 1.8V supply to the circuit, in case it was somehow finding its way over to the pin, without seeing any change. Thus for now, I will assume I managed to fluke matching the voltages exactly. The main benefit of this is that with matched voltages there will be no wasted power from the mis-match.
So, it seems that everything looks okay in terms of function.
Next step, make sure we can get the cellular modem talking PCM, and that it really does show up on the pins we expect.
The AT+QDAI command controls the audio path for this module:
AT+QDAI=<io>[,<mode>,<fsync>,<clock>[,<format>[,<sample>[,<num_slots>,<slot_mapping>]]]]
io=1 for digital PCM
mode=0 for master, i.e., where the modem generates the PCM_CLK and PCM_SYNC signals. We don't want this once we are hooked up, because the FPGA will generate these signals for both modem slots. But for initial testing, it will be nice to see the modem produce some PCM audio, so we will set it to 0.
fsync=0 for normal short-synchronisation
clock=4 for 2MHz
format=0 for 16-bit linear samples
sample=0 for 8K (or 1 for 16K)
We will ignore num_slots and slot_mapping for now.
So the command we want is:
AT+QDAI=1,0,4,0,0
In theory, before running this, we should see no PCM audio signals from the module, but after, we should see clock and sync, and indeed, the audio data.
Hooking the oscilloscope up, I am indeed seeing nothing before running the command. After running the command, I can see the PCM sync and clock pulses. I can also see what I think is the audio from the phone call, except that it doesn't seem to react at all when I talk into the phone at the other end of the call.
I did try hooking the audio lines together, so that it would try to play exactly what it heard back over the line, but I still hear nothing in the phone call. Bit it might be that the echo cancellation detects this perfect image of the audio and eliminates it, thus leaving the call silent.
But back to basics, let's confirm that the pins on the breakout do what we expect:
19 = PCM_SYNC - confirmed. Pin JD8 on FPGA board.
18 = PCM_CLK - confirmed. Pin JD7 on FPGA board.
17 = PCM_OUT (from modem). Pin JD9 on FPGA board.
20 = PCM_IN (to modem). Pin JD10 on FPGA board.
The UART for talking to the modem is JC3,4, connecting to pins 8 and 5 respectively on the modem.
Super. So I tried a little trick, and connected PCM_OUT to PCM_IN, so that the modem would basically act as a loop-back of the caller's audio. This works quite nicely, and you get to discover just how much lag the mobile network introduces! Close to half a second in these tests.
So the one thing I had to do to make this work, was to disable in-call mute with at+cmut=0, as the call was muted by default.
Well, now it looks like everything is ready to hook up the audio path all the way through.
Hooking everything up, I noticed that the PCM_CLK line was being dampened. After a bit of poking around, I discovered this is because the modem still thinks it is in PCM master mode, i.e., it is trying to generate the clock and sync pulses. This is confusing, because I have told it explicitly via AT+QDAI=1,1,0,4,0 to become a PCM audio slave.
So, some quick changes to the VHDL to allow it to take the PCM clock and sync from the external source, and building a new level converter with three inputs on the FPGA side (PCM_IN, PCM_CLK and PCM_SYNC), and a lot of pulling my hair out at random problems with the test setup, and I started getting sound out the phone that was related to what the MEGA65 was producing.
One of the weird things is that the sound output from the modem, i.e., for sound coming from the caller, stopped working all of a sudden. My new level converter is floating to about 2V, and I might have applied some slightly higher voltages to the PCM lines while trying to figure out what was going on, so it is possible I have fried it. I'm hoping not.
Also there was a lot of fiddling with the audio mixer gain controls in the MEGA65 to get it sounding sensible on the output side. Initially the audio was over-driving and distorting. Then it was too quiet. I might need to adjust the scale on the master volume control to allow scaling up quieter inputs more, so that the distortion can be avoided with smaller coefficients in the mixing stage, but still end out loud enough on the output. This might even need to be different for the different audio output types.
To produce sound from the MEGA65 for testing in call, I loaded Impossible Mission and used both
the music from the crack intro, as well as the sounds in the game
itself. Hence, while the first words on a telephone were "come here
Watson", the first words communicated over the MEGA65 phone were,
"Another visitor. Stay a while. Stay forever!" This seems quite fitting to me.
After all that, I realised I hadn't read the documentation for
the modem closely enough: The AT+QDAI command only takes effect when
the modem is reset. Grrrr.. So back to the first level converter board
for me. Now I am having trouble getting the modem to even talk on its serial line to me. The UART TX line on the modem seems to cycle between 0 and 3.3v every few seconds. Now I am really hoping I haven't fried it. After a bit of fiddling, it looks like the USB power lead I was using was not connected properly. It is one of those that has two heads for supplying more current, and it is a bit funny which one you plug in, if you only plug in one of them.
After switching that, the modem looks like it boots again, but I am still not getting any serial output from it. Well, the oscilloscope says that it is indeed producing serial output again, but I am not seeing it on the FPGA. After a bit of fiddling, I have the signal on the FPGA pin again, but still not communicating with my little terminal program. Restart FPGA in desperation. Now it is there again. Okay, now I am finally back at the starting line, to try using the modem as a PCM slave.
I can now see the audio in both directions again, which is very nice :)
However, for some reason, the audio isn't synchronised to the sync pulses I am giving the modem. This manifests itself in the audio coming out being quite garbled, because the modem is sampling at random intervals. I can also see the audio from the modem is quite unsynchronised with the sync pulses, which is also very wrong.
A little more careful reading of the PCM specification for this module reveals that the bit order is opposite to in I2S, i.e., the least significant bit should come first. That probably explains the garbled audio output. So, I'll make that change and resynthesise.
However, the bit order doesn't really explain why the incoming audio samples were not synchronised to the sync pulse, but seemed to be arriving at any old time they liked. It is possible that they are still being clocked out from the module's internal 2MHz clock. I'll have to examine the relative timing of those a little more to figure out what is going on. If they are clocked from the internal clock, I might be able to do something clever to infer and latch the clock.
Hmm... Now trying another call, I can see the audio going from the FPGA to the modem, but I can't hear anything at all from the phone. Yet the PCM_CLK and PCM_SYNC lines are still being fed correctly. Also, I am now not seeing the audio from the modem. Most weird. And rather frustrating that it doesn't behave the same every time.
Synthesis with bit order reversed has now completed, so tried that. Now I have audio in the call (in both directions again), but still not at all synchronised.
Also, I can see that the audio samples from the modem seem to be at 4KHz, but that could be the bottom bit alternating.
However, I can also see that the sync pulses I am generating are occuring every ~60usec, which is 16.7KHz, not 8KHz, which might be why the modem is refusing to synchronise correctly. Although it does claim to be able to operate at 16KHz. Anyway, it makes sense for me to fix the SYNC clock rate in the FPGA, so that we are not obviously out of spec. Looking at things closer, I can see that my sync pulses are also only half the width that they should be -- which might well explain why they only get detected some of the time by the modem, and also why the sample rate is double what it should be.
Why it is 16.7KHz instead of 16KHz, I am not sure. This could be the clock frequency on the FPGA being a bit out, but ~4% sounds like quite a lot. Ah, it could be that my clock divider is to blame here: It flips the clock every 25MHz/2MHz = 12.5 clock cycles, but the counter is an integer, thus it will flip every 12 cycles. 12.5/12 x 16KHz = 16.667KHz -- exactly what I am seeing on the oscilliscope. So mystery solved there. The solution is to allow for not exactly 50% duty cycle on the sample clock, so that the overall timing can be preserved.
Okay, so now the clock and sync pulses are correct as required in the documentation. However, when I make a call, I am still seeing rather strange effects.
This strangest thing is that there was no audio at all in the call until around 7 minutes -- at which point, suddenly I started getting audio in both directions, where as previously there was none. The audio started as I was probing various things with the oscilloscope, which might suggest that there is still some subtle problem with the signalling I am producing. However, what that problem might be, I am at a loss to speculate.
The problem with the lack of synchronisation of the audio coming from the modem to the sync pulses I am providing continues, and the apparent sample rate continues to be 4KHz, instead of 8KHz. As an experiment, I am now trying to make my PCM transceiver synchronise to the leading edge of the incoming audio sample. If that works, then I will have some reasonable evidence that the EC25AU is being naughty when in PCM slave mode. Unfortunately that didn't work. I have contacted Quectel to see if they have a solution for this, or indeed, if I am doing something incorrectly.
In the meantime, I am returning my focus to using the modem as PCM master, and making the FPGA be the slave. My previous level converter had a number of problems, so I gave in and bought a couple of two-line bidirectional level contverters from Jaycar, and after a number of false-starts and general jiggery-pokery to workout what is going on, and get it all together, I think I now have it together.
However, when I tried to power it up and make a call, I noticed that the sync pulse on the FPGA side was <1.8V, instead of 3.3V. A bit of exploring revealed that I was accidentally driving that FPGA line low, as I hadn't correctly switched things around for the FPGA to be PCM slave. The fix for that is now synthesising, and hopefully it will get things going.
After a bit more fiddling, I realised that the PCM audio feed is 16-bit signed, not unsigned. While this isn't such a problem for the output, it is a problem for the audio from the modem, as the sample values frequently cross the zero line. So, another quick synthesis run to sort that out. Discovering that this was a problem was a bit complicated by the fact that the modem mixes some of the outbound audio onto the inbound audio to provide "side tone", so that the user of a phone doesn't think that the line is dead.
Finally, after all of this mucking about, I managed to get the audio path working in both directions. The process was longer than I hoped, and yet, looking back, it took less than two weeks to do, which is not an unreasonable amount of time for a first attempt at creating the complete audio path for a mobile phone.