Saturday 13 April 2024

Controlling a EC25-AU cellular modem using a MEGA65

I'm hoping to get some more work done on the handheld "MEGAphone" version of the MEGA65 this year, as well as supporting Wi-Fi and cellular modems on the expansion board bridge.  So it's time to stick my head back into the fun of cellular modems.

I'm using a Quectel evaluation board, because I have it laying around, and because it has all the bits on it already for connecting to a headset/handset, and later, to the PCM digital audio interface of a MEGA65.

First challenge was to power it. Digging around, it turns out it can be powered via the micro USB port on the board, and sliding the power switch to the ON position.  It won't then actually power up until you press the power button. It is possible to hard-wire the PWRKEY jumper next to that switch to make it turn on automatically when power is applied. I should fit that jumper.

For the MEGA65, I don't want to communicate over the USB port, but using the raw UART interface. This is on J806.  I was trying to use a faulty FTDI cable earlier, which caused me some weird problems. Before I realised it was the FTDI cable at fault, I resorted to isolating the UART from the USB interface by removing R407 and R411.  But once I was using a good USB UART adapter, I was able to send and receive AT commands.

The EC25-AU module defaults to echo off, so I normally set ATE1.

The evaluation board has a 4-pin RJ jack that it turns out can take a normal telephone handset, which is super handy! I had a couple in the shed, one of which works and the other of which has a dead speaker it seems, so good thing that I got them both out, or I would have been pulling out my hair!  

But initially neither worked, because the PCM audio master mode on the EC25-AU wasn't working. After poking about for a bit, I had the vague recollection from the earlier prototypes that the original firmware of the EC25-AU had a problem with this -- only PCM slave mode would work.  But I figured that between 2017 and now that there was probably some new firmware out there that fixed the problem.

Getting firmware updates for these modems is a bit weird: Quectel for Reasons Unknown To Any Mortal don't have a public repository with the various firmware versions for their modems. Instead, you have to join their forum and ask randomly for it somewhere, and then someone will send it to you via a direct message. All quite weird.

Anyway, then I had to figure out how to apply the update.  There is a libqmi-utils package that has a tool that should be able to do it, but I don't know how to drive it. Instead, I eventually found that Quectel have made a utility for Linux for doing this:

https://github.com/nippynetworks/qfirehose/blob/main/qfirehose.c

To use this, you have to drop the modem into "emergency boot mode". For the eval board, you do this by shorting the USB_BOOT pin to 1.8V which means jumpering pins 7 and 9 of J805 together and then giving it power. The jumper can then be removed.

Then after unzipping the firmware update zip file, you use a command like this:

sudo ./qfirehose/QFirehose -f directory-where-you-extracted-the-firmware

It takes a few minutes, and then the modem automatically reboots.

With that working, I then had a working audio interface. It was a bit quiet in the earpiece of the telephone handset, so I used AT+QRXGAIN=65535 to maximise the volume. It's not super loud, but loud enough for what I need for now.  Once the MEGA65 is driving the audio path directly, we will be able to make it much louder if we want.

A few other handy AT commands to make life easier:

AT+QURCCFG="urcport","all" -- This puts the RING, NO CARRIER, CONNECTED messages onto all serial ports of the modem, ensuring we see them.

AT+CRC=1 -- use more verbose status messages, e.g., +CRING: VOICE instead of just RING.

AT+CLCC -- Reports on current call state, including data connections. This allows a state rather than event based management of calls, which is nice for warm-starting telephony software.

AT+CPAS -- Reports if there is an active call, an incoming call, or no calls, again for state rather than event-based call management.

So I think that's probably everything I need for now to allow the MEGA65 to make, receive and indicate call status.  For now, I'll connect to the modem UART using the UART break-out header I put on the expansion board connector board, and just hang it out the back of the MEGA65. 

Next steps will be making sure the UART is plumbed through the MEGA65 to the expansion board, and also to add support for number pads via the Grove I2C port, so that I can add a dial-pad. Then it's time to make some more telephony software!

I'm going to use the Nexys4 non-DDR board as the host for this, since I have one laying around, and the design currently doesn't use PMOD A, which means I have 8 IOs available for this.  PCM audio interface will need 4 pins, I2C for keypads etc will need 2, and then the last 2 will be needed for the UART to the 4G cellular modem. I'll track this with Issue #798.

The easy part is connecting the cellular modem to buffered UART #0. 

But first I have to get the nexys4 target building again, as it's got a bit stale. Fortunately I only had to add the missing NEO True Random Number Generator (TRNG) to get it to start synthesising. I'm not sure if the target will try to have too much stuff and over-fill the A100T FPGA.  I guess I'll find out in about 10 minutes when it finishes. 

Yup -- synthesised just fine. So let's try it out, and see if this old board still works. Yes -- it has started fine. It doesn't have an SD card installed, so it has just dropped to the OpenROM built into the bitstream. But that's fine for this, as I can write and compile a simple C program to talk to the buffered UART that I'll connect to the cellular modem.

Here is the body of my little test program that verifies that the buffered UART is plumbed in:

  // Select Buffered UART 0
  POKE(0xD0E0,0x00);


  // Set UART to 115200.
  // We do this by providing the divisor for 40.5MHz
  // 40.5MHz / 115200 = 351 = $00015F
  POKE(0xD0E4,0x5f); POKE(0xD0E5,0x01); POKE(0xD0E6,0x00);

  // Send HI on the UART
  POKE(0xD0E3,0x48);
  POKE(0xD0E3,0x49);
  POKE(0xD0E3,0x0d);
  POKE(0xD0E3,0x0a);
 
  while(1) {
    // Wait for char to be RXd
    while(PEEK(0xD0E1) & 0x40) continue;
    printf("%c",PEEK(0xD0E2));
    POKE(0xD0E2,0); // acknowledge reception of byte
  }


This is really a nice little UART facility that I built a while back -- the interface is as simple as you can see above. It has 8 UARTs, each with 256 bytes each of RX and TX buffer, so it's really convenient to work with, compared with the UART chips that were available for the C64 back in the day.

Anyway, with that working, I can start writing some actual cellular modem control code.  It's just going to be simple for now, so will fit in a single program. I'll start with some routines to send and receive lines of text from the modem, and keep track of the state that the modem is in.

This is more or less working, and the modem gets detected, and I send a configuration command, but then the modem seems to stop responding. But I don't know if that's actually the case, or if it is the buffered UART stops receiving. Bridging the UART pins seems to indicate that the UART is still working properly, so I'm assuming that it must be the cellular modem stopping sending responses for some reason. Maybe because I am sending too many commands? My periodic ticker is based on 1Hz, which at 115200bps should be totally fine.

It looks like the modem responds to the first command I send, and then nothing after.  Which doesn't make much sense, since when I run the program again without powering off the MEGA65 it works again to send a single command.

Am I doing something weird like acknowledging the reading of one char too many? Doesn't look like it.  Using the oscilloscope, I have confirmed that it is the modem that stops sending.  So what is going wrong? We should be sending ATI + a carriage return endlessly.  If I loop the UART pins, that's what I see being returned. 

On the oscilloscope it looks like the middle character of the "ATI" string I am sending is wrong. It should be $41 $54 $49. But it looks like $41 $2B $49 is sent. Why $2B ? The bit pattern for T = $54 should be 01010100. The bits I am seeing look like 00101011. Is there an extra bit being smashed in there somehow?

Actually, no, there is something else going wrong with the buffered UART. The total bit sequence looks like:

1010000011100101011101001001...

We expect to see $41 = 01000001, $54 = 01010100, $49 = 01001001. Let's try to line those up:

10100000111000101011101001001

So maybe the $54 is being sent as $15 instead?  That would be shifted right one bit. It's possible that my hand-reading the oscilloscope display is a bit wonky, though, and that it's fine. But this probably says that I should make a quick VHDL test case sending this sequence like this, and see if it looks ok, or if it has a bit shift in it like this.

Nope -- the problem was much simpler: CC65 converting ASCII to PETSCII causing commands in capitals to fail.

Okay, so I am happy that the UART is working now. Let's tackle the I2C interface to the keypads next. But first a brief detour as I plan ahead...

For the first prototype I am using an off-the-shelf EC25 evaluation board. This is great for development, but they are relatively expensive, around AU$600. Once I have this first unit built up, it will be deployed at my Mum's place (she wants a "landline" phone that "just works", rather than the rubbish ones on the market right now).  So I will need more hardware for ongoing development. The EC25-AU 4G modems themselves are quite cheap, under AU$100 (about US$65), and I already have a spare one.  Theoretically I also have a little mPCIe breakout board that I got at the time, but I can't for the life of me find where I put it. So I've looked to see what other options there are for this.

The Osmocom project designed such a board (and it might even be what I bought before, but can't remember).  Anyway, there is a German company who makes a variant of this: https://shop.sysmocom.de/miniPCIe-WWAN-Modem-USB-break-out-board-v3-kit/mpcie-breakout-v3-kit.  So I'll probably order one of those as soon as I deploy this initial prototype to my Mum's place. Anyway, back to the I2C stuff now...

The MEGA65 supports custom I2C device sets for each target, so I will use that facility in iomapper.vhdl to add a new device page for the nexys4 board that supports having up to 8 I2C IO expanders, and just makes all their registers visible in memory.  I can later on make them automatically simulate the MEGA65 keyboard if I want, but initially I can just probe them to detect key presses, and also to control the LEDs for each key. I'm also not going to worry about hardware support for making the bi-colour LEDs do yellow by quickly toggling the polarity of the IO expander pins.  Just red, green or off will be fine for now.

Okay, so let's dig in and prepare the I2C device page. I did write a tool ages ago to automatically generate a lot of the boiler plate for this. I've used that to populate the I2C state machine, and setup the I2C device page.  This synthesises now, but I'm not seeing any traffic on the I2C pins I have assigned. I'm not quite sure why, as I have configured iomapper.vhdl to synthesise the keypad_i2c.vhdl file that I have added for this, and I can see that it gets synthesised by Vivado.  Might be time for simulation to see what is going on...

Simulation is running, but of course is quite slow. So I've also added an extra debug register to the new I2C device page so that I can see for sure if it is being mapped or not. I'm not currently seeing that register responding, so I should probably debug if the accesses to the register page is working.

Found one stupid error that was preventing me from reading from the I2C register page: I had plumbed all the bits and pieces for it into the fast IO interface bus, except for the read data lines :) Now I can read the registers in simulation.  So I'll resynthesise that -- and indeed that fixes that problem -- I can now see the I2C register page in memory at $FFD75xx. It doesn't explain why the I2C lines are not being driven, though.

For the I2C lines, I do have some debug infrastructure I can add that allows manual control of those lines from the I2C register page. I should probably add that back in. That'll need another resynthesis, which I am doing now. While that was running I traced the plumbing through and found where I hadn't connected the I2C lines in machine.vhdl, so that's almost certainly the reason for that problem! Now resynthesising...

Now I can read the I2C registers from the connected board :) There is a wrinkle though, which I had forgotten about with these I2C IO expanders: They read the same 2 addresses repeatedly, so to read all 8 registers I have to schedule 4 separate reads. This is actually a feature of these IO expanders, as it reduces the latency if you are scanning the input lines on a single IO expander, as you can basically just read them continuously, without having to reschedule a new I2C transaction each time. But we want to be able to read multiple IO expanders, and also check the other registers.  So I have rejigged this and am resynthesising.

While that runs I can actually test output, though. This is because while I can't read the registers, I should still be able to write to them.

To do this, I need to write to registers $06 and $07 to set the direction. Zero bits mean output (opposite to on a C64 CIA). Then I can set the output values via registers 2 and 3.  With that I should be able to control the LED I have hooked up on the test board. Because I connected both pins of the LED to output pins I can reverse the polarity of the LED under software control, and thus use a bi-colour LED. And that all works: I can turn the LED on and off :)

The only problem I am seeing now is that when I set any of the other registers, I stop being able to read the input registers that the keys are connected to, and instead end up reading from the register I last wrote to -- but this is with the bitstream from before breaking the I2C transactions down into the register pairs, so it might magically sort out when that is done.

Okay, that bitstream has finished synthesising now, and now I can see all 8 registers, which is good. I can still read the key switches as well. But now it seems that I can't write to the I2C registers. I think I know the cause of that, though: I messed up the state machine number for the writing phase, leaving an undefined state just before it, which causes the state machine to reset.  Resynthesising that now.

While that is running, I am reminded of an issue I had with these I2C IO expanders where funny things happen if you do back-to-back reads and writes. But that's not an issue until I have at least fixed the register writing, which is still broken. I have added a debug register that will let me check if the writes are actually occurring.

... and the writes aren't occurring. My debug register confirms that they aren't being performed.  Going through the state machine management logic for this, I think I might have found the problem: The state machine is extended by a couple of states if there is a write pending, and there were some out-by-one errors in that from when I changed the number of states for the I2C expanders. Let's see if it fixes it. Nope. Writes still seem to not be occurring. Might be time to simulate it. But first I need some more sleep.

I've started writing a VHDL unit test for the keypad_i2c.vhdl, and is often the case, in the process of doing so found at least part of the problem: I was never asserting write_job_pending. I've added in the missing line to set that when we try to write to a register, but that hasn't solved the problem -- so some further investigation is required. But I can now do explorations in about 20 seconds instead of 10+ minutes :)

Much of the problem was the test wasn't allowing time for the I2C write to occur, as this only happens once per pass of all I2C devices. But there is still something really messed up going on, as the test harness reveals that the I2C IO expender thinks that the read address is being written to.  It could be that my simulation of the I2C IO expander is flaky, or it could be that my I2C transaction generator has a bug in it.  I can test the latter by exporting a waveform file of the test run and trying to interpret it as an I2C transaction.

In theory, the sigrok protocol analyser software for Linux should let me do this. To be honest, I have found it a completely frustrating experience trying to get sigrok to actually build and run stably. Maybe the new snap packaged version of it will make this easier? Nope, it still segfaults.

Well, in the meantime the bitstream has built, and it works, so the problem is in my simulation of the IO expander.  That is, I can read the key switches and control the LEDs, so that's all I need for now.

Anyway, now that I have the ability to interface with the I2C keypads, let's get back to implementing the phone control stuff.

I want to implement checking of the cellular signal strength etc, and start updating the display with relevant information. This means I need to probe the modem regularly with a set of AT commands that will return the information that I am looking for. One command has to finish before the next can be sent, so I have to watch for the OK responses, and wait until then, or until a timeout, so that it can't get deadlocked.

I should probably also keep an eye on whether the modem sends anything over a period of time, and if not, then assume the modem has crashed, gone missing or otherwise not responding so that this can be indicated on the display.

I've implemented that, as well as retrieving the time and date from the cellular network, and computing the day of week from that.  So the display is now very uncomplicated, but provides some key information:

Compare this with a typical Android phone or "senior phone" display that clogs the display up with stuff that the person (in this case, my Mum) doesn't want, and then half the time covers it all with inane dialogue messages. This in contrast, just always shows you the key information.

Now to get the missing LEDs and resistors I need to populate the rest of the keypads. I also need more key switches. It looks I got lucky and bought the last bulk kit of 400 TTC Golden Brown switches from MechStock in Australia for 23 cents a switch, including postage!  The good thing about a phone is that you don't need 100 million perfect key presses per phone, unless you really like to play IVR RPGs endlessly.

Anyway... off to go and get those LEDs so that I can start making the context-sensitive illumination of the keys to indicate which are possible at any point in time.

LEDs acquired, and the key pad assembled, and code implemented to write to the I2C IO expander to control the LEDs. That all seems like it should do the right thing, but it's behaving oddly. Digging around, I have found that the IO expanders are writing to two registers at a time, instead of just one. This means that the data direction lines for the LEDs isn't being set correctly. Also it means that if an LED has its control wires on the two different ports of the IO expander that it can't be correctly controlled.

This probably means its time to go back to the simulation and see if I can visualise the I2C transaction to see what I am doing wrong.  In theory it shouldn't be too hard to find and fix -- especially since I have managed to reproduce the bug in simulation of the VHDL :)

It looks like no I2C STOP signal is ever generated, which is a bit weird. Not even when reading registers. Actually, a STOP does get generated at the start of the write, but nowhere else. We should generate a STOP at the end of the write, and that should fix everything up. We do already put a delay in that should cause the STOP to be emitted, but for some reason the i2c_command_en line pulses low only very briefly, and is then reasserted when it should be low due to the delay period. 

The problem turned out to be the lack of an extra dummy state at the end of the state machine to force the I2C transaction to emit a STOP at the end. The tests under simulation now pass, so time to resynthesise.

While that happens I can work on implementing scanning the keys on the I2C devices, as that is not affected by this bug.  I should then be able to tie one to pick up or hang up a call.

The I2C write fix seems to have worked. But something is going funny in my key scanner: It seems to alternate between reading the correct register and reading the adjacent register. I can't see why this would be. I also can't see it happening in the serial monitor when I repeatedly read the I2C registers. It happens many times per second in the code, though.

Looking a bit more, it looks like the problem might be a common problem I have seen with these IO expanders before: Sometimes they read $FF instead of the correct register values. Perhaps I am clocking something a bit fast somewhere or something. I should look into it a bit more at some point. But for now, I can just enforce de-bouncing of the keys -- reading the I2C register 3 times in a row seems to be enough to force clean de-bouncing. It probably needs 3 not 2, so that it takes long enough for another scan of the I2C bus to occur in between.

Now it's back to the LEDs that are still doing weird things. Since controlling the LEDs also uses reading and writing I2C registers, I have to debounce those as well. That has things slightly better, but still things are messed up.

The latency of the I2C controller is proving a bit annoying, because when we want to modify two bits in a single byte, as is required for switching the LEDs (we have a pin for the anode and one for the cathode, so that we can use bi-colour red/green LEDs), if we do it as two writes the 2nd will mess up, because it works out the bit mask based on the previously cached value.  E.g., if we had $42 in the register and decide we need to set bits 0 and 3 we will read $42, set bit 0 = $43, and write that back, but when we go to set bit 3 we will most likely read $42 still, rather than $43, unless we wait long enough for it to propagate.

With that fixed, it's still doing something odd: Sometimes after writing to the output registers for the LEDs, it reads back the write ports instead of the read ports, i.e., registers 2 and 3 appear imaged (and byte swapped) at registers 0 and 1.  This makes me think that it has something to do with the lack of detection of the I2C STOP signal by the IO expander. So I guess I really do need to look into that and figure it out. This probably can only happen if the write is to the address of the first I2C device in the read loop. 

By adding a 50ms delay after each I2C write I can work-around this problem. This works because the I2C bus will read multiple times after the write, and thus get back to normal.  But I should still fix it at the root cause, if nothing else so that I don't need to add big delays into what is supposed to be real-time code.  The simplest approach here is to add a dummy read of a non-existent I2C device after the write slot, so that the first IO expander doesn't stay under attention.  

That solved that problem nicely.

Now I am working more on the call handling, and making the pick up and hang up buttons light up green and red when they are able to be pressed, along with other buttons for quick-dial also lighting up when configured and able to be pressed (i.e., not already in a call).  This is all a bit fiddly to get the logic right, but I'm progressing, and can answer and hang up calls, although answering sometimes fails because the modem hangs up if you try to enter any command after ATA before it has confirmed the connection or the caller has aborted.

I think to debug that and the remaining LED misbehaviour by implementing a log of activity on the serial monitor interface.  For this I'll use the Hypervisor's ability to write to the serial monitor port with this routine from mega65-libc:

unsigned char the_char;
void debug_msg(char *m)
{
  // Write debug message to serial monitor
  while(*m) {
    the_char=*m;
    __asm__ ("LDA %v",the_char);
    __asm__ ("STA $D643");
    __asm__ ("NOP");
    m++;
  }
  __asm__ ("LDA #$0d");
    __asm__ ("STA $D643");
    __asm__ ("NOP");
  __asm__ ("LDA #$0a");
    __asm__ ("STA $D643");
    __asm__ ("NOP");
}

This has already let me confirm that the LED glitches were not from being commanded to do the wrong thing during the incoming call state (state 3):

=STATE 3
>MODEM AT+CSQ
>LED : LED#15 = 2
>LED : LED#14 = 0
<MODEM : OK
>MODEM AT+QSPN
<MODEM : +CLCC: 1,1,0,1,0,"",128
<MODEM : +CLCC: 2,1,4,0,0,"0427679796",
<MODEM : +CSQ: 25,99
<MODEM : +QSPN: "Boost","Boost","BOOST",0,"50501"
<MODEM : OK
>MODEM AT+QLTS=2
<MODEM : +QLTS: "2024/04/07,10:07:23+38,0"
<MODEM : +CLCC: 1,1,0,1,0,"",128
<MODEM : +CLCC: 2,1,4,0,0,"04276

Led #15 (pick up) is the one we expected to switch to green (value 2), and it is being commanded to do so.  LED #14 (hang up) is expected to be turned off, which it is. This points to problems with the latency of an I2C write to show up when reading the I2C registers to apply the mask for setting the next LED. To confirm this and solve the problem I implemented a cache of the I2C register values, which has completely solved that problem. So I think that was almost certainly the problem.

Next bug is that the logic that waits for an OK to be sent gets out of sync sometimes. This causes a variety of problems, but the one that caught my eye is that when there is an incoming call, the call state sometimes drops back from INCOMING CALL to IDLE, because the AT+CLCC command's response hasn't been observed. We can see this in the log below:

>MODM AT+QSPN
<MODM : +CSQ: 24,99
<MODM : OK
>MODM AT+QLTS=2
=STAT 3
>MODM AT+CLCC
>LD : LD#15 = 2
>LD : LD#14 = 0
<MODM : +QSPN: "Boost","Boost","BOOST",0,"50501"
<MODM : OK
...
=STAT 2

We can see that the underlined AT+QSPN command doesn't get its response immediately, but rather we see the response to some previous commands. Likewise when we send the AT+CLCC command (bold), my code expects to get a list of calls followed by OK. But it sees the OK without seeing any calls, so thinks there aren't any, and drops out of the incoming call mode.

I'm suspecting that this is responsible for several other strange effects, including responses from some commands being cut short, because another command has already been dispatched, and the EC25-AU doesn't seem to buffer the output of previous commands to flush them through.

The question is how the communications gets out of sync in the first place. It happens before a call even occurs. Found and fixed: In the periodic state management function, it would try to send the next AT command, without first checking if we were still waiting for an OK from the previous.

A similar thing happens when answering a call if the ATA is sent in the middle of the +CLCC response block. So I probably need to schedule the ATA, instead of sending it immediately. I'll have to do the same for hanging up via AT+CHUP (ATH is recommended only for terminating data calls on the EC25-AU, apparently).

Generally, I'm working through the state machine logic for call handling still, as it seems that when I plug one problem, another remains, or perhaps is caused by that fix.  What seems like a deceptively simple state model for call handling always has a lot of subtlety to it.

Some of this comes from some annoying features of the modem. For example, the ATA command reports NO CARRIER if the call fails, and CONNECT if a data call connects, but it just says OK if a voice call connects, which creates ambiguity with the responses from other commands. 

Anyway, I have the call answering logic now working, and the LEDs doing what they should. Next step is getting the quick dial functionality working. It tries to dial, and seems to send the modem the complete ATDT command with the number, but the modem only echos back the first few digits of the number, rather than the complete number, suggesting that it doesn't receive it all.  The modem also fails to establish the call, which reinforces this possibility.

ATDT can take 5 seconds to complete dialling, during which time no other AT commands should be sent to the modem, as otherwise the call may be terminated. Or at least that seems to be what happens. 

But even with that, I'm not convinced that it's working properly to dial. I am using a pre-paid SIM card, but it should be expired until tomorrow. Unfortunately Boost Mobile don't have a way to check balance via SMS, and I don't have an account setup for it. I'd actually like an SMS-based method, because then I could make the phone periodically query to see if it is running low on credit or not, and provide a visual indication.

I think I found the problem with dialling -- I was using ATDT, which for old land line modems indicated tone instead of pulse dialling. But I don't think the EC25-AU supports that. You just have to use ATD only.  Another problem was that I wasn't waiting for the previous command to complete before sending the ATD command.

With that fixed, and some other minor bits and pieces, it now fairly reliably places calls. Sometimes it does still fail, but it does recover from that, and go back to the idle state. I still want to figure out why, and fix the problem, though.

I've also made the behaviour of the LEDs simpler and more uniform for a user to know what options are available at any point in time. I've also made the background of the screen change colour to indicate state, so that it's easy to work out what state the phone is in: Black is default, so that it doesn't cast too much stray light, especially if you used an OLED monitor.  Blue in calls, grey when trying to get the modem configured, orange during call transitions, such as calling or answering.  I'm sure the design language will evolve, but its already a pretty simple and understandable interface.

Next up I've implemented manual dialling and sending DTMF tones during a call. DTMF in a call isn't perfect, because the play back in the local handset and sending via the network to the remote device are serialised, and there is a limit to how fast it can happen. To keep things simple for now, I turn off the number pad LEDs while the DTMF is in progress, so that the user knows that they can't dial another digit yet.  Later when the audio path is via the FPGA it will be possible to play the DTMF locally simultaneously while sending. But we will still need to allow queuing up multiple DTMF characters to be sent as a single command to allow rapid DTMF dialling. But what we have now works. Manually dialling a number and hitting the "pick up/call" button also works now.

While still disconbobulated in multiple pieces, it's probably now possible to guess that it might be supposed to be a phone :)


This also confirms that both PCBs function correctly -- no errata that needs fixing, which is a pleasant change from many of the PCBs I develop.  Admitedly the design of these keyboard PCBs is really very simple, and leverages the past work I did on a similar alternate design for the full keyboard of the MEGA65.

So all that's left for absolute core functionality of a telephone is for it to be able to ring. The FPGA board I am using has a 3.5mm audio output that the MEGA65 core already supports, so in principle I can just play it out that.  I've found an attribution licensed recording of the right vintage of Australian telephone that I want it to sound like: https://freesound.org/people/petaj/sounds/28353/. It really does need 44KHz to sound nice at 8 bit, and once complete ring phase is ~3 seconds. So this means about 128KB of RAM for the sample. That's too big to compile into a C64-mode memory context program (which is how I am writing this for now).  So I'll have to use the mega65-libc functions to ask the hypervisor to load the file into higher RAM banks from the FAT32 file system on the SD card. That would load fastest. Or I could read it from a D81 image, but that would be slower and fiddlier.

Hmm... another problem for now is that the audio DMA facility on the MEGA65 only supports samples upto 64KB. Maybe I will need to trim it to ~22KHz for now. I do plan to implement direct audio streaming from SD card, which will totally solve this problem, and allow me to have a single very long ring tone, instead of repeating the same 3 seconds of audio. But that's not for just yet.

The main impediment right now is that I can't find a microSD card to put in this FPGA board. I know I have a bunch of them lurking around. It's just that I don't know where I put them. I need to organise one for this, anyway, so that I can have the program on it to be loaded by the MEGA65 ROM on boot, anyway. So that's probably the next step. I'll pick one up tomorrow.

What I did do tonight, though, is dig up the code for asking the hypervisor to load files from the FAT32 filesystem of the SDcard. This provides me with the handy function read_file_fromsdcard(char *filename, uint32_t load_address), which will make it trivial for me to load the ringer sample into memory once I put it on that SD card.

Micro SD card acquired, inserted, formatted using the MEGA65. Also flashed the bitstream to the QSPI flash on the Nexys4 board, and copied on a MEGA65 ROM and the other files needed for the MEGA65 to boot. So now the Nexys4 board boots to BASIC65. Configured it to mount TELEFONE.D81 as the defailt disk image. Also made Makefile rules to build TELEFONE.D81 and push it to the Nexys4 board.

The result is I now have the hardware booting automatically and running the telephone program automatically as well.

So now I can push that WAV file to the SD card, and hook in the code to load it into an upper bank of RAM, probably BANK 4 at $40000. And it all works, except that it sounds like rubbish. I'm not quite sure why, yet.  The test sine wave tone in the MEGA65's audio DMA system plays nicely without static. But the RINGER.WAV file I have created is really bad.  Yet it plays cleanly in VLC.

Found a few problems: I had signed/unsigned wrong, hadn't normalised the sample to maximum volume, and generally had some of the audio DMA registers messed up. It now plays properly. The Nexys4 audio output is still a bit hissy when playing audio, so I may need to add a PMOD audio codec to get the SNR good enough that the hiss isn't annoying when the phone rings loudly.

But this means we have a working ringer -- which completes the bare minimum functionality required for a telephone: We can make and receive calls, including with DTMF tones to navigate voice menus, and the phone rings when there is an incoming call! 

It would be nice to have a ring tone in your ear while waiting for a call to get answered, but that will require me to route the cellular audio through the FPGA, which I will do, but not just yet. I could actually do that by playing it through the ringer speaker at a reduced volume to simulate it. That's probably a good idea.

It's probably time for me to make up a box to fit all this stuff in a hopefully nice looking box. I have a cunning plan for how to make that box look nice, without spending a lot of money. But first, I need to assemble the other four 4-way key pad PCBs, so that I have the complete set of 20 keys in addition to the dial pad.  I've built those up now, and used 510 Ohm resistors for the LEDs instead of 130 Ohm ones, so that they aren't so bright, and don't suck so much current, since with 32 LEDs, it can add up quickly.

Rather annoyingly I have two brands of red-green LEDs, and one of them is green-red, rather than red-green. It's easy enough to sort in software to make it possible to switch the polarity of any given LED.

In the meanwhile, I have mounted it all on a piece of MDF, so that I can start putting it into a case of some sort.  It's all a bit rough and ready for now, but that's okay. The main thing is that it is functional, and easy to use.

I'll probably work on making the key allocations software remappable, as well as the red/green vs green red problem.  I'm thinking I will add some kind of test mode for mapping this out, although it won't be able to save the configuration to SD card yet. I might make it assemble a valid configuration in memory, that I can then easily pull down and copy on to the SD card manually for now. Eventually it would make sense to allow this to all happen on the device.  

One thing I am careful with here, is that I don't want the configuration mode to be able to entered by interacting with the phone. This is to avoid Sod's Law that a user will somehow end up in the configuration mode and make a mess the instant my back is turned. So instead, it will likely require putting a special file on the SD card and rebooting the phone, after which it will operate only in the configuration mode until the file is removed. It will also say that it is in the configuration mode, and that the file has to be removed to restore normal operation.

The key remapping problem could be reduced somewhat if I enforced that the keypad PCBs had to be assembled with the correct order of I2C addresses. But I prefer to allow the build process to be a bit more relaxed and less frustrating.

So that leaves the following three items that I need to explore soon:

1. Speed dial key / LED configuration, so that you can indicate which LEDs are backwards, and which keys correspond to which contacts / phone numbers.

2. When placing an outbound call, make the expected ringing noises in the handset. 

3. When you pick up the handset to make a call, have a dial-tone in the handset.

These last two require the FPGA to interrupt the audio path between the cellular modem and the handset, which we have proven previously with the MEGAphone prototypes, so will require a little more work to build a custom PCB to do that, but its not a massive amount of work.  The configuration management is the obvious low-hanging fruit, though, so I will likely tackle that next.

Friday 29 March 2024

Expansion Board revE Bring-Up and Shake-Down - Part 1

The revE expansion board and bridge board (that connects it to the MEGA65 main board) have arrived, as have the remaining components that I needed.  The PCBs arrived within 72 hours of shipping from China which is a new record for me -- they were even delivered on a Sunday!

I've already spotted the first stupid error I made: J6 on the bridge board has been placed next to J13, when it should have been about 15mm to the right, to line up with the other power connector on the expansion board. Oh well.

I'll start by putting the connectors onto the bridge board, and confirm that it has comfortable fit to the MEGA65's main board on all the connectors.  It connects the JTAG, floppy 34 pin and floppy power, and also J19 on the MEGA65 R4+ boards (including of course the R6 boards that are shipping around the middle of this year). J19 is a break-out of the 3.5mm audio plug on the MEGA65 main board and allows us to also deliver the audio to the C64-compatible analog A/V plug on the expansion board, without requiring extra FPGA pins and another DAC on the expansion board.

If that fits, I'll then put the connectors except for J15 on the expansion board (which is the one that is supposed to connect to that J6 that I put in the wrong place), and make sure that that all fits, and that the spacing of the PMODs is correct in that configuration for the existing PMOD connector boards to also comfortably fit.  If that all works, I'll be pretty happy for a starting point.

And it does by and large fit:


It's still a bit tight to fit it all together, but I think this is mostly down to the tolerance of the connector placement.  This really isn't surprising for something hand-assembled, and that is effectively pinned in multiple directions at the same time.  There is probably some further refinement of the relative connector positions, but I don't know that it is worth pursuing, as the errors in connector direction and position that result from hand-soldering are already the most significant factor.  You do have to take care when soldering the connector to make sure that they are sitting flat against the PCB! 

Fitting the boards together is best done by connecting the bridge board to the expansion board, and then connecting that pair to the main board. Only after that should the PMOD connector board be fitted, and as absolute final step, screwing the expansion board into place. I'll probably make a video showing the process once I have finished assembling the boards.

So I think I can now safely add the missing components on to the expansion board and start testing that. I'll test the ESP32 on the bridge board after I am happy that the latest expansion board revision is good.  

Actually, I already have enough components on there that I can test the TE0790 JTAG relay... If I can find where on earth the TE0790 has gotten to, after I've been plugging and unplugging it from boards over the past few weeks. And it works fine for both JTAG and serial communications :) I'm really happy, as this was the biggest risk.  First, because on the previous revision it didn't work, and second, because it would have been easy for me to get the pinout reversals not cancelling each other out that I put on the bridge board to further simplify the wiring.  I'll test it again once we have the ESP32 fitted, but for now, it's working just fine, and has me a lot happier.

Meanwhile, it's out to the shed to solder on the remaining components, and pull the chips out of the previous prototypes and re-use them on this one.  That's all done, and I now have the complete assembled board in my MEGA65 R5 test machine.  I can power it on without any smoke coming out, so that's a good start :)

So now to test the various ports to make sure that they all still work.

First, we have the A/V port. I have colour PAL signal coming out, which is good. I added jumpers to allow me to enable/disable and completely disconnect the low-pass filter circuit that I added on the revE in case it didn't work, but also so that I can compare the picture quality with and without it. And the result is that there is no perceivable difference.  This is likely the case because the monitor is already internally doing a similar low-pass filter.  Anyway, it's still safer to have the filter in there for older monitors.

But the fact that the A/V output still works says that I got all the other changes right that I made for this: The resistor ladder is now only plumbed with 2 bits per channel instead of 4, and uses 2x 1K Ohm in serial to get 2K Ohm and 4x 1K Ohm in parallel to get 250 Ohms. So there are quite a few changes from the revD, and the output looks at least as good as it did on the revD. 

Now, it has to be said that this economical approach of using any resistor ladder and the approximate signal processing that I am layering on top of that still isn't perfect, and I don't know where the limit of the hardware design I have made is versus how much further improvement is possible in the VHDL by producing a better composite video signal, nor how much of a difference the monitor is making in all of this. The gradient of blue values actually looks to be really quite good, while red and green have some weirdnesses in them, and the colour is generally a bit under-saturated which make me think that the problem is really in my composite generation VHDL, rather than a limitation of the hardware circuit. In theory changing some coefficients in the calculations for that should improve that quite a bit.

So anyway, the video output is more or less as before, and thus confirmed working.  I would like to test the audio relaying via the bridge board, but I'll need a separate monitor or TV with an audio input to really test that. 

Meanwhile I've also confirmed that the tape port still works, with Tunnel Vision successfully loading on first attempt from the old tape and datasette I have here, and thus the ring buffers must still be working. A more thorough testing of the user-port again would still be a good idea here, but that can happen later.

Maybe I'll next test that the internal floppy drive still works with the relay via the bridge board, and that the floppy cable origami is all laid out appropriately in terms of where I moved the floppy connector to on the expansion board to facilitate this. The cable routing is now much easier... The only problem is that the floppy drive doesn't seem to be working.  I'm guessing that I have something in the relay board routing of floppy signals wrong -- like tying all the signals together as GND and vice-versa from having the signals and GND sides of the connectors flipped.  I'll have to investigate that. Time to investigate this. 

Step 1: Does the floppy drive still work fine when connected directly to the MEGA65 mainboard - yes.

Step 2: Test it again with expansion board to make sure I'm not imagining the problem.

Actually, Step 1.5: Look carefully at all the boards to make sure I had the signal pins on the correct sides of the connectors and notice that I hadn't soldered half the pins on one of the connectors!  That seems highly probable as the root cause :)

Let's give that a go after I give it a quick talking to with the soldering iron... and that indeed seems to have fixed the problem.  The disk I have in the drive has a read error, but that is behaving the same both with and without the expansion board connected, so it's probably fine. But just to be sure I'm going to try reformatting the disk. Hmm... BASIC65 refuses to format the disk -- even without the expansion board connected, i.e., just the way it used to be.  So I've tried formatting it using my test utility, and it looks like one side is totally fine, but the reverse side is refusing to read.  Is it that my drive has dropped a head or something else? 

Yup -- looks like my internal drive has dropped a head! Using a different drive  Well, I have flogged it pretty hard. It might be possible to clean the head or something.  The main nuisance is that the eject button for the MEGA65 assumes an ALPS drive, as they eject mechanism are in slightly different positions on all models of drive.  I might have another ALPS drive lurking around, which would be the convenient solution to that. Otherwise I'll have to stick with a half-working drive in my MEGA65 until I make the working M1565 controller board, and can then use an externally connected drive in place, or alternatively pickup another ALPS drive when I next visit the folks in Germany.  Anyway, let's call this mystery solved.

So now I think it's probably safe to solder on the ESP32 now, which needs to happen before I can really screw everything into place permanently, as to remove the bridge board you really need to first remove it and the expansion board from the case, as its quite difficult to separate from both the mainboard and the expansion board simultaneously. 

I've soldered the ESP32 on. This is a bit fiddly, as it is a surface-mount module. But fortunately it has big enough pads that are crenelated rather than a nasty ball-grid array that would require a reflow oven. The only pad on the underside is the thermal transfer pad.   The holes for that are a bit small on this revision to easily wick solder through by applying solder to the reverse side. So I've logged an issue to enlarge the holes on the next revision.

Now, for testing the ESP32, I was quite conservative and added in a bunch of ways that I can try to talk to it. This includes a TE0790 compatible header that can be directly connected to the ESP32 UART pins:

Basically we can connect via the J6 connector that goes to the MEGA65's PMODs, or we can communicate via the TE0790 header if we put the right jumpers on J3, which is there to allow for if I got the TX and RX around the wrong way, which Sod's Law says would be the case if I hadn't added in the ability to switch it around. 

This TE0790 option is there so that I don't have to get a bitstream working first -- I can confirm that the ESP32 is talking over the UART without having to do anything else. Well, apart from ensure that the WIFI_EN line is set correctly to turn the unit on. According to the datasheet, this pin is active high. 

Now, by default that line is low from the ring buffers, which by Sod's Law is the case, since I failed to add a way to reconfigure that line with jumpers. This means I will need to connect the PMOD board after all, and make sure that the bitstream I use doesn't drive the ESP32 RX and TX lines. Ah, except that because of what a good friend of mine would call a Self-Correcting Stuff Up, the WIFI_EN line connects to the expansion board via that header I put in the wrong place, so it's currently not being driven, and I can just jumper it to 3.3V if I can find a convenient source somewhere. The only possible source is the TE0790 right now, because the 3.3V supply would normally come from that same header that's in the wrong place and thus not connected.  Well, I could connect just the 3.3V and GND pins of that connector. That's probably a better and easier solution.

... except that it isn't running. Poking around, it looks like my hand-soldering of the pads hasn't been an unmitigated success. The WIFI_EN line at least seems to not be connected.  The 3.3V power pin seems to be fine, though.  I guess I'll pull the board back out, and probe all the solder joints to find the ones that need reflowing, and see if I can't fix those without bridging others. This would be a great time to use the HDMI microscope, but we have currently lent it out to our kid's school. Oh well.  Maybe I should use the hot air gun to reflow it...

In the end I used the "lots of flux on everything and wipe the soldering iron sideways along the row of pins" trick, which works pretty well. I will make the pads for the ESP32 bigger though, on the next revision. That will make it much easier to get decent soldering iron contact onto the pads, which are otherwise mostly covered by the ESP pins.  The RF shield on the ESP32 is also connected to a few of the pads which makes them really hard to reflow, but such is life.

Okay, that has it booting, because I can see action on one of the UART pins. Now let's see if I got them around the right way :) And I did! 

I can now see the ESP32 stuck in a boot loop via TE0790 serial connection. This will let me debug that quickly, directly from my Linux machine.  My best guess is that one of the bootstrap pins is not set correctly, and it's trying to boot from some odd method, or that it needs initial flashing, in which case, again, the TE0790 will make that much much easier.

This is the message the ESP32 keeps spewing out when I have WIFI_EN set high:

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fffeba4,len:4
load:0x4009f000,len:3248
entry 0x4009f574
�OHAI�ets Jul 29 2019 12:21:46

I am now working to plumb this all through so that the ESP32 UART can be accessed from the MEGA65 core via the buffered UART interface at $D0Ex. That interface has 8 buffered UARTs, designed for exactly this kind of thing, especially for the future hand-held that will have a bunch of UART peripherals, e.g., for the cellular modems).  I've also added $D0E7 to allow the control of the ESP32/accessory enable in bit 0. 

This is already quite a long post, so I'm going to wrap it up here, and leave the following for the next post:

1. Test ESP32-enabled bitstream built ok

2. Test ESP32 "Accessory enable signal" on $D0E7 bit 0

3. Test ESP32 UART on buffered uart #0 (TX/RX swap?)

4. Load AT firmware on ESP32

5. Test simple operation of ESP32

6. Plumb FPGA PMOD lines for UART to buffered serial port and test from BASIC65?

7. Test C1565 port direct lines can be controlled

8. Test C1565 port direct lines can be read

9. Test C1565 port ring-buffered lines can be controlled

10. Test audio relay via bridge board 

That list is as much for me to not forget what needs doing as much as anything else -- but if anyone wants to tackle any of those steps before I have the chance to attack them, drop me a line. This is a community project after all :)

M1565 External Drive Controller Board Design

As part of the work on the MEGA65 expansion board we are going to have a working 1565 external drive port -- well almost. I have made the 1565 port on the revF board use a 9-pin instead of an 8-pin mini-DIN, so that it can also power the drive. 

We'll call the enhanced port and external drive specification the "M1565".  The M1565 port is designed to talk to an original 1565 using a special adapter cable -- much like we changed the MEGA65 cartridge port from the C65's funny one (which is quite similar to the Plus/4 cartridge port) to a C64-compatible one to make life on the MEGA65 nicer.

But to test the M1565 port we need an M1565 drive! So I have designed up a little board that can connect to a conventional 3.5" floppy drive (or for that matter, a 5.25" one) and talk an enhanced version of the 1565 serial protocol.

The original 1565 protocol doesn't allow you to detect if there is actually a unit connected, nor to identify if its a 3.5" or 5.25" drive (the 1565 was designed only to ever be a 3.5" drive).  This would let us have a 1541-format compatible external drive that was as fast as the internal drive. It would also let me experiment with seeing how much data I can fit on a 5.25" disk some time, too :)

It would also be nice to know if the drive is a DD, HD (eg 1.44MB 3.5" or 1.2MB 5.25"), QD (eg SFD1001 style mechanism, if I remember correctly), or ED (eg 2.88MB 3.5").

We'd also like to support having more than one drive daisy-chained on the port, since the controller supports up to 7 external drives, and also to be able to write-protect the drive competely, e.g., if you are using it to archive disks.

This means we need some dip switches on the drive, as well some way to read those bits of information.

Fortunately the 1565 serial protocol has an unused status bit. I'm hijacking that to allow reading a serial bit stream, one bit per frame of data. This will also let us tell a 1565 from a M1565, as the 1565 will return all 1s, while this can only happen if the M1565 claims to be device 7 with a write-protected 2.88MB ED 5.25" floppy drive -- a drive that doesn't exist.

The logic for this is fairly simple, and I use the RESET pin on the 1565 port to synchronise this bit stream.  I'll go into more detail when I implement the communications protocol. For now, I'm just trying to get the PCB ready to send of for fabrication with a couple of other ones I am ordering, so the description here will be uncharacteristically short.

The other nice thing I have implemented is to make it work with either a twisted or straight-through floppy cable, since each board can connect to only a single drive.

This is what the prototype board looks like right now. Once again its designed to be all hand-solderable with no surface-mount components. 


Note that it is too long to fit in a real 1565 case, because that's not my objective right now.  The form-factor doesn't matter so much for testing. So I've just made the board the same width as a 3.5" floppy drive, and with those two bottom holes in theory lining up with the screw holes in the bottom of a 3.5" drive and placed appropriately that it should be possible to easily connect a short 34 pin data cable with the drive sitting there. We'll see how it goes when the boards arrive.

So that's really about it for now.  Hopefully I'll get the blog post out for the recent work on the expansion board and friends shortly as well.  There is still more testing required for that, but it's all looking quite promising, and some folks in the USA and Germany will be building their own prototypes of that soon to test, and will probably also want to build up one of these boards, too at some point :)

Oh, I should say that the KiCad design for this board can be found at  https://github.com/MEGA65/mega65-r3-expansion-board/tree/master/external_drive_board for the curious.

Sunday 25 February 2024

Expansion board revD and new connector PCBs

Spoiler: This is the new revE expansion board, and matching bridge board to connect it to the MEGA65 motherboard.  Read below for the full story and adventure as usual, including to find out what that big hole in the bridging board is all about...

The new revD MEGA65 expansion board PCBs arrived today, as well as the prototype connector PCBs to replace the need for cables to bridge between the expansion board and the MEGA65 motherboard. Yay! So now it's time to see how well I did with the various things I was trying to fix / implement.

First, the hole positions on the revD expansion board are essentially in the right places, although I realised that I still missed one whole hole in the motherboard. Fortunately it's not in a position that will cause physical interference, so I'm going to ignore it for now. We'll call that a victory. 

Second, the thin connector PCB that joins the PMODs of the MEGA65 to those of the expansion board is indeed thin enough that it doesn't cause physical interference with the MEGA65 keyboard, which was a bit of a worry.  So that's another good victory. You can see from the shadow in this photo that there is clearance between the two, and that's without the connector PCB being fully inserted, which will increase the free space. This was a pain to photograph, because the inside of the case is in the dark, of course. So I had to use the flash, which resulted in annoying external shadows. But as said, the connector PCB, the thing with the two sets of 12 pins poking through, is well and truly clear of the keyboard PCB, i.e., the big green PCB covering most of the trapdoor.

We can also get a general idea of how the various boards will fit together in the picture below:

The expansion board sits in the bottom of the case, as you'd expect, and then the thin connector board joins the PMODs on the front edge of the two boards, and then the other bridge board connects the JTAG, floppy connectors and power connectors between the MEGA65 main board and the expansion board.  As you can probably already see with that PMOD connector board, the relative positions of the connectors is not quite right...

In fact, almost all the connectors on both boards are not correctly located. I did try during the initial design of these boards to get all the measurements right, but I failed fairly dismally in the end.  I'm a bit annoyed with the result, partly because I can see several of the problems that I really should have been able to pick up during the design phase, as well as others that were always likely to be a bit tricky, but I had tried to get right by printing pieces of paper with the PCB outlines and connector positions so that I could validate them before manufacture.  It's probably not surprising that errors of upto about 1mm remain after the paper method, as it was quite hard to line things up when the MEGA65 main board is fully populated, and I didn't have a printable version of the bare board to do the paper trick for that.

All in all it's ok, because much of the point of making these prototypes was to identify exactly this kind of issue, and the chances were slim that I'd get them _all_ right first time. However, getting at least some of them right first time would have been nice... What I am going to do, though, is make the changes to the bridge and connector PCBs, rather than the expansion board PCB, in the hope that I don't have to change the expansion board PCB, partly because it's more work to do so, and it would be nice to not waste those boards that I have had made.

So I soldered some connectors on to some of the boards so that I could work out the errors in the positions, especially between the MEGA65 motherboard and the expansion board.  I'm noting the required changes down as I go:

P2 - move right approximately 0.5 mm

J9 - move left approximately 0.75 mm

J10 - move left approximately 3.0 mm

JB1 - move up approximately 0.5 mm

JB1 - move right approximately 0.5 mm

J1 - move up approximately 0.25 mm

J1 - move right approximately 0.5 mm

J4 - move left approximately 1.0 mm

J4 - move down approximately 2.0 mm

J2 - move to have the correct relative position to J4.

JTAG RELAY - move to have the correct relative position to J4

All of those "approximately" measures are those that relate to the relative position of the MEGA65 PCB and the expansion board PCB, and/or the unknown exact relative positions of the connectors on the MEGA65 motherboard.  Some I can deduce from the drawings that I have managed to find (and I retrospect should have found and done before sending the PCBs off to manufacture...) such as:

J1 should have the holes centred horizontally compared to J3, based on drawings of the MEGA65 main board.   But vertical distance relative to J3 cannot be determined.

Well, that's actually the only one I can be certain of. 

P1 and P2 should almost certainly be 0.9 inches apart, as they are based on 2.54mm = 0.1" spacing.

J9 and J10 should also be similarly aligned to 0.1" of an inch, although they seem to be 0.8" apart, rather than 0.9" apart, which I can at least verify from the expansion board PCB. But I'm still relying on hand measurements to work out their horizontal displacement relative to P1.

I just don't have the drawings to improve the rest. So I'm just going to have to trust my measurements of the errors, and try to get those accurate enough, that the connector placements all fall within acceptable tolerance.

I've now gone through all the changes, and I think I have everything correctly placed. But then, I did last time, too.  I'm going to sleep on it, and check it all again in the morning.  I might also see if I can't relate the positions between the MEGA65 main board drawing and the expansion board PCB using the mounting holes in the case, as they do provide an absolute reference.  I just need to make sure I measure relative to one of the holes that is well centred, rather than H3 and H9 that are a little offset to one side.

Well, it's another day, and I've converted the PMOD connector board to 2-layer instead of 4-layer, which reduces the build time to ~24 hours instead of 4 - 5 days and ordered the two boards as is, as I needed to put in an order to PCBway, anyway, for some C64 cartridge break-out boards to debug an unrelated problem.  So I should have the new boards within a week, probably on the 29th or 30th Jan.

The conversion to 2-layer for the PMOD board required the board to be a little wider, but it should still comfortably fit in the case with a couple of millimetres of clearance between the edge of the board and the bottom case.

Anyway, they are ordered, and should be en-route to me here in Australia within 24 hours, and at my door early next week.  I'll continue this post when they arrive.

The boards have arrived and they very nearly fit nicely. I can force it all together, but then the screw holes don't line up exactly.  But it gives a nice idea of the final appearance. But I am going to have to re-spin at least the bridge board, and probably the PMOD connector board, too.


I do still like how nice and neat it is when it is all hooked up.  The spacing between each PMOD in a pair of PMODs on the PMOD connector board is fine.  But the spacing between the pairs of the PMODs is about 0.5mm too narrow. It can be forced, but it really is a force.

For the bridge board, it looks like the JTAG connector on the MEGA65 main board side need to be shifted up about 1mm, and the other connectors on the MEGA65 side up by about 0.5mm. I would like to double check that with a bare board MEGA65 board, but I don't have one.  So I've emailed Trenz Electronic to see if I can get exact measurements from their design files.  But the bridge board does fit easily onto the expansion board:

The third and final issue I have seen so far is that I wasn't able to get JTAG working via the bridge board -- the serial interface works on the TE0790, and while the JTAG interface gets detected, every time I have tried to send a bitstream via JTAG it has failed.  So I'll need to investigate that further.

The first thing to try is to use a cable to do the JTAG connection instead of the bridge board. That way we will know if the revD expansion board can do JTAG at all. If that works, then I'll suspect the bridge board. If it doesn't work, then the expansion board will need another re-spin.

Interestingly, with a cable instead of the board, I can't even get the JTAG adapter to be recognised on the revD expansion board. I'm suspecting that the more circuitous routing on the revD expansion board for this connector is causing problems.  But that shouldn't stop the JTAG adapter being detected, only prevent it from succeeding with JTAG operations. Squeezing the cables around I can get the JTAG device appearing, but the JTAG communications are still thoroughly messed up.

I think the solution here will be to put the relay connector much closer to the TE0790 connector. I might also try putting the header for the TEI0004 on the revD board I have assembled, and test that, as it is at the end of the traces, so should not have any reflection problems.  It might in fact be the presence of that connector that is pushing things over the edge.

Hmm, I found one problem with the cable: One of the pins has broken the little housing it is in, so it was only making intermittent connection. That's probably why the JTAG interface was only being intermittently detected. 

So, back to the JTAG not working to load a bitstream, if I have a TEI0004 fitted and connect the cable to where I would have the TE0790 normally, then I can push a bitstream over JTAG without problem. The serial monitor interface also works. This results in a short and direct path between the TEI0004 and the off-load from the expansion board, as they are basically next to each other:

 


So now let's try moving the cable to the header where it is supposed to go. In this configuration that path will still not have reflection problems, because it will still be linking end-to-end over the circuit path, but it will have the more convoluted routing in the path:

And that works fine, too, being able to push a bitstream to it.  So that's a convenient enough work around for my test board.  But I would still like to get to the bottom of this problem, as the reflections weren't a problem with the previous revision. My gut feeling is that the combined impact of the reflections from the traces to the TEI0004 socket and those from the vias and other routing changes that I made are enough to tip it over the edge.

Now I should solder up another bridge board with JTAG relay headers and confirm that it works with the bridge PCB, rather than the cable.  I'm not expecting any problems, as the bridge PCB should result in better not worse signal integrity. But for completeness I should check this. And it works!


Now I want to confirm that the reflections are the problem here, by soldering the TE0790 and relay connectors onto an unpopulated revD expansion board, but only after cutting the traces to the TEI0004 header.  If that makes it work, then we know at least one of the factors involved -- although the worsened routing must be having an impact as well, since the TEI0004 header was fine to be connected on the revC board. The challenge is going to be accurately cutting those traces. We'll see how I go, but maybe after lunch.

I had a quick go, but I don't really have the tools on hand to do it confidently.

Anyway, as much as I might be trying to avoid it, I think the solution is that I need to do another revision of both the bridge board, but also the expansion board itself.  The PMOD connector board will be okay as it is, because I can move the PMOD connectors on the expansion board to correct for the placement offset.

What I think I need to do is to move U6 and U7 towards where the TE0790 relay header is currently located, and then move the TE0790 relay header to be as close as possible to the TE0790 connector itself.  It's also an open question as to whether I leave the TEI0004 connector on the expansion board, as I think most people have a TE0790, anyway.

Reworking the expansion board will let me do a couple of other helpful things: First, if I test the A/V output lines with just 2 resistors, and doing the 8x over-drive trick that I do on the least significant bit, and confirm that this works, then that will let me free up 6 IO pins.  I can then use some of those to directly connect the C1565 serial link without going through the ring buffers.  And the left over ones I can route to another header to the bridge board, and perhaps stick a WiFi enabled ESP32 on the bridge board, for the folks who would really like to have a built-in WiFi interface for the MEGA65.

The first step is to make a bitstream that just drives the 500 Ohm and 8x 500 Ohm = 4K Ohm resistors in the A/V outputs to see if we still get decent video quality.  I'll just ignore the other 2 resistors for now. Let's start with our test-pattern on digital video output as reference:

So we have a set of nice colour gradients and colour bars. Now with our existing 4-pin DAC implementation:

The camera doesn't really capture it particularly accurately. But it's pretty clear (and totally expected) that the colour reproduction for composite video is much worse than a puree digital video output. For real-life use, it actually looks not too bad, at least as good as a C64 on a real TV did back in the day, and probably on a par with component video output to a proper CRT monitor -- and this is with the MEGA65 just producing a single composite output. With separation into chroma and luma channels, it should be better again. I just haven't implemented it yet.

But dropping to 2 pins only results in a much worse outcome, with colours distorted and quite washed out:

Either we need to increase the frequency of the over-sampling, or we need more pins, or both.

Thanks to some help from the CVBS community, I looked at this nice calculator to design a low-pass filter for the A/V output: https://markimicrowave.com/technical-resources/tools/lc-filter-design-tool/, but I'm not totally confident working with filters. For example, if we are using a ~500 Ohm based resistor ladder, does that mean that our input impedance is ~500 Ohm? I think so, but I'm not totally sure.

So I think the step before that is to see how 3 bits of output performs, without filter or over-sampling the bottom bit.  If it turns out to be enough for good colour PAL reproduction, then that's a convenient solution to freeing up a few pins.

Ah, except in the investigation I found that I am probably not looking at the composite channel at all, but rather at the luma channel, and it's just leakage between traces causing a very low-saturation colour effect on the signal.  That is, I'm probably connected to the wrong pin on my video output cable. Also, the cable is connected to the revC expansion board, which I recall now has an issue with one of the channels.  

To work around this, I'm going to do something that I had intended to do anyway, which is to make each output channel configurable. This will also allow the board to output composite, S-Video, component video and other formats in future.  That's now synthesising, and will allow selection of each output on the A/V connector independently, initially just from chroma, luma, composite and left and right audio. This will be via $FFD8000-2, with the lower 4 bits of each register selecting the source.  I'll document it further in future.

In the process I also noticed some bugs with NTSC colour generation, and that I was not actually producing a pure luma signal, except when the MONO bit of the VIC-III was enabled.  I also messed up the source select logic, so that any IO write would update the source, rather than just one targetted at the correct address.  So reconfiguring with all those fixes. Will be nice to see if it fixes the weird NTSC colour problem issue (beyond NTSC's normal colour weirdness ;)...

Well, it didn't fix the NTSC problem, but it does look like I can select the various outputs on the various channels of the A/V plug now, which is good. So now to try again to reduce to 2 pins with 3-bits per pin.  And the result is much better. The colour is still a bit washed out compared to using 4 bits directly, at least I think so. But it is way better than before when I had stuff things up in various ways. To get an idea of the difference:

The images in the left-column use only 2 pins each, with the 8x over-sampling used to get 3 bits from each pin, for a total of 6 bits of resolution. The right-column uses 4 pins, with three pins directly connected to resistors, and the 4th pin using 8x over-sampling to get 3 bits instead of 1. The result is 1+1+1+3 = 6 bits of resolution. In other words, the two should be effectively equivalent in terms of output.  

Now, my camera doesn't really do a perfect job of capturing the colour, or all of the video artefacts for that matter.  In particular, the colour gradients are much better in real-life, and the colour intensity is also a bit better. At least with the DELL monitor I am testing with. With real CRT monitors, the results could be totally different.

Anyway, the overall difference is a bit reduced colour saturation and brightness when using only 2 pins.  I have every reason to believe that those can be corrected in VHDL, at least partially.  Thus I feel safe in moving to only 2 pins per colour channel, and thus recovering 3 channels x 2 pins = 6 pins total.

I had a problem where the monitor was not getting VSYNC or correct HSYNC, and I wasted quite a few hours chasing my tail on this. In the end turning the monitor off and on again fixed it. Of course, this means that something must still be a bit marginal.  To help debug that, I have added sawtooth and sine wave test signal sources to the A/V output, so that I can verify that the resulting waveforms looks fine.  

The complication for me to evaluate this, is that my oscilloscope has a bandwidth of 100MHz, which means that it is not integrating the over-sampled signal as much as I would like, and as much as I expect typical CRT monitors will do.  That said, the sawtooth waveform already looks pretty good. The sine curve had some stupid errors in the generation, so I'm resynthesising that right now.  As mentioned, the higher bandwidth of the oscilloscope means that we will see oscillation around the mean point of the signal.  

That said, as the images below show, it's still really not that bad:

First up, we have the saw-tooth waveform:

There is more noise at low values, which is not so great, presumably at the level where only 1 pin is really being active.  Also, it's not totally linear, but it's still not that bad.

Then for the sine-curve we get a really nice clear sine waveform visible, again with noise, especially at lower absolute values:

If we zoom in on the time domain, we can see that the noise is substantially caused by rapid oscillations, i.e., the over-sampling method we use to generate the signal:
The oscilloscope is showing an approximate frequency of this oscillation of ~65MHz, which is a bit of a mystery, as our pixel clock rate is 27MHz, so I'd expect it to be an integer multiple of that, but things are never that simple. Anyway, what it clearly shows is that if we do add a filter, we would like at least 10dB of attenuation by 2x27MHz = 54MHz to really smooth this out. Anyway, it's enough for our purposes right now.

Otherwise, the main issue with the video is we have a bit of a red tint on the composite video, so we will need to adjust the coefficients that control that at some point. That doesn't require changing the hardware, so that's good enough for now.

A more annoying issue is that the expansion board is causing spurious resets, presumably because it thinks the user port /RESET line is being pulled low.  This is despite the /RESET pin on the user port seeming to be sitting comfortably at +5V the whole time. This makes me think that maybe the ring buffer for reading the pins is a bit wonky.

My best guess here is that the latching of the read ring is a not rock-solid. What I should be doing is reading the previous bit on the instant just before I clock the next bit. To debug this I have added a few things: 1) I have made the controller check that the ID lines are indeed one high and the other low, which should prevent framing errors from sneaking through; (2) In the process, I have made it easier for future revisions of the expansion board to offer different ports; (3) Added the option to mask the user port reset line, in case it is still causing problems; (4) Count the number of reset events on the user port, in case that is the problem; (5) Allow direct reading of the input ring from the expansion board; (6) Generally document the registers that control all this stuff.

With (1) in place, the spurious resetting has totally stopped, so I'm guessing that was the problem.  I'll leave it at that for now.

Meanwhile I now have the dimensions sheet for the MEGA65 main board, so I should be able to refine the positions of the connectors to the MEGA65 main board. That also reminds me, that on the R4/R5/R6 boards, we now have an analog audio output header that is conveniently located enough that I should be able to make it an option to pipe to the audio pin of the analog video port, which is an other advantage.

So all in all, I think we can add the WiFi adaptor, as well as improve the plumbing to the C1565 port. This will be in addition to the correction of the connector locations, and re-routing the TE0790 relay connector so that JTAG can work reliably.  That sounds to me like more than enough changes for one revision of the board.

So let's look at those connector positions, and work out what needs adjusting, and by how much, now that I have the ground-truth relative positions of the MEGA65 main board. But before that, I really should double-check that everything that did work on the revC still works on the revD, and that the positional corrections I made for that are correct.

One thing I noticed is that the floppy connector on the expansion board lines up with one of the screw points for the floppy drive:

This isn't a show-stopper, but it does require more cable origami to get the cable to be able to get into the connector:

I can move the header on the expansion board towards the front of the MEGA65 by 18mm to clear it. This will slightly increase the cost of the PCB, but it shouldn't be too tragic. The main question really, is whether moving the cable that far forward will collide with the back of the keyboard.

I can test this by purposely mis-seating the floppy cable, and seeing if I can still close the case. Each position is a movement of 0.1" = 2.54mm. So 18mm = 7.1 positions, so I'll try 7 positions.

It's close, but doesn't quite fit. Now, part of the reason for that is that the edge of the keyboard PCB is still just over the connector. If I move the floppy connector to the right 7mm, it should clear it comfortably. This will require extending the right edge of that part of the PCB, but there is comfortably room to do this, so that's fine.

I also double-checked that there is still space for the TE0790 behind the floppy cable, as I did move that down on the last revision. It does still fit, but with not too much space to spare!

The tape port still works, and I'll assume that the user port still works, too, since they are both on the same ring buffer loop. I do get errors loading from the test tape sometimes, but at least 1 out of 3 times I am able to correctly load a game from tape that uses a tape fast loader. So I'm going to assume for now that it is likely the age of the tape and datasette, rather than the expansion board that is to blame here.  I might later experiment with adjusting the ring buffer clock speed to see if it makes it better or worse.

I'm now going through the process of updating the schematic and PCB for the various changes. 

I'll start with simplifying the resistor ladders for the video output channels: Instead of using 4 pins for each, I'll use only 2, with 500 Ohm and 4K Ohm, constructed using only 1K resistors in series and parallel, which will simplify the bill of materials. This means each channel now looks like this:


That's a net increase of 2 resistors per channel, which I'll need to fit on the PCB, but that should be ok.  

I'd also like to add provision for optional low-pass filters on the three channels.  Based on the oscilloscope probing, we need to have at least 10dB roll-off by ~54MHz.  I'm using the great filter design tool at https://markimicrowave.com/technical-resources/tools/lc-filter-design-tool/.  The output impedance of analog video is normally 75 Ohms, and the input impedance is approximately the resistance of the lowest resistance rung of our resistor ladder, so 500 Ohm in this case.  Out cut-off frequency can be set to 13.5MHz, the maximum channel bandwidth for a PAL or NTSC signal -- in reality it probably only needs to be half that, but it's certainly enough.  So let's put the appropriate parameters into the model:

and see what the response of the filter looks like:

Well, that looks pretty okay to me. The insertion loss below 13.5MHz is about 2dB, which means that about 2/3 of the input voltage will be able to pass through. Then we have about 3dB filter loss below 13.5MHz, so our total loss for the frequency range we want is maybe 5dB. That's a bit more than I'd like, as it means that our output voltage will be divided by about 3.1. Given our output impedance of 500 Ohm and the input impedance of a monitor at 75 Ohms, this means that before the filter, our output voltage should be something like 3.3V x 75 / (500 + 75) = 3.3V x 0.13 = 0.43V, which is already at the lower end of things, since the target peak voltage is around 0.7V.  The filter will divide this by a further factor of 3.1, to give a peak voltage of only ~0.14V, which is really quite low.

We can improve this by reducing the output impedance from 500 Ohms to 250 Ohms, which will bring the peak output voltage up to 0.76V, which is safe without further reduction by the filter, and still allow the voltage after filtering to nearly double to ~0.25V ... except that filters are never that simple. We need to update the filter model to reflect the 250 Ohm input impedance, which gives this response curve:

In other words, even though we have nearly doubled the input impedance (for a gain of ~3dB), the input loss of the filter has increased by almost exactly the same amount! That is, our filtered signal will not have any greater voltage range than with an output impedance of 500 Ohms. Grr! Well, in any case, 500 Ohms over 3.3V = 6.6mA, and 250 Ohms would mean 13.2mA, which is still within the max current limits of the FPGA, so let's halve the output impedance, but add a bypass-able resistor that increases the impedance back to 500 Ohms by adding a 250 Ohm resistance in the path, as well as making the output filter selectable.  

The main trade-off here will be adding yet more components on the board, as it would require either one 250 Ohm resistor (which I don't have in stock), or more conveniently, four 1K Ohm resistors in parallel, but that would take the most space.  The filter also requires 3 components, and then we would have two jumpers to allow the bypassing of the extra output impedance and the filter. So in the worse-case scenario, this would mean we would have the existing 6 resistors for the resistor ladder, plus 3 filter components, 2 jumpers to select function, and between 1 and 4 resistors for the output impedance trim resistors. Multiplied by 3 channels. That's probably more than I can realistically cram in, even given the extra space available from increasing the board size to accommodate relocated connectors.

I thought I would just check the voltage I get on the video channels with a 75 Ohm load, just to be sure. I didn't have a 75 Ohm resistor handy, but I did have a 68 Ohm resistor, which should be fine, just resulting in slightly higher voltages than with a real monitor. I was expecting to see ~ 3.3V x 75 / (500 + 75) = 0.43V. But instead I am seeing a peak voltage of more like 4V instead of 0.4V... which is also the voltage I see when it's open circuit. Ah, I was measuring it incorrectly. It is indeed somewhere around 0.4V, so halving the output impedance to 250 Ohms makes sense, and I won't need the impedance selection jumper and resistor -- just the filter and filter bypass jumper.

So let's start moving things around and see how much space I can free up for the filters etc.  I've also flipped the connector over that carries the JTAG signals to the bridge board, to avoid the need for vias, that would degrade the signal integrity.  The difference in routing is now quite profound with those changes, with the JTAG lines now running simple straight paths, without any vias at all, and no non-JTAG signals crossing the path of the JTAG signals.  I really think that they should have much better signal integrity now. You can see the improved routing between the two 12-pin TE0790 connectors here:

As a reminder of what it looked like before:

There were those two ICs in the way, and crazy circuitous routing to get around them and the existing circuitry. I also moved J8 further up the board, to minimise the path lengths, so I'll have to update the bridge board position of J8, as well as horizontally flipping it (which should also remove some vias on that board, too).

I've also moved the PMOD connectors on the expansion board left by 0.5 mm, so that the existing revD PMOD connector PCB spacing will be exactly correct. Well, I hope exactly correct.  I also adjusted the position of the other MEGA65-board side connectors to try to get it all right.  This was a bit fiddly to work from the various precise and non-precise sources of information I had, and to then put the positional markings on the bridge board PCB layout as measurements, to make sure that I have everything as correct as possible:

You can also see that I have added place for the ESP32 WiFi module to the middle of the PCB:

I have included a complete cut-out zone for the external antennae, located below the straight and direct traces for the TE0790 relay to the expansion board. Note that I have also added an extra TE0790-compatible connector to this bridge board, to allow easy connection and debug of the ESP32 module. That is, the header labelled "ESP32 JTAG+UART (TE0790)" is for that purpose alone.  I've also added a header for the ESP32 sensor pins, in case that turns out handy for something.

I still need to add a few more break-out pins for the ESP32, so that all the bootstrap pins are exposed conveniently, in case I need to adjust them. I've done that now, as well as adding a dedicated ESP32 UART break-out connector for convenience.

I'm thinking that that is probably about it for the bridge board.  Now I need to add the plumbing to connect this to the expansion board beneath it. That's done as well now, using 3 of the 6 lines to connect UART RX & TX, as well as the WIFI_EN line, which needs to be 3.3V.  That leaves only 3 pins for the C1565 interface improvements, which hopefully will be enough.

So let's look at the C1565 interface improvements.  Ideally we want the ring clock and latch as well as the SERIO data pin to be fast by being directly connected to the FPGA pins.  Three signals, three pins, sounds easy. The trouble is that those are all 5V on the C1565 plug, but 3.3V on the FPGA pins, and the SERIO pin is bi-directional.  But let's start with the easy ones: We can just connect the C1565 clock to the expansion board's existing ring clock.  So that just leaves the latch and data pins. 

The latch is fairly straight forward: It's an output only from the FPGA to the C1565 port, so we just need a 3.3V to 5V level converter. If we were lucky, we'd have a spare buffer on one of the 74LS125's, but of course they are totally fully. So we will need at least one more IC.  But before we just go an add another 74LS125, let's see what we need to make the SERIO line bidirectional. We can do the output direction using part of a 74LS125. But for the input direction we need to level convert from 5V to 3.3V. SERIO is output when the latch line is high, and input when low, like this:

2.5.5. F011 Disk Expansion Port Serial Protocol

     +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +--
     | | | | | | | | | | | | | | | | | | | | | | | | | | | | | CLK
   --+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+

    +---------------------------------+                     +--
    |                                 |                     |  LD
   -+                                 +---------------------+

   --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-
     |LED|MOT|STP|DIR|SID|DS2|DS1|DS0|SPR|DKI|DKC|IND|PRT|TK0| SERIO
   --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-

So we can in fact control the output control of SERIO using LD: When LD is high, then we want the output enable on the 74LS125 buffer for SERIO.  I'll connect that now, and then think about what to use for the input side.  So the outputs are connected, and that uses 2 of the 3 pins, and I still have one pin free for the input.  So I don't even need to do anything fancy to switch the direction if I don't want to. I can just use a simple "always on" input buffer.

Meanwhile, I am hitting a problem with KiCad 7 complaining about the user port and tape port edge connectors.  Older versions of KiCad didn't have this problem, and it is apparently a known false alarm, but still quite annoying. It seems the two options are to ignore the errors in the design rule checker (which I never like doing), or adding a bit of solder mask back between the each pin on these edge connectors, which is also not that great. I guess I'll have to add exceptions to the design rule checker...

Okay, so now that I have the design rules passing again, I can go back to planning how I am going to read the SERIO line. I think I'm just going to add yet another 74LS125 powered on 3.3V rail and with a 1K Ohm resistor on the input from SERIO, to limit the maximum current to 5V / 1K Ohm = 5mA, which should be fine.

Okay, so I think I have SERIO input now correctly plumbed in. The schematic page is getting a bit crowded, though, so it's not as immediately obvious if I have it right or wrong, which I don't like.  So I'll just have to go over it carefully for now.

I'm going to pull out the 1565 port stuff onto a separate sheet in the schematics so that I can reason about it much more easily. This is the result:

You can also see that the WIFI_EN signal for the ESP32 wifi adapter is also generated by this block. This is because it needs to be 3.3V, and I didn't want to waste a whole FPGA pin on a line that might toggle once every time you start a wifi-enabled program.  

I have used the FPGA pin that freed up to separate the C1565 port clock from the ring buffer clock, so that they can be clocked at different rates. This is to allow the C1565 bus to be slower, which will likely be required if we want to allow reasonably long cables to external drives.

The only problem I hit in the above is that I had a +5V symbol lurking spare after I rearranged everything. I can't see where it is missing from, if I did accidentally drag it when copying something to the new sheet. I did check PCB to schematic parity, and nothing showed up. So I still have zero idea where it came from. Maybe I had +5V attached to a single net twice.  Anyway, if there is going to be a problem with the next revision of the board, this is what I am going to guess will be a likely cause. Fortunately if that happens, it will be easy to bodge a blue wire to whatever pin and a pin with +5V.

So, I'm getting towards the end of the list of things I want to address. The very last thing I am thinking of doing for the C1565 port is to address what I see as a missed opportunity by Commodore at the time: The C1565 port has no +5V or +12V, which means that the external drive needs a separate power pack, unlike on the Amiga.  Modern 3.5" drives don't even need 12V, so we only need to include +5V.  But there are no spare pins in the mini-DIN8 connector. 

Fortunately there are 9-pin mini-DIN connectors, so I can just use one of those. This will even still allow use of a genuine C1565 drive if someone has one. All that they would need would be an adaptor cable. But as there are about 2 such drives in existence, I'm not really worried about even that inconvenience.  Switching the connector over wasn't particularly hard -- the biggest effort was finding a variant of the connector for which I could easily find a Kicad footprint.  This probably means I'm not using the cheapest version of this connector, but I can live with that. I might buy a couple of the cheapest ones, and confirm that they still fit in this footprint, as they should be identical. The datasheets look like this is the case.

What would be great for finally testing the C1565 is for me to design up my own C1565 compatible PCB to go with a standard PC 3.5" floppy drive. I might do that soon, but first I want to finish off this expansion board.

This means we are now up to adding that low-pass filter to the A/V output lines.  As discussed above, a 3rd order low-pass filter should be adequate, like this one:

The 500 Ohms represents our source, and the 75 Ohms the monitor it is connected to, so we don't need either of those. Just the two capacitors and the inductor between them. As a safety catch, I'm going to make the filter able to be bypassed, in case I stuff it up somehow. This will also let me real-time see how the filter performs, if I can easily switch it in and out by moving a jumper. This is how I am planning to hook each one up:

I say planning, because the board is getting a bit busy, and I want the filter switch jumpers to be accessible when the bridge board is fitted, and I don't want any signals crossing those JTAG lines that caused me trouble before. And, I'd ideally like all the filter switches in the same place. Oh, and I want to route lines for the audio pin on the A/V jack, too, so that the audio header on the MEGA65 R4+ boards can be fed directly to the A/V jack if desired.

Well, I succeeded in the end, but it wasn't easy, and I'm not totally comfortable that there won't be a lot of cross-talk on the A/V output lines that might compromise signal quality. But we will have to see.  But if the result is not too bad, then I'll be happy enough, as the idea of this board is that you can build it yourself, not get the most perfect picture and sound possible -- the digital video output and 3.5mm audio jacks are there for that.

Anyway, this is how the board looks now, with, I think, everything it needs:

In the end I couldn't get all the filter jumpers in the one place, so one of them is up to the left of the tape port, where it should be exposed even with the bridge board over the top.  The other two are to the left of the two mounting holes that are below the right-hand side of the user-port. Those are together, but are likely to be covered by the bridge board when fitted.  That doesn't greatly worry me, as after testing is done, those jumpers will either be permanently fitted (or not), and quite likely replaced with simple tracks instead.

Other things to observe on this revision of the board compared to the last one, include the movement of the floppy cable header down a bit as I discussed some time back to make the cable origami routing easier.  Also, just above the MEGA65 logo is the new 8-pin header to connect to the peripherals on the bridge board, primarily the ESP32 wifi adapter and to route the audio signals to the A/V jack.

Speaking of which, I have routed both left and right audio to the A/V jack, even though the C64 only supported mono on it. This is done by using the almost never used "audio in" pin on the C64 audio jack as a second audio output pin.

So the bridge board now looks like this:

I have contemplated making this board 4 layer instead of 2 layer, but I'm not sure that it is worth the extra cost for whatever benefit it might give for signal integrity, given that most of the signals are quite slow, or are simply being routed between pairs of connectors (like the TE0790 JTAG relay).  Si I'll leave it 2 layer for now.

So that just leaves the C1565 drive board before I can place my order for the revised PCBs, assuming I want to do that.  It should be a simple board to design, and with no tricky routing, so I'll give it a quick go, and if it takes too long, I'll just stop and place the order for the other boards.

Someone has already made a replica C1565 PCB that works with real C65s (except for formatting, because I believe the index hole sensor is required to know when to enable the write gate). However, they haven't released the schematic.  Also, Bo Zimmerman has the schematic for the original 1565 PCB. The main tid-bid from that is to know that it uses a 74LS125 to tri-state the F_READ line from the floppy drive when the drive is not selected.

I'll start by adding the 34-pin floppy data connector and 4-pin floppy power connector, and then the 9-pin mini-DIN. Actually, we can have 2 of the mini-DINs and allow daisy-chaining, like with the Commodore serial drives. The C65 ROMs don't support more than 2, but it doesn't mean that we can't. The main limitation will be the current draw from the cumulative circuits on the relatively small pins of the mini DIN connector.  But we may as well see what we can achieve.

Next we need to put a tri-state gate on the RDATA line via a 74LS125.  I'll use a 74HC85 4-bit comparator to check whether the C1565 unit is the currently selected one.  Those are active high, while the 74LS125 is active low, so I'll use a buffer from the 74LS125 to make an inverted driver for that. Or I could switch to an active high buffer like the 74LS126 -- but let's see what the other bits of logic we need require, before making decisions like that.

Next up we will need an 8-bit latch. Going back to our 1565 protocol diagram:

2.5.5. F011 Disk Expansion Port Serial Protocol

     +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +--
     | | | | | | | | | | | | | | | | | | | | | | | | | | | | | CLK
   --+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+

    +---------------------------------+                     +--
    |                                 |                     |  LD
   -+                                 +---------------------+

   --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-
     |LED|MOT|STP|DIR|SID|DS2|DS1|DS0|SPR|DKI|DKC|IND|PRT|TK0| SERIO
   --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-

We can see that we need to latch the data word on the falling edge of LD. But the 74LS595 latches on the rising edge. So we need to either invert LD, or find a latch chip that has a negative edge latch. We can probably invert LD using one of the spare buffers in the 74LS125, if we need to.  We also need to gate the SERIO line to only be driven by the drive when LD is low.  The 74LS165 to serialise the drive status bits also needs the opposite sense to work. It really is a pain, in fact, that LD is not the opposite to what they chose, as it would have been quite a bit simpler for the logic decoding. 

The existing replica only needs 4 chips, but that's because they are only doing a single bit of drive ID decoding. I'd still like to keep the option for more than 1 external drive.I could solve this by adding a 5th chip, e.g., a hex inverter, to allow me to invert this signal, but a whole chip to do that is a bit of a pain, when I am only exactly one gate short of what I need.

One option might be to use a 74LS139 2-to-4 decoder, as those have two such decoders, so I could still allow 4 drive IDs (probably the odd ones, 1, 3, 5 and 7), and then use the second one as a make-shift inverter. But maybe I'm just trying to be too darn clever, and should just get over myself and use the inverter.

I think I have a sensible schematic now, and the beginnings of a PCB layout. But then I realised that the reason I was looking at doing this now is because I wanted to be able to make sure the 1565 port would work.  That was an issue when I had some of the control lines on the ring buffers, which would have caused them to be much slower. But now I have it nicely directly connected to the FPGA, so that isn't an issue. 

In short, I can leave that for another time. Which is good, because I've just about run out of weekend. So I'm just going to give the expansion and bridge boards a last going over, and then generate the gerbers to send off for fabrication.

The PCBs have been ordered, so all that's left now is for me to order the last few parts that I don't have laying around, so that they can ship while the PCBs are being manufactured. Normally a 4-layer board takes about 5 days for PCBway to fabricate, and another 4 days or so to ship. So probably I'll have the boards in a couple of weekends time -- which is also about how long it takes for an order to ship from digikey in the USA, because they use FedEx who are a bit slower to get to Australia than DHL from China.

Well, that order is in now, too. So now it's hurry up an wait for a couple of weeks for everything to arrive, and see what silly mistakes I have made on these boards ;) But seriously, I'm hopeful that we are now on the home-run with this, and will have a nice functional and fun expansion board solution for the MEGA65 in the not too distant future... maybe even around the same time that the back-log of MEGA65s ships around the middle of the year...