Mip Interface
                                                                         The MIP interface is a high-level abstraction of a physical device which

communicates using the MIP protocol.

It provides both data callbacks and command functions for controlling and configuring the device:

mip_interface.svg

When an application calls one of the command functions, the MIP interface creates a packet, sends it to the device, and waits for a reply. When the reply is received, the command function returns the reply code to the application. If no reply is received, or if an error occurs, the function will return a status code.

Sending and receiving to or from the device occurs via two functions:

The application must define these two C functions, or subclass mip::DeviceInterface and implement the pure virtual functions. This should be straightforward; simply pass the bytes between the MIP interface and the connection.

Because the device transmits continuously when streaming data, the application must poll the connection for new data frequently. This is done via the update function.

An application obtains sensor data via the dispatch subsystem. There are 3 ways to do so:

Sending Commands

Typically an application will configure the device, initialize some settings, and start streaming. To do so, it must send commands. In most cases, applications will call a single function for each needed command. These functions take the command parameters as arguments, send the packet, wait for the reply, and return a result code. When reading from the device, these commands will also report the device response information, assuming the command was successful. The command functions are blocking, that is, they halt execution until the device responds or the command times out.

The Dispatch System

Because of the limited resources on embedded platforms, the MIP SDK will not buffer received data, and instead requires the application to process data as it arrives. The MIP interface will dispatch callbacks to the application when the requested data is received.

The MIP interface can dispatch data in 3 ways:

With the first two options, the callback function will receive a handle to the MIP interface, the associated MIP packet or field, and the reception timestamp.

An application must register callbacks with the system during initialization. Each method requires a pointer and the MIP descriptor associated with the data of interest. There is no limit on the number of registered dispatchers, though performance may be affected by using too many. Multiple dispatchers may be registered to the same data.

Packet callbacks
void packet_callback(void* userdata, const Packet& packet, Timestamp parseTime)

Packet callbacks are invoked when a packet is received which matches the registered descriptor set. The descriptor set may also be a wildcard, allowing the application to process any type of packet.

An application can register a packet callback to occur either before or after the field callbacks for the data in the same packet. For example, to print a summary of the packet before displaying information about each field, an application would set the callback to occur first. Usually applications will set a packet callback to occur last, so that they can determine if all of the fields have been processed.

Field callbacks
void field_callback(void* userdata, const Field& field, Timestamp parseTime)

Similar to packet callbacks, field callbacks are invoked when a MIP field is received which matches the specified descriptor set and field descriptor. Either descriptor may be a wildcard.

Data pointers

Data pointer dispatchers can alleviate a lot of boilerplate code having to do with deserializing a MIP field and storing it somewhere. An application can register a pointer one of the MIP data structures, along with the associated descriptors, and have it automatically updated. The descriptors cannot be wildcards because the type of the data structure is fixed.

Data callbacks
void data_callback(void* userdata, const data_sensor::ScaledAccel& packet, Timestamp parseTime)

Thanks to the power of templates, one additional dispatch mechanism is available for C++ applications. A data callback is similar to a field callback except that instead of getting the raw MIP field data, the function is passed the fully-deserialized data structure.

Typically an application will register a series of data or field callbacks and write the data to some kind of data structure. Because the order of these callbacks depends on the device configuration, it can be difficult to know which fields belong together in one sample. The solution is to use a packet callback after all of the fields are received. In the case of wraparound "overflow" MIP packets (see the MIP documentation), packets containing a shared timestamp or event source field at the beginning can be used to group data together.

The Update Function

The application should call mip_interface_update() periodically to process data sent by the device. This update function will call mip_interface_user_recv_from_device() to parse packets. When a data packet is received, the list of packet and data callbacks is checked, and any matching callbacks are invoked. The update function should be called at a high enough rate to avoid overflowing the connection buffers. The precision of the reception timestamp is dependent on the update rate.

The command functions in MIP Commands [C] / MipCommands_cpp (e.g. mip_write_message_format() / mip::writeMessageFormat()) will block execution until the command completes. Either the device will respond with an ack/nack code, or the command will time out. During this time, the system must be able to receive data from the device in order for command replies to be detected. This occurs via the mip_interface_update() function as well.

Single-threaded applications

For single-threaded applications, data can be read from the port directly from within the command function. While the command is waiting (status code MIP_STATUS_WAITING / CmdResult::STATUS_WAITING), repeated calls to the update function will be made. By default, the update function calls mip_interface_user_recv_from_device(). Because the function is called from within a loop, it should sleep for a short time to wait for data if none has been received yet. Doing so prevents excessive CPU usage and lowers power consumption.

The following diagram shows the typical control flow for a single-threaded application. First, the device is configured by setting the message format. Execution flows down into the command processing functions until mip_interface_wait_for_reply() is called. This will repeatedly call mip_interface_update() to pump packets from the device through the system, until either an ack/nack is received or the command times out. Once the device acknowledges the command, control is returned to the application which then registers some data or packet callbacks. It finally goes into a loop in collect_data(). Inside this loop, the update function is called to process data packets.

Notice that the same update function is called from both the command function and the data collection loop. If any data packets are received while waiting for a command reply, associated callbacks may be executed. This is why this example application registers its callbacks after the format is configured properly.

device_update.svg
Multi-threaded applications

For some applications, it may be desirable to run all of the data collection from a separate thread. In this case, the command functions must not call the update function as that would cause a race condition between the command thread and the data thread. Instead, the command thread should simply sleep or yield and let the data thread process the ack/nack packet.

To allow this behavior, the update function takes a boolean parameter which is true when waiting on a command and false when processing data. The default update function, mip_interface_default_update(), ignores this flag, but applications may override it via mip_interface_set_update_function(). In this case, a wrapper function can be created which implements the above behavior:

bool user_update_function(struct mip_device* device, bool blocking)
{
// If called from the data thread, do the normal processing.
if( !blocking )
return mip_interface_default_update(device, blocking);
// Otherwise, sleep and let the data thread process the reply.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return true;
}
mip_interface_set_update_function(device, &user_update_function);
device_update_threaded.svg

See the threading demo for an example application.

Other thread-safety concerns
Using a custom update function for other purposes

An alternate update function may be used for single-threaded applications, too:

Data may be pushed into the system by calling any of these functions:



microstrain_inertial_driver
Author(s): Brian Bingham, Parker Hannifin Corp
autogenerated on Wed Mar 22 2023 02:35:07