Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MotionSensor SDK (C)

A small C library and example program for the HASOMED MotionSensor 2.0, an IMU that communicates over a serial link (Bluetooth SPP or USB-CDC, e.g. /dev/ttyACM0) using a binary, package-based protocol.

This library implements the following commands, each including its acknowledgment handling: Init, GetSerial, GetSensorPosition, GetCalibrationData, GetScaleFactor, GetAvailableMeasurementModes, PrepareMeasurement, UnPrepareMeasurement, StartMeasurement, StopMeasurement, SetMeasurementMode, GetMeasurementMode, MeasurementActive, SensorStatsExt, RawData14, RawData15, Error, KeepAlive.

Project layout

include/motionsensor.h   Public API (types, constants, function declarations)
src/ms_transport.c       Serial port setup, byte-stuffing/checksum framing, send/receive
src/ms_commands.c        High-level command wrappers (Init, GetSerial, ...)
src/ms_measdata.c        Parsing of RawData14/RawData15/Error, calibration math
src/ms_error.c           Human readable error strings
src/ms_internal.h        Internal shared definitions
examples/example_use.c   Example program (see below)
Makefile                 Builds libmotionsensor.a and the example program

Building

Requires a C11 compiler and Linux (uses termios/poll).

make

This produces libmotionsensor.a and the example binary example_use.

make clean

removes all build artifacts.

Running the example

The example follows the "Example of use" chapter of the user manual: it initializes the connection, reads sensor information, configures a measurement mode, starts a measurement, prints incoming data packages for a few seconds, then stops it.

./example_use /dev/ttyACM0

The device path defaults to /dev/ttyACM0 if omitted. Make sure your user has permission to access the serial device (usually the dialout group on Linux).

Using the library in your own program

Link against libmotionsensor.a and include the header:

cc -Iinclude myprogram.c libmotionsensor.a -o myprogram
#include "motionsensor.h"

MotionSensor *ms = ms_open("/dev/ttyACM0");
if (!ms) { /* handle error */ }

MsInitInfo info;
if (ms_init(ms, &info, 2000) != MS_OK) { /* handle error */ }

ms_close(ms);

General conventions

  • Every command function returns an int:
    • MS_OK (0) on success.
    • A negative MS_ERR_* code for local failures (see below).
    • MS_ERR_DEVICE (-5) if the sensor answered with an Error package; call ms_last_device_error(ms) to get the sensor's error code and ms_device_strerror(code) for a human readable description.
  • Every command function takes a timeout_ms parameter: the maximum time to wait for the sensor's acknowledgment.
  • Output values are written through pointer parameters; passing NULL for an output parameter you don't need is fine.
  • ms_strerror(rc) turns any library return code into a readable string.
int rc = ms_get_serial(ms, &serial, 2000);
if (rc != MS_OK) {
    if (rc == MS_ERR_DEVICE) {
        fprintf(stderr, "device error: %s\n", ms_device_strerror(ms_last_device_error(ms)));
    } else {
        fprintf(stderr, "error: %s\n", ms_strerror(rc));
    }
}

Library return codes

Code Meaning
MS_OK Success
MS_ERR_TIMEOUT No (matching) response within timeout_ms
MS_ERR_IO Serial port read/write failed
MS_ERR_CHECKSUM Received a package with a wrong checksum
MS_ERR_PROTOCOL Malformed or unexpected package
MS_ERR_DEVICE Sensor answered with an Error package (see ms_last_device_error)
MS_ERR_ARG Invalid argument passed to a library call

Connection management

ms_open

MotionSensor *ms_open(const char *device);

Opens the serial/USB-CDC device and configures the line (460800 baud, 8N1, no flow control, as specified by the protocol). Returns NULL on failure (e.g. wrong path or missing permissions).

MotionSensor *ms = ms_open("/dev/ttyACM0");

ms_close

void ms_close(MotionSensor *ms);

Closes the device and frees the handle.

ms_close(ms);

Commands

Every command below must be called after a successful ms_init(). timeout_ms is the time to wait for the sensor's response, e.g. 2000 (2 seconds).

Init

int ms_init(MotionSensor *ms, MsInitInfo *info, int timeout_ms);

Initializes the connection. Must be called once at the beginning, before any other command. Returns the protocol version and a welcome text via info.

MsInitInfo info;
if (ms_init(ms, &info, 2000) == MS_OK) {
    printf("protocol version %u: %s\n", info.protocol_version, info.welcome_text);
}

KeepAlive

int ms_keep_alive(MotionSensor *ms, int timeout_ms);

Keeps the connection alive and resets the sensor's StandBy timer (the sensor turns itself off after a period of inactivity, see ConfigStandBy in the manual). Call this periodically if you are not otherwise communicating with the sensor (e.g. during a long idle period).

ms_keep_alive(ms, 2000);

GetSerial

int ms_get_serial(MotionSensor *ms, uint32_t *serial, int timeout_ms);

Reads the sensor's unique serial number (also printed on the sensor's label).

uint32_t serial;
if (ms_get_serial(ms, &serial, 2000) == MS_OK) {
    printf("serial: %u\n", serial);
}

GetSensorPosition

int ms_get_sensor_position(MotionSensor *ms, uint32_t *position, int timeout_ms);

Reads the body position the sensor is configured for (see the MsSensorPosition enum: MS_POS_FOOT_LEFT, MS_POS_FOOT_RIGHT, MS_POS_SHANK_LEFT, MS_POS_SHANK_RIGHT, MS_POS_THIGH_LEFT, MS_POS_THIGH_RIGHT, MS_POS_PELVIS, MS_POS_STERNUM, MS_POS_WRIST_LEFT, MS_POS_WRIST_RIGHT, MS_POS_PELVIS_DAY, MS_POS_PELVIS_NIGHT).

uint32_t position;
if (ms_get_sensor_position(ms, &position, 2000) == MS_OK) {
    printf("position: %u\n", position);
}

GetCalibrationData

int ms_get_calibration_data(MotionSensor *ms, uint32_t channel, uint32_t mode,
                             MsCalibrationRaw *calib, int timeout_ms);

Reads the factory calibration data (bias, rotation matrix, Cg1/Cg2 polynomial coefficients) for a given sensor chip (channel: MS_CHANNEL_ACC, MS_CHANNEL_GYRO, MS_CHANNEL_MAG, MS_CHANNEL_PRESSURE) and measurement range (mode, e.g. MS_ACC_MODE_8G for the accelerometer or MS_GYRO_MODE_1000DPS for the gyroscope). Convert the result to physical (double) values with ms_calibration_to_double() and use ms_apply_calibration() to calibrate raw samples (see Converting measurement data below).

MsCalibrationRaw raw;
MsCalibration acc_calib;
if (ms_get_calibration_data(ms, MS_CHANNEL_ACC, MS_ACC_MODE_8G, &raw, 2000) == MS_OK) {
    ms_calibration_to_double(&raw, &acc_calib);
}

GetScaleFactor

int ms_get_scale_factor(MotionSensor *ms, uint32_t channel, uint32_t mode,
                         double *scale_factor, int timeout_ms);

Reads the scale factor (LSB per physical unit) for a channel/mode combination, needed to convert raw RawDataXX values into physical units (scaled_value = LSB / scale_factor).

double acc_scale;
if (ms_get_scale_factor(ms, MS_CHANNEL_ACC, MS_ACC_MODE_8G, &acc_scale, 2000) == MS_OK) {
    printf("acc scale factor: %f LSB per m/s^2\n", acc_scale);
}

GetAvailableMeasurementModes

int ms_get_available_measurement_modes(MotionSensor *ms, uint32_t *modes,
                                        uint32_t max_modes, uint32_t *num_modes,
                                        int timeout_ms);

Lists the measurement data commands (e.g. MS_CMD_RAW_DATA14) supported by this particular sensor's hardware revision. modes must point to an array with room for at least max_modes entries; *num_modes receives the number of modes the sensor actually reported (which may be larger than max_modes if the array was too small).

uint32_t modes[64], num_modes;
if (ms_get_available_measurement_modes(ms, modes, 64, &num_modes, 2000) == MS_OK) {
    for (uint32_t i = 0; i < num_modes; ++i) {
        printf("mode %u\n", modes[i]);
    }
}

SetMeasurementMode

int ms_set_measurement_mode(MotionSensor *ms, const MsMeasurementMode *mode, int timeout_ms);

Configures the sample rate and the measurement data package to be produced, plus the range of each sensor chip. The sensor must not be prepared (call ms_unprepare_measurement() first if needed).

Fields of MsMeasurementMode:

Field Meaning
sample_rate Sample rate in Hz (max. 600, see manual for hardware-dependent limits)
measurement_mode Command number of the data package to stream, e.g. MS_CMD_RAW_DATA14
acc_mode Accelerometer range, e.g. MS_ACC_MODE_8G
gyro_mode Gyroscope range, e.g. MS_GYRO_MODE_1000DPS
mag_mode Magnetometer range, e.g. MS_MAG_MODE_1_3GS
send_storage_mode MS_SEND_STORAGE_SEND_NOT_STORE, ..._SEND_AND_STORE, or ..._NOT_SEND_STORE
MsMeasurementMode mode = {
    .sample_rate = 100,
    .measurement_mode = MS_CMD_RAW_DATA14,
    .acc_mode = MS_ACC_MODE_8G,
    .gyro_mode = MS_GYRO_MODE_1000DPS,
    .mag_mode = MS_MAG_MODE_1_3GS,
    .send_storage_mode = MS_SEND_STORAGE_SEND_NOT_STORE,
};
ms_set_measurement_mode(ms, &mode, 2000);

GetMeasurementMode

int ms_get_measurement_mode(MotionSensor *ms, MsMeasurementMode *mode, int timeout_ms);

Reads back the currently configured measurement mode (same fields as above).

MsMeasurementMode mode;
if (ms_get_measurement_mode(ms, &mode, 2000) == MS_OK) {
    printf("sample rate: %u Hz\n", mode.sample_rate);
}

PrepareMeasurement

int ms_prepare_measurement(MotionSensor *ms, uint32_t *session_id, int timeout_ms);

Prepares the sensor for a measurement (resets the package counter, starts an internal SD-card session, etc.). Must be called before every ms_start_measurement(). Returns a session_id needed to request retransmission of lost packages.

uint32_t session_id;
ms_prepare_measurement(ms, &session_id, 2000);

UnPrepareMeasurement

int ms_unprepare_measurement(MotionSensor *ms, int timeout_ms);

Cancels a preparation made with ms_prepare_measurement() without starting a measurement (e.g. to change the measurement mode again). Not needed after ms_stop_measurement(), which un-prepares implicitly.

ms_unprepare_measurement(ms, 2000);

StartMeasurement

int ms_start_measurement(MotionSensor *ms, uint32_t *start_time_ms, int timeout_ms);

Starts a previously prepared measurement. From this point on the sensor streams measurement data packages (e.g. RawData14) until ms_stop_measurement() is called; read them with ms_receive() (see Receiving measurement data). start_time_ms receives the sensor's internal clock value (ms since power-on) at the moment the measurement started.

uint32_t start_time;
ms_start_measurement(ms, &start_time, 2000);

StopMeasurement

int ms_stop_measurement(MotionSensor *ms, uint32_t *stop_time_ms,
                         uint32_t *num_packages, int timeout_ms);

Stops a running measurement. stop_time_ms receives the sensor's clock value at the time of stopping, num_packages the total number of measurement data packages the sensor sent.

uint32_t stop_time, num_packages;
ms_stop_measurement(ms, &stop_time, &num_packages, 2000);

MeasurementActive

int ms_measurement_active(MotionSensor *ms, int *active, int timeout_ms);

Checks whether a measurement is currently running (*active is set to 1 or 0).

int active;
if (ms_measurement_active(ms, &active, 2000) == MS_OK) {
    printf("measurement active: %d\n", active);
}

SensorStatsExt

int ms_sensor_stats_ext(MotionSensor *ms, MsSensorStatsExt *stats, int timeout_ms);

Reads extended battery/link statistics: csoc (state of charge, %), ttecp (time to empty at constant power, minutes), ai (average current, mA), rsoc (relative state of charge, %), volt (voltage, mV).

MsSensorStatsExt stats;
if (ms_sensor_stats_ext(ms, &stats, 2000) == MS_OK) {
    printf("battery: %d%%, %d mV\n", stats.csoc, stats.volt);
}

Receiving measurement data

Once a measurement is started, the sensor streams data packages asynchronously (not as command/acknowledgment pairs). Read them in a loop with the low-level ms_receive() function and dispatch on pkt.command:

MsPacket pkt;
int rc = ms_receive(ms, &pkt, 2000);
if (rc != MS_OK) {
    /* MS_ERR_TIMEOUT, MS_ERR_IO, MS_ERR_CHECKSUM, or MS_ERR_PROTOCOL */
}

RawData14 / RawData15

int ms_parse_rawdata_imu(const MsPacket *pkt, MsRawDataImu *out);

Decodes a RawData14 (21 accelerometer+gyroscope samples per package) or RawData15 (1 sample per package) measurement data package. pkt->command must be MS_CMD_RAW_DATA14 or MS_CMD_RAW_DATA15. out->num_samples tells you how many entries of out->samples[] are valid (21 or 1); each sample holds raw LSB values acc[3] and gyro[3].

if (pkt.command == MS_CMD_RAW_DATA14 || pkt.command == MS_CMD_RAW_DATA15) {
    MsRawDataImu imu;
    if (ms_parse_rawdata_imu(&pkt, &imu) == MS_OK) {
        printf("package #%u, %u samples, first AccX = %d\n",
               imu.package_number, imu.num_samples, imu.samples[0].acc[0]);
    }
}

Error

int ms_parse_error(const MsPacket *pkt, uint32_t *error_code);

Decodes an Error package (pkt->command == MS_CMD_ERROR), which the sensor can send instead of an acknowledgment, or asynchronously while a measurement is running. ms_transact() (used internally by every command function above) already handles Error responses to a command automatically and returns MS_ERR_DEVICE; use ms_parse_error() yourself only when reading raw packages during a measurement.

if (pkt.command == MS_CMD_ERROR) {
    uint32_t err;
    ms_parse_error(&pkt, &err);
    fprintf(stderr, "sensor error: %s\n", ms_device_strerror(err));
}

Converting measurement data

Raw RawDataXX samples are signed LSB values. To turn them into physical units:

  1. Divide by the scale factor from ms_get_scale_factor(): ms_scale_lsb(lsb, scale_factor).
  2. Apply the calibration data from ms_get_calibration_data() (converted to double with ms_calibration_to_double()): ms_apply_calibration(scaled, &calib, out).
double scale_factor;      /* from ms_get_scale_factor() */
MsCalibration calib;      /* from ms_get_calibration_data() + ms_calibration_to_double() */

int16_t lsb[3] = { imu.samples[0].acc[0], imu.samples[0].acc[1], imu.samples[0].acc[2] };
double scaled[3], physical[3];
for (int i = 0; i < 3; ++i) {
    scaled[i] = ms_scale_lsb(lsb[i], scale_factor);
}
ms_apply_calibration(scaled, &calib, physical);
/* physical[] now holds AccX/Y/Z in m/s^2 (or GyroX/Y/Z in deg/s for the gyroscope) */

If no calibration data is available (or you want uncalibrated values), you may skip ms_apply_calibration() and use the scaled values directly.

Low-level access

For advanced use cases (e.g. commands not wrapped by this library), the underlying framing functions are also exposed:

int ms_send(MotionSensor *ms, uint16_t command, const uint8_t *data, uint16_t data_len);
int ms_receive(MotionSensor *ms, MsPacket *pkt, int timeout_ms);
int ms_transact(MotionSensor *ms, uint16_t command, const uint8_t *data, uint16_t data_len,
                 uint16_t expected_ack, MsPacket *response, int timeout_ms);

ms_transact() sends a command and waits for either the expected acknowledgment or an Error package, discarding any unrelated packages (such as measurement data) received while waiting. It's what every command function in this library uses internally.

Notes

  • This code is fully written and tested by AI

About

HASOMED MotionSensor SDK C

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages