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. 

No comments:

Post a Comment