dm42

created November 9th, 2023 • 9m reading time • 16 views

Note

This is the first project project page I am working on, so I am still kinda figuring out how to structure them. Sorry for this being so long…

The DM42 is a modern recreation of the HP42s scientific calculator created by SwissMicros. It runs free42, a reimplamentation of the original software. Like many of the high end calculators from this time, the HP42s is programmable, using ‘keystroke programming’, which is basically a list of keys to press with some fancy instructions for conditions, loops, and that kinda stuff.

Although you can do a lot with keystroke programming, it is not very user friendly. For my Statistics class, I wrote my first big program for 1-variable statistics, but it very quickly got hard to manage without any functions, just labels and gotos. So I decided that I would try to make a simple transpiler to add nicer syntax for functions, conditions, and loops.

The Transpiler

Here is an annotated example program for sorting a column matrix that shows some of the new syntax.

// An exported function is exposed in the main program menu.
// Non-exported functions are only accessible by other functions
// within this program.
export def sort {
    // MAT? is a built-in condition, and the then block is omitted
    if { MAT? } else {
        "X is not a matrix", AVIEW
        STOP
    }

    // Put the matrix into named variable "A"
    // Index into the first element (1, 1)
    STO "A"
    INDEX "A"
    1, 1, STOIJ
    DROPN 2

    // Flag 76 tells if the bounds of a matrix have been exceeded
    // So this loop will go through each element then use the built-ing
    // [MIN] function to get the index of the next smallest element,
    // Which is then swapped into the current location with R<>R
    while { FC? 76 } {
        [MIN]
        DROP
        RCLIJ
        DROP
        R<>R
        DROPN 2
        I+
    }

    // Recalls the value of "A" back onto the stack
    RCL "A"
}

Compiled Program
LBL "sort"
MAT?
GTO 00
GTO 01
LBL 00
STO "A"
INDEX "A"
1
1
STOIJ
DROPN 2
GTO 03
LBL 02
[MIN]
DROP
RCLIJ
DROP
R<>R
DROPN 2
I+
LBL 03
FC? 76
GTO 02
RCL "A"
RTN
LBL 01
"X is not a matrix"
AVIEW
STOP
GTO 00
RTN

The whole transpiler is still very early because it was working well enough. In the future I would like to get it to output .raw files, which are the bytecode representation of the text based code above. This will take a lot of effort though as there are like hundreds of instructions that need to be accounted for. But if the compiler has more of an understanding of the code it is working with, there will be more opportunities for outputting more optimized code.

Side quests

Aside from the transpiler and random small programs, I also made some bigger programs.

Playing MIDI

The first of these bigger programs started when I noticed that there was a ‘TONE’ instruction. This command plays an about half second tone from 0 to 9 (165Hz, 220Hz, 247Hz, 277Hz, 294Hz, 330Hz, 370Hz, 415Hz, 440Hz, 554Hz). After this the first program I made just let you use the calculator keypad to play the different tones, which was pretty fun. After that I wanted to see if I could somehow convert existing music into a program for playback on the calculator.

I quickly wrote a program to read in a MIDI file, and convert one channel into a keystroke program. But the songs I was trying didn’t sound right, and as it turns out, that was because the 10 tones it can play are not mapped to musical notes at all. They are just like some randomly chosen frequencies or something. There is no way around this, so I ended up modifying the MIDI songs I downloaded to sound better with the weird tone selection available.

This became very annoying to modify the MIDI track, re-run the program, than copy the output into free42 just to hear the difference. At this time, I was just randomly interested in virtual MIDI synthesizers, so I ended up making a simple MIDI synth that emulated the calculator’s behavior. By setting the output of my MIDI editor to this synth, I could get a live preview of what it would sound like, which made converting existing songs much easier. Below is a screenshot of one of the songs (I Just Died in your Arms) open in MidiEditor.

MidiEditor with “I Just Died in your Arms” open

Examples

Well thats enough talking, lets hear some examples. All of these audio tracks have been recorded from the actual dm42.

I Just Died in your Arms (Cutting Crew)

Say So (Doja Cat)

Wii Theme Music

Take on Me (a-ha)

A Thousand Miles (Vanessa Carlton)

Barbie Girl (Aqua)

Future Plans

I really like this idea of playing music on a calculator (for whatever reason), and in the future I want to write a custom firmware for the DM42 so that I can get more control over the internal buzzer and get better MIDI playback. The reason I haven’t tried working on this at all so far is because if I loaded a new firmware on it, I wouldn’t be able to use it as a calculator, which would be very annoying as the school year is still in progress.

Printing Bitmaps

For some reason the HP42s had an IP port for connecting to a wireless printer (the HP 82240A/B). You could use this to keep track of your calculations, or to plot a function using a custom program. Orr to print fun little images.

The way this works is with the PRLCD instruction, which prints the current screen (160x16) on the thermal printer. So all I have to do is write a program that splits an image into segments, then displays the image on the screen (when in the HP42s compatible graphics mode) and send them to the printer.

The interesting part here is how to work with the image data on a the calculator. The only two graphics drawling functions it has are PIXEL to enable a single pixel on the screen, and AGRAPH, which uses alpha register (which can hold 44 characters) to encode an image that it draws to the screen. The calculator uses the revised FOCAL character set, which has more than 256 characters. So to use ARGAPH, you supply it with a string, where each character is a bitmap for a 8 tall colum of pixels.

pub const INVALID_CHARS: &[usize] = &[4, 6, 13, 27, 30, 128, 129];
pub const CHARSET: &[char] = &[
    '÷', '×', '√', '∫', '\0', 'Σ', '\0', 'π', '¿', '≤', '␊', '≥', '≠', '\0', '↓', '→', '←', 'μ',
    '£', '°', 'Å', 'Ñ', 'Ä', '∡', 'ᴇ', 'Æ', '…', '\0', 'Ö', 'Ü', '\0', '•', ' ', '!', '\"', '#',
    '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6',
    '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
    'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',
    ']', '↑', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⊦', '\0', '\0',
];

Notice the invalid characters, some characters can’t be put in string literals, so you have to use a different instruction to append them to the string. This is not too big of a deal, but it is annoying and it makes the final program bigger, which turned out to be a bit of an issue later.

Anyway, it took a bit of trial and error, but I eventually got it all working on the emulator! So I put the program on the actual calculator, after splitting it into multiple programs because it would fail to load a program if it was too big. Unfortunately, it didn’t exactly work as expected.

Failed Prints

It took a little while to figure out what the issue was, but it turns out that the thermal printer has a really small memory, so with all the data being sent so fast it would not be able to store it all in the buffer and drop some bytes. After looking around in the user manual, I found that you can add a delay to any printing functions to prevent this issue and it worked! Very slowly, but it worked.

As you can see, its a little stretched, but after all the work it took to get here, and all the failed prints (it was super finicky), I was satisfied with this result.

ASCII Art

Because the large bitmaps don’t play nice with the calculator, I decided to also try some ASCII art, and it turned out much more fun than I expected. The software to convert a text file to a program was easy enough, just make string literals for each like and use the AVIEW instruction to print it.

I wanted to find some authentic 90s ASCII art, so I went looking on geocities archives, and found Joan Stark. Now that geocities is long gone, you can view her site at https://oldcompcz.github.io/jgs/joan_stark. I probably spent an hour exploring each page of her site and saving the art pieces that would fit in the 22 character width limit.

Programs

Since writing the transpiler, I have written a bunch of programs, here I will to into some of the more intreating ones. Some of the bigger programs we just looked at have more of a story behind them than these ones.

Statistics

Screenshot

[download .raw] [source code]

Setting flag 05 marks that the data is of a population rather than a sample. This is currently just used in the standard deviation calculation.

To use this program start by creating an N×1 matrix (1, 1, MATRIX>NEW) and adding your data (MATRIX>EDIT). The run the “STAT” program (XEQ>STAT), this will open a two page menu with the following options, when run, each function will push its result to the stack and allow you to run another.

  • Mean (MEAN)
  • Median (MEAD)
  • Standard Deviation (STD)
  • Inter-Quartile Range (IRQ)
  • 5-Var Summery (SUMMR)
  • Range (RANGE)
  • Outlier Fences (OUTLR)
  • Sum (SUM)
  • Square Sum (SUM↑2) — Sums the square of each datapoint
  • Toggle Sample (TSAM) — Toggles flag 05
  • Min (MIN)
  • Max (MAX)
  • Q1 (Q1)
  • Q3 (Q3)

Fractions

Screenshot

[download .raw] [source code]

Lets you work with fractions! Flag 03 will open the custom menu on exit. Fractions are stored as rectangular complex numbers with the numerators in the real part and the denominator in the imaginary part. The program exposes some operators that work with these fractions through custom menus. Listed below are all of the functions:

  • Frac — Makes a fraction out of yx. Automatically simplifies.
  • Addition
  • Subtraction
  • Multiplication
  • Division
  • Decimal Conversion — Replaces the fraction on the stack with its decimal representation.
  • Simplification — Simplifies the fraction. This is preformed automatically after every operation.
  • Reciprocal — Swaps the numerator and denominator.
  • Flip Signs — Flips the sign of the numerator, the denominator should never be negative.
  • Preview — Shows the decimal representation of the fraction without consuming it.

Unit Converter

Screenshot

[download .raw] [source code]

Simple unit converter that just supports converting between predefined unit dimensions (i.e. it does no dimensional analysis, just uses Unit ⇒ Base conversion factors).

Setting flag 03 will open the custom menu when you exit the program. I use this as I like always having my custom menu open :3. Setting flag 02 will keep the value you are converting from on the stack, otherwise it will be dropped (although converting back to the unit you are converting from will bring it back).

To use, have the value you want to convert in the X reg, run the program, select the unit dimension you want to convert within, select the unit the value is in, then select the unit you want to convert into. Clicking multiple units at this point will continue converting the original value into new units. To convert a new value exit the menu and re-select a dimension and select a new from unit.

Currently the units it supports are as follows:

  • Length: Meter, Inch, Foot, Yard, Mile, Nautical Mile
  • Mass: Kilogram, Pound, Tonne
  • Speed: Meters per second, Kilometers per hour, Miles per hour, Knot
  • Acceleration: Meters per second squared, Gravity
  • Force: Newton, Pound force
  • Energy: Joule, Calorie, Kilowatt hour, Electronvolt
  • Area: Square meter, Square inch, Square foot, Square yard, Square mile
  • Volume: Cubic meter, Cubic inch, Cubic foot
  • Time: Second, Minute, Hour, Day, Julian year
  • Bytes: Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte
  • Pressure: Pascal, Bar, Atmosphere, Pounds per square inch
  • Power: Watt, Horsepower

Normal Distribution