Hapé MI-23 MK3: RS-232 protocol

As I could not find any information on this, here is the result of my attempt to reverse engineer the RS-232 protocol the Hapé MI-23 MK3 uses. An example implementation can be found on GitHub.

Serial Port Configuration

The serial port has to be configured as following. Also make sure any special character handling is disabled.

Property Value
Baud 2400
Data bits 8
Stop bits 1
Parity None

Some example code in Python:

import termios, tty

# open serial port
fd = open('/dev/ttyS0', 'rb')

# set raw mode (sets data/stop bits and parity as well)
tty.setraw(fd)

# set baud rate
attr = termios.tcgetattr(f)
attr[4] = termios.B2400 # input speed
attr[5] = termios.B2400 # output speed (not used, but seems
                        # to be the only one that actually
                        # changes the speed)
termios.tcsetattr(f, termios.TCSAFLUSH, attr)

Protocol

The protocol consists of sending measurements as sequences of 14 bytes, with bits that represent mostly elements on the display. In practice, you might also see an additional byte when RS-232 is switched on (0xf8 for me). As far as I can tell this is not part of the protocol but external "noise".

Each byte in the measurement byte sequence uses the high nibble to indicate the index in the sequence and the low nibble to send actual data. The indices range from 1 to 14. The bytes are always in order. For example:

13 20 30 47 5d 6e 78 80 90 a0 b2 c4 d0 e1

Although each "data" bit in the low nibbles can be considered a individual flag, the data in bytes 2 through 9 can be considered to be four groups of two bytes that each describe a single digit of the measured value. As such, I will handle them separately.

Below are the meanings of the data bits for bytes 1 and 10 to 14. Elements in yellow are guesses, elements in red are currently unknown to me.

Index Data bit Display symbol Meaning
18ACAC measurement
14DC measurement
12AutoAuto ranging enabled
11RS232RS-232 output enabled
a8μUnit prefix 'micro' (10-6)
a4nUnit prefix 'nano' (10-9)
a2kUnit prefix 'kilo' (103)
a1(diode)Diode mode enabled
b8mUnit prefix 'milli' (10-3)
b4%Duty cycle percentage
b2MUnit prefix 'mega' (106)
b1(buzzer)Continuity test enabled
c8FUnit 'farad'
c4ΩUnit 'ohm'
c2(triangle)Relative measurement enabled
c1
d8AUnit 'ampere'
d4VUnit 'volt'
d2HzUnit 'hertz'
d1
e8
e4°CUnit 'degree Celsius'
e2
e1Only off when measuring temperature.

The remaining unknown bits correspond most likely to the 'hold', battery, 'hfe' and 'k rpm' segments on the display. You can see them displayed shortly when you turn on the device.

For the digits, the data consist of 1+7 bits. The most significant bit indicates either the minus sign, for the first digit (bytes 2 and 3), or the preceding period, for the other three digits. The remaining 7 bits correspond to the 7 segments of the digit on the display. If we label these bits 'abcdefg' (from most significant to least), the relation with the segments is as follows:

c
bg
f
ae
d

The following digits/symbols are used:

Bits Hex Symbol
000 000000  (space)
111 11017d0
000 0101051
101 10115b2
001 11111f3
010 0111274
011 11103e5
111 11107e6
001 0101157
111 11117f8
011 11113f9
110 100068L

To illustrate, the value -12.34 would be represented as follows, including high nibbles:

28 35 45 5b 69 7f 82 97

Putting everything together, you can deduce that the first example above corresponds to ' 0L MΩ' with 'auto' and 'rs232' on.

Note that when switching modes on the device, sometimes conflicting bits are set that are the result of mixing of the old and new state. This resolves itself with the next sequence.