KLINI 400 Posthole Tiltmeter
This documentation covers the KLINI 400 Posthole Tiltmeter, a KLINI 400 (1 µrad-and-finer resolution class) two-axis instrument built for posthole/borehole deployment. The case currently shipping is 2" OD, approximately 34" long; additional case variants are planned for the future, so treat "the case" throughout this manual as describing the current one unless a later revision says otherwise.
About This Manual
Firmware Versions Covered
This manual covers instrument firmware 1.0.0 — the first version installed on shipped units. The KLINI 400 Posthole Tiltmeter runs its own firmware, developed and versioned independently of the KLINI Surface Inclinometer's 1.1/1.2/1.3 line — the two share a family name and some sensor hardware, but not a codebase, a version number, or an enclosure. Nothing in the Surface Inclinometer manual should be assumed to apply here unless this manual says so.
Finding Your Firmware Version
- The power-up banner prints a line such as
# Firmware: 1.0.0 (built Sep 29 2026 15:47:33). - The VERSION serial command reports the same line on demand.
Don't confuse this with the
Version:line inSHOW's EEPROM dump — that number (e.g.20) is the internal settings-storage layout revision, not the firmware version. It changes far more often thanFW_VERSIONdoes and isn't meaningful on its own; useVERSIONfor the real firmware identity.
Overview
The KLINI 400 Posthole Tiltmeter measures tilt on two perpendicular axes (X and Y) using two independent sensing technologies per axis: an electrolytic (fluid-based) tilt sensor and a MEMS inclinometer, both read through the same command interface and both independently calibrated to nanoradians. Each reading is reported alongside the on-board temperature. The instrument also carries a 3-axis magnetometer (for a gross orientation/heading check after installation), an environmental sensor (temperature, humidity, pressure), and two built-in leveling motors that can drive the instrument back toward level either on command or fully automatically.
Everything — configuration, data retrieval, leveling, and calibration — goes through a single serial command interface (§"Operation" below); there is no local display, SD card, or Wi-Fi interface on this instrument (unlike the KLINI Surface Inclinometer — do not assume those apply here).
The current case is a cylindrical, 6061 aluminum tube, 2" OD and approximately 34" long, designed for shallow deployments (100m or less). Weight is TBD.
Specifications
| Parameter | Value | Source / Notes |
| Tilt Measurement | ||
| Axes | 2 (X and Y) | - |
| Sensing technologies | Electrolytic tilt sensor + MEMS inclinometer, independent, both per axis | - |
| Tilt output resolution | Sub-microradian (factory-calibrated slope on shipped units: 0.61-0.76 nrad/count) | Measured across 3 fielded units' factory calibration reports; see "Calibration" below |
| Tilt ADC | 24-bit (±8,388,608 counts full scale) | Kept in its highest-resolution power mode at all times by design |
| Other Sensors | ||
| Magnetometer | 3-axis, µT output, used for a gross orientation/heading check (`ORIENT`) | Not a precision heading reference - see "Checking Orientation" below |
| Environmental | Temperature, humidity, pressure | - |
| Board temperature | Reported with every electrolytic reading | - |
| Electrical / Communication | ||
| Serial interface | RS232 (9600 baud) or RS485 (1200 baud), both 8N1 - set at the factory, not user-selectable | - |
| Motor current limit (default) | 200 mA | Adjustable, `SCURLIM` |
| Measured operating current (bench) | ~54 mA idle, ~138-145 mA during a leveling motor pulse, at 12V supply | - |
| Connector | Blue Trail Cobalt 4-pin bulkhead | Posthole case only - the deep-hole variant uses a different connector |
| Mechanical | ||
| Case (current) | Cylindrical, 6061 aluminum, 2" OD × ~34" long | Additional case variants are planned; this describes the current shipping case only |
| Depth rating | Shallow deployments, 100m or less | - |
| Weight | TBD | - |
Installation
Electrical Connection
The communication mode (RS232 or RS485) is set at the factory by an internal
strap and is not customer-adjustable; confirm which mode your unit is set to
with the factory or by checking SHOW's Comm mode line after power-up.
| Mode | Baud | Format |
|---|---|---|
| RS232 | 9600 | 8 data bits, no parity, 1 stop bit (8N1) |
| RS485 | 1200 | 8 data bits, no parity, 1 stop bit (8N1) |
Any standard serial terminal works (PuTTY, screen, CoolTerm, etc.). Both
bare LF and CRLF line endings are accepted when sending commands.
Connector (Posthole Case)
The posthole case uses a Blue Trail Cobalt 4-pin bulkhead connector. The deep-hole case variant uses a different connector — this pinout applies to the posthole case only.
| Pin | Wire | Function |
|---|---|---|
| A | Black | GND |
| B | Red | VDC |
| C | White | Instrument RX (from logger) |
| D | Yellow | Instrument TX (to logger) |
"Instrument RX"/"Instrument TX" are named from the instrument's point of view — wire the logger's TX to pin C and its RX to pin D.
Powering Up
At power-up, the instrument prints a startup banner, for example:
# Tiltmeter starting up...
# Firmware: 1.0.0 (built Sep 29 2026 15:47:33)
# Comm mode: RS232
# Register Commands... DONE
# Reading EEPROM settings... DONE
# Serial number: SN001
# Packet format: WIDE_CSV
# Columns: t_ms,type,mems_x,mems_y,elec_status,elec_ch0,elec_ch1,pcb_temp,env_temp,env_humidity,env_pressure,mag_x,mag_y,mag_z,mems_x_nrad,mems_y_nrad,elec_x_nrad,elec_y_nrad
Lines starting with # are informational text, never data to parse. For
about the first 10 seconds after this banner, the instrument holds off
automatic telemetry so you have a clear window to send setup commands (like
checking SHOW) before data starts flowing. After that window, telemetry
begins per whatever intervals are configured, and commands can still be sent
at any time — replies interleave with the telemetry stream.
Checking a Unit Before Deployment
Before lowering the instrument into the hole, confirm it's the unit you think it is and that its calibration is loaded:
SHOW
Check the reported Serial number against the unit's physical marking,
and confirm Elec angle cal points (X,Y) reads 2,2 or higher (not 0,0
— an uncalibrated axis reports elec_x_nrad/elec_y_nrad as a flat 0,
which looks deceptively like "perfectly level" rather than "not
calibrated"). See "Calibration" below for how to verify the calibration
itself, not just that one is present.
Checking Orientation After Installation
Once the instrument is set in the hole, ORIENT gives a one-shot "gross
orientation" report — not part of the regular telemetry, run it whenever you
want a check:
ORIENT
# ==================== ORIENTATION ====================
# MEMS tilt X: 1234 bits ( 2.21 deg )
# MEMS tilt Y: -567 bits ( -1.01 deg )
# Magnetometer raw (uT): X=48.32 Y=3.11 Z=-12.04
# Magnetometer, tool axes (uT): X=-3.11 Y=-12.04 Z(up)=-48.32
# Tilt from vertical: 2.44 deg
# Estimated tilt-corrected magnetic azimuth of tool +Y: 121.37 deg
# NOTE: sign convention field-verified to ~3-25 deg - see sensors.cpp
# ======================================================
OK
Finding +X on the case: a small dimple on the instrument's top cap marks the +X direction — use it to know which way the instrument is facing before it goes in the hole, and to set a known rotational reference as you lower and backfill/grout it.
Keep three things in mind, all of which depend on how the instrument is physically oriented in the hole:
- Tool axes are right-handed with Z up (not down-hole), and the heading reported is the azimuth of the tool's +Y axis, not +X — think of it as a map grid where Y points toward the reference direction. It's magnetic, not true, north — apply your site's declination if you need true north.
- The sign convention was checked against a real 4-point compass calibration
(+Y held pointed N, E, S, W in turn); residual error was roughly 3-25
degrees after that check — small compared to the gross ~180-degree error
the same check caught and fixed, but real. Treat
ORIENTas a gross orientation check, not a precision heading, until that residual is characterized further — the original check was run indoors, where nearby steel structure is expected to add magnetic distortion beyond the sign convention itself. - The MEMS tilt shown by
ORIENTuses a fixed, generic conversion, not the per-unit factory calibration table that the data stream'smems_x_nrad/mems_y_nradcolumns and theRTILTcommand use. It's a quick gross check, not a precision reading — for the calibrated value, read the telemetry stream orRTILTinstead.
The dimple tells you where +X is on the instrument; it doesn't by itself
tell you what compass direction +X ends up facing once the instrument is
down the hole — that depends on how it was lowered and oriented going in.
Record the installation orientation (e.g. with a known alignment relative to
the dimple as you lower it, and/or an ORIENT reading immediately after
setting the instrument, before backfilling or grouting) if you need to relate
the X/Y data to a real-world direction later.
Operation
Serial Command Basics
- Commands are case-sensitive and must be typed in UPPERCASE exactly as documented.
- Arguments are separated by single spaces.
- A successful command replies
OK. An invalid one (wrong argument count, an out-of-range value, or a prerequisite sensor that isn't available) replies!. - A command the instrument doesn't recognize gets no reply at all — if you send something and nothing comes back, suspect a typo or wrong firmware version before suspecting a wiring problem.
- Telemetry rows are not prefixed with
#; every other line the instrument sends is. That's the cheapest way to separate the two programmatically.
Telemetry Format
Every reading is one CSV row:
t_ms,type,mems_x,mems_y,elec_status,elec_ch0,elec_ch1,pcb_temp,env_temp,env_humidity,env_pressure,mag_x,mag_y,mag_z,mems_x_nrad,mems_y_nrad,elec_x_nrad,elec_y_nrad
| Column | Meaning | Units |
|---|---|---|
t_ms |
Milliseconds since the instrument booted (not wall-clock time — there is no real-time clock) | ms |
type |
Record type — see table below | - |
mems_x, mems_y |
Raw MEMS tilt sensor counts | counts |
elec_status |
Electrolytic ADC status word | - |
elec_ch0, elec_ch1 |
Raw electrolytic tilt counts (X, Y) | counts |
pcb_temp |
Board temperature | °C |
env_temp, env_humidity, env_pressure |
Environmental sensor readings | °C, %RH, hPa |
mag_x, mag_y, mag_z |
Magnetometer readings | µT |
mems_x_nrad, mems_y_nrad |
Calibrated MEMS tilt | nanoradians |
elec_x_nrad, elec_y_nrad |
Calibrated electrolytic tilt | nanoradians |
Only the columns relevant to a given record's type are filled in; the rest
are blank. A calibrated (_nrad) column reads a flat 0 until its
calibration table is loaded — that's expected on an uncalibrated axis, not a
fault; see "Checking a Unit Before Deployment" above.
type |
Populated columns | Sent |
|---|---|---|
MEMS |
mems_x, mems_y, mems_x_nrad, mems_y_nrad |
On RMEMS, or automatically on the configured MEMS interval (off by default) |
ELEC |
elec_status, elec_ch0, elec_ch1, elec_x_nrad, elec_y_nrad |
On RELEC, or automatically every 1 second by default |
TEMP |
pcb_temp |
Alongside every automatic ELEC record |
ENV |
env_temp, env_humidity, env_pressure |
On RENV, or automatically every 60 seconds by default |
MAG |
mag_x, mag_y, mag_z |
On RMAG, or automatically on the configured magnetometer interval (off by default) |
MEMS and magnetometer automatic streaming ship disabled. Turn them on
with SMEMSINT/SMAGINT if you want them in the regular data stream, or
just use RMEMS/RMAG/ORIENT to read those two sensors on demand without
changing anything.
Example Data Row
A single ELEC record, with the TEMP record always sent alongside it:
25137,ELEC,,,1283,-8388608,-748487,,,,,,,,,,0,-458768
25138,TEMP,,,,,,25.41,,,,,,,,,,
Reading this: at t_ms=25137, the electrolytic sensor's status word was
1283, X read -8388608 raw counts (the ADC's negative full-scale value —
this particular bench unit was sitting pinned at an extreme tilt when this
row was captured), Y read -748487 raw counts, X's calibrated reading was
0 (no calibration table loaded for X at that moment), and Y's calibrated
reading was -458768 nrad. The following row, 1ms later, reports the board
at 25.41°C. All other columns are blank because neither record type
populates them.
Self-Leveling
The instrument can drive its own leveling motors to correct tilt, either on command or fully automatically.
LEVEL(optionallyLEVEL XorLEVEL Y) runs the self-leveling motors on one or both axes right now, and reportsDONEorTIMEOUTper axis when finished.LEVELSTOPstops it immediately (takes effect within about 100ms, even mid-pulse).- Full-auto leveling (
SLEVELAUTO 1) makes the instrument level itself whenever tilt drifts outside a configured bound, with no command needed — it's off by default. The trigger bound is set deliberately wide (83% of the sensor's full counting range on shipped units) — it's meant as a rare "something has moved a lot, go fix it" safety net, not a routine fine-leveling trigger. After a successful auto-correction it waits at least a minute before checking again; after one that times out without converging, it waits much longer (30 minutes by default) rather than repeatedly cycling the motors on a problem a quick retry won't fix. - A leveling pulse is current-limited (
SCURLIM, 200mA by default) — if a pulse draws more current than that, it stops immediately rather than continuing to drive against whatever's causing the extra load. Repeated current-limit trips are worth investigating mechanically before assuming it's a settings problem (see "Troubleshooting"). - Leveling bounds and timing are normally set once at the factory and shouldn't need routine adjustment. The full list of leveling settings is in the command reference below if you do need to check or change one.
Full Command Reference
Leveling
| Command | Args | Description |
|---|---|---|
LEVEL |
[X\|Y] |
Runs the self-leveling motors. No argument levels X then Y; X or Y runs just that axis. Fails (!) if already leveling (send LEVELSTOP first), or if a required sensor/motor driver isn't available. |
LEVELSTOP |
- | Immediately stops any in-progress leveling and brakes the motors. |
SLEVELAUTO |
<0\|1> |
Enables/disables fully-automatic leveling. |
SLEVELUB / SLEVELLB |
<X\|Y> <value> |
Upper/lower auto-trigger bound (raw counts) for an axis. |
SLEVELDB |
<X\|Y> <value> |
Deadband (± counts considered "level") for an axis. |
SLEVELTARGET |
<X\|Y> <value> |
Target band: landing within this on one reading is accepted immediately. Must be less than the deadband. |
SLEVELCYCLES |
<X\|Y> <1-255> |
How many times the reading must cross zero (inside the deadband, outside the target band) before an axis is accepted, instead of waiting for a lucky sample inside the target band. |
SLEVELDIR |
<X\|Y> <0\|1> |
Reverses the leveling motor's pulse direction for an axis (set at the factory). |
SLEVELSPD |
<X\|Y> <0-255> |
Leveling motor pulse strength (PWM), fixed for every pulse. |
SLEVELMINPULSE / SLEVELMAXPULSE |
<X\|Y> <1-5000> |
Floor/ceiling (ms) for the adaptive pulse duration — it shrinks or grows automatically pulse to pulse rather than using one fixed strength. |
SLEVELSETTLE |
<X\|Y> <0-65535> |
How long (ms) to wait after a full-strength pulse before trusting the next reading. |
SLEVELMAXMS |
<X\|Y> <0-4294967295> |
Safety timeout (ms) for one axis's leveling attempt. |
SLEVELCOOLDOWN |
<0-65535> |
Minimum time (ms) between full-auto attempts, after a successful one. |
SLEVELFAILCOOLDOWN |
<0-4294967295> |
Minimum time (ms) before full-auto retries after a TIMEOUT. |
SLEVELCHKMS |
<0-65535> |
How often (ms) full-auto checks whether leveling is needed. |
Reading Sensors On Demand
| Command | Description |
|---|---|
RMEMS |
Sends one MEMS telemetry record right now. |
RELEC |
Sends one ELEC telemetry record right now. |
RENV |
Sends one ENV telemetry record right now. |
RMAG |
Sends one MAG telemetry record right now. |
ORIENT |
One-shot orientation report — see "Checking Orientation" above. |
RTILT |
Calibrated tilt (nrad) for MEMS X/Y and electrolytic X/Y, printed as text, not a telemetry row. |
RCUR |
Raw motor current-sense count and calibrated current (mA). |
Status and Diagnostics
| Command | Description |
|---|---|
SHOW |
The full status report: serial number, detected hardware, current settings, and what's actually stored on flash (with a CRC check). |
HELP |
Condensed on-instrument reference for every command, for use without this manual handy. |
VERSION |
Firmware version and build date/time — see "Finding Your Firmware Version" above. |
SETSN |
<value> — sets the instrument's serial number (metadata only, up to 19 printable characters, no spaces). Normally set once, at commissioning. |
I2CSCAN |
Scans the internal I2C bus and lists every address that responds — useful if a sensor is reported "NOT DETECTED" at boot. |
Sensor Configuration
| Command | Args | Description |
|---|---|---|
SMEMSEN / SELECEN |
<0\|1> |
Enables/disables the MEMS or electrolytic tilt sensor. Disabling is immediate; re-enabling needs a RESET to actually resume reading (see note below). |
SELECAVG / SMEMSAVG / SMAGAVG |
<1-255> |
Samples averaged per reading, per sensor. |
SELECINT / SMEMSINT / SMAGINT / SSOHINT |
<0-65535> |
Automatic telemetry interval (ms) for electrolytic/MEMS/magnetometer/environmental data. 0 disables automatic sending for that sensor. |
SPKTFMT |
<format id> |
Telemetry packet format. Only 0 (WIDE_CSV, the format this manual documents) exists today. |
Note: disabling a sensor takes effect immediately, but re-enabling it only updates the stored setting — the sensor doesn't actually resume reading until the next
RESETor power cycle.
Firmware Update and Reset
| Command | Description |
|---|---|
BOOTLOAD |
Restarts into the serial bootloader for a firmware update. Refused while leveling — send LEVELSTOP first. Resumes normal operation on its own if no update follows within about 2 seconds. |
RESET |
Restarts the instrument (like a power cycle). Replies OK first, then resets. Refused while leveling. |
FACTORYRESET CONFIRM |
Erases all settings and calibration data and restores factory defaults — except the serial number, which is preserved. Irreversible short of reloading your calibration data; the literal second argument CONFIRM is required as a typo safeguard. Refused while leveling. |
Calibration
How Readings Are Calculated
Each axis's raw sensor counts are converted to a calibrated angle in
nanoradians using a table of (raw count, angle) points, loaded at the
factory. A table needs at least two points to represent a real slope; with
none loaded, that axis's _nrad output is a flat 0 (see "Checking a Unit
Before Deployment" above). The electrolytic sensor additionally supports an
optional temperature-compensation model (a table correcting the sensor's
zero point for temperature, plus a single sensitivity-vs-temperature
coefficient) — the MEMS sensor has no temperature compensation modeled at
all.
What's Loaded on Your Unit
Each instrument ships with a single-slope, zero-offset electrolytic calibration per axis — the slope from that unit's own factory calibration report at its reference temperature (~20°C), with no temperature compensation currently loaded. This is a deliberate choice to keep the on-instrument conversion simple; it is not the only thing the factory calibration process measures.
Your factory calibration report, if you have one, characterizes real temperature-dependent drift beyond what's loaded into the instrument today — in factory calibration testing, the zero-point (intercept) alone was seen to drift by as much as several hundred thousand nanoradians across a -20°C to 40°C sweep, axis- and unit-dependent. If your deployment sees meaningful temperature swings and that level of residual matters for your application, two options:
- Ask the factory to load the full temperature-compensated model (the
SNULLPT/SSCALETCcommands in the reference above exist for this). - Every telemetry row already reports raw counts and board temperature together, so the full correction from your calibration report can always be applied afterward in your own post-processing, even if the instrument itself isn't doing it in real time.
Viewing the Calibration
SHOW
reports how many calibration points are loaded per axis/sensor
(Elec angle cal points (X,Y), MEMS angle cal points (X,Y),
Elec null cal points (X,Y)) and the scale temperature coefficient, if any.
To see the actual loaded points:
RANGTBL ELEC X
RANGTBL ELEC Y
(substitute MEMS for the MEMS table, or RNULLTBL <X|Y> for the
temperature-compensation table).
Changing the Calibration
Loading a new table: SANGPT loads one point at a time (in increasing
raw-count order), then SANGCNT sets how many of the loaded points are
valid. See the command reference above for exact syntax. FACTORYRESET
clears all calibration data along with every other setting (serial number
excepted) — reload calibration afterward before trusting the data.
Data Interpretation
Raw Counts vs. Calibrated Tilt
Use the _nrad columns for anything quantitative — they're the
factory-calibrated values. The raw mems_x/mems_y/elec_ch0/elec_ch1
columns are there for diagnostics and for redoing the calibration yourself
if you ever need to (see "Calibration" above); they are not directly
comparable between units or even between the electrolytic and MEMS sensors
on the same unit, since each is calibrated independently.
What a Positive Reading Means
The factory calibration makes increasing raw counts correspond to increasing (more positive) nanoradians, consistently for both axes. What that positive direction corresponds to physically — which way the instrument tips for a positive X or Y reading — depends entirely on how the instrument is mounted and oriented in the hole. The dimple on the top cap marks +X on the instrument itself, but there is no fixed "positive = this compass direction" rule beyond that; record the installation orientation (see "Checking Orientation After Installation" above) if you need to relate sign to a real-world direction.
Temperature and Environmental Columns
pcb_temp is the board's own temperature, reported with every automatic
electrolytic reading — useful both as a health check and, per "Calibration"
above, as an input if you want to apply temperature compensation yourself
in post-processing. env_temp/env_humidity/env_pressure come from a
separate environmental sensor and are reported on their own slower
interval.
Troubleshooting
| Symptom | Likely cause / what to check |
|---|---|
| No reply to a command at all | The instrument doesn't recognize it — check spelling and that it's in UPPERCASE. Confirm you're talking to the right port/baud (see "Electrical Connection"). |
Command replies ! |
Rejected: wrong argument count, an out-of-range value, or a sensor/motor it needs isn't available right now (e.g. sent while already leveling). |
| A sensor reports "NOT DETECTED" at boot | Run I2CSCAN to see what actually responds on the internal bus, to help narrow down a wiring or sensor fault. |
_nrad column reads a flat 0 |
That axis/sensor has no calibration table loaded — see "Checking a Unit Before Deployment." Not a sensor fault. |
LEVEL reports TIMEOUT |
The axis didn't converge within its safety timeout. Worth a bench check of mechanical freedom of motion for that axis before assuming it's a settings problem. |
| Leveling stops immediately / current-limit related messages | The motor pulse drew more current than SCURLIM allows and was cut off — check for a mechanical obstruction before raising the limit. |
| Communication is unreliable or garbled | Confirm the comm mode (RS232 vs RS485) and matching baud rate (SHOW reports which mode the instrument detected) and check cabling against the connector pinout above. |
| Need to start over completely | FACTORYRESET CONFIRM restores factory defaults (serial number preserved) — reload your calibration afterward; see "Calibration." |
Firmware Change Log
Only firmware actually installed on a customer unit is listed here.
| Version | Status | Notes |
| 1.0.0 | Released | First released version. Dual-sensor (electrolytic + MEMS) tilt sensing on two axes, magnetometer-based gross orientation check, environmental monitoring, adaptive self-leveling (manual and full-auto) with current-limited motor protection, per-axis raw-to-angle calibration with optional electrolytic temperature compensation, and the full serial command interface documented in this manual. |
Revision History
| Date | Changes |
| September 2026 | Initial release, covering firmware 1.0.0. |