Sunday, 7 December 2025

MEGAphone Contact list and Dialer Integration

Okay, in the last post, I got the dialer and contacts screen and SMS thread handling etc, all largely in place and tied together.

We're on the home-stretch for the user-interface side of the telephony software, but there are a few things left to implement/do:

First up, we need to split the binary, as I keep hitting running out of RAM for the program in the bottom 64KB.

Second, I need to implement the ability to select a contact to call them, i.e., having it copy the name and number across to the dialpad side.

Third, I need to finish implementing the telephony state management, so that the phone knows when it should ring etc.

Fourth, we need a ring-tone of some sort, for testing at least.  There's a whole can of worms as for how I want to manage that down the track, but for now, we'll just stick a SID file at $E000-$EFFF and do a bit of ROM banking.

So let's get started with binary splitting. I think I'm just going to use #defines to conditionally include the various functions, so that it's easier to share some of the stuff.

We do need a way to keep track of state between the various binaries. I'm thinking that I'll define a nice little structure that we can hard-code it's address to, say, $0200, and any state that needs to be shared can go down in there --- e.g., dialpad partially entered number, currently selected contact, telephone state etc.  It shouldn't need to be very much.

I've created that in shstate.h, and made just dialer.c use it for it's shared state initially. But now the program crashes when I type digits into the dialer.  So I must have stuffed something up.  The structure is at 0x0200, and it's outputting asm that targets there.  But the stacktrace is also a bit weird, making it hard to figure out the real cause.

Interestingly it is not the memory write-protection that's triggering it. Rather it thinks an NMI is being triggered. I was accidentally stomping on the $031x vectors, and I'd rather hoped that fixing that would solve the problem. But it hasn't.

The backtrace seems to show that the problem is happening in our __cyg_profile_func_enter() handler that does the backtrace storage. Or rather, it happens on the JSR to that routine -- which feels totally bonkers.  The alleged BRK instruction is at some low ZP address, e.g., $0008, which suggests that it's jumping somewhere silly, or the NMI handler is being called some other way, and it just looks like it's happening there.

Nothing untoward seems to happen in the profile enter function, looking at the assembly language. Found it -- the KERNAL IRQ is doing C64 keyboard scanning type stuff, and didn't take kindly to me stomping all over $02xx.  So now I've restricted myself to the tape buffer area at $33C - $3FB, and it's working better.

So let's continue with the splitting, then... 

The obvious first line to split down, is between the contact list and the SMS thread screens.  We just need to have the two binaries call each other when switching views.

Ideally, they shouldn't try to redraw the entire screen when switching, so we want to suppress the whole video setup stuff, which of course can just go in it's own separate binary that does that setup, then effectively throws away the code. 

Ah, yes, and we need to unplug our IRQ and NMI catchers before switching binaries. We can do that with a JSR $FF8A.  With that, and some general bug-fixery, we now have FONEINIT.PRG doing the screen setup and then launching FONEMAIN.PRG from the SD card :)

Now we just need to work out how we intend to split the programs.  My current thinking is:

FONEINIT -- Screen initialisation etc

FONEMAIN -- Contact list display and telephony state management

FONESMS -- SMS thread display and editing and telephony statemanagement

FONESMSW -- SMS "write" (i.e., store SMS draft into the thread, update indexes etc, then return to FONESMS or whichever caller was indicated in the shared state area)

I'm sure there will be more than this needed, but that should get each small enough to leave for the telephony state management stuff to be included in each.  There may yet need to be a "telephony event" program, but we'll tackle that when we get to it.

I have it somewhat working, but llvm-mos's C64 CRT0 bootstrap routine makes a call to $FFD2 to set lower-case mode.  I have two choices: Find how to change the CRT0 routine, or just spoof the vector at $0326 that $FFD2 jumps through, so that it does nothing :)

That gets it a bit further, but it still bombs out, looking like it hits a BRK or NMI.  So need to figure out what's causing that. 

Putting a trap on $FE66, the default $0316 vector shows that it is being trapped. So apparently a BRK instruction. But weirdly, if I single-step, the problem never occurs -- it only happens when the CPU is free-running. 

Well, it gets weirder. Today it's triggering a RUN/STOP-RESTORE type situation, but it doesn't hit $FE47, which is where the $0318 vector points.

I'm wondering if llvm binaries don't like being loaded the way I'm loading them. Or if BASIC or the KERNAL are winding up in some messed-up state.  

There is still really something cooky going on, though, because what happens depends on whether the CPU is single-stepping or free-running.  t1 and single stepping doesn't crash. tc with free-running single-stepping also doesn't crash. But t0 to set the CPU going loose totally does crash.

So now I'm putting infinite loops in the initial code in the binary so that I can catch where the crash happens.

Disabling IRQs in the helper loader seems to get it to go a lot further, which makes sense, since single-stepping generally disables IRQs.

With that, it looks like it actually loads the next program, but that then crashes with a report from our traceback code:

>>> NMI/BRK triggered.
 A:4A X:00 Y:08 Z:00 P:B0 S:E0
  BRK source @ 2A44 dialpad_draw_button+0x0209
Backtrace (most recent call first):
[03] 0x0C64 main+0x01A0, SP=0x0C26
[02] 0x0A3F lpeek+0x000D, SP=0x0A85
[01] 0xA504 __mulhi3+0x783B, SP=0x2905
[00] 0x850A __mulhi3+0x5841, SP=0x260B
 

But then with another run:

>>> NMI/BRK triggered.
 A:20 X:3E Y:02 Z:00 P:30 S:DE
  BRK source @ 0A18 lpoke+0x0001
Backtrace (most recent call first):
[03] 0x0C64 main+0x01A0, SP=0x0C26
[02] 0x0A3F lpeek+0x000D, SP=0x0A85
[01] 0xA504 __mulhi3+0x783B, SP=0x2905
[00] 0x850A __mulhi3+0x5841, SP=0x260B
 

So it looks like it's not the program, but some interrupt that's happening.  So what on earth could be enabling and NMI? Not many things in the MEGA65 can generate an NMI.

I've added a Makefile target that use mega65_ftp to push the updated files onto the SD card and then resets and runs FONEINIT.PRG, so that I have a simpler work-flow for tracking this down.

Let's find out if it's a BRK or an NMI that's triggering this for starters.  Well, it turns out it was my memory write protection stuff, by the look of things. I have it setup to auto-protect the heap and RODATA of the LLVM compiled binaries -- which is ostensibly a good thing -- except when you switch binaries and the old write protect region is being used while the new binary is trying to set things up...

Okay, with that fixed, the binaries are being loaded now. But the display is being messed up a bit, specifically the status bar and the selected contact / dialed number bit above the dial-pad. It does look like the SMS display executable is being loaded, even though it should be staying in FONEMAIN which has the contact selection loop... Except that it seems that FONESMS is actually the one that actually has the contact list.  So let's rename them, and make this all a bit more logical.

Done. Now, investigating the display glitches, this looks like the problem is that the glyph cache is not in the shared state area. The problem is that it's too big to fit in the cassette buffer where I have stashed it.  The whole thing is 3KB.  A nice solution would be to tell LLVM that it can't use $C000-$CFFF, and then I instantiate that whole area with my complete shared state structure.   That just requires me to mess with the linker script thing.  

So now we're kind of back to where we were before hit the program size issue.  So let's not get it working so that we can select a phone number to dial from the contact list. We also want to be able to edit the dialing number field. 

Done -- we can now press F1 to load a contact's number into the dialer field.  Maybe I should make it so that if you press F1 a second time, it tries to commence the call. I can do this as part of the whole call state mangement, which it's high-time we implemented.

We have the following states defined right now:

#define CALLSTATE_NUMBER_ENTRY 0 
#define CALLSTATE_CONNECTING 1
#define CALLSTATE_RINGING 2
#define CALLSTATE_CONNECTED 3
#define CALLSTATE_DISCONNECTED 4
#define CALLSTATE_IDLE 5

Let's work on a simple state diagram, so that we can make sure that this makes sense.


So we probably don't need IDLE. And DISCONNECTED becomes NUMBER_ENTRY whenever we modify the number to dial.

I've implemented some stub functions for the various modem functions (like call, answer, reject etc), and added a timer that we can use to timeout from attempting to make or receive a call, so that it then moves back to the DISCONNECTED state.

The dialpad buttons are supposed to change as well, when the call state changes, but that doesn't seem to be happening, at least when we request an outbound call.  Okay, fixed that, and implemented F5 to toggle call mute, and F7 to end a call.

Well, I think that's everything except for a quick demo video!


Next stop is the power management stuff, and gettiing it to talk to the cellular modem. 

Tuesday, 2 December 2025

MEGAphone Case Design

I've been thinking about this part of things for a while, because it's clear that the first iteration of the modular MEGAphone is going to be larger than originally planned, and subject to more evolution for a while.  The goal is to make something that is easy to work on software-wise, as well as being easy to fabricate, and then switch out initial larger modules for smaller more integrated modules later.

So instead of going for a custom machined case, it occurred to me in the shower this morning, that I can just use Pelican style tough cases, with some inserts to hold everything in place.

Doing it this way, we can even allow for the inclusion of a full MEGA65 keyboard, which will make the earlier stages of testing easier.  Similarly, a larger screen could be included.

The more I think about this, the more I like the idea.  Basically we will be making a small number of portable MEGA65s, with cellular capability -- so all the existing development tools and work-flows will still work.

The keyboard PCB is ~32x15cm, and a standard MEGA65 mainboard is smaller than that, so there'd even be room to route standard HDMI to a larger screen in the lid.

It looks like the Tactix Large tough cases will be a good size. And they even come in a nice blue that suits an 8-bit device.  So I've gone and grabbed one, and also fished out the 15" LCD panel I had in the shed from a previous iteration of making a laptop MEGA65. I also have some SVGs with the right cut positions for a MEGA65 keyboard, too.  So this is looking like a good and fast way to build a prototype.

I also have a different prototype MEGAphone that was designed as a land-line, but still has integration of dialer and cellular modem onto a MEGA65 core.  I could in principle fit that into this case and make a working luggable MEGAphone.

Let's start with fitting the LCD, since I'll need that, regardless of what hardware I put in the bottom half of the case. I'll mock it all with MDF panels that I can easily crock up here at home. Then once I know the dimensions of the panels and locations of cut-outs, I can order machine cuts ones online from one of the various fabricators.

The LP154WU1 panel I have is a bit old now, but can still be found online for AU$100 - AU$200 in small quantities, which is all we need for now. The only issue is the driver boards seem to vary with the control button placement. But I can work around that by designing my own, as their interface is extremely simple.  So let's work out how we'll hold the panel in place.  

The panel is just over 5mm thick around the edges, so I could order some 5mm thick shims with a selection of screw hole offsets, so that the screen position and fit can be trimmed a bit, without having to vary the position of holes on the front of the panel.  I'll probably go for 2 screws on either side and on the bottom, and four on the top, with one shim on each side and the bottom and two on the top edge. That should hold in firmly enough if I then have panels that sit behind them and go behind the edges of the panel --- and be safer for allowing variance in the exact dimensions of the panels.

I'll also need a rear panel onto which the driver PCBs can be mounted. That can probably sit in the bottom of the lid, and will have to be a bit smaller to allow for the curve radius of the lid. That panel can probably be fixed to the brace I'll put across the rear of the panel.

Now, that's all a lot of words, so I'll start mocking up the pieces for this, and then convert that to a set of pieces. If I can get all clever, it should be possible to make it able to be cut from a single panel, if the diagonal is long enough to accommodate the back brace, or I do some other horrible hack.  

Anyway, let's start by cutting the aperture for the LCD panel and working out where everything needs to go.

Well, that was helpful in some ways. But it got way too fiddly too fast. So I checked and found our local Maker Space is open on Saturdays, signed up, and went in and did an initial MDF laser cut to test the measurements I'd made.  One of the nice volunteers ran it for me, because I haven't done the laser cutter induction yet:

So much nicer than me hacking away with a jigsaw: 
There were of course lots of holes in the wrong places, so I marked up the MDF and will go back during the week with the updated file: 
 
 
 


In the meantime, I can probably start planning the same for the lower-half.

Years ago I started work on making a laptop MEGA65, and did make SVG outlines for the keyboard cut-out, which would save me a bunch of time about now.  The question is whether I can find it... Nope. So I'll just have to start from scratch. I might be able to save some time by using the MK-II keyboard layout to trace the key blocks.    

Okay, so I've done that, and also gone and picked up a pile more MDF panels ready for testing and revising with the laser-cutter.

So let's finish out the design:  The lower case is deeper, and will need to have, keeping it very modular for now:

1. Keyboard

2. Main logic board

3. Cellular and related modules board

4. Power management board

5. Batteries

6. Solar panel(s)

My plan of using a regular MEGA65 mainboard slightly complicates things, in that it needs 12V DC in, which means we'll need another higher output-voltage DC:DC converter that can be switched by the low-power FPGA.  This is really the trickiest part of the whole thing, because I can't find a DC:DC converter that will go from 2.5V (lowest single-cell LiFePO4 cell voltage) to 12V in a single go -- and that can provide ~2A output.  (The 15" LCD uses about 0.8A, and a MEGA65 uses about 0.3A -- so allowing for some in-rush head-room, 2A feels safe).

Now, the MEGA65 can start with >9V, but the LCD panel's backlight probably really needs 12V, so we'll have to stick with 12V for now.

What I'm looking at right now is one of these eval boards: TPS61088Q1EVM-037, which in theory can Just Do It All :tm:. It even has a nice EN line, so we can control the power from the low-power FPGA board.

So I'll order one, and try it out. The only issue with it is that whoever designed it made lots of components big to allow experimentation... except the resistors that set the output voltage. Those are 0402 fly-poop sized. Fortunately there's a couple of test points I can solder a through-hole resistor to, and then use the soldering iron to erase the corresponding piece of fly poop.

Anyway, that board's ordered. 

Thinking through the other bits and pieces, if I use an R3 or R3A board (or an R2 for that matter), then it has on-board speaker drivers and amplifiers that can be used for the ringer.  I'll use an actual hand-set on this unit, because I have a cellular carrier board on-hand that has audio CODEC and handset drivers -- the benefit of modular design strikes again!

So now I just need to make sure that the board stack will physically fit in the depth of the case, and design a simple board stack that everything can be attached to, and that can attach to the shell of the tough case. This will need a few layers:

1. Top layer to retain the keyboard.

2. Second layer to prevent the keyboard shorting on anything below it.

3. Third layer to hold the MEGA65 main board, low-power FPGA board, battery/charge controller.

4. Fourth layer to hold cellular communications board and 12V regulator board.

5. Fifth layer which is the bottom, to hold the batteries in place, and allow fixing to the shell.

The fifth and possibly fourth layers will need to be slightly smaller to accommodate the curvature at the back of the case.  

Now we need to work out the vertical spacing required between each of those layers, and confirm that it doesn't exceed the total case depth. 

Okay, I've been at the laser cutter in the Makerspace iterating away.  I'll save you the 90 minute build montage, and just show the results so far:



So basically we have the screen bezel and related bits and pieces to go in the top of the case.  The last remaining challenge there is working out where to drill the four holes.  This is a bit of a pain, as they have to be located correctly, as well as have the correct spacing, so that the screen bezel is correctly located.

I've also been iterating on the cuts for the lower-half, to hold the keyboard, phone handset, MEGA65 components as well as the power control and other circuits.  As expected, those are being similarly fiddly to get everything right, as we have quite a few boards and other components to fit in.  I've been working from the top down, and have drafts for the top deck to which the keyboard is screwed, then the deck below that that really just has the floppy drive and phone handset mounts, then the deck below that which hosts the MEGA65 mainboard and the 1S battery to 12V adaptor board. There is space on that one for the power management FPGA, the solar/battery charge controller board and a few other bits and pieces.

Below that I'll still need the cellular communications deck, and then the batteries will sit behind that --- and like the screen in the lid, we'll need some way to hold it in place. 

The cellular comms deck might need to be two panels, so that none of the screws or hole-throughs from the cellular communications deck can contact the batteries.

Okay, I'm back in the Adelaide Maker Space to work on this.  In the meantime I have got the upper-half all fitting, and even put enough of the lower-half together to be able to fire up the MEGA65, even if the lower-half doesn't fit in the case yet.

So let's start with the upper half:

Oops! No cut-outs to allow cables to feed from the top to the bottom half.  Fixed that, and adjusted the hole sizes for the LCD control board in the lower-right, and I can now connect it to power and VGA, and thus to a MEGA65 main-board.  It looks pretty nice, if I say so myself. I'm thinking that I'll likely spray the exposed panels white or "C65 beige" at some point, which I think will look really nice with the blue case:

So let's focus on the lower-half now.  This will be a bit of a slide-show, that doesn't quite reflect the number of iterations or the fiddling about to get everything right.

Let's start with the handset recepticle -- since working as a phone is kind of the whole point of this.  A nice 1990s corporate desk phone handset, itself a bit yellowed, works really nicely for this, and does just fit next to the keyboard:

With a bit of fiddling about, we can pseudo-assemble this (even though the MEGA65 main board is sitting outside the box at this point, out of screen-shot:
But we can load something up :) This show below shows more of the layers of the "PCB Torte" assembled -- in the shot below the MEGA65 main-board is actually in the stack. It's just that the stack doesn't yet fit in the box properly. Only power is being provided externally.
And just a short video snippet, showing it running. Oh, yes, and you can see a floppy disk can be inserted via the cut-out in the upper right into the floppy drive that is hiding under the blank area behind the keyboard.  That would be a great spot for a logo and some real or fake ventilation slots for improved visual effect.

 

So let's tackle the speaker for the ringer next. By using a MEGA65 R3 or R3A mainboard, we can use their on-board speaker drivers. They're not great for long-running audio, as the speakers get rather warm due to high-frequency noise (which is why we discontinued them on the R4+ boards), but for 30 seconds of phone ringing from time to time, they will be totally fine.  So time to measure them up: 

 

 Make sure they fit the cut-outs I've designed for them:

  

Speaking of cut-outs, I learned the hard-way that it's a really good idea to put cut-outs in these panels to make it easier to insert and remove cables: 

 Then it was time to do a bunch of further trimming of the board sizes so that the whole stack can fit in the box.  I also reworked the phone handset holder area, so that the handset could sit a few mm lower, thus allowing the lid to close:

 

With all that done, we can see that it's possible to sit everything at a reasonable depth in the case: 


Okay, so now let's work on the cellular modem board and antennae. This was a real pain, as the cellular eval module board uses big chunky antennae (that should get great reception), but were too tall to mount on the board.  So I designed up set of tabs that allow their mounts to be rotated 90 degrees, thus allowing the height between decks to remain withing the ~26mm limit I can fit:

The tabs are held in place from the rear-size using 3mm screws and spacers.  It's a bit clunky, but works quite well:
 
 
 So also on this deck is where we are putting the big LiFePO4 cells.  They need to be held in place, so a fence of 3mm screws and nylon spacers works for those, too. As they are not 26mm thick, I'll put some spacing material above and below them, or simply make a little brace piece that holds the battery pressed against the lower board.
 
 
And that's really the last of the problems that I had to solve with the case, we've successfully done the (1) Case design; (2) Fabricated the case; (3) Managed to assemble one; and (4) Remediated and mitigated a whole pile of fitment issues via various means:
 
 
I do like the cold-war nuclear codes football vibe that this gives off. Already a few folks in the MEGA65 community have expressed interest in building one of their own -- so it's a case of the aesthetic helping to foster interest. 
 
The source code for the laser-cut files are all up at: https://github.com/gardners/mega65-rugged-case. There's a certain elegance to how simple the design has worked out to be -- five laser-cut panels plus one off-the-shelf case, that you can buy from Bunnings here in Australia, or find something equivalent easily enough wherever you are.   
 
And, look, the lid even shuts!
 

So the next steps here are that I've ordered the parts I need to build up 2 or 3 more of these to distribute to the MEGA65 developer community, once all the software is complete.