Monday, 31 August 2026

MEGA65 Multiverse Bitstream Loader

We've got a great problem on the MEGA65 right now -- there are more cores coming out than you can fit in the 8 slots that the MEGA65 motherboard's flash can fit. But we have a JTAG port, so we have the means to push bitstreams in just a few seconds. A lot of us use a JTAG adaptor connected to a PC to do this and other things. But sometimes you just want to be able to do it without a PC or extra external cabling.  Thus this little project.

The core of the idea is to put a microcontroller on a board that connects to the JTAG port that can push bitstreams.  Now, this can't be from the existing SD cards on the MEGA65, because they're connected via the FPGA that we're trying to re-configure. So it will need an SD card slot of it's own -- which is fine, it means you can have potentially thousands of cores on there, and have the MEGA65 be able to request that it load any of them. The request mechanism will be via a UART.  

So we need JTAG, UART and microSD card.  A cheap PicoPi should be able to talk to all those. JTAG and UART are simple, and it has SPI that can talk to a microSD card. I have a pile of microSD card breakout boards around here that will help me to prototype it.

There's even a WiFi enabled version of the PicoPi, so I might even roll this into the expansion board bridge board and replace the ESP32 on there. (I might be able to make the ESP32 do the microSD card and JTAG, too, but I've not got good enough handle on the toolchain to know if that's possible -- but the PicoPi should be a doddle.)

As this is a little utility thing, I'm vibe-coding the firmware for the Pi. It _claims_ to have implemented the UART control interface and pulled in some FAT file system library etc to talk to the SD card, and adapted the m65 utility's JTAG bitstream pushing. So let's see if it can push a bitstream via UART to JTAG.

To do that, I need to work out the pinout from the Pico to the TE0790 JTAG header on the MEGA65's mainboard.

Pico pin 8 white -> TE0790 pin 2 GND
Pico pin 9 grey -> TE0790 pin 4 TCK
Pico pin 10 green -> TE0790 pin 12 TMS
Pico pin 11 blue -> TE0790 pin 10 TDI
Pico pin 12 purple -> TE0790 pin 8 TDO

With that, I can totally push a bitstream over the USB UART of the PicoPi to the MEGA65! Great news!

The not so great news is it takes 107 seconds!

Now, the UART at 2Mbps means ~5 sec per MB, so about 35 seconds for a 6.6MB bitstream, just for UART transport.  But that still leaves a full minute for the JTAG bit-bashing.

Here's the pretty picture ChatGPT drew me for the pin connections from an SD card to microSD adapter to the Pico Pi, so that I can quickly hack up a test cable: 

 

Textually, that's this:

SD pin 1 CS -> Pico GP5, physical pin 7 
SD pin 2 MOSI -> Pico GP3, physical pin 5 
SD pin 3 GND -> Pico GND (e.g. pin 13)
SD pin 4 3.3V -> Pico 3V3 OUT, physical pin 36 
SD pin 5 SCK -> Pico GP2, physical pin 4 
SD pin 6 GND -> Pico GND (e.g. pin 18)
SD pin 7 MISO -> Pico GP4, physical pin 6

Here's our little octopus (ok, heptopus) assembled, ready for testing: 



Now I just have to find a spare microSD card. I'm sure I have numerous laying around, but can't find any. So I'll go to the supermarket and grab one this morning while running errands, format it for FAT, and then see if the firmware ChatGPT built me can read from it.

Meanwhile, this looks like a great switch for the JTAG bus to allow TE0790 to operate normally, except when we want to push a bitstream: SN74CBTLV3257-Q1.

Meanwhile, I went and got a cheap 32GB SD card from the supermarket, copied some bitstreams and core files on... and it works! I can push a MEGA65 core, and it loads. Okay, it takes 18 seconds, which is annoying, but it loads. The AExp core is refusing to load, though, which I'll have to investigate the reason behind. The problem was in the JTAG closing after pushing the core.

Now it works! I can load the AExp core, for example, and see the workbench hand and load disks and things. Nice.

And with a bit of optimising I've cut the bitstream load time from ~18 sec to ~6 seconds, which feels totally reasonable -- that's about how long it takes via TE0790 with m65 -b.  I could get it down closer to 4 or so seconds by trying to speed up the JTAG, but that's not our top priority right now.

Top priority is thinking through security and physical arrangement.

Security matters here, because the UART  that controls the Pico will be accessible via the MEGA65 user mode as one of the buffered UARTs (it will take the place of the ESP32 on the previous expansion bridge board design).  That means rogue software could install and launch a rogue core that then does whatever it wants -- including fry your MEGA65 if it was nasty enough.

The simplest solution is leave the SD card read-only in this module. Then you have to open the case and take the SD card out to add cores. Doable, but not super nice.

So my two key refinements are:

1. Have a jumper accessible via the user-port cut-out that if fitted, allows writing to the SD card on the module.

2. Position the SD card on the module so that it can be accessed via the user port cut out (even if a bit fiddly so), so that no-case-opening adding of cores is possible.

So I think I'll try to design up the changes to the PCB to support all this, and then we can look to proof-of-concept native M65 software that can talk to this.  The MEGA-FLASH program might want to be updated to allow querying of the SD card for additional cores to keep a single unified core launching experience. But that's a question for after we have working hardware.

Okay, so in the meantime I've finished up an initial PCB design, then made corrections to the SPI pinout just hours after PCBWay started fabrication, so added a 2nd order with the fixed version, and then after all that improved the power circuit so that the Pi can't back-power the MEGA65 (PCBs for that not yet ordered -- I might hand-modify the revH PCBs when they arrive to do this). 

Here's the current (revJ) state of affairs:

 

And how the PCB looks:

 

So that leaves the firmware. I've been shamelessly vibe-coding this using ChatGPT, and have something that works well enough for now: It support AT-style commands on the USB serial or direct UART pins, as well as running a little web server that lets you interact wirelessly to push bitstreams:

And it works! It can load a new core in 5 - 8 seconds, depending on size of core and phase of moon.

The key remaining thing that I want to implement is allowing the Pico to download core files from a specially prepared mirror website, so that you can get all the latest cores on demand.

I'm busy bashing my head against this at the moment. First, WiFi doesn't always power up cleanly. Then when it does, sometimes it fails to join the AP. And then there's firmware bugs with the fetch logic on top of that.  Layers of failure that all have to not happen to progress. Quite annoying.

I'm making good progress on this, getting it to download a core file, and have it in a .partial file while downloading, and cleaning up the .partial files when starting a fetch. But the little TCP stack keeps failing in fascinating ways. I'll keep working on that in the background while I think about other bits.

The other main bit I'd like is to add support for hijacking the MEGA65 Serial Monitor Interface to allow loading of programs remotely etc, and wireless debugging. The question is whether this merely _sounds_ like a good idea, or whether it actually _is_ a good idea. 

Okay, we'll go back to the TCP stuff -- and it's finally working now, downloading core files at 500KB/sec from a mirror on my lan. Will likely be slower over the open internet, but it's all nice an automatic. It now has a "I'm busy updating" web page.

Okay, lots of progress to report! The PCBs arrived, and I've assembled one:


_And_ the SD card _and_ the JTAG bitstream loading on the MEGA65 worked first time! :)

That all makes me very happy. What makes me less happy, is that the JTAG-pass through from the TE0790 to the MEGA65 mainboard doesn't seem to be working. It doesn't even recognise the FPGA on the JTAG chain:

$ m65 --autodiscover --verbose
2026-07-26T07:32:04.864Z NOTE MEGA65 Cross-Development Tool 20260124.13-develo-355b567
2026-07-26T07:32:04.864Z INFO using default FPGA ID ffffffff
2026-07-26T07:32:04.884Z INFO found 0 candidate USB devices.
2026-07-26T07:32:04.884Z INFO requested id ffffffff does not match any usb interface
2026-07-26T07:32:04.884Z CRIT no matching jtag port found, aborting

There's a few possible things that could be wrong here:

1. I have the pinout messed up.

2. The JTAG bus switch is doing something dumb

3. The extra electrical load of the JTAG bus switch is slowing things.

(3) I can check by dropping the JTAG frequency.

I think I might just have spotted part of the problem. Can you spot it in the following schematic snapshot?


 

Let's just say it would be nice if Kicad had a spell checker ;)

Well, except that I had it wrong everywhere which means that it was fine. 

So why aren't I seeing 3.3V on the JTAG header power pins? Okay, forget that. I just had the MEGA65 turned off :/

Time to trace from the TE0790 back towards the MEGA65.

The TCK line is at 3.3V on pin 3 of the TE0790 header. But it's like 0.5V at the input TE0790 relay header on the expansion connector board. This is odd, because those two pins should be directly connected.

Okay, I've definitely screwed things up: On the old connector PCB I apparently flipped the order of the pins of the TE0790 relay to make routing it out on the expansion board easier.  But on the new one I haven't done that. Which means that pins are totally wrong, fortunately not in a let-the-smoke-out kind of way. But certainly in a no-hope-of-the-jtag-working kind of way.

I've fixed that on the schematic. It would be nice to be able to test the electrical path without having to wait for a new PCB spin, but it's not really practical.  The main thing is that I've found a highly probable cause, and can test it out easily once I get the new PCBs spun.

If I'm going to get a batch of those PCBs, I should also get a batch of the M1565 external drive boards made, since I can't find the ones that I'm sure I had made previously. It's a 100x100 2-layer board, so it is only like US$5 to get another 5 made.  The question is whether I have the parts for assembling one lurking around, too, and if so, where on earth I've stashed them. 

I do hate having to prepare BoMs from schematics. So the sooner it's started the sooner its done. And if I order everything today, it should show up by next weekend, all going well.

Digikey parts: 

5mm LED - 732-5016-ND

1565 mini-din - MD-90SM 

A pile of 3.3K Ohm resistors -- those I almost certainly have on hand.

And 1K Ohm resistors.

8-way DIP switch - CT2088-ND 

74LS125 - 296-1638-5-ND

74LS85 - 296-14900-5-ND

74LS595 - Okay, those are like $30 each in LS series! Fortunately HC are cheap still: 296-1600-5-ND

74LS165 x2 - 296-14885-5-ND

74LS04 -  296-1629-5-ND

Okay, I'll order enough parts to make 3 to get free shipping.

Then I just need a mini-din cable that suits the MD-90SM.

For the cable, I also hate soldering mini-DINs, so I'm just going to buy a random one off ebay. This one claims Australian stock, so I'll go with them:

https://www.ebay.com.au/itm/336344275528

Well, cable, PCBs and components all ordered. So that's all I can do on this project until the bits arrive.

It's the following weekend, and the revJ PCBs have arrived, along with a batch of M1565 external drive PCBs... which of course means that I found the ones I already had out in the shed this morning when I went to assemble the revJ board.

I also realised I still hadn't updated the outline of the revJ PCB for the microUSB cable access to the Pi Pico. Nor moved the SD card slot back a bit to make insertion/removal easier.  So RevK here we come.

But the good news: The revJ works, and the TE0790 via the relay port now also works! So we have integrated JTAG with the web interface (and it came up on WiFi on first boot :) and a connected TE0790 works again, too, now :)  The JTAG switch is indeed doing its thing -- if the Pico is JTAGing, then the TE0790 sees an empty JTAG chain.

The next step is to see whether we can talk to the Pico via UART from on the MEGA65 itself. It should work, provided I haven't got the UART pins backwards.

My plan is to test the UART using megacom, the little minicom-like terminal program I made and used in this blog post: https://c65gs.blogspot.com/2026/01/megaphone-cellular-modem-and-power.html which currently lives here https://github.com/MEGA65/megaphone-modular/tree/main/src/telephony.

The UART pins themselves are on P1HI(2) (RX for MEGA65) and P1HI1(1) (TX for MEGA65). But either I'm not using the right bitstream for the MEGA65 core, or megacom doesn't work, because I don't see any UART signalling on either pin when I use MEGAcom to talk to buffered uart 0, which is what (I believe) it's mapped to. And it's not mapped to any of the higher numbered buffered UARTs (just tried them all in MEGAcom).

I can't see what the problem is, but suspect I might be using an old bitstream. So I'm rebuilding a 932-* bitstream, and we'll see if that helps.

Okay, I'm back investigating this again after too long a break.

First up, it looks like the PMOD pins were declared high-impedance above where they get assigned to the UART plumbing. That matches what I'm seeing, in that the pins are floating, rather than driven. So I'll cook a bitstream to test that and see if that gets those pins behaving: Specifically TX pin should be driven high, and the RX pin should still be floating.

First attempt didn't fix it. I'm suspicious that Vivado might be caching some build thing, so I'm re-running it now with make clean first.

Nope, the pins are still floating.

And I'm not getting anything useful out of the keyboard-connector 2nd UART, either. So I'll put a super-simple loopback on the PMOD pins. If that doesn't work, then there's something really really wrong going on.

Well, connecting clocks to the pins I've at least allayed my fear that I had fried those FPGA pins.  So now to work my way slowly back up from this foundation, first by looping back the pins in VHDL, and then after that connecting them to the buffered UARTs.

I think part of the issue is that I'd assumed the p1hi pins were the top row on the PMOD. They're not. They're the bottom physical row which is the high-numbered pins. That probably explains a lot of my headless-chicken chasing.

And that was indeed the root cause. So I have buffered UART 0 talking again on the PMOD, and buffered UART 1 on the keyboard connector.

So the next step is to finally see whether we can talk to the Pi Pico via the PMOD relay to the expansion board. And it looks like I have the UART pins on the PMOD backwards. So I'll fix that, and then check if I can finally talk to the Pi Pico.

Ok -- confirmed that the pins have switched. Now to see whether I can get the Pi to talk to me via the UART.

Hmm.. I got something at 2Mbps, but it was quite garbled.

That said, I can get the same garbled output by typing ATI + CRLF into it repeatedly. 

Yup, looks like it's receiving commands from the UART just fine, but the output coming back is all messed up. 

I've just fixed a bunch of bugs in the Pi Pico firmware, and now AT+JTAGLOAD= works to load a core from the MEGA65 :)

I should probably make a native utility that reads the list and lets the user pick one to start.  I'll probably try getting an LLM to make the proof-of-concept code for that, based on the megacom stuff. It won't be particularly elegant, but it will be a basis on which a real human can build something better. Ideally it would eventually be integrated with the NO SCROLL on boot core loader. And maybe some little program you can put on a floppy, and have it autoboot using the C65/MEGA65 ROM's autoboot.c65 file thing. That way you could have a "kickstart" disk that when inserted and you reset the machine really boots it into an amiga.  And another for C64 mode etc.

Anyway, here's the little thing the LLM hacked up for me from the megacom source (that had the necessary UART code in it). The source is in https://github.com/MEGA65/megaphone-modular/blob/main/src/telephony/jcore.c.

So at this point, we have all the bits we need. 

And that feels like a good place to end this post.  The next one will probably be tackling some of the MEGAphone stuff that all this UART work has setup.

 

Sunday, 18 January 2026

MEGAphone Hardware Module Progress Update

While I've been focusing on the software and case of late, work has been continuing on the hardware modules as well, as you can see below:

 

This post is really to give an check-in on everything that has got us to that point, and show the progress against the milestones -- and focusing just on the milestones.

k.2.2.4 Low-energy power-management module to minimise energy consumption of device, especially in low-power modes:  PCB Fabrication

l.2.2.5 Low-energy power-management module to minimise energy consumption of device, especially in low-power modes: Assembly

m.2.2.6 Low-energy power-management module to minimise energy consumption of device, especially in low-power modes: Testing

n.2.2.7 Low-energy power-management module to minimise energy consumption of device, especially in low-power modes: Revision

These are pretty simple to address: We've described the testing of the FPGA that sits on the carrier PCB in this blog post: https://c65gs.blogspot.com/2025/12/megaphone-power-management-fpga-and.html 

All that's left is to confirm that the carrier PCB functions correctly once assembled. Here it is assembled:

And... it doesn't work.  

After a pile of testing to figure out how a module that is mostly just 1:1 pin connections could be wrong, I discovered that I had wired one of the ports backwards. This means that pins A3 and B3 that are the USB UART are tied to 3.3V and GND, respectively.  Not conducive for a working UART.

Okay, fixed that now:

The correcting commit is https://github.com/MEGA65/megaphone-modular/commit/7a2151f767eb1be3f8f2eaac9641930a6c5fc54e, and the source files are at: https://github.com/MEGA65/megaphone-modular/tree/main/modules/icesugar-nano-carrier

s.2.3.5 Battery management and energy harvesting module to allow USB-C, intergrated solar panel and external 12/24V vehicle battery power sources, and efficient management of the integrated rechargeable battery: Assembly

t.2.3.6 Battery management and energy harvesting module to allow USB-C, intergrated solar panel and external 12/24V vehicle battery power sources, and efficient management of the integrated rechargeable battery: Testing

This one is also fairly simple to report on, but with less positive news.

We've assembled the module, but despite considerable testing and attempts at finding the problems with it, it refuses to work. This module was based on an existing open-source design, hence the "module in module" appearance.  We've now come to the conclusion that we will need to completely redesign this from scratch using the datasheets, so milestone u.2.3.7 is not yet achieved. 


aj. 2.6.2 External speaker and bluetooth headset interface module: Schematic

ak. 2.6.3 External speaker and bluetooth headset interface module: PCB layout

al. 2.6.4 External speaker and bluetooth headset interface module: PCB fabrication

am. 2.6.5 External speaker and bluetooth headset interface module: Assembly

These two modules have been designed and assembled, as we can see in the images below, based on the schematic and PCB designs on github: https://github.com/gardners/mega65-megaphone-hardware-modules/tree/main/modules/bluetooth


at. 2.7.4 Primary FPGA carrier module, leveraging off-the-shelf FPGA modules if at all possible: PCB Fabrication

au. 2.7.5 Primary FPGA carrier module, leveraging off-the-shelf FPGA modules if at all possible: Assembly

av. 2.7.6 Primary FPGA carrier module, leveraging off-the-shelf FPGA modules if at all possible: Testing

aw. 2.7.7 Primary FPGA carrier module, leveraging off-the-shelf FPGA modules if at all possible: Revision

This module has been assembled and confirmed to work. Again, it's a fairly simple pin-mapping module.  Schematic and PCB layouts in github at: https://github.com/gardners/mega65-megaphone-hardware-modules/tree/main/modules/te0725-carrier

 

ay 2.8.2 LCD panel interface module: Schematic

az 2.8.3 LCD panel interface module: PCB Layout

ba 2.8.4 LCD panel interface module: PCB Fabrication

bb 2.8.5 LCD panel interface module: Assembly 

The schematic and layout are in github as usual at https://github.com/gardners/mega65-megaphone-hardware-modules/tree/main/modules/lcd-panel, and the images below show the fabricated and assembled PCB, and confirmation of fit-testing with the LCD panel.


bf. 2.9.2 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: Schematic 

bg. 2.9.3 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: PCB Layout

bh. 2.9.4 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: PCB Fabrication

bi. 2.9.5 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: Assembly

bj. 2.9.6 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: Testing

bk. 2.9.7 Auxiliary communications module, e.g., LoRa, ultra-sonic, infra-red or mm wave: Revision

This is another of the simpler modules, with just direct pin-to-pin mapping. The image below shows it assembled into the main carrier board.  Testing was performed by ensuring that the pin mappings are all valid.  Schematics and PCB layout as usual in gitlab at: https://github.com/gardners/mega65-megaphone-hardware-modules/tree/main/modules/lora


bm. 2.10.2 Main carrier board and assembly, into which the various modules connect: Schematic

bn. 2.10.2 Main carrier board and assembly, into which the various modules connect: PCB Layout 

bo. 2.10.2 Main carrier board and assembly, into which the various modules connect: PCB Fabrication 

bp. 2.10.2 Main carrier board and assembly, into which the various modules connect: Assembly

Here we again have mixed news: The carrier board has been assembled, but it's clear that we need substantial revisions on this, in part due to having placed some module recepticles up-side-down.  We will need to work out a way to make that harder to do by mistake when designing with this modular system.  Schematic and PCB layout on github as usual: https://github.com/gardners/mega65-megaphone-hardware-modules/tree/main/modules/main-carrier














 

Friday, 2 January 2026

MEGAphone: Cellular Modem Voice Circuit Control

Okay, so I have SMS working already, and a nice library to let me send and receive them, complete with emojis and international characters etc.  But what I don't yet have is the voice call control working in the same way.  The reason being that the EC25 module I have, while supporting 4G, lacks VoLTE support for the Optus Network in Australia.

I've poked the support at Quectel who make the modem, and I have a new firmware build for the modem from them which apparently supports VoLTE.  So let's try that.

Except to do that we have to get the modem into Emergency Download Mode (EDL).

I tried tying the BOOT pin to GND (=normal boot) or to 1.8V. Tying it to 1.8V gets some other situation, but I don't know if it's actually EDL mode. At least QFirehose still fails to talk to it.

So it turns out you can't do it over the UART pins -- it has to be via the micro USB port on the evaluation board.

With that, it was easy to do the firmware update. So now let's try to enable VoLTE.

So, the firmware still lacks an Optus VoLTE profile -- but it does have a Telstra one.

After some poke and fiddle, I now have the modem such that it will receive phone calls, but trying to make one still results in NO CARRIER.  This could be because of differences in the VoLTE profiles between Optus and Telstra, but it still strikes me as odd.

To get it to this state I had to use:

at+qmbncfg="AutoSel",0

at+qmbncfg="Select","Telstra-Commercial_VoLTE"

at+CFUN=1,1

<wait for the modem to reboot>

Then check that this command:

at+qcfg="ims" 

Returns +QCFG: "ims",1,1 instead of +QCFG: "ims",1,0.

So there probably isn't any change to the MEGAphone software required for this -- it's just a once-off modem setup.  We could make a MEGA65 native utility to set this all up, though. And perhaps we should use at+qcfg="ims" periodically to get the 3G/4G/VoLTE status and show that on the status bar, so that if this is a problem for others, they can at least see what's going on.


 

Okay, so the solution to that is to put a ; at end of the dial command to tell the Quectel modem that we want a voice call, not a data call.

So I now have working in-bound and out-bound calls :)

Requirements Specification 

So I can finally get back to implementing our call state management library that will meet the following requirements:

1. Provide a mechanism to dial a number to establish a call.

2.  Monitor the modem through +QIND status messages to tell the software what call state we are in.

3. Provide a function to accept a call.

4. Provide a function to hang up or reject a call.

5. Not allow the establishing of a second call while one is already in progress.

6. Periodically issue AT+qcfg="ims" and report current VoLTE status. 

7. Provide a function to query the current microphone and speaker gain for the handset.

8. Provide a function to set the microphone gain.

9. Provide a function to set the speaker gain. 

10. Provide a function to set the side-tone (i.e., "fold back from microphone into headset so you have a sense of being heard by the phone" gain)  

11. Provide a function to mute a call.

12. Provide a function to unmute a call. 

It probably makes sense to expand the existing modem library I've made to include those functions, rather than split the library, when both need to be parsing modem responses etc.

So let's start with the functions to establish, accept and end calls, since they're nice and easy.  As are the mute, unmute and the various set gain functions.  That gets us 1,3-4,7-11.  We'll tackle the remaining ones in turn, starting with parsing for +QIND and other relevant messages.

We need to parse for +QIND messages that indicate call state, as well as +QCFG messages that tell us our VoLTE status.  The place for this to happen is in modem_parse_line() where we already look for ERROR, OK and +CMGL.

The +QIND messages for ccinfo have this format:

+QIND: "ccinfo",<id>,<dir>,<state>,<mode>,<mpty>,<number>,<type>[,<alpha>] 

 Examples I've seen are like this:

RING

+QIND: "ccinfo",4,1,4,0,0,"+61870106565",128
ata
OK

+QIND: "ccinfo",4,1,0,0,0,"+61870106565",128
ath0
OK

+QIND: "ccinfo",4,1,-1,0,0,"+61870106565",128
 

and

 

atdt+61870106565;
OK

+QIND: "ccinfo",3,0,3,0,0,"61870106565",129

+QIND: "ccinfo",3,0,0,0,0,"61870106565",129

+QIND: "ccinfo",3,0,-1,0,0,"61870106565",129

NO CARRIER
 

The meanings of those fields are documented with the AT+CLCC command, e.g., here

But the run-down of the fields in order is:

ID : Internal call number, e.g., for using with AT commands for handling conference calls etc.

DIR : 0 = call from the phone, 1 = call to the phone.

STATE : 0= active, 1= held, 2=dialing (outbound calls), 3=alerting (inbound calls), 4=ringing (inbound calls), 5=waiting (inbound call). There is also the undocumented value "-1", which means "call just ended" as far as I can tell.

MODE: 0=voice, 1=data, 2=fax

MPTY: 0=not part of a multi-party/conference call, 1= it is part of one

NUMBER: The phone number

TYPE: Type of phone number: 129 = unknown type, 145 = international number (beginning with +), 161= national number. Looks like 129 is international number without the +.

ALPHA: Text of contact name if the number matches an entry in the phone's internal contact list (which we're not using).

So the DIR and STATE numbers are important for us to determine the actual call-state of the phone, and NUMBER and TYPE for getting the phone number of inbound calls.  The rest is probably not that relevant for us.

Okay, I have the call state parsing working.  The modem by default doesn't play a "calling tone" when you dial, nor when the call is being connected.  The EC25 has support for playing a single tone, and I may eventually use that to send pulses of tone to indicate ringing. But for now, I'm using a much more fun feature of the modem: It has a built-in text-to-speech reader!

So now when you dial, it literally says "dialing" in your ear. And then it says "ring ring" when the far end starts ringing.

Ironically on the real MEGA65 hardware it will be easier to put timed tone work in here, case we have the video frame counter to keep time, but in the standalone Linux test program, that's obviously not available.

I've also put in some code to make sure that a previous command finishes before we send the next command to the modem, so that no commands get lost, and generally fixed a number of call handling bugs.

But I can now establish and hang up a call.

So now to implement waiting for an incoming call -- which was super simple.

That just leaves checking the VoLTE state periodically.  The easiest way I can think to do that, is to do a randomised check when modem_poll() is being called, but no command response is pending, and with some low probability query the VoLTE status, so that it happens often enough.  

For now, I'll just implement a modem_query_volte() command.  This will work asynchronously -- i.e., you will have to call modem_poll() and then it will parse the response whenever it comes and update the shared.volte_enabled variable.

The question is how to demonstrate this? I'd go for a video, but the only practical ways I can show it working will need to show my phone number.  So let's instead take a look at the API, some of which will be familiar from the SMS API post:

char modem_init(void); 

Initialise the modem for operation.

char modem_poll(void);

Poll the modem for any responses, or to get a line of input from the modem for custom processing.

void modem_parse_line(void);

Automatically called by modem_poll() to process a line from the modem, e.g., a +QIND line.

char modem_place_call(void);

Establish a call, and set the call state accordingly to the CALLSTATE_CALLING state.

void modem_answer_call(void);

Answer an in-coming call and set the call state to CALLSTATE_CONNECTED.

void modem_hangup_call(void);

Terminate (or reject) a call and set the call state to CALLSTATE_DISCONNECTED. Also cancels the mute state.

void modem_mute_call(void);

Mute the audio channel in a call.

void modem_unmute_call(void);

Cancel the muting of the audio channel in a call.

void modem_toggle_mute(void);

Toggle the mute status of a call (convenient for just having a MUTE button that toggles).

char modem_set_mic_gain(uint8_t gain);

Set the microphone gain: 0 - 255, 255 = loudest.

char modem_set_headset_gain(uint8_t gain);

Set the headset gain: 0 - 255, 255 = loudest.

char modem_set_sidetone_gain(uint8_t gain);

Set the side-tone (microphone to speaker "comfort" loop) gain: 0 - 255, 255 = loudest.

void modem_query_volte(void);

Check whether the modem's cellular connection supports VoLTE.

void modem_query_network(void);

Request that the modem provide the name of the cellular network we are connected to.


Okay, so that's the API described. This lets us do things like this:

 $ bin/modem /dev/ttyUSB1 115200 init sidetone=50 headset=100 mic=100 network volte callrx
make: 'bin/modem' is up to date.
INFO: Setting side-tone level to '50'
INFO: Setting headset level to '100'
INFO: Network name is 'YES OPTUS'.
INFO: VoLTE is enabled.

Then it will sit there until a call is incoming, because I asked it to wait for a call with callrx. Then when that call comes we get something like this:

Answer incoming call from '<number redacted>'
INFO: Notifying user of changed call state. 
 

Okay, so that more or less confirms that. 

And here's a short video showing it in action, with numbers redacted via a bit of shell-scripting magic: 

 


 

 


MEGAphone Cellular Modem and Power Control Hook-up for R3 Boards and Phone Call Handling

For the current MEGAphone prototyping, I am using the MEGA65 R3 mainboards. The R3 specifically has on-board speaker drivers that I'm going to use for the ringer.  Those drivers do have a bit of a heat-generating issue with some speakers because the audio is not filtered from digital before it hits them, but for ring indication purposes they should be just fine. 

Anyway, whichever main board revision I use, I need to set it up with two UARTs: One to the cellular modem, and the other to the power control FPGA board.

I'd like to do that in a way that's compatible with the MEGA65 expansion board configuration, which means I can only put one of those UARTs on the PMODs: The one that would otherwise go to an ESP32 WiFi module -- or now, to a cellular modem.

But first, a sneak-peak of where I get to:

 

Looking at the board layout, there are three likely places I can find the extra 2 GPIOs I need:  The keyboard connector, J17 or J21.

J21 has a bunch of GPIOs that are connected to the MAX10 FPGA that's on the R3 boards but not on the R6 boards. So I could use them, but to do that I have to reflash the MAX10, which is just a bit fiddly -- but doable.

J17 is the JTAG connector for the MAX10 FPGA, so similar issues there, except that it's harder to override the use of the JTAG pins.

The keyboard connector has the JTAG interface to the keyboard's FPGA (yes, everything on the MEGA65 is handled by an FPGA of some sort -- but I promise, there's only these 3 ;) But it also has 3 GPIOs for talking directly to the keyboard, and the JTAG pins on it are switchable via a JTAG enable line.  But those pins also route through the MAX10 FPGA.

So it looks like I'll have to use the MAX10 FPGA one way or another. Well, except that for the R3 board, we already have the MAX10 relay those JTAG pins.

Okay, so here's my solution, which is only _mildly_ horrid:  We make a funky keyboard cable that splits off the "JTAG" pins to the UART, and keeps the keyboard protocol pins going to the keyboard.

Importantly, this trick can also work on the MEGA65 R6, where the MAX10 is absent. So that gives me a board revision independent solution, where the only difference is what drives the ringer / internal speaker.

Okay, sounds like a plan.

So I just need to hook up the two UARTs to the MEGA65's buffered UART (I'll do it for R3 -- R6 targets) in a way that's compatible with the expansion board pinout for the UART on the PMOD.  Should be super simple. 

Tracking it with an issue: https://github.com/MEGA65/mega65-core/issues/932.

Let's see if I have it right on the first commit for any or all of the R3-R6 targets.

Now to do this, I'm going to need to go and get some wire and crimpers to make the cables (and also some of the other internal cables in the brief-case MEGAphone prototype). So a trip to Jaycar is in order.

Well, one typo on all targets. R3 builds now. R4 was broken, but for some other reason. Fixed that, and it's synthesising now.  R5 and R6 are effectively the same target, so I'll just build R6. It's building fine, so I'll go and do a couple of things in the meantime.

Okay, one pin placement error for R5/R6 -- fixing that one, too.

While that runs, I might go and get the bits and pieces I need to more easily make the cables I'll need for this and for generally assembling the MEGAphone prototype with full wiring.

I was hoping to get loose dupont connectors and crimpy bits, so that I could just make up cables of the right lengths. But Jaycar only sells the pre-made 15 or 20cm long cables. So I'll have to cut them, solder in extensions and heat shrink them.  It's just extra annoying work.  But it will be manageable.  I should also solder on the headers to the power management FPGA board that I've been studiously dodging by shoving male dupont connectors through the PCB holes and only having them fall out from time to time.

Let's start by looking at the keyboard connector and work out what needs to go where, and then I can pull out the soldering iron.

 

So we need 3.3V, GND and K_IO1 -- K_IO3 for the keyboard, so that's pins 1-2 and 8-10 that have to pass through to the keyboard.  For the UART we need only pins 6 and 7.

So I've mode my extension cable for the keyboard, and now also soldered in extensions to 8 dupont 0.1" header leads.  I'm still a bit annoyed that Jaycar doesn't have the raw parts for those, as it took an hour just to make those.  But such is life.

Now in theory I have a MEGA65 bitstream that has the two UARTs plumbed to the PMOD and keyboard JTAG pins.  The next step is to test them and that the buffered UARTs are working as I expect.  I'll need a simple API for managing the buffered UARTs, so let's start with that, and then make a simple "MEGAcom" program that can work with them. I'll loosely base it on minicom.

The API for our purposes just needs these functions:

uint16_t modem_uart_write(uint8_t *buffer, uint16_t size);

uint16_t modem_uart_read(uint8_t *buffer, uint16_t size);

char modem_setup_serial(uint8t_ port_number, uint32_t baud_rate);

Okay, got those implemented.  Now to work on the simple terminal program. I've hacked up a simple 80x50 text mode driver and some routines to draw boxes and make some simple menus. The actual serial port control code isn't in there yet, but it should be trivial to plumb it in.  As described above, I've gone for making the structure mirror minicom so that muscle memory Just Works :tm:

 



Not bad looking, if I say so myself :)

Okay, so now I've got the serial port control in place, and with a bit of fiddling, I can even send data to a connected UART. Yay!

But for some reason nothing is being received.  And that remains the case when I enable the UART loopback mode, which should cause local echoing of bytes I send... Ah, except that loopback mode is funny -- it connects the buffered UARTs to each other.

Anyway, once I had realised that, I can see that there seems to be something coming in. So it's possible that I have the plumbing for the UARTs wrong on the PMOD.  Let's try the keyboard connector.

Hmm.. No sign of life on there yet. But it's the middle of the night, and I need some sleep.  But it feels like it should be fairly easy going from here to make it work now.

Well, I stayed up some more, and got the MEGAcom working a bit more :)


Anyway, this all proves that the UART on the keyboard connector is working fine. It's just the PMOD one that's not receiving -- which I suspect is because the pin is set to inout, but hasn't been tristated. I'll resynthesise a bitstream with a fix for that while I get some more sleep.

Okay -- found the problem, I think I had the TX line on the wrong PMOD line -- anyway, with that it works now. So that means we know we have two working UARTs available for connecting the cellular modem and power control FPGA module :)

But after being up most of the night working on this, I need a snooze before I tackle the next part, which will be hooking those things all up, and documenting the pin connections on the various connectors to do so.

To summarise what we need for connections:

1. Cellular modem RX from UART0 (Left PMOD on MEGA65, bottom row, 2nd pin from the right) (purple/yellow wire in the photos below).

2. Cellular modem TX to Power Management FPGA pin E3 (short blue lead in the photos below).

3. Cellular modem TX relay from Power Management FPGA pin E1 to UART0 (Left PMOD on MEGA65, bottom row, 3rd pin from the right) (white wire in the photos below).

4. Power Management FPGA UART pins B3 (long blue wire in the photos below) and A3 (green in the photos below) to UART1 on MEGA65 keyboard cable pins 7 (second pin from left on rear row, when looking from front of the MEGA65 towards the back) and 8 (third pin from the left on the front row, when looking from front of the MEGA65 towards the back).  

5. GND from MEGA65 PMOD (second pin from left, either top or bottom, either PMOD) to cellular modem (black wire in the photos below).

6. GND from MEGA65 PMOD (second pin from left, either top or bottom, either PMOD) to Power Management FPGA (brown wire in the photos below). 

7. (In the phone, but not on my test unit) 3.3V from somewhere to the Power Management FPGA. 

I've started with the cellular modem.  And I think the connection is there, but I'm reading gibberish back from the modem, even though I'm set to 115,200bps as is correct.

Okay, so I did an experiment: I can talk to the cellular modem just fine, e.g., by telling it to dial my mobile phone. But the output from the modem is all messed up.

So what's really weird, is that echoing commands as I type them comes back ok. It's just when there's back-to-back character output that it's a problem, I think... And now it's suddenly completely fine (!!):

 

Was it just a bad connection?

Hmm.. Something more is going on here, as it's varying as to how good/messed up it is. 

Slight baud rate mis-match, perhaps?

We use 40.5MHz/X to get baud rate. 40.5M / 115200 = 351.56, so it's possible that by rounding that down to 351 we're slightly too fast, and as a result pick up the end of the stop bit as the start bit of the next byte, which would explain the behaviour.

Well, except now it's all gone to gibberish again -- even command echo is messed up again. This happened after I moved some of the cables around. So maybe one of them has a bad connection, or a wonky solder joint?

And then without the ATI command echoing properly I just got one perfect ATI response !?

So I'm having a poke at the power management FPGA UART connection now.  That seems to be rock solid. I can send the ? command repeatedly and get a solid slab of sensible looking text back, wit no apparent errors.

Switching the cables around, the problem follows the cellular modem, not the cables. So I think it's something specific there.

What I am going to try now is to hook up the UART relay from the cellular modem to the power management FPGA: It's possible that the drivers on the UART TX line of the cellular modem are a bit weak or off spec or something, so having the signal regenerated in the power management FPGA might just solve the problem.

Yup -- it's rock-solid when it goes through the little Lattice FPGA. 

So let's have some images to help guide the wiring.

In the first two images we can see the keyboard cable made from dupont connectors, with two removed to let me sneak the UART connections in (the blue and green wires that don't follow the rest of the cable up):


(Note that the MEGA65 has an unsoldered second keyboard connector that could in theory be leveraged to allow use of the normal MEGA65 keyboard cable, assuming the JTAG lines don't mind actually still being connected to the keyboard's Lattice FPGA.  As I've reasoned above, I believe that this should be fine, but don't go trying it until I confirm it's safe. (Of course, if you're using a DIY "MK-II" MEGA65 keyboard, that doesn't have JTAG at all, so would absolutely be safe. But unless you're one of the two people who've made one, you don't have a MK-II keyboard -- all retail MEGA65s come with the "MK-I" FPGA-based keyboard).

Don't forget that the purple wire in the bottom of the next image turns yellow in the middle! The yellow wire you can see in the top-left of the image is a red-herring -- just ignore it:

The blue lead in the following image is the short one to the power management FPGA board:

Note in the following photo, the long blue wire to the MEGA65 keyboard connector is the one next to the green, while the short blue wire to the cellular modem is the one on the right next to the white wire.


Okay, so in theory we have the connections we need, and we have the UART communications library all ready, so there doesn't feel like any reason why we can't just plumb it all together, and get the phone software talking with the cellular modem, and, say, show the VoLTE and mobile carrier name in the status bar.

Except... it looks like the weird UART gibberish problem from the cellular modem is rearing it's ugly head again.  I think I need to get to the bottom of that next. My spidey-senses still tell me that there's a timing issue in there somewhere, or at least de-bounce.

I think the first thing I should do is make the UART RX sample mid-bit, instead of at the start, as it strikes me that there can be all kinds of problems with the way I've been doing it to date... except it looks like I fixed that ages ago. So it's not that. And it's de-bounced. So I'm really not sure what's going on here. It's only on the RX side as far as I can tell: The modem hears our commands just fine.

Okay, so I think I might have gotten to the bottom of it: The EC25 Eval Board has an on-board level converter that lifts the 1.8V UART lines to 3.3V. But sometimes that level shifter is having problems.  This explains why the serial output is sometimes fine, sometimes marginal and sometimes rubbish.  Here's some oscilloscope captures the contrast. Contributing to my suspicions here, the UART also feeds two other level converters on the eval board: One for the USB UART interface and another for the DB9 RS232 port.  So it's totally possible that 1.8V UART is sagging a bit sometimes, and the 3.3V level converter gets confused as to what it should be doing.

Anyway, let's look at how this looks in practice: 

Here it is mostly fine, but showing slow rising edges. In this state we get some bit-flips from 1 to 0 (I was probing both sides of a 10W 47 Ohm resistor to see if that helped, but it didn't. And, yes, I know that 10W monster resistor will be somewhat inductive, but I saw the same problems without it, I just didn't take pictures):

But then when it goes truly awful it looks like this:
or this:
When it should be looking reliably like this:

 


So my thinking here is to add a 10K pull-up to 3.3V on this line, so that when the level converter is having a bad day, we still get a clean signal.

So time to make up a little jumper lead with 10K pull-up side-line.  This needs to go on the cellular modem to power management FPGA leg, so that we relay the cleaned up signal to the MEGA65.

I've made the jumper up, and connected it, but with the 3.3K Ohm (I couldn't find my 10K resistors) pull-up not connected, and am now waiting for a bad phase with the UART -- which of course isn't happening now that I want it.  So I'll get on with the integration, and then worry about it when it shows up again, and connect the pull-up to see if it fixes it.

Okay, so back in that world, I'm trying to debug why the phone software is only seeing empty lines from the modem, even though we're clearly receiving, for example, a RING message from the modem:

*** FONEINIT entered
*** FONECLST entered
MODEM CHAR: 0D
Modem line: ''
MODEM CHAR: 0A
MODEM CHAR: 52
MODEM CHAR: 49
MODEM CHAR: 4E
MODEM CHAR: 47
MODEM CHAR: 0D
Modem line: ''

Note that  the modem line is reported as empty ('') the second time, even though clearly we have seen the 52 49 4E 47 bytes = R I N G.  So that's the place for me to start looking.

Well, for whatever reason, it looks like the shared.modem_line buffer ends up with all $00 bytes in it. So why?  They don't get written into the array at all, it seems.

This is all a bit fishy. I've confirmed it's not the write-protection of memory (that triggers a backtrace when violated, anyway).

To make things fishier and more annoying, after receiving the 2nd line, it seems to ignore all serial input, but it hasn't crashed, per se, it continues to update the status bar.  

Okay, weird, it looks like the shared.modem_line thing is at $D0C0... Maybe I've got >4KB in my shared state structure. That could certainly cause problems.

Okay, that's indeed the case: It's almost 5KB! But I can't quite see why.

Ah, the shared resource structures allow for a resource name that's 240 bytes long x 4 fonts, and we have almost 1KB.

I tried increasing the size for the shared memory region, but then FONESMS doesn't fit (there are only about 700 bytes free in BSS, and I need slightly more than that)!

Now, the nuisance is that once a shared resource has been loaded, we don't need the name field anymore, but the shared resource API keeps the whole record around.  It's literally only needed during the shared resource find and open process. 

So I could potentially refactor that, and thus claw back almost 1KB.  It's probably worth it. Done and tracked via this issue: https://github.com/MEGA65/mega65-libc/issues/76

Right, with that fixed, we now have lines being recorded and seen:

...
Modem line: '+QIND: "ccinfo",4,1,-1,0,0,"XXXXXXXXX",128', line len = 0x2B, first bytes = 2B51494E
...
Modem line: 'NO CARRIER', line len = 0x0A, first bytes = 4E4F2043
 

So that's all great. But I'm not seeing call state events be handled. But let's go back a step, and make sure that FONEINIT actually initialises the cellular modem. Yep, that all looks fine. So really what we need now is something to make the modem periodically report things of interest to us -- like network name, network time and signal strength.

Okay, so I have added an automatic polling function that sends the AT commands to the modem every second or so to get network time, the network name etc.  The first of those I've plumbed in with a parser is the network name function... and tada! We can now see the true network name:

Well, I've hit another nuisance here as I implement the rest of the modem response scanning and status bar update stuff: I've run out of program size again in the SMS program.  I'm tempted to just not build that for the moment, and finish getting the modem response stuff in. That way I'll know just how short I am of space, rather than playing whack-a-mole repeatedly.

Okay, so now I have the network time parsing done.  Let's move onto the network signal level. Got that, and VoLTE indication. So now it's onto the call state management stuff.

I've now plumbed in RETURN to answer or hangup a call -- and that works -- I've now got it so that I can accept a phone call... And make calls. There's still a bunch of rough edges to sort out, but it basically works for phone calls now :)

Here you can see it in action, after I added some fixes for handling calls without caller ID:


 

But let's go over what getting us to being able to place and receive calls achieves in terms of milestones:

Milestone: j. 1.3 Telephony software: Basic Communications With Cellular Modem: Implementation

Demonstrated by the correct display of the cellular network name and time, among other things. 

Milestone: k. 1.3 Telephony software: Basic Communications With Cellular Modem: Testing

Demonstrated by getting the display of the status bar information as well as the placing and receiving of call sequences (private video is available for NLnet Foundation for verifying this). 

Milestone: l. 1.3 Telephony software: Basic Communications With Cellular Modem: Revision 

Documented in this blog post and in the source code, as I've gotten everything working to this point. 

Milestone: y. 1.7 Integration testing and remediation of software: Requirements Specification 
Milestone: z. 1.7 Integration testing and remediation of software: Implementation
Milestone: aa. 1.7 Integration testing and remediation of software: Testing
Milestone: ab. 1.7 Integration testing and remediation of software: Revision

These four milestones have been addressed in this blog post and in the source code, as I've gotten everything working together: The cellular modem interface software, the GUI, the call state management etc.