So I've been attempting, somewhat unsuccessfully, to run TI-BASIC programs from C for some time now, getting to the point where the programs will run, but can't return successfully. I'd appreciate any guidance about what I'm doing wrong, or suggestions on how to improve it. Here's the code I'm using:

Excerpt from main.c, containing the program running stuff:
Code:
char name[10] = " ";
size_t *asm_prgm_size = (size_t *)0x0d0118c;
size_t size_bkp = *asm_prgm_size;
name[0] = TI_PRGM_TYPE;
strcat(name, "MASTER");
strcpy(os_OP1, name);
os_PushErrorHandler();
os_ClrHomeFull();
RunPrgmBASIC();
*asm_prgm_size = size_bkp;
os_PopErrorHandler();
//just used to test if it returns correctly, which it doesn't
gfx_Begin();
gfx_FillScreen(224);
delay(100);

The RunPrgmBASIC() function is just the one from ICE:

Code:
.assume adl = 1
segment data
.def _RunPrgmBASIC

_RunPrgmBASIC:
   ld   iy, 0D00080h
   ld   hl, (0D0118Ch)
   push   hl
   ld   hl, 0
   call   0020798h
   set   1, (iy+8)
   call   0020F00h
   call   002079Ch
   res   1, (iy+8)
   pop   hl
   ld   (0D0118Ch), hl
   ret

Thanks in advance for your help, I don't really know what I'm doing with assembly so I'll appreciate any guidance you can give me. The current issue seems to be that the TI-BASIC program runs, but on returning, it crashes the C program.
You cannot launch a TI Basic program from C, as it will trash the context. This needs to be done in assembly. The steps are:


    Delete the current C program from usermem
    Push context and stub handler to the stack
    Push error handlers pointing to the stub
    Launch basic program
    Return to stub
    Lookup C program
    Copy C program to usermem and jump to it


Anything else is wrong and will be unstable at best.
Made an attempt at this, pulled from several different sources to make a (hopefully) working product:
Code:
_RunProg:
RelocBase:
    ;delete the current C program from usermem
    ld hl, userMem
    ld de, (asm_prgm_size)
    call DelMem
    ;install put pointer to stub on the stack
    ld hl, start_return - end_return
    add hl, sp
    ex de, hl
    ld hl, stub
    ld bc, start_return - end_return
    ldir
    ;get the basic program and run it
    lea hl,ix-RelocBase+BasicProg
    call _mov9toop1
    jq ti_ParseInp
BasicProg:
    .db ProbObj, "VYSTEMP", 0

_stub:
start_return:
RelocBase:
    pop ix
    lea hl,ix-RelocBase+ShellPrgm
    call _mov9toop1
    call _ExecutePrgm
    ld a,0
    ld (plotSScreen),a
return:
    ld a,(plotSScreen)
    ret
ShellPrgm:
    .db ProtProgObj,"VYSION2",0
end_return:

Would this work?
It's completely incorrect.

You need to relocate this code somewhere else before you delete the program, otherwise you will delete the code itself!
And I don't know what kind of mismash of code that is, I can't make any sense of it. Maybe you should start with one of the steps I mentioned above and make sure you get it right before moving to the next step rather than trying to do it all at once.
How would this work for the first step (delete the current C program from usermem)?
Code:
ld hl, userMem
ld de, (asm_prgm_size)
call _DelMem
I also don't know how to relocate code, which I'll have to do to get this to work. How would I relocate these functions to run outside of userMem?
You can copy the code to a safe region of RAM, such as the LCD, and then just jump to it.
Here's an attempt at the first step: relocating the code to safe RAM and using it to delete the current C program from userMem. Once this is right, I'll start on the next step.
Code:
assume adl =1
.def _runBasicPrgm
.def _copyFuncToSafeRam

_copyFuncToSafeRam:
ld bc, end_relocate - start_relocate
ld hl, _runBasicPrgm
ld de, ti.vRam
ldir
jp ti.vRam

start_relocate:
_runBasicPrgm:
;delete the current C program from userMem
ld hl, userMem
ld de, (asm_prgm_size)
call _DelMem
;do the other steps (will add later)
end_relocate:

Thanks to everyone for your help, I'm looking forward to getting this working.
That's really close. There's only two main issues: you need to tell the assembler where the code that is being relocated to. This is so the assembler can fixup absolute jumps and calls. The other thing is that you need to set asm_prgm_size to zero as DelMem doesn't accomplish this.

Here's some code that you can assemble in order to check for errors with this.


Code:
include 'include/ez80.inc'
include 'include/tiformat.inc'
include 'include/ti84pceg.inc'
format ti executable 'DEMO'

copy_to_saferam:
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   jp   run_basic_prgm

run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   hl,ti.userMem         ; delete the current program from usermem
   ld   de,(ti.asm_prgm_size)
   call   ti.DelMem
   or   a,a
   sbc   hl,hl
   ld   (ti.asm_prgm_size),hl
run_basic_prgm_end:
Firstly, thanks a lot to Mateo and fghsgh for helping me out with this on Discord. Here's some more code, this time attempting the next step: copying the context and stub handler to the stack.

Code:
include 'include/ez80.inc'
include 'include/tiformat.inc'
include 'include/ti84pceg.inc'
format ti executable 'RUNPRGM'

copy_to_saferam:
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   jp   run_basic_prgm

run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   hl,ti.userMem         ; delete the current program from usermem
   ld   de,(ti.asm_prgm_size)
   call   ti.DelMem
   ld hl, 0
   ld   (ti.asm_prgm_size),hl
   ;attempt to copy the reloader to the stack and set up a return address
   ;not sure how to do negative numbers but maybe this would work?
   ;should end up with -(reloader_end - reloader_start)
   ld hl, reloader_start - reloader_end
   add hl,sp
   ld sp,hl
   push hl
   call ti.PushErrorHandler
   ex de,hl
   ld hl, reloader_start
   ld bc, reloader_end - reloader_start
   ldir
   ;run the basic program
reloader_start:
   ;update the stack pointer by the reloader size, effectively removing the stub from it
   ld hl, reloader_end - reloader_start
   add hl,sp
   ld sp,hl
   ;rerun VYSION
reloader_end:

run_basic_prgm_end:
That looks fine to me. You could do this to avoid having to do weird calculations:


Code:

reloader_start:
   ;... code here
reloader_size := $ - reloader_start


Then you just need to use -reloader_size and reloader_size in their respective locations. What you have now is fine though, and should work. Now for the next step: Push error handlers pointing to the stub. This uses ti.PushErrorHandler.

EDIT:

You can then use this code snippet to run a basic program with the type+name stored in OP1.


Code:
set   ti.graphDraw,(iy + ti.graphFlags)
   set   ti.appTextSave,(iy + ti.appFlags)
   set   ti.progExecuting,(iy + ti.newDispF)
   set   ti.appAutoScroll,(iy + ti.appFlags)
   set   ti.cmdExec,(iy + ti.cmdFlags)
   res   ti.onInterrupt,(iy + ti.onFlags)
   xor   a,a
   ld   (ti.kbdGetKy),a
   call   ti.EnableAPD
   ei
   jq   ti.ParseInp
Let's try this again (this time with an attempt at running the basic program, which is assumed to be named "VYSTEMP"):
Code:

include 'include/ez80.inc'
include 'include/tiformat.inc'
include 'include/ti84pceg.inc'
format ti executable 'RUNPRGM'

copy_to_saferam:
   ;OP1 to be reloaded later
   ld hl,(ti.OP1)
   ld (tmp0),hl
   ld hl (ti.OP1 + 3)
   ld (tmp1),hl
   ld hl (ti.OP1 + 6)
   ld (tmp2),hl
   ;this part will also copy the program to be run and its type to OP1
   ld hl, program_name
   call _mov9toop1
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   jp   run_basic_prgm
program_name:
   .db ProgObj, "VYSTEMP", 0



run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   hl,ti.userMem         ; delete the current program from usermem
   ld   de,(ti.asm_prgm_size)
   call   ti.DelMem
   ld hl, 0
   ld   (ti.asm_prgm_size),hl
   ;attempt to copy the reloader to the stack and set up a return address
   ;not sure how to do negative numbers but maybe this would work?
   ;should end up with -(reloader_end - reloader_start)
   ld hl, reloader_start - reloader_end
   add hl,sp
   ld sp,hl
   push hl
   ex de,hl
   ld hl, reloader_start
   ld bc, reloader_end - reloader_start
   ldir
   ;run the basic program
   ;we stored the name and type into OP1 earlier
   set   ti.graphDraw,(iy + ti.graphFlags)
   set   ti.appTextSave,(iy + ti.appFlags)
   set   ti.progExecuting,(iy + ti.newDispF)
   set   ti.appAutoScroll,(iy + ti.appFlags)
   set   ti.cmdExec,(iy + ti.cmdFlags)
   res   ti.onInterrupt,(iy + ti.onFlags)
   xor   a,a
   ld   (ti.kbdGetKy),a
   call   ti.EnableAPD
   ei
   jq   ti.ParseInp
reloader_start:
   ;update the stack pointer by the reloader size, effectively removing the stub from it
   ld hl, reloader_end - reloader_start
   add hl,sp
   ld sp,hl
   ;rerun VYSION (or whatever program called it, will be reloaded to OP1
   ;get the old old OP1
   ld hl,0
   tmp0 := $-3
   ld (ti.OP1),hl
   ld hl,0
   tmp1 := $-3
   ld (ti.OP1 + 3),hl
   ld hl,0
   tmp2 := $-3
   ld (ti.OP1 + 6),hl
   ;copy to usermem
   call util_move_prgm_to_usermem
   ;jump to it
   call   ti.DisableAPD
   set   ti.appAutoScroll,(iy + ti.appFlags)   ; allow scrolling
   jq    ti.userMem
   ;is this stolen from Cesium?
   ;why yes, yes it is
   util_move_prgm_to_usermem:
   ld   a,$9            ; 'add hl,bc'
   ld   (.smc),a
   call   ti.ChkFindSym
   jr   c,.error_not_found         ; hope this doesn't happen
   call   ti.ChkInRam
   ex   de,hl
   jr   z,.in_ram
   xor   a,a
   ld   (.smc),a
   ld   de,9
   add   hl,de
   ld   e,(hl)
   add   hl,de
   inc   hl
.in_ram:               ; hl -> size bytes
   call   ti.LoadDEInd_s
   inc   hl
   inc   hl            ; bypass tExtTok, tAsm84CECmp
   push   hl
   push   de
   ex   de,hl
   call   util_check_free_ram      ; check and see if we have enough memory
   pop   hl
   jr   c,.error_ram
   ld   (ti.asm_prgm_size),hl      ; store the size of the program
   ld   de,ti.userMem
   call   ti.InsertMem         ; insert memory into usermem
   pop   hl            ; hl -> start of program
   ld   bc,(ti.asm_prgm_size)      ; load size of current program
.smc := $
   add   hl,bc            ; if not in ram smc it so it doesn't execute
   ldir               ; copy the program to userMem
   xor   a,a
   ret               ; return
.error_ram:
   pop   hl            ; pop start of program
.error_not_found:
   xor   a,a
   inc   a
   ret

util_check_free_ram:
   push   hl
   ld   de,128
   add   hl,de            ; for safety
   call   ti.EnoughMem
   pop   hl
   ret   nc
        ;yeah let's not do that
        ;call   gui_ram_error


reloader_end:

run_basic_prgm_end:
Not even close. For heaven's sake, do one step at a time.
The last code attempted to do too much, so here's a revision, this time just adding the code that will run a BASIC program called "VYSTEMP," and pushing the error handler to the stack.

Code:
include 'include/ez80.inc'
include 'include/tiformat.inc'
include 'include/ti84pceg.inc'
format ti executable 'RUNPRGM'

copy_to_saferam:
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   ;copy the program name and type to OP1
   ld hl, program_name
   call _mov9toop1
   jp   run_basic_prgm
program_name:
   .db ProgObj, "VYSTEMP", 0


run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   hl,ti.userMem         ; delete the current program from usermem
   ld   de,(ti.asm_prgm_size)
   call   ti.DelMem
   ld hl, 0
   ld   (ti.asm_prgm_size),hl
   ;attempt to copy the reloader to the stack and set up a return address
   ;not sure how to do negative numbers but maybe this would work?
   ;should end up with -(reloader_end - reloader_start)
   ld hl, reloader_start - reloader_end
   add hl,sp
   ld sp,hl
   push hl
   ;push that error handler
   call ti.PushErrorHandler
   ex de,hl
   ld hl, reloader_start
   ld bc, reloader_end - reloader_start
   ldir
   ;run the basic program
   ;we stored the program name to op1 earlier
   set   ti.graphDraw,(iy + ti.graphFlags)
   set   ti.appTextSave,(iy + ti.appFlags)
   set   ti.progExecuting,(iy + ti.newDispF)
   set   ti.appAutoScroll,(iy + ti.appFlags)
   set   ti.cmdExec,(iy + ti.cmdFlags)
   res   ti.onInterrupt,(iy + ti.onFlags)
   xor   a,a
   ld   (ti.kbdGetKy),a
   call   ti.EnableAPD
   ei
   jq   ti.ParseInp
reloader_start:
   ;update the stack pointer by the reloader size, effectively removing the stub from it
   ld hl, reloader_end - reloader_start
   add hl,sp
   ld sp,hl
   ;rerun VYSION
reloader_end:

run_basic_prgm_end:

Is this better?
So I've got the TI-BASIC running complete, all there is to do now is to reload the original program that called it. Here is my attempt at that (which seems to not work, seemingly because OP1 is not correctly reloaded):
Code:

include 'include/ez80.inc'
include 'include/tiformat.inc'
include 'include/ti84pceg.inc'
format ti executable 'RUNPRGM'

copy_to_saferam:
   ;OP1 to be reloaded later
   ld hl,(ti.OP1)
   ld (tmp0),hl
   ld hl, (ti.OP1 + 3)
   ld (tmp1),hl
   ld hl, (ti.OP1 + 6)
   ld (tmp2),hl
   ;this part will also copy the program to be run and its type to OP1
   ld hl, program_name
   call ti.Mov9ToOP1
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   jp   run_basic_prgm
program_name:
   db ti.ProgObj, "VYSTEMP", 0



run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   hl,ti.userMem         ; delete the current program from usermem
   ld   de,(ti.asm_prgm_size)
   call   ti.DelMem
   ld hl, 0
   ld   (ti.asm_prgm_size),hl
   ;attempt to copy the reloader to the stack and set up a return address
   ;not sure how to do negative numbers but maybe this would work?
   ;should end up with -(reloader_end - reloader_start)
   ld hl, reloader_start - reloader_end
   add hl,sp
   ld sp,hl
   push hl
   ex de,hl
   ld hl, reloader_start
   ld bc, reloader_end - reloader_start
   ldir
   ;run the basic program
   ;we stored the name and type into OP1 earlier
   set   ti.graphDraw,(iy + ti.graphFlags)
   set   ti.appTextSave,(iy + ti.appFlags)
   set   ti.progExecuting,(iy + ti.newDispF)
   set   ti.appAutoScroll,(iy + ti.appFlags)
   set   ti.cmdExec,(iy + ti.cmdFlags)
   res   ti.onInterrupt,(iy + ti.onFlags)
   xor   a,a
   ld   (ti.kbdGetKy),a
   call   ti.EnableAPD
   ei
   jq   ti.ParseInp
reloader_start:
   ;rerun VYSION (or whatever program called it, will be reloaded to OP1
   ;get the old old OP1
   ld hl, 0
   tmp0 := $-3
   ld (ti.OP1),hl
   ld hl,0
   tmp1 := $-3
   ld (ti.OP1 + 3),hl
   ld hl,0
   tmp2 := $-3
   ld (ti.OP1 + 6), hl
   ;ld hl, ti.OP1
   ;call ti.PutS
   ;di
   ;halt
   ;copy to usermem
   call ti.os.GetVarSize
   ld   (ti.asm_prgm_size), hl   ;store the size of the program
   ld   de, ti.userMem
   ;insert memory into usermem
   call   ti.InsertMem
   call ti.ChkFindSym
   ex de, hl
   ld de, ti.userMem
   ld   bc, (ti.asm_prgm_size)      ;load size of current program
   ldir
   ;jump to it
   call   ti.DisableAPD
   set   ti.appAutoScroll,(iy + ti.appFlags)   ; allow scrolling
   ;update the stack pointer by the reloader size, effectively removing the stub from it
   ;tell that we aren't executing anything anymore
   di
   set ti.progExecuting, (iy + ti.newDispF)
   set ti.cmdExec, (iy + ti.cmdFlags)
   ld hl, reloader_end - reloader_start
   add hl,sp
   ld sp,hl
   jp   ti.userMem
reloader_end:

run_basic_prgm_end:

This code runs the TI-BASIC program correctly, but resets on trying to run the asm program again. Thanks to everyone who has helped out with this, and talked to me on Discord/IRC about it.
Does this mean that a C shell for BASIC programs is technically possible? If you can get it to return to the C program, one could say it technically qualifies as one...
Oxiti8 wrote:
Does this mean that a C shell for BASIC programs is technically possible? If you can get it to return to the C program, one could say it technically qualifies as one...
That's the goal, more information will come once I get this to work.
The issue is that you aren't storing OP1 to the stub, but rather writing to the *relocated* stub, because of the org command. You need to write the tmp values after the stub has been relocated, for example like below:


Code:
copy_to_saferam:
   ld   bc,run_basic_prgm_end - run_basic_prgm
   ld   hl,run_basic_prgm_addr
   ld   de,ti.vRam
   ldir
   ld hl,(ti.OP1)
   ld (tmp0),hl
   ld hl, (ti.OP1 + 3)
   ld (tmp1),hl
   ld hl, (ti.OP1 + 6)
   ld (tmp2),hl
   ld hl, program_name
   call ti.Mov9ToOP1
   jp   run_basic_prgm


The other thing is that I know I've mentioned you should use util_move_prgm_to_usermem from Cesium more than once Wink The way you are trying to do it is incorrect and will not work; especially if your program is located in the archive, or was deleted by another program! I've gone and made it position independent below so I don't have to say it again; this is what you should use to run an assembly program with the name in OP1:


Code:
reloader_start:
    call    ti.PopErrorHandler
reloader_error:
    di
    res   ti.progExecuting,(iy + ti.newDispF)
    res    ti.cmdExec,(iy + ti.cmdFlags)
    bit    ti.onInterrupt,(iy + ti.onFlags)
    jr  nz,.error_on
   ld hl, 0
tmp0 := $-3
   ld (ti.OP1),hl
   ld hl,0
tmp1 := $-3
   ld (ti.OP1 + 3),hl
   ld hl,0
tmp2 := $-3
   ld (ti.OP1 + 6), hl
   res 0,(iy + ti.asm_Flag2)
.getprgm:
   call   ti.ChkFindSym
   jr   c,.error_not_found
   call   ti.ChkInRam
   ex   de,hl
   jr   z,.in_ram
   ld   bc,9
   add   hl,bc
   ld   c,(hl)
   add   hl,bc
   inc   hl
.in_ram:
   call   ti.LoadDEInd_s
    bit 0,(iy + ti.asm_Flag2)
    jr  z,.getsize
    push    de
    pop bc
    inc hl
    inc hl
    ld  de,ti.userMem
   ldir
   call   ti.DisableAPD
    di
    ld hl,reloader_end - reloader_start
    add hl,sp
    ld sp,hl
    jp  ti.userMem
.getsize:
    push    de
    ex  de,hl
   call   ti.EnoughMem
   pop hl
   jr   c,.error_mem
    push    hl
   ld   de,ti.userMem
   call   ti.InsertMem
   pop   bc
   ld   (ti.asm_prgm_size),bc
    set 0,(iy + ti.asm_Flag2)
    jr  .getprgm
.error_mem:
.error_not_found:
.error_on:
    di
    ld hl,reloader_end - reloader_start
    add hl,sp
    ld sp,hl
   ret
reloader_end:


The next thing is that you have to push an error handler for catching TI-BASIC or OS errors... I think you removed it? Anyway, I just edited the section of code that runs the basic program. It should look like this:


Code:
run_basic_prgm_addr:
   org   ti.vRam
run_basic_prgm:
   ld   de,(ti.asm_prgm_size)
   ld hl, 0
   ld   (ti.asm_prgm_size),hl
   ld   hl,ti.userMem         ; delete the current program from usermem
   call   ti.DelMem
   ;attempt to copy the reloader to the stack and set up a return address
   ;not sure how to do negative numbers but maybe this would work?
   ;should end up with -(reloader_end - reloader_start)
   ld hl,reloader_start - reloader_end
   add hl,sp
   ld sp,hl
   ld (.returnaddr),hl
   ex de,hl
   ld hl,reloader_start
   ld bc,reloader_end - reloader_start
   ldir
   ld hl,reloader_error - reloader_start
   add hl,sp
   call ti.PushErrorHandler
   ;run the basic program
   ;we stored the name and type into OP1 earlier
   set   ti.graphDraw,(iy + ti.graphFlags)
   set   ti.appTextSave,(iy + ti.appFlags)
   set   ti.progExecuting,(iy + ti.newDispF)
   set   ti.appAutoScroll,(iy + ti.appFlags)
   set   ti.cmdExec,(iy + ti.cmdFlags)
   res   ti.onInterrupt,(iy + ti.onFlags)
   xor   a,a
   ld   (ti.kbdGetKy),a
   call   ti.EnableAPD
   ei
    ld hl,0
.returnaddr := $-3
    push hl
   jq   ti.ParseInp


Let me know if you have any issues.
Since you also want to run this code from C, you need to clean up the C context before you run the basic program.
This is pretty simple to do; just use the below code and put it directly after the copy_to_saferam line.


Code:
   ld   sp,(__exitsp)
   pop   iy,af,hl
   ld   (hl),a
   call   $04F0


And then put this line somewhere else in your code:


Code:
   extern __exitsp
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement