Path: cdc486.cdc.polimi.it!ghost.dsi.unimi.it!batcomputer!caen!usenet.cis.ufl.edu!usenet.ufl.edu!gatech!europa.eng.gtefsd.com!avdms8.msfc.nasa.gov!sol.ctr.columbia.edu!hamblin.math.byu.edu!hamblin!dougc From: dougc@bert.cs.byu.edu (Douglas R. Cannon) Newsgroups: comp.sys.hp48 Subject: S/SX and G/GX software developing tips updated Date: 14 Dec 1993 02:15:01 GMT Organization: BYU, Provo, Utah Lines: 676 Distribution: world Message-ID: NNTP-Posting-Host: bert.cs.byu.edu S/SX AND G/GX SOFTWARE DEVELOPING TIPS (version 2) Last Updated: December 13, 1993 This document provides methods for writing assembly language software that will work on all ROM versions of the HP48 (S, SX, G, or GX). Many of these methods may be used for system-RPL programming as well, but the main focus of the document is for assembly language developers. All of the ROM entry points used in this document are supported by Hewlett-Packard. All source code is written in SASM format. It is assumed that you understand basic sys-RPL commands as well as the Saturn assembly instruction set. This document could easily mushroom into a 200 page manual describing every detail of the problem at hand. Rather than try to accomplish this, I will present these methods that will work under certain conditions. You can choose the methods that work best for your application, or use the ideas to taylor these methods to work even better for you. This document is not intended to be a comprehensive guide for assembly language programming. The main focus of this document is to help others create applications that will work on the SX and the GX. For example, key input is the same for both machines, so it is not covered in this document. There are other areas that are not covered, because they work the same with both the SX and GX. It is assumed that you will get this information elsewhere. I have emailed this document to the following people, and all of them have made corrections and additions. As I still consider myself a student in these programming areas, I greatly appreciate the support and help that I received from these people: Joe Ervin Mohamed Fatri Mika Heiskanen Detlef Mueller Bob Worsley As a result of the help from these people, this document is written mainly by them. I started the idea, and I have compiled these routines and ideas. I didn't want help from too many people, because then I would have to choose between 10 or more routines and ideas for each concept. To create this document as quickly as possible, and to make it as effective as possible, I have tried to limit its input. However, I do hope to support this document, so if you have some ideas or suggestions, then please send them to me. I will post any updates to this document. Thanks, Douglas R. Cannon dougc@bert.cs.byu.edu ----- Why do so many sys-RPL and ML applications written for the S/SX not work on the G/GX machines? One of the biggest reasons is that HP is no longer supporting the use of system RAM pointers, as these pointers have been moved from their old positions in the S/SX machines to new ones in the G/GX. All pointers in the S/SX RAM area from #70000h to approximately #70700h have moved into the new G/GX RAM area starting at #80000h. It is not as simple as adding #10000h to an SX address to get the corresponding GX address. Many addresses have changed their order, or new things have been added to offset the addresses. It is simple to modify an S/SX program to work on a G/GX by changing these pointers to their new G/GX values, but then we start seeing programs that have an S/SX version and a G/GX version. This is poor programming practice, especially since it is so easy to make ONE version that works on both machines. It is my opinion that only HP supported entry points should be used in all cases. This guarantees a longer shelf-life of your software, and you never have to worry if it will work on the next ROM revision. Re-writing an unsupported routine, or using a sequence of slower, supported routines is always better than using an unsupported routine. Slower, bigger programs that work are much better than faster, smaller programs that crash. The methods described below may not be the absolute best methods, but they work, and may help you develop your own ways of doing the same thing. ---------------------------------------- --- DISPLAYING GRAPHICS ---------------- ---------------------------------------- First of all, the OLD method that was previously good would be something like this: Using GDISP (#70565h) you can find the address of the PICT GROB and use it to display your graphics. D0=(5) =GDISP * (#70565h) A=DAT0 A * read the address of PICT LC(5) 20 * offset past prolog, dimensions, etc. C=C+A A * C now contains the bitmap address of PICT Now I have the address of PICT where I can store graphics to display them, or whatever. All I have to do to get this to work on a G/GX is change the GDISP address to the new one which is #806E4h. HOWEVER, using this method is not wise, since the code will only work either on the S/SX, or on the G/GX, but not both. Also, HP is no longer supporting GDISP, so it could change in the future. There are some new supported entry points (as of 10/7/93), and among them are: addrVDISP2 #1264Ah (for getting a pointer to the menu GROB) addrVDISP #1263Ah (for getting a pointer to current GROB) addrADISP #1265Ah (for getting a pointer to the stack GROB) Each of these should be used the same way. You can disassemble the ROM at these areas to see what is really going on (it's simple, really), but that isn't important. Two nibbles past each of these addresses is contained a pointer to a pointer to the GROB in question. Thus, the contents must be dereferenced twice before getting the address you are looking for. For example, lets say that you want to get the address of the stack GROB: D1=(5) (=addrADISP)+2 * D1=(5) #1265C A=DAT1 A * read in the pointer D1=C * put pointer into D1 A=DAT1 A * read pointer to stack GROB LC(5) 20 * to skip past prolog, etc. C=C+A A * C now contains bitmap address of stack GROB It's best to use the stack GROB for most graphics, because then you don't disturb the contents of PICT. If you want to find the address of the PICT GROB, then you need to be sure that PICT is being displayed, and use the same routine above, only use the address addrVDISP. If you want the address of the menu GROB, use addrHARDBUFF2. ---------------------------------------- --- DELAY LOOPS ------------------------ ---------------------------------------- As you surely know, the G/GX machine runs at a faster CPU speed than the S/SX. If you would like to create delay loops that delay the same amount of time on either machine, then you will need to base these delays on the clock. One simple method that I have found is to use TIMER2 (#00138h) which is a supported entry. I won't give full details of this timer, but I will summarize it's behavior. TIMER2 is an 8 nibble value of clock ticks (1 tick == 1/8192 seconds). TIMER2 counts backwards, but its starting value can be different depending on the folowing conditions: - If the user has enabled the ticking clock in the status display (system flag -40 is set) then TIMER2 will count in intervals of 1 second. Thus, it will count backwards from #00001FFFh to #00000000h. - If the clock is not enabled, and an alarm is due in less than 1 hour, then TIMER2 will contain the number of ticks left until that alarm comes due. TIMER2 will continue to count backwards. - If the clock is not enabled, and there is no alarm due in less than 1 hour, then TIMER2 will count in intervals of 1 hour. Thus it will count backwards from #01C20000h to #00000000h. There is, however a small problem with using TIMER2. Before reading its value, you have to synchronize the CPU with the TIMER, otherwise you risk reading garbage. By disassembling the ROM TICKS command, you can see how HP does this. Obviously it was important enough to them to safeguard against reading garbage. I've included the information about TIMER2 to let you know that it is one possibility, and can be a simple method of reading the clock using few registers. However, there are two methods that are much better, the only drawback being that they use a lot of registers. This next routine uses the supported entry GetTimChk (#12EEh). GetTimChk will simply get the 13 nibble ticks value and return it in C[W]. If something with the system time was corrupt, then it performs a warmstart. Before using this routine, it is necessary that interrupts are disabled via the ST=0 15 command. GetTimChk must not be interrupted while it's reading the timer, or the value will not be valid. If GetTimChk is interrupted, you stand a good chance of having a warmstart. An interrupt that happens while the stored "next event" time is being read could cause a checksum miscompare, which GetTimChk considers a fatal error (and branches to the warm-start code). The following assembler slice will wait n ticks (1 tick == 1/8192 s), n is passed in A[W]. Uses A, C, P, R0 and R1. GetTimChk alters A, B, C, D, P, D1 and CARRY, and uses 3 RSTK levels: DELAY R0=A * put n into R0 GOSBVL =GetTimChk * (#012EEh) This routine returns the 13 * nibble system time into C[W]. R1=C * current time into R1 DelayLP GOSBVL =GetTimChk * time A=R1 * start time P= 12 C=C-A WP * elapsed time A=R0 * n ?A>=C WP * delay some more? GOYES DelayLP P= 0 The next routine is pratically identical, only it uses the supported entry, GetTime++ (#130Eh). GetTime++ performs the same function as GetTimChk, only it calls the disable and enable interrupt routines. Therefore, you can use this routine when you don't want interrupts disabled. In fact, if they are disabled, then GetTime++ will have enabled them again before it exits. The following assembler slice will wait n ticks (1 tick == 1/8192 s), n is passed in A[W]. Uses A, C, P, R0 and R1. GetTime++ alters A, B, C, D, P, D1 and CARRY, calls the enable and disable interrupt routines, and uses 4 RSTK levels: DELAY R0=A * put n into R0 GOSBVL =GetTime++ * (#0130Eh) This routine returns the 13 * nibble system time into C[W]. R1=C * current time into R1 DelayLP GOSBVL =GetTime++ * time A=R1 * start time P= 12 C=C-A WP * elapsed time A=R0 * n ?A>=C WP * delay some more? GOYES DelayLP P= 0 ---------------------------------------- --- DETECTING WHICH MACHINE YOU'RE ON -- ---------------------------------------- In many cases, you can write software to work on both the S/SX and the G/GX without ever detecting which machine you're on. However, there are some valid reasons to want to know which machine you are currently running on. Sound effects are one good example. Most good sound effects are not possible if you try to use a clock-based delay loop, so you use definite delay loops. All definite delay loops will run faster on a G/GX than an S/SX, so the sounds will have a higher pitch. If you write two separate instances of sound code, one for the S/SX, and one for the G/GX, then you can check which machine you're on and run the proper sound code. This would result in a sound effect that appears identical when run on both machines. In sys-RPL, you should call VERSTRING (#30794h) to push the ROM version string to the stack. Then you can check this value from ML to see which ROM version you are running on. On my SX, ROM ver E, this returns "HPHP48-E" and on my GX, ROM ver L, this returns "HPHP48-L". All ROM versions <= "J" are an S/SX machine, and all ROM versions > "J" are a G/GX machine. For example: :: VERSTRING CODE sGX EQU 0 * ST flag clear == S/SX, set == G/GX GOSBVL =PopASavptr * (#3251Ch) Pop VERSTRING into A[A]. LC(5) 10+2*7 * point past prolog, length, and to 8th char A=A+C A D1=A * D1 is pointing to the version letter A=DAT1 B * read version letter into A LCASC 'J' * ASCII for "J", last S/SX version ST=0 sGX * Assume S/SX ?A<=C B * Assumption correct? GOYES CONT * Yes? continue ST=1 sGX * No, set G/GX CONT * the rest of your program here... END GOVLNG =GETPTRLOOP * restore RPL pointers, return to RPL ENDCODE ; Some have mentioned that the easiest way to detect which machine you're on is to check the nibble at (=INHARDROM?)+14. It's the most significant nibble of the RAM start address (7 on an S/SX, and 8 on a G/GX). Using this method is clearly better, since you don't need sys-RPL to push anything to the stack. However, although INHARDROM? is a supported entry, there is no guarantee that the nibble at (=INHARDROM?)+14 will remain the same. HP could possibly leave this entry frozen by only moving the code, not the pointer at the position (=INHARDROM?). Because of this, it is better to use the method described above. ---------------------------------------- --- CHECKING THE SOUND FLAG ------------ ---------------------------------------- You may need to check many of the System flags. Checking the sound flag is probably the most common, so I will give an example here. Checking the other flags would be similar. Since the entry SystemFlags (#706C5h in SX, #80843h in GX) may not be supported, and it's different in the two machines, this is not a good approach. Instead, a sys-RPL/ML combination may be used. In sys-RPL, execute the command: 56 TestSysFlag (or any other system flag) This will return TRUE if system flag #56 is set. (See RPLMAN.DOC page 123 for details. See page 76 for details on the FALSE and TRUE flags). In ML, you would do this: GOSBVL =popflag * Pops flag from stack, sets carry if TRUE GONC SNDON SNDOFF ... SNDON ... The SNDOFF and SNDON routines can be as simple as setting a status flag. All you are doing is determining whether system flag 56 is clear or set. Then your sound routines will know whether or not they should play the sounds. Because the above example is a good generic way of checking the status of a system flag, I have left it in. However, there is a much easier way of checking the sound flag itself, using the routine getBPOFF (# D809h). getBPOFF will clear the CARRY and ST4 if System flag 56 is clear (sound ON), or it will set the CARRY and ST4 is System flag 56 is set (sound OFF). getBPOFF uses D1, C[0], ST4, and CARRY. You can use this routine in this manner: GOSBVL =getBPOFF * check sound flag (System flag 56) GONC SNDON SNDOFF ... SNDON ... Use the routines SNDOFF and SNDON if you need to, otherwise just call getBPOFF once, and then ST4 is automatically set if sound is OFF, and it is clear if sound in ON. You can use this flag anywhere else in your program. ---------------------------------------- --- SOURCE CODE EXAMPLE ---------------- ---------------------------------------- I took the liberty of writing a little piece of code to show all the above examples at work together. This program doesn't do anything much, just a little graphics, a little sound, and a little delay. If you have the chance to try this out on an SX and a GX, then try and see if you can tell a difference. Try it with sound on with both machines, sound off with both machines, or sound on with one, sound off with the other... Getting it to work this way took very little effort. This source code can be compiled with Detlef Mueller's <-RPL-> 5.0 Thanks Detlef! I used Jean-Yves Avenard's StringWriter 4.1 for the GX to enter this source code. Thanks Jean-Yves! %%HP: T(1)A(D)F(.); " :: TOADISP (be sure we're looking at text GROB) VERSTRING (put the VERSTRING on the stack) CODE sGX EQU 0 * clr == SX; set == GX sSND EQU 4 * clr == sound; set == no sound ST=0 15 INTOFF VER GOSBVL =PopASavptr * pop VERSTRING addr into A, savptr again LC(5) 10+2*7 * point to version letter A=A+C A D1=A A=DAT1 B * read the letter into A[B] LCASC 'J' * last version letter for S/SX ST=0 sGX * assume G/GX ?A<=C B GOYES SCREEN * it's a G/GX, go get ABUFF addr ST=1 sGX * it's an S/SX SCREEN D1=(5) (=addrADISP)+2 * pointer to the pointer to stack GROB. A=DAT1 A * read in the pointer D1=A * put pointer into D1 A=DAT1 A * read pointer to stack GROB LC(5) 20 * point past prolog, etc. C=C+A A R0=C.F A * store ABUFF bitmap addr here permanently GOSBVL =getBPOFF * sSND set/clear automatically START GOSUB ERASE * Erase the status line LC(5) 34+1 * one line down, 1 nibble right GOSUB EMPTY * draw an empty dot C=C+CON A,4 * four nibbles right GOSUB EMPTY * draw an empty dot C=C+CON A,4 * four nibbles right GOSUB EMPTY * draw an empty dot C=C+CON A,4 * four nibbles right GOSUB EMPTY * draw an empty dot C=0 W LC(4) 8192 * 8192 ticks, or 1 second GOSUB DELAY * delay 1 second LC(5) 34+1 * one line down, 1 nibble right GOSUB FULL * draw a filled dot GOSUB S1 * make sound #1 C=0 W LC(4) 8192 GOSUB DELAY * delay 1 second LC(5) 34+5 * 1 line down, 5 nibbles right GOSUB FULL * draw a filled dot GOSUB S1 * make sound #1 C=0 W LC(4) 8192 GOSUB DELAY * delay 1 second LC(5) 34+9 * 1 line down, 9 nibbles right GOSUB FULL * draw a filled dot GOSUB S1 * make sound #1 C=0 W LC(4) 8192 GOSUB DELAY * delay 1 second LC(5) 34+13 * 1 line down, 13 nibbles right GOSUB FULL * draw a filled dot GOSUB S2 * make sound #2 C=0 W LC(4) 8192 GOSUB DELAY * delay for 1 second END INTON ST=1 15 GOVLNG =GETPTRLOOP * GETPTR, return to RPL * The following assembler routine will wait n ticks, n is * passed in C[W]. Uses A, C, P, R1, and R2. GetTimChk alters * A, B, C, D, P, D1, and CARRY, and uses 3 RSTK levels. DELAY R1=C * put n into R1 GOSBVL =GetTimChk * get 13 nibble system time into C[W] R2=C * current time into R2 DelayLP GOSBVL =GetTimChk * time A=R2 * start time P= 12 C=C-A WP * elapsed time A=R1 * n ?A>=C WP * delay some more? GOYES DelayLP P= 0 RTN * The following routine will erase the status line. * 34 nibbles on each line, 14 lines total, 476 (#1DCh) nibbles total. * It assumes that a pointer to the screen bitmap is in R0. ERASE A=R0.F A D0=A * put screen pointer into D0 LC(5) 34-1 * do loop 34 times A=0 W ERloop DAT0=A 14 * write 14 nibbles of white D0=D0+ 14 * increment screen pointer C=C-1 A * decrement counter GONC ERloop * do again until counter == FFFFF RTN * The next routine will draw an empty dot on the screen. * It assumes that a pointer to the screen bitmap is in R0. * C[A] must contain the nibble offset of where to draw the dot. EMPTY A=PC * get PC, so we can find data GOTO ENEXT NIBHEX 0F0C03 * data... bitmap of the dot NIBHEX 204204 NIBHEX 108108 NIBHEX 108108 NIBHEX 204204 NIBHEX C030F0 ENEXT A=A+CON A,4 * A now points to 1st nibble of data D1=A A=R0.F A A=A+C A D0=A * D0 points to correct screen position LA(2) 12-1 * 12 lines in graphic B=A B Eline A=DAT1 3 * read 3 nibbles (one line of graphic) DAT0=A 3 * write to screen D1=D1+ 3 D0=D0+ 16 D0=D0+ 16 D0=D0+ 2 B=B-1 B * decrement counter GONC Eline RTN * The next routine will draw a filled dot on the screen. * It assumes that a pointer to the screen bitmap is in R0. * C[A] must contain the nibble offset of where to draw the dot. FULL A=PC * comments here are the same as GOTO Fnext * those for EMPTY NIBHEX 0F0CF3 NIBHEX EF7EF7 NIBHEX FFFFFF NIBHEX FFFFFF NIBHEX EF7EF7 NIBHEX CF30F0 Fnext A=A+CON A,4 D1=A A=R0.F A A=A+C A D0=A LA(2) 12-1 B=A B Fline A=DAT1 3 DAT0=A 3 D1=D1+ 3 D0=D0+ 16 D0=D0+ 16 D0=D0+ 2 B=B-1 B GONC Fline RTN * This next routine plays sound #1 * * Notice that the sound code for the S/SX and the * sound code for the G/GX is practically identical. * The only difference is the pitch. I found that if you * multiply the S/SX pitch by 1.55, then you get an almost * exact value for the same sound on the G/GX (using this * sound method). Notice also that the length of the sounds * are the same. Since the G/GX pitch takes longer to execute, * this is where the length is lengthened. Both sounds take * the same amount of time to execute, within a few mili-seconds. S1 ?ST=0 sSND * is SND ON? GOYES S1DOIT * do sound C=0 W LC(3) 1819 * Sound #1 is about 1819 ticks GOTO DELAY * If sound is off, delay, and RTN S1DOIT ?ST=0 sGX * is this an SX? GOYES SXS1 * Sound code for the G/GX LA(2) 100 * length S1Glp LCHEX 800 OUT=C LC(2) 155 * pitch #1 S1GD1 C=C-1 B GONC S1GD1 C=0 A OUT=C LC(2) 155 * pitch #2 S1GD2 C=C-1 B GONC S1GD2 A=A-1 B GONC S1Glp RTN * Sound code for the S/SX SXS1 LA(2) 100 * length S1Slp LCHEX 800 OUT=C LC(2) 100 * pitch #1 S1SD1 C=C-1 B GONC S1SD1 C=0 A OUT=C LC(2) 100 * pitch #2 S1SD2 C=C-1 B GONC S1SD2 A=A-1 B GONC S1Slp RTN * This next routine plays sound #2 S2 ?ST=0 sSND * is SND ON? GOYES S2DOIT C=0 W LC(4) 8184 * Sound #2 is about 8184 ticks GOTO DELAY * if sound is off, delay, then RTN S2DOIT ?ST=0 sGX * is this an SX? GOYES SXS2 * Sound code for the G/GX LA(3) 1000 * length S2Glp LCHEX 800 OUT=C LC(2) 70 * pitch #1 S2GD1 C=C-1 B GONC S2GD1 C=0 A OUT=C LC(2) 67 * pitch #2 S2GD2 C=C-1 B GONC S2GD2 A=A-1 X GONC S2Glp RTN * Sound code for the S/SX SXS2 LA(3) 1000 * length S2Slp LCHEX 800 OUT=C LC(2) 45 * pitch #1 S2SD1 C=C-1 B GONC S2SD1 C=0 A OUT=C LC(2) 43 * pitch #2 S2SD2 C=C-1 B GONC S2SD2 A=A-1 X GONC S2Slp RTN ENDCODE ; " ---------------------------------------- --- UUENCDOED VERSION OF DOTS ---------- ---------------------------------------- begin 644 DOTS M2%!(4#0X+4R=+=`4$Y0'P]P"$P.`]`CX^!PE,X0!`*PQ$;032DB0K@58$,]E> M$D$3$T$S1`$`+!CZ@/@)V'#<,#0"`";%_ MWX(0$YMJ7OQJ7-L0""A!-@*`"#%!IN;%WX(0$V1J7OQJ7-L0:/2@+S/X'V;F6 M:#"#@"+H,P*`"#%AI.;%WX(0$T-J7OPZ7-L0""B"/B,`B!`3+6I>_"T(,;&B4 )YL6OP[4-L1(#^ `` end Path: cdc486.cdc.polimi.it!ghost.dsi.unimi.it!batcomputer!caen!uwm.edu!math.ohio-state.edu!cs.utexas.edu!bcm!TAMUTS.TAMU.EDU!not-for-mail From: ftg0673@tamsun.tamu.edu (Rick Grevelle) Newsgroups: comp.sys.hp48 Subject: Re: S/SX and G/GX software developing tips updated Date: 14 Dec 1993 03:24:25 -0600 Organization: Texas A&M University, College Station Lines: 78 Message-ID: <2ek0o9$kuh@tamsun.tamu.edu> References: NNTP-Posting-Host: tamsun.tamu.edu In article , Douglas R. Cannon wrote: >S/SX AND G/GX SOFTWARE DEVELOPING TIPS (version 2) > >Last Updated: December 13, 1993 > > [...] >---------------------------------------- >--- DETECTING WHICH MACHINE YOU'RE ON -- >---------------------------------------- Please change this. I assume that English is your native language so show it. The other grammatical errors in this document may be due to the contributions of the other individuals for whom English is a second, or even third language. However, such an error in a title stands out, and really doesn't look good. >EMPTY A=PC * get PC, so we can find data > GOTO ENEXT > > NIBHEX 0F0C03 * data... bitmap of the dot > NIBHEX 204204 > NIBHEX 108108 > NIBHEX 108108 > NIBHEX 204204 > NIBHEX C030F0 > >ENEXT A=A+CON A,4 * A now points to 1st nibble of data > D1=A This is sissy baby and seems like something a beginner would write. Perhaps you should take the time to review the work of others and you might pick up on those things you are unable to figure out for yourself. The following is a more elegant solution, but requires that a level of the return stack be available, for obvious reasons. EMPTY GOSUB ENEXT NIBHEX 0F0C03 * data... bitmap of the dot NIBHEX 204204 NIBHEX 108108 NIBHEX 108108 NIBHEX 204204 NIBHEX C030F0 ENEXT C=RSTK * C[A]: now points to 1st nibble of data D1=C Similarly you should also change this segment. >FULL A=PC * comments here are the same as > GOTO Fnext * those for EMPTY > > NIBHEX 0F0CF3 > NIBHEX EF7EF7 > NIBHEX FFFFFF > NIBHEX FFFFFF > NIBHEX EF7EF7 > NIBHEX CF30F0 > >Fnext A=A+CON A,4 > D1=A FULL GOSUB Fnext NIBHEX 0F0CF3 NIBHEX EF7EF7 NIBHEX FFFFFF NIBHEX FFFFFF NIBHEX EF7EF7 NIBHEX CF30F0 Fnext C=RSTK D1=C Rick Grevelle (409) 774-1169 ftg0673@tamsun.tamu.edu