HP48 FAQ Section 9: Appendix A: Various Useful Functions

Previous Contents Next

9.1 ASC Functions

Note: Although this document mentions SX only, ASC\-> and \->ASC work on both the SX and GX.

From: Bill Wickes

ASCII Encoding HP48 SX Objects

Sending an HP48 SX object via electronic mail can be difficult if the object does not have an ASCII form, such as is the case for library objects. There are various encoding schemes available on different computer systems, but these require that the sender and receiver have similar computers, or at least compatible encode/decode schemes. The programs listed below perform the encoding and decoding on the HP48 SX itself, which has the advantage of being completely independent of any computer.

The programs are nominally called \->ASC and ASC\->. The former takes an object from the stack and converts it to a string, in which each nibble of the object and its checksum is converted to a character 0-9 or A-F. (The object must be in RAM, otherwise a "ROM Object" error is returned.) For sake of easy inclusion in e-mail letters, the string is broken up by linefeed characters after every 64 characters.

ASC\-> is the inverse of \->ASC: it takes a string created by \->ASC and converts it back into an object. When you transmit the encoded strings, be sure not to change the string; ASC\-> uses the checksum encoded in the string to verify that the decoding is correct. An "Invalid String" error is returned if the result object does not match the original object encoded by \->ASC. When you upload a string to your computer, use HP48 translate mode 3 so that the HP48 will convert any CR/LF's back to LF's when the string is later downloaded.

Two versions of ASC\-> are included here. The first (P1) is in HP48 user language, using SYSEVALs to execute system objects. P2 is a string that the setup program uses P1 to decode into an executable ASC\-> - then P1 is discarded. The second version is more compact than the first, and also uneditable and therefore safer (but it can't be transmitted in ASCII form, which helps to make the point of this exercise).

Here are the programs, contained in a directory (if you have problems with this and you are using the HTML version of the FAQ, you may wish to try the plain text version or download a binary copy of the program):

%%HP: T(3)A(D)F(.);
DIR
P1              @ ASC\-> Version 1.
\<<
  IF DUP TYPE 2 \=/
  THEN "Not A String" DOERR
  END RCWS \-> ws
  \<< 16 STWS
    #0 NEWOB SWAP DUP SIZE
    IF DUP 4 <
    THEN DROP SWAP DROP "Invalid String" DOERR
    END
    DUP 65 / IP - 4 - # 18CEAh SYSEVAL
    "" OVER # 61C1Ch SYSEVAL
    SWAP # 6641F8000AF02DCCh
    # 130480679BF8CC0h # 518Ah SYSEVAL
    # 19610313418D7EA4h # 518Ah SYSEVAL
    # 7134147114103123h # 518Ah SYSEVAL
    # 5F6A971131607414h # 518Ah SYSEVAL
    # 12EA1717EA3F130Ch # 518Ah SYSEVAL
    # 280826B3012808F4h # 518Ah SYSEVAL
    # 6B7028080BEE9091h # 518Ah SYSEVAL
    # BE5DC1710610C512h # 518Ah SYSEVAL
    # 705D00003431A078h # 518Ah SYSEVAL
    # 3D8FA26058961431h # 518Ah SYSEVAL
    # 312B0514h # 518Ah SYSEVAL
    # 18F23h SYSEVAL
    DUP BYTES DROP 4 ROLL
    IF ==
    THEN SWAP DROP
    ELSE DROP "Invalid String" DOERR
    END ws STWS
  \>>
\>>

P2      @ ASC\->  Version 2.  To be converted by ASC\-> version 1.

"D9D20D29512BF81D0040D9D20E4A209000000007566074726636508813011920
140007FE30B9F060ED3071040CA1304EC3039916D9D2085230B9F06C2A201200
094E66716C696460235472796E676933A1B21300ED30FD5502C230C1C1632230
CCD20FA0008F14660CC8FB97608403104AE7D814313016913213014117414317
414706131179A6F5C031F3AE7171AE214F8082103B6280821909EEB0808207B6
215C0160171CD5EB870A13430000D50713416985062AF8D341508813044950B9
F06BBF06EFC36B9F0644230C2A201200094E66716C696460235472796E676933
A1B2130B21300373"

P3      @\->ASC.     To be converted by ASC\->.
"D9D20D2951881304495032230FD5502C230A752688130ADB467FE30322306AC3
0CB916E0E30CBD30F6E30C1C1632230CCD20DC0008F14660CC8FB97608403104
AE7D8143130169174147061741431311534AC6B4415141534946908D9B026155
4A6F53131F3AE731A014C161AE215F08082103A6280821939EEC08082170A621
4C161170CD56B870A18503430000D5071351796A9F8D2D02639916D9D2085230
C2A209100025F4D402F426A6563647933A1B2130A2116B213033C0"

SETUP   @Automatic setup program
\<< P2 P1 'ASC\->' STO
    P3 ASC\-> '\->ASC' STO
    { P1 P2 P3 SETUP } PURGE
\>>

END

Installation instructions:

  1. Save the above text into a text file named CONV (for example). Be sure that you leave the strings exactly as entered above, with no extra spaces or other invisible characters at the beginnings or ends of the lines.
  2. Set the HP48 SX into ASCII transfer mode.
  3. Using Kermit, download CONV text file to the 48, verify its checksum (6C8Ah).
  4. Execute CONV to make it the current directory.
  5. Execute SETUP.
  6. The directory CONV now contains ASC\-> and \->ASC, ready to use.

To archive the decoded versions of ASC\-> and \->ASC back on your computer, be sure to set the HP48 SX in binary transfer mode before uploading.

Disclaimers:

9.2 OBJFIX

When a binary object received by Kermit on the HP-48 is left as a string beginning with HPHP48, OBJFIX will extract the HP-48 object if the only problem is that extra bytes got appended to the end.

OBJFIX takes a variable name in stack level 1 and modifies the contents of the variable if no other problems are detected.

Note: This is like FIXIT by Horn and Heiskanen on Goodies Disk 8, but this one is by HP and so I suppose it's more reliable. Although it fails the test cases included with FIXIT, that may be because they were artificially contrived cases. Try both on real-world downloads that need fixing. Which do you like better?

OBJFIX.ASC

%%HP: T(3)A(D)F(.);
"D9D202BA81D9F81B2040D9D20F2A26DA91629C8145126489162C23072C80CCD2
0BD0008FB9760147108134164142C2818F24D534501008B2F41643150D73B840
58405438314A161966D2BF6BF6A6F5BE16314213114334CF8208A6F58F235A04
55136D7D4EA494D231A1CA101110131CA130DBE284F8FC0760D41198F29960D4
130142119EA1408F5E0108D341503223072D70B2130B21301460"

9.3 FIXIT

From: Joe Horn and Mika Heiskanen

PURPOSE:

Converts a badly uploaded string into the original object.

THEORY:

A lot of folks upload HP48 objects poorly, such that when you download them, you just get strings full of garbage that look something like this:

"HPHP48-E#c&r$a%p@!*!..."     [looks familiar, eh?]
That's because they uploaded it using XMODEM, or managed to screw it up some other way. The following FIXIT program takes such a string and extracts the actual HP48 object that they originally intended to upload (if at all possible).

Such object extraction can be done by hand, but it's too dangerous. FIXIT minimizes the danger of Memory Clear. It checks whether the extracted object is a valid one, and if not, drops it from the stack before the HP48 attempts to display it. All of the many bad downloads I've archived over the years are fixed by FIXIT, whereas about half of them cause a Memory Clear when extracted manually. No guarantees, however. Use at your own risk.

The actual extraction is done by a "Code object" written by Mika Heiskanen. The User RPL "shell" around this code object is what minimizes the danger of Memory Clear; it was written by Joe Horn.

INSTRUCTIONS:

BACKUP YOUR MEMORY, just in case the string contains a logic bomb.

Place the bad download on the stack (see "HPHP48-...") and run FIXIT.

Possible results:

EXAMPLES:

To do the following examples, download the FIXIT directory to your HP48 and get into it.

FIXIT.ASC
%%HP: T(3)A(D)F(.);
"69A20FF7CE20000000402414442340C2A203B000840584054383D25403A20FF7
2500000000403535947440D9D20E16329C2A2DBBF13013216DF1406A1C42328D
BF193632B213034000407545146540D9D20E163292CF1EFFB1DBBF1EBFB150FA
193632B2130003030303034C000402414441340C2A203B000840584054383D25
469A20FF72500000000403535947440D9D20E16329C2A2DBBF13013216DF1406
A1C42328DBF193632B213034000407545146540D9D20E163292CF1EFFB1DBBF1
EBFB150FA193632B2131313131313134C000407545146540C2A203B000840584
054383D25469A20FF72500000000403535947440D9D20E16329C2A2DBBF13013
216DF1406A1C42328DBF193632B213034000407545146540D9D20E163292CF1E
FFB1DBBF1EBFB150FA193632B2130003030303034C00020849420C2A20570008
40584054383D254D9D20E163284E2050841607079784E20603416D6075627936
32B2130A0BA02D456C616E63686F6C697022416269702BB28000506494859445
50D9D20E16323CE2278BF168BC1ED2A2167E1AFE22D9D203CE2278BF19C2A274
3A2C58C1C2A2031000840584054383D2167E1AFE22D9D2078BF18B9C1DBBF1AA
F028DBF1CCD201200014713717917F137145142164808C5BF22D9D2033920200
0000000005150933A1B21305DF22B21305BF22D9D20339202000000000004150
933A1B21305DF223CE2278BF168BC1D8DC1167E1AFE22D9D203FBF1339202000
000000002770933A1B21305DF223CE2278BF19D1A1DBBF18DBF1E0CF1D5CE1AF
E22D9D208DBF1339202000000000000030933A1B21305DF22CB2A193632B2130
B21303D4F"

9.4 LASTX

The LASTX function is useful in calculations where a number occurs more than once. By recovering a number using LASTX, you do not have to key that number into the calculator again. Note however that LASTX uses the built in last argument feature, so if you use LASTX you will lose the contents of your LASTARG.

For example, calculate:

 96.704 + 52.394706
--------------------
      52.394706

Keystrokes:                     Stack:
-----------------              --------------------
96.704 ENTER                    96.704

52.304706 +                     149.098706

LASTX                           149.098706
                                52.304706

/                               2.84568265351

@ This is a version of LASTX for the HP48
@
%%HP: T(3)A(D)F(.);
\<< DEPTH \-> n
  \<< LASTARG DEPTH n
- DUP \-> s
    \<< ROLLD s 1 -
DROPN
    \>>
  \>>
\>>

9.5 Compact Data Storage

From: Jim Donnelly

A simple length-encoding technique can be put to use for a free-format, very compact multi-field data storage system. Two tiny programs, SUBNUM and STRCON are here to help the process, and are listed near the end of this note. At the end of the note is a directory that may be downloaded into the HP48 that contains the examples.

The principle is to store starting indices in the beginning of a string that point to fields stored subsequently in the string. The indices are stored in field order, with an additional index at the end to accommodate the last field. There are several small points worth mentioning:

EXAMPLE:

               Indices  |          Fields
Character               |     1 11111111 12222222222
Position :   1  2  3  4 |567890 12345678 90123456789
            +--+--+--+--+------+--------+-----------+
String :    | 5|11|19|30|Field1| Field2 |  Field 3  |
            +--+--+--+--+------+--------+-----------+
This is a string that contains 3 fields, and therefore 4 index entries. The first field begins at character 5, the second field begins at character 11, and the third field begins at character 19. To keep the pattern consistent, notice that the index for field 4 is 30, which is one more than the length of the 29 character data string.

To extract the second field, place the string on the stack, use SUBNUM on character 2 to extract the starting position, use SUBNUM on character 3 to extract the (ending position +1), subtract 1 from the (ending position+1), then do a SUB to get the field data. NOTE: The index for field 1 is stored as char. data 5, NOT the string "5"! To place the field index for field 1 in the string, you would execute "data" 1 5 CHR REPL.

PROGRAM:

The following program accepts an encoded data string in level 2 and a field number in level 1:

DECODE   "data"  field#  -->  "field"

<<  --> f
  <<
    DUP f SUBNUM                ; "data" start -->
    OVER f 1 + SUBNUM           ; "data" start end+1 -->
    1 -                         ; "data" start end -->
    SUB                         ; "field" -->
  >>
>>
DATA ENCODING:

The following program expects a series of 'n' strings on the stack and encodes them into a data string suitable for reading by the first example above.

The programs SUBNUM and STRCON are used to assemble the indices.

ENCODE      field n  ...  field 1   n   -->  "data"

<< DUP 2 + DUP 1 - STRCON --> n  data
  <<
    1 n
    FOR i
      data i SUBNUM OVER SIZE   ; ... field index fieldsize
      + data SWAP               ; ... field "data" index'
      i 1 + SWAP CHR REPL       ; ... field "data"'
      SWAP + 'data' STO         ; ...
    NEXT
    data                        ; "data"
  >>
>>
In this example, four strings are encoded:
Input:  5: "String"
        4: "Str"
        3: "STR"
        2: "STRING"
        1:         4
Output: "xxxxxSTRINGSTRStrString" (23 character string) (The first five characters have codes 6, 12, 15, 18, and 24)
VARIATION:

The technique above has a practical limit of storing up to 254 characters of data in a string. To overcome this, just allocate two bytes for each field position. The code to extract the starting index for becomes a little more busy. In this case, the index is stored as two characters in hex.

               Indices  |          Fields
Character               | 11111 11111222 22222223333
Position :   12 34 56 78|901234 56789012 34567890123
            +--+--+--+--+------+--------+-----------+
String :    |09|0F|17|21|Field1| Field2 |  Field 3  |
            +--+--+--+--+------+--------+-----------+
<<  --> f
  <<
     DUP f 2 * 1 -           ; "data" "data" indx1 -->
     SUBNUM 16 *             ; "data" 16*start_left_byte  -->
     OVER f 2 * SUBNUM +     ; "data" start
     OVER f 2 * 1 + SUBNUM   ; "data" start end_left_byte -->
     16 * 3PICK f 1 + 2 *
     SUBNUM + 1 -            ; "data" start end -->
     SUB                     ; "field"  -->
  >>
>>
TWO VERY TINY HELPFUL PROGRAMS:

SUBNUM          "string"  position  -->  code

<< DUP SUB NUM >>
STRCON          code  count  -->  "repeated string"

<< -->  code count
  << "" code CHR 'code' STO
     1 count START code + NEXT
  >>
>>

Alternative Solution #1:

From: Matjaz Vencelj <vencelj@fmf.uni-lj.si>

Jim allocates two bytes for each index entry (to handle longer strings), but on the other side obviously only uses values 00...FF for index dublets, which just doesn't make sense. He's at a 16*16 = 255 chars limit again!

I have put together a working set of commands which support up to 65K strings, using two-byte indexing.

The encoder (Encode) is User-RPL program which calls the binary N2C which converts the level 1 real into a 2 character string:

@*** Encode ***
%%HP: T(3)A(R)F(.);
\<<
  IF DUP TYPE 0 = THEN 514 DOERR END
  \-> N
    \<<
    N 1 + 2 * #18CEAh SYSEVAL #45676h SYSEVAL
    1 N FOR I I 2 * 1 - OVER SIZE 1 + N2C REPL SWAP + NEXT
    N 2 * 1 + OVER SIZE 1 + N2C REPL
    \>>
\>>

@*** N2C, cksum=#8919h ***
%%HP: T(3)A(R)F(.);
"D9D202BA812BF819FF30D9D20AEC8111920001007FE3057A50C57463223057A5
0EE250B2130B21307206"

ASCII download them with translate code 3, then call program 'Encode' with data strings in levels 2..n+1 and a real n in level 1 indicating the number of strings.

The string decoder (Decode), which is usually speed-critical, is the following \->ASC encoded binary:

@*** Decode, cksum=#38E1h ***
%%HP: T(3)A(R)F(.);
"D9D20D8A81D9F811192013000D9D20AEC8113D26CA130F6E30CA130E0E305080
311920001002CE30CAF0650803CBD30CAF06FED30F6E30CA130E0E3050803119
20001002CE30CAF0650803CBD30E0E3033750B2130B21309534"

It takes a `database' string on level 2 and a real (record position) on level 1, then comes back with the appropriate substring (record).

Alternative Solution #2:

From: Thorsten Kampe <thorsten_kampe@hotmail.com>

The wrong approach (Donnelly's) is to store absolute indices (pointers to the position in the string). Thereby the length of the resulting string is limited to 254 characters.

The solution is to store the relative indices (length of your fields) in your coded string, headed by a field counter, so you can go up to 257 fields - you have to pack at least two fields, so shift [0 255] to [2 257]. Each of the fields can be 0 to 255 characters long.

You only have to allocate one byte for each field. If you would use another byte for the field counter, you could go up to 65027 fields, each 255 characters long. But this is just one byte plus, not one byte plus for each index! By allocating two bytes for each index, you could go up to fields of 65025-length. But this would be useless, because it's not the long fields that count towards compression, but the many short ones!

EXAMPLE:

             Indices |          Fields
Character            |000000 11111111 11122222222
Position :   1  2  3 |456789 01234567 89012345678
            +--+--+--+------+--------+-----------+
String :    |03|06|08|Field1| Field2 |  Field 3  |
            +--+--+--+------+--------+-----------+

This is a string that contains 3 fields, and therefore 3 index entries. The first index contains the number of fields (03) - shifted by 2: so it's actually CHR 01. The first field has a length of 6 characters, the second field has a length of 8 characters, and the third field 11 - but the last one is ignored because the Partitioning tool PART will take care of that.

 ENCODE      { field 1  ...  field n }   -->  "data"

<< DUP SIZE 2 - CHR
OVER 1 OVER SIZE 1
- SUB 1
  << SIZE CHR +
  >> DOLIST SWAP +
\GSLIST >>

DECODE   "data"  -->  { field 1  ...  field n }

<< TAIL LASTARG HEAD
NUM 1 + 1 \->LIST
PART EVAL SWAP 1
PART NUM PART >>

These routines rely on a separate module named PART, which is provided below:

PART
<< DUP TYPE
  IF NOT
  THEN 1 \->LIST OVER
SIZE OVER / CEIL {
} + LIST\-> 1 + ROLL
2 LASTARG
    START SWAP OVER
SIZE * SWAP
      DO DUP + DUP2
SIZE
      UNTIL \<=
      END 1 ROT SUB
1 \->LIST
    NEXT EVAL PART
1 OVER SIZE 1 - SUB
  ELSE 1
    << 1 SWAP SUB
LASTARG + OVER SIZE
SUB
    >> DOLIST LIST\->
1 + \->LIST
  END >>

9.6 HP82240B Printer Codes

From: Jarno Peschier

Size of physical row

One printed row is either 24 normal characters, 12 expanded characters or 168 pixels wide. This means that one normal character has a width of 7 pixels. Any printed data "falling off the row" will be truncated and ignored by the printer.

Reset

ESC 255d
This resets the printer to the following state: Roman8 character set (watch out: the power-up character set is ECMA94), both expanded and underline printing off, buffer cleared.

Self test

ESC 254d
This causes the printer to print a selftest pattern. This mainly consists of a printout of the Roman8 character set.

Expanded printing

ESC 253d
This turns expanded printing on. This means that from this code on all characters that are printed will be printed at twice the normal character width because each column of pixels is printed twice. Has no effect if expanded printing is already on.

ESC 252d
This turns expanded printing off. This means that from this code on all characters that are printed at normal character width again. Has no effect if expanded printing is already off.

Underlined printing

ESC 251d
This turns underlined printing on. This means that from this code on all characters that are printed will be underlined because the bottom-most pixel in each columns of pixels is now always on. Has no effect if underlined printing is already on.

ESC 250d
This turns underlined printing off. This means that from this code on all characters that are printed will not be underlined anymore. The bottom-most pixel in each columns of pixels is printed as it is defined for the printed character. Has no effect if underlined printing is already off.

Character sets

ESC 249d
This switches the printer to use the ECMA94 character set. This set is 100% identical to the character set used in HP48 calculators. This is the power up default of the printer. Has no effect if ECMA94 is the current character set already.

ESC 248d
This switches the printer to use the Roman8 character set. This set is different than the character set used in HP48 calculators in the 128 last characters. As far as I know this set is used by older printers like the HP82240A and by HP28 calculators (hence the need for the OLDPRT command in HP48 calculators if you are printing to a HP82240A). This character set is turned on if you reset the printer with the reset code. Has no effect if Roman8 is the current character set already.

Graphics

ESC n data (with n between 1 and 247)
This causes the printer to print graphics specified by the specified data (one byte per pixel column). The value n specifies the number of bytes of data that follow the printer code that will be interpreted as graphics data. Any data after pixel column 168 will be truncated and ignored.


Previous Contents Next

Part of the HP Calculator Archive - http://www.hpcalc.org/